@monospace/sdk
v1.0.0
Published
Readme
@monospace/sdk
TypeScript client for Monospace.
Generate a client from your workspace schema to query typed collections. Field selection, aliases, and included relations determine the result type.
const posts = await client.Post.readMany({
fields: ['id', 'title'],
filter: { views: { _gte: 1000 } },
sort: [{ published_date: { direction: 'desc' } }],
include: {
author: { fields: ['name'] },
comments: { fields: ['text'], limit: 3 },
},
limit: 10,
});
for (const post of posts) {
post.author; // { name: string | null } | null
post.comments; // { data: { text: string | null }[] } | null
post.body; // compile error: `body` was not selected
}Quick start
1. Install
Install the SDK and the monospace CLI. The CLI generates types and is a dev dependency.
npm install @monospace/sdk
npm install --save-dev @monospace/cliWith pnpm, Yarn, or Bun, use pnpm add, yarn add, or bun add (-D for the CLI).
2. Generate your client
npx @monospace/cli sdk init # writes monospace.config.ts
npx @monospace/cli sdk generate # writes ./src/generated/monospace/index.tssdk init asks for your instance URL, workspace, and output directory. You can also write the config yourself.
// monospace.config.ts
import { defineConfig } from '@monospace/sdk/config';
export default defineConfig({
url: 'https://example.monospace.io',
workspace: 'blog',
output: './src/generated/monospace',
});The generator needs an API key to read your schema. Set MONOSPACE_API_KEY in your environment or a .env file, or run npx @monospace/cli login once. Run sdk generate again whenever your schema changes to update the generated types.
3. Query
import { createClient } from './generated/monospace';
export const client = createClient({
url: 'https://example.monospace.io',
workspace: 'blog',
apiKey: process.env.MONOSPACE_API_KEY,
});
const post = await client.Post.readOne({ key: 1, fields: ['title', 'body'] });The generated module exports createClient, your Schema type, and helper types for each collection, such as PostKey, PostCreateOneInput, and PostReadManyResult.
For JavaScript, import createClient from @monospace/sdk and run the same queries without types. The CLI is optional.
Usage
Read
readMany, readOne (by primary key), and readFirst (first match or null) all accept the same query options. Collections without a primary key have no readOne.
Include typed relations at any depth in one request, each with its own filter, sort, limit, and offset.
const latest = await client.Post.readFirst({
fields: ['id', 'title', 'published_date'],
filter: { author: { active: { _eq: true } } },
sort: [{ published_date: { direction: 'desc', nulls: 'last' } }],
});Alias fields and relations
Use responseName:source to rename a field, or to include the same relation twice with different options.
const authors = await client.Author.readMany({
fields: ['id', 'displayName:name'],
include: {
'latest:posts': { fields: ['title'], sort: [{ published_date: { direction: 'desc' } }], limit: 1 },
'popular:posts': { fields: ['title'], sort: [{ views: { direction: 'desc' } }], limit: 3 },
},
});
authors[0].displayName;
authors[0].latest?.data;
authors[0].popular?.data;Filter
Filters support comparison, string, logical, and relational operators. TypeScript rejects unknown fields and operators unsupported by a field's type. To-many relations use _some, _every, and _none.
const posts = await client.Post.readMany({
fields: ['id', 'title'],
filter: {
_or: [{ title: { _icontains: 'rust' } }, { score: { _gt: 4.5 } }],
comments: { _some: { text: { _contains: 'thanks' } } },
},
});Write
Mutations accept fields and include to determine the result type. Nested writes can create, connect, disconnect, update, and delete related items in one transaction. Read-only collections have no write methods.
const post = await client.Post.createOne({
data: {
title: 'Hello, Monospace',
views: 0,
author: { _connect: { key: { id: authorId } } },
},
fields: ['id', 'title'],
include: { author: { fields: ['name'] } },
});
await client.Post.updateOne({
key: 42,
data: { post_tags: [{ _connect: { keys: [{ id: 7 }] } }] },
});
await client.Post.deleteMany({ filter: { views: { _eq: 0 } } });Handle errors
Failed requests throw a MonospaceError with the HTTP status. Authentication and permission failures have their own subclasses.
import { MonospaceAuthError, MonospaceError, MonospacePermissionError } from '@monospace/sdk';
try {
await client.Post.readMany({ fields: ['id'] });
}
catch (error) {
if (error instanceof MonospaceAuthError) {
// 401: missing or invalid API key
}
else if (error instanceof MonospacePermissionError) {
// 403: the key's policies don't allow this
}
else if (error instanceof MonospaceError) {
console.error(error.status, error.message);
}
}Query collections not known at compile time
For collection names chosen at runtime, every method has an untyped $ variant that takes the collection name and your own result type.
const rows = await client.$readMany<{ id: string; name: string }>(collectionName, {
fields: ['id', 'name'],
limit: 50,
});Types
64-bit integers are returned as strings to preserve precision.
Monospace permissions can hide any field from a caller, so every selected value is typed as possibly null by default. If your API key can always read the selected fields, set strictNull: false to use your schema's nullability. This setting applies to the whole client or a single query.
const client = createClient({ url, workspace, apiKey, strictNull: false });
const post = await client.Post.readOne({ key: 1, fields: ['title', 'body'] }, { strictNull: false });
// { title: string; body: string | null }Documentation
docs.monospace.io has the SDK guides.
- SDK Quickstart
- Installation and Client Setup
- Type System
- Filtering, Field Selection, Sorting & Pagination, and Relational Data
- Advanced: composite keys, dynamic collections, and other special cases
- CLI reference
Requirements
- TypeScript 5.7 or newer for generated types, with
strictenabled so nullable fields keep their| null - A runtime with a global
fetch
Versioning
@monospace/sdk follows Semantic Versioning. Breaking changes to the public API ship only in a new major version. Generated types target the SDK version they were generated with, so run sdk generate again after upgrading.
Feedback
Open an issue to report a bug or suggest a change.
