npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

droopy-typesense

v0.0.12

Published

Wrapper around Typesense SDK with advanced TypeScript inference

Downloads

7

Readme

droopy-typesense

Enhances your experience with Typesense Node SDK by providing better type inference when querying and importing documents.

Key Features

  • Simplified Typesense Client creation.
  • Enhanced type inference for improved code completion and error checking when working with a Typesense Collection

Table of Contents

Installation

npm install droopy-typesense
yarn add droopy-typesense

createTypesenseCollection

The createTypesenseCollection function is a factory function that creates a repository object for interacting with a Typesense collection.

The function takes a Typesense client instance and a collection schema as its arguments and returns an object with methods for creating, updating, deleting, and searching documents in the collection.

It safely ensures the collection exists, creating it if it does not.

import { createTypesenseCollection } from "droopy-typesense";

let bookmarks = createTypesenseCollection(
  createTypesenseClient({
    apiKey: process.env.TYPESENSE_API_KEY,
    url: process.env.TYPESENSE_URL,
  }),
  {
    name: "bookmarks",
    fields: {
      id: {
        type: "string",
      },
      title: {
        type: "string",
        facet: false,
      },
      description: {
        type: "string",
        facet: false,
      },
      host: {
        type: "string",
        facet: true,
      },
      url: {
        type: "string",
        index: false,
      },
    },
  }
);
// Import documents
let bookmarksFromDb = await getAllBookmarksFromDB();
await bookmarks.importDocuments(bookmarksFromDb);

// Search documents
let searchResults = await bookmarks.search({
  q: "typesense",
  facets: ["host"],
});

Params

  • client: An instance of the Typesense Client. You can create a Typesense client using the createTypesenseClient or with the Typesense SDK's new Client() constructor.
  • collectionSchema: A schema defining the structure of the collection, including its name and fields. This is where all the type inference will come from.

Returns

The function returns an object with the following methods:

| Method | Description | | ---------------- | --------------------------------------------------------------------------------------------------------- | | ensureCollection | Ensures the collection exists. If it doesn't, the collection is created with the provided schema. | | importDocuments | Imports documents into the collection. Takes an array of documents as its argument. | | deleteDocument | Deletes a document by its ID. | | deleteDocuments | Deletes documents based on filter criteria | | updateDocument | Updates a document by its ID. If the document doesn't exist, it creates a new one with the provided data. | | deleteCollection | Deletes the entire collection. | | search | Searches the collection using search criteria. | | _collection | The underlying Typesense collection object. |

Defining a Schema

A TypesenseCollectionSchema is an object that describes the structure of a collection in Typesense. It includes properties like the collection name, fields, and default sorting field.

Fields

The fields property in the schema is an object that defines the fields and their properties for the documents in the collection. Each key in the fields object represents a field in the document. The value of each key is another object containing the properties for that field.

Each field object can have the following properties:

| Field | Description | | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | type | (Required) The Typesense data type of the field. | | facet | (Optional, default=false) A boolean value that indicates whether the field should be faceted or not. If set to true, Typesense can return facet counts for this field in search results. | | sort | (Optional, default=false) A boolean value that indicates whether the field can be used for sorting search results. If set to true, Typesense allows sorting by this field. | | index | (Optional) A boolean value that indicates whether the field should be indexed for searching. If set to false, Typesense will not index the field, and it won't be searchable. If not provided, it defaults to true. |

An id field is required

When defining a TypesenseCollectionSchema, you must include an id field. Typesense uses this for direct document retrieval. You can't search by this field.

createTypesenseClient

The createTypesenseClient function is a utility function that helps you easily create a new Typesense client instance by providing the required configuration. The function takes a configuration object as its argument and returns a new instance of the Typesense client.

Once you have the Typesense Client instance, you can use it to interact with the Typesense server by performing various actions such as creating, updating, or deleting collections, and searching documents.

Configuration Object

  • apiKey: The API key to authenticate your requests to the Typesense server.
  • url: The URL of the Typesense server, including protocol, hostname, and port number.
type TypesenseConfig = {
  apiKey: string;
  url: string;
};

Usage

import { createTypesenseClient } from "droopy-typesense";

const config = {
  apiKey: "your-api-key",
  url: "https://your-typesense-server.com",
};

const client = createTypesenseClient(config);

Full Usage Example

This example shows you you can create dyanmic Typesense collections but use a shared Fields schema.

export const bookmarkSearchFields = {
  id: {
    type: "string",
    facet: false,
    sort: false,
  },
  title: {
    type: "string",
    facet: false,
    sort: false,
  },
  description: {
    type: "string",
    facet: false,
    sort: false,
  },
  text: {
    type: "string",
    facet: false,
    sort: false,
  },
  host: {
    type: "string",
    facet: true,
    sort: false,
  },
  createdBy: {
    type: "string",
    facet: true,
    sort: true,
  },
  createdAt: {
    type: "int64",
    facet: false,
    sort: true,
  },
  image: {
    type: "string",
    facet: false,
    sort: false,
    index: false,
  },
  url: {
    type: "string",
    facet: false,
    sort: false,
    index: false,
  },
} satisfies TypesenseFieldsSchema;

const getCollectionName = (collectionId: string) => `bookmarks_${collectionId}`;

export const createSearchService = (collectionId: string) => {
  const schema = {
    name: getCollectionName(collectionId),
    fields: bookmarkSearchFields,
    default_sorting_field: "createdAt",
  } satisfies TypesenseCollectionSchema;
  let client = createTypesenseClient({
    apiKey: getEnvVar("TYPESENSE_API_KEY"),
    url: getEnvVar("TYPESENSE_URL"),
  });
  let bookmarks = createTypesenseClient(client, schema);
  return {
    bookmarks,
  };
};

type BookmarkFromDb =
  GetBookmarksByCollectionForSearchImportQuery["bookmarks"][number];
export type BookmarkSearchDocument = TypeSenseCollectionDocument<
  typeof bookmarkSearchFields
>;

export const fullCrawlForCollection = async (collectionId: string) => {
  let adminClient = createAdminGqlClient();
  let data = await adminClient.request(
    GetBookmarksByCollectionForSearchImportDocument,
    {
      collectionId,
    }
  );
  let searchService = await createSearchService(collectionId);
  await searchService.bookmarks.deleteCollection();
  await importSearchDocuments(collectionId, data.bookmarks);
  let results = await searchService.bookmarks.search({
    per_page: 1,
  });

  return {
    found: results.found,
    out_of: results.out_of,
  };
};

export const importSearchDocuments = async (
  collectionId: string,
  bookmarks: BookmarkFromDb[]
) => {
  let searchDocs = bookmarks
    ?.map(toTypesenseDocument)
    .filter(Boolean) as BookmarkSearchDocument[];
  let searchService = await createSearchService(collectionId);
  if (searchDocs?.length) {
    await searchService.bookmarks.importDocuments(searchDocs);
  }
};

const toTypesenseDocument = (
  dbItem: BookmarkFromDb
): BookmarkSearchDocument | null => {
  try {
    let url = new URL(dbItem.url);

    let document: BookmarkSearchDocument = {
      id: dbItem.id,
      title: dbItem.title || "",
      description: dbItem.description || "",
      text: dbItem.text || "",
      host: url.host,
      createdBy: dbItem.createdBy?.name || dbItem.createdBy?.email || "",
      createdAt: dayjs(dbItem.createdAt).unix(),
      url: dbItem.url,
      image: dbItem.image || "",
    };

    return document;
  } catch (err: any) {
    console.log("🚀 | toTypesenseDocument | err", err.message);
    console.log(JSON.stringify(dbItem, null, 2));
    return null;
  }
};

export const BookmarkSearchCriteriaSchema = z.object({
  q: z.string().optional(),
  host: z.string().optional(),
  sort: z.string().optional(),
  page: z.coerce.number().optional(),
});

export type BookmarkSearchCriteria = z.infer<
  typeof BookmarkSearchCriteriaSchema
>;

export const searchBookmarks = async (
  collectionId: string,
  criteria: BookmarkSearchCriteria
) => {
  let searchService = await createSearchService(collectionId);
  let searchResults = await searchService.bookmarks.search({
    q: criteria.q,
    page: criteria.page || 1,
    per_page: 20,
    filter: {
      host: criteria.host || undefined,
    },
  });

  return searchResults;
};