cosmos-orm
Version:
A simple ORM for Cosmos DB
197 lines (196 loc) • 6.64 kB
JavaScript
// src/base-model.ts
import { input } from "@azure/functions";
import { ulid } from "ulidx";
var BaseModel = class {
constructor(options) {
this.options = options;
if (options.connectionStringSetting) {
this.connectionStringSetting = options.connectionStringSetting;
}
if (typeof this.options === "boolean" && !this.options) {
this.fields = { id: false, timestamp: false };
}
if (typeof this.options === "object") {
this.fields = {
...this.fields,
// Don't know why this won't remove the bool type
...this.options.fields
};
}
this.client = options.client.database(options.database).container(options.container);
}
client;
connectionStringSetting = "COSMOS_CONNECTION_STRING";
fields = {
id: true,
timestamp: true
};
/**
* Create an Azure Function app input binding to find a specific document by an input variable.
* You can also provide a type (matching `Record<string, any>`) as the first type generic,
* and it will validate the variable to be one of the types keys.
*
* @example
* ```ts
* const shopInput = orm.shops.createFindBinding(`shop`)
* ```
* @example
* An example with a type to check the variable:
* ```ts
* interface ActivityInput {
* shop: string
* // ...
* }
*
* const shopDocument = orm.shops.createFindBinding<ActivityInput>('shop') // ✅
* const shopDocument2 = orm.shops.createFindBinding<ActivityInput>('id') // ❌
* ```
*/
createFindBinding(variable) {
const binding = `{${variable}}`;
return input.cosmosDB({
databaseName: this.options.database,
containerName: this.options.container,
connection: this.connectionStringSetting,
id: binding,
partitionKey: binding
});
}
/** Create an Azure Function app input binding to fetch all documents from this container. */
createAllBinding() {
return input.cosmosDB({
databaseName: this.options.database,
containerName: this.options.container,
connection: this.connectionStringSetting
});
}
/**
* Create an Azure Function app input binding with a custom SQL query.
* @example
* ```ts
* const inputDoc = orm.posts.createSQLBinding(`SELECT * FROM c WHERE c.deleted_at IS NULL`)
* ```
*/
createSQLBinding(sqlQuery) {
return input.cosmosDB({
databaseName: this.options.database,
containerName: this.options.container,
connection: this.connectionStringSetting,
sqlQuery
});
}
/** Fetch all resources in a container */
async all() {
const { resources } = await this.client.items.readAll().fetchAll();
return resources;
}
/** Fetch a specific resource by its ID */
async find(id) {
const { resource } = await this.client.item(id, id).read();
return resource;
}
/** Fetch multiple resources using their ID's */
async findMany(ids) {
const { resources } = await this.client.items.query({
query: "SELECT * FROM C WHERE ARRAY_CONTAINS(@ids, C.id)",
parameters: [{ name: "@ids", value: ids }]
}).fetchAll();
return resources;
}
/** Find a resource by a specific key */
async findBy(key, value) {
const { resources } = await this.client.items.query({
query: "SELECT * FROM C WHERE C[@key] = @value OFFSET 0 LIMIT 1",
parameters: [
{ name: "@key", value: String(key) },
{ name: "@value", value }
]
}).fetchAll();
const [resource] = resources;
return resource;
}
/** Find multiple resources by a specific key */
async findManyBy(key, value) {
const { resources } = await this.client.items.query({
query: "SELECT * FROM C WHERE C[@key] = @value",
parameters: [
{ name: "@key", value: String(key) },
{ name: "@value", value }
]
}).fetchAll();
const [resource] = resources;
return resource;
}
/** Create a resource */
async create(input2) {
const merged = {
...input2,
id: this.fields.id ? ulid() : input2.id,
createdAt: this.fields.timestamp ? (/* @__PURE__ */ new Date()).toISOString() : input2.createdAt,
updatedAt: this.fields.timestamp ? (/* @__PURE__ */ new Date()).toISOString() : input2.updatedAt
};
const { resource } = await this.client.items.create(merged);
return resource;
}
/** Either update or create a resource */
async upsert(input2) {
const { resource } = await this.client.items.upsert(input2);
return resource;
}
/** Update a resource - replaces the whole resource, so make sure to provide a full input */
async replace(id, input2) {
const merged = {
...input2,
updatedAt: this.fields.timestamp ? (/* @__PURE__ */ new Date()).toISOString() : input2.updatedAt
};
const { resource } = await this.client.item(id, id).replace(merged);
return resource;
}
/** Delete a resource */
async delete(id) {
const { resource } = await this.client.item(id, id).delete();
return resource;
}
/**
* Run a query, and fetch all results
*
* This function accepts a generic, so you can pass in the type of the response if
* you are running a custom select query - for example:
*
* `.query<{ id: string }>('SELECT c.id FROM c') // returns { id: string }[]`
*
* `.query<number>('SELECT VALUE count(c.id) FROM c') // returns [number]`
*
* This is just a wrapper of the `.client.items.query()` function, so you can use that
* instead if you need access to the request metrics for example.
*/
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
async query(query, options) {
const { resources } = await this.client.items.query(query, options).fetchAll();
return resources;
}
};
// src/config.ts
import { CosmosClient } from "@azure/cosmos";
function createClient(options) {
const connectionStringSetting = options.connectionStringSetting || "COSMOS_CONNECTION_STRING";
const connectionString = options.connectionString ?? process.env[connectionStringSetting];
if (typeof connectionString !== "string") {
if (options.connectionString) throw new Error("Missing connection string value (from `options.connectionString`)");
throw new Error(`Missing connection string for ${connectionStringSetting}`);
}
const client = new CosmosClient(connectionString);
const builder = {
createModel: (container, config = {}) => new BaseModel({ client, container, ...options, ...config })
};
const models = options.models(builder);
return {
client,
...models
};
}
export {
BaseModel,
createClient
};
//# sourceMappingURL=index.mjs.map