TypeScript client for Azure Cosmos DB
Your Cosmos DB bill,type-checked.
CosmosQL turns your schema into types and your partition key into a compile-time requirement. Point reads stay at 1 RU, scans across partitions are opt-in, and the package ships with zero runtime dependencies.
$ npm install cosmosql- runtime dependencies
- 0
- per point read
- 1 RU
- types from your schema
- TS 5+
- and Bun 1.0+
- Node 18+
- open source
- MIT
§01/The bill
Every query is a line item.
Cosmos DB bills in request units, and the way you address a document decides how many you spend. CosmosQL makes the cheap path the default and the expensive one explicit.
Point read
findUnique({ where: { id, email } })The default. Won't compile without the partition key.
Partition query
findMany({ partitionKey, where })Filters, sorting and paging inside one partition.
Cross-partition scan
findMany({ enableCrossPartitionQuery: true })Refused unless you opt in, by name, in the call.
§02/Compared
Same lookup. Fewer ways to get it wrong.
The official SDK will happily run a query without a partition key and hand you back any. CosmosQL reads the partition key off your schema and won't build until you supply it.
import { CosmosClient } from "@azure/cosmos";
const client = new CosmosClient(connectionString);
const container = client
.database("app")
.container("users");
const { resources } = await container.items
.query({
query: "SELECT * FROM c WHERE c.id = @id",
parameters: [{ name: "@id", value: "u_1" }],
})
.fetchAll();
const user = resources[0]; // any- Result type
- any
- Partition key
- optional, easy to forget
- Runtime dependencies
- 11 direct
import { createClient } from "cosmosql";
import { users } from "./schema";
const db = await createClient({
connectionString,
database: "app",
}).withContainers({ users });
const user = await db.users.findUnique({
where: { id: "u_1", email: "ada@lovelace.dev" },
});
// { id: string; email: string; name: string } | null- Result type
- inferred from your schema
- Partition key
- required by the compiler
- Runtime dependencies
- 0
§03/The API
Prisma-shaped. Partition-aware.
If you have used Prisma, you already know the method names. The difference is that every one of them knows where your partition key lives.
import { container, field } from "cosmosql";
export const users = container("users", {
id: field.string(),
email: field.string(),
name: field.string(),
bio: field.string().optional(),
age: field.number(),
isActive: field.boolean().default(true),
createdAt: field.date(),
}).partitionKey("email");§04/In the box
Everything after the first query.
The parts you would otherwise write yourself, built on the same schema and the same types.
- 01MigrationsVersioned up and down migrations with dry-run plans, checksums and rollback.
defineMigration({ up, down }) - 02Bulk operationsupdateMany and deleteMany in batches, with progress callbacks and retries.
updateMany({ onProgress }) - 03Aggregationscount, sum, avg, min, max and groupBy, typed end to end.
groupBy({ by: "category" }) - 04ManagementHealth checks, schema drift detection and orphaned container cleanup.
management.diffSchema() - 05RetriesThrottled and unavailable responses back off and retry on their own.
retryOptions: { maxRetries: 3 } - 06Raw SQLDrop down to Cosmos SQL with parameters and a typed result.
query<T>({ sql, parameters })
Start with one container.
- 01Install
- 02Define a container
- 03Query it
$ npm install cosmosql