cosmos-orm
Version:
A simple ORM for Cosmos DB
1 lines • 14.2 kB
Source Map (JSON)
{"version":3,"sources":["../src/base-model.ts","../src/config.ts"],"sourcesContent":["import type { Container, FeedOptions, ItemDefinition, Resource } from '@azure/cosmos'\nimport type { CosmosClient, SqlQuerySpec } from '@azure/cosmos'\nimport { input } from '@azure/functions'\nimport { ulid } from 'ulidx'\n\nexport type Base = object\n\n// TODO: Make sure these always returns the correct type\ntype CosmosResource<T extends Base> = Resource & T\ntype CosmosItemDefinition<T extends Base> = ItemDefinition & T\n\ninterface AutoFields {\n /** Automatically generate an ID on document creation - defaults to true */\n id?: boolean\n /** Automatically generate createdAt and updatedAt fields on document create/updates - defaults to true */\n timestamp?: boolean\n}\n\nexport interface ModelOptions {\n /** The name of the Cosmos database */\n database: string\n /** The name of the Cosmos container within the database */\n container: string\n /** The instantiated Cosmos client */\n client: CosmosClient\n /** The name of the env of the Cosmos connection string - defaults to `COSMOS_CONNECTION_STRING` */\n connectionStringSetting?: string\n /** Automatic fields creation - defaults to true */\n fields?: AutoFields | boolean\n}\n\nconst initial = {}\n\nexport class BaseModel<T extends Base = typeof initial> {\n client: Container\n\n connectionStringSetting = 'COSMOS_CONNECTION_STRING'\n\n fields: AutoFields = {\n id: true,\n timestamp: true,\n }\n\n constructor(private options: ModelOptions) {\n if (options.connectionStringSetting) {\n this.connectionStringSetting = options.connectionStringSetting\n }\n\n if (typeof this.options === 'boolean' && !this.options) {\n this.fields = { id: false, timestamp: false }\n }\n if (typeof this.options === 'object') {\n this.fields = {\n ...this.fields,\n // Don't know why this won't remove the bool type\n ...(this.options.fields as AutoFields),\n }\n }\n\n this.client = options.client.database(options.database).container(options.container)\n }\n\n /**\n * Create an Azure Function app input binding to find a specific document by an input variable.\n * You can also provide a type (matching `Record<string, any>`) as the first type generic,\n * and it will validate the variable to be one of the types keys.\n *\n * @example\n * ```ts\n * const shopInput = orm.shops.createFindBinding(`shop`)\n * ```\n * @example\n * An example with a type to check the variable:\n * ```ts\n * interface ActivityInput {\n * shop: string\n * // ...\n * }\n *\n * const shopDocument = orm.shops.createFindBinding<ActivityInput>('shop') // ✅\n * const shopDocument2 = orm.shops.createFindBinding<ActivityInput>('id') // ❌\n * ```\n */\n public createFindBinding<Input extends object = object, Key = keyof Input & string>(variable: Key) {\n const binding = `{${variable}}`\n\n return input.cosmosDB({\n databaseName: this.options.database,\n containerName: this.options.container,\n connection: this.connectionStringSetting,\n id: binding,\n partitionKey: binding,\n })\n }\n\n /** Create an Azure Function app input binding to fetch all documents from this container. */\n public createAllBinding() {\n return input.cosmosDB({\n databaseName: this.options.database,\n containerName: this.options.container,\n connection: this.connectionStringSetting,\n })\n }\n\n /**\n * Create an Azure Function app input binding with a custom SQL query.\n * @example\n * ```ts\n * const inputDoc = orm.posts.createSQLBinding(`SELECT * FROM c WHERE c.deleted_at IS NULL`)\n * ```\n */\n public createSQLBinding(sqlQuery: string) {\n return input.cosmosDB({\n databaseName: this.options.database,\n containerName: this.options.container,\n connection: this.connectionStringSetting,\n sqlQuery,\n })\n }\n\n /** Fetch all resources in a container */\n public async all(): Promise<CosmosItemDefinition<T>[]> {\n const { resources } = await this.client.items.readAll().fetchAll()\n return resources as CosmosItemDefinition<T>[]\n }\n\n /** Fetch a specific resource by its ID */\n public async find(id: string): Promise<CosmosResource<T> | undefined> {\n const { resource } = await this.client.item(id, id).read()\n return resource as CosmosResource<T> | undefined\n }\n\n /** Fetch multiple resources using their ID's */\n public async findMany(ids: string[]): Promise<CosmosResource<T>[]> {\n const { resources } = await this.client.items\n .query({\n query: 'SELECT * FROM C WHERE ARRAY_CONTAINS(@ids, C.id)',\n parameters: [{ name: '@ids', value: ids }],\n })\n .fetchAll()\n\n return resources as CosmosResource<T>[]\n }\n\n /** Find a resource by a specific key */\n public async findBy(key: keyof T, value: string): Promise<CosmosResource<T> | undefined> {\n const { resources } = await this.client.items\n .query({\n query: 'SELECT * FROM C WHERE C[@key] = @value OFFSET 0 LIMIT 1',\n parameters: [\n { name: '@key', value: String(key) },\n { name: '@value', value: value },\n ],\n })\n .fetchAll()\n\n const [resource] = resources\n\n return resource as CosmosResource<T> | undefined\n }\n\n /** Find multiple resources by a specific key */\n public async findManyBy(key: keyof T, value: string): Promise<CosmosResource<T>[]> {\n const { resources } = await this.client.items\n .query({\n query: 'SELECT * FROM C WHERE C[@key] = @value',\n parameters: [\n { name: '@key', value: String(key) },\n { name: '@value', value: value },\n ],\n })\n .fetchAll()\n\n const [resource] = resources\n\n return resource as CosmosResource<T>[]\n }\n\n /** Create a resource */\n public async create(\n input: Omit<T, 'id' | 'createdAt' | 'updatedAt'> & Partial<{ id: string; createdAt: string; updatedAt: string }>,\n ): Promise<CosmosResource<T> | undefined> {\n const merged = {\n ...input,\n id: this.fields.id ? ulid() : input.id,\n createdAt: this.fields.timestamp ? new Date().toISOString() : input.createdAt,\n updatedAt: this.fields.timestamp ? new Date().toISOString() : input.updatedAt,\n }\n\n const { resource } = await this.client.items.create(merged)\n return resource as CosmosResource<T> | undefined\n }\n\n /** Either update or create a resource */\n public async upsert(input: T & { id: string }): Promise<CosmosResource<T> | undefined> {\n const { resource } = await this.client.items.upsert(input)\n return resource as CosmosResource<T> | undefined\n }\n\n /** Update a resource - replaces the whole resource, so make sure to provide a full input */\n public async replace(\n id: string,\n input: Omit<T, 'updatedAt'> & Partial<{ updatedAt: string }>,\n ): Promise<CosmosResource<T> | undefined> {\n const merged = {\n ...input,\n updatedAt: this.fields.timestamp ? new Date().toISOString() : input.updatedAt,\n }\n\n const { resource } = await this.client.item(id, id).replace(merged)\n return resource as CosmosResource<T> | undefined\n }\n\n /** Delete a resource */\n public async delete(id: string): Promise<CosmosResource<T> | undefined> {\n const { resource } = await this.client.item(id, id).delete<T>()\n return resource as CosmosResource<T>\n }\n\n /**\n * Run a query, and fetch all results\n *\n * This function accepts a generic, so you can pass in the type of the response if\n * you are running a custom select query - for example:\n *\n * `.query<{ id: string }>('SELECT c.id FROM c') // returns { id: string }[]`\n *\n * `.query<number>('SELECT VALUE count(c.id) FROM c') // returns [number]`\n *\n * This is just a wrapper of the `.client.items.query()` function, so you can use that\n * instead if you need access to the request metrics for example.\n */\n\n // biome-ignore lint/suspicious/noExplicitAny: <explanation>\n public async query<R = any>(query: string | SqlQuerySpec, options?: Pick<FeedOptions, 'maxItemCount'>): Promise<R[]> {\n const { resources } = await this.client.items.query(query, options).fetchAll()\n return resources as R[]\n }\n}\n","import { CosmosClient } from '@azure/cosmos'\nimport { type Base, BaseModel } from './base-model'\nimport type { ModelOptions } from './base-model'\n\ninterface Builder {\n createModel: <T extends Base>(container: string, options?: Pick<ModelOptions, 'fields'>) => BaseModel<T>\n}\n\n/** Default client configuration - for example the connection string setting, and the database name. */\nexport interface Options<M extends { [K: string]: BaseModel }> {\n /** The name of the Cosmos database */\n database: string\n /**\n * The name of the env of the Cosmos connection string - defaults to `COSMOS_CONNECTION_STRING`.\n * This can be replaced by directly passing in the connection string with the `connectionString` option,\n * but if you are using the binding shortcuts then this setting is required as it is used in the Azure Function bindings.\n */\n connectionStringSetting?: string\n /**\n * The Cosmos connection string - overrides using the `connectionStringSetting` env.\n *\n * Preferably use the `connectionStringSetting` with the connection string as an environment variable if you are using\n * this within an Azure Functions app.\n */\n connectionString?: string\n /** A list of the models to create, and their container names. */\n models: (builder: Builder) => M\n}\n\nexport type DB<M extends Record<string, BaseModel>> = ReturnType<Options<M>['models']> & {\n client: CosmosClient\n}\n\nexport function createClient<M extends Record<string, BaseModel>>(options: Options<M>): DB<M> {\n const connectionStringSetting = options.connectionStringSetting || 'COSMOS_CONNECTION_STRING'\n const connectionString = options.connectionString ?? process.env[connectionStringSetting]\n\n if (typeof connectionString !== 'string') {\n if (options.connectionString) throw new Error('Missing connection string value (from `options.connectionString`)')\n throw new Error(`Missing connection string for ${connectionStringSetting}`)\n }\n\n const client = new CosmosClient(connectionString)\n\n const builder: Builder = {\n createModel: (container, config = {}) => new BaseModel({ client, container, ...options, ...config }),\n }\n\n const models = options.models(builder)\n\n return {\n client,\n ...models,\n }\n}\n"],"mappings":"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","names":["input"]}