DocsPricingBlogChangelog
Examples use: Lease

Every code example, in your word.

Sign inStart building

SDK

Open

npm i leaseclock installs a typed client generated from your definition. $.Lease.renew('4b', { endsOn }) is checked by your editor before it runs. The package is republished whenever the definition changes.

In short

The client is published to npm as leaseclock, under your name. It calls leaseclock.com/api, its errors link to leaseclock.com/docs, and nothing in it names SaaS.Dev. Northgate Rentals' developers use it to script their own leases, and your team uses the same package, with the same permissions as the app.

Each Noun is a property on the client. $.Lease has list, get, create and update, plus renew and end from the Lease file, and a Verb you add appears as a method in the next version. Field types, required fields and choice options become TypeScript types. A Verb's does: line is its method's hover text, and each input's says: line describes that argument.

Leaseclock's client

Northgate Rentals renews Dana Whitfield's lease on 4B from a script:

renew.ts
import { Leaseclock } from 'leaseclock'

const $ = new Leaseclock({ token: process.env.LEASECLOCK_TOKEN })

const lease = await $.Lease.renew('4b', { endsOn: '2027-11-30' })

lease.endsOn   // '2027-11-30'
lease.status   // 'active'

Hovering renew in the editor shows the method as Lease.mdx declares it:

Hover on renew
(method) Lease.renew(
  id: string,
  input: { endsOn: string }
): Promise<Lease>

// Move the end date forward. Runs from: active.
// @param input.endsOn  the new end date

A misspelled field is caught as it is typed, before anything reaches the API:

renew.ts
await $.Lease.renew('4b', { endsAt: '2027-11-30' })
//                          ~~~~~~
// Object literal may only specify known properties, and 'endsAt'
// does not exist in type '{ endsOn: string; }'.

How to call it

Install and sign in

Terminal
npm i leaseclock

Each caller creates a key in Leaseclock under Settings → API keys, sets it as LEASECLOCK_TOKEN and passes it to the client. A key acts with its owner's role, so Priya Shah's key reaches Northgate's records and no one else's. A Test key reaches the Test copy, where Stripe charges nothing.

const $ = new Leaseclock({ token: process.env.LEASECLOCK_TOKEN })

Read and write records

list filters on any field and sorts on any field marked indexed: true. It returns up to 200 records a page, and for await walks every page. Priya's renewal reminder lists the active leases ending this year:

ending-soon.ts
for await (const lease of $.Lease.list({
  status: 'active',
  endsOn: { before: '2027-01-01' },
  sort: 'endsOn',
})) {
  console.log(lease.unit, lease.renter, lease.endsOn)
}
Output
4b  dana.whitfield@example.com  2026-11-30
2a  marcus.bell@example.com     2026-12-15

get, create and update take the same field names as the Lease file. A missing required field is a type error.

const lease = await $.Lease.get('4b')

await $.Lease.create({ unit: '5a', renter: 'ines.moreau@example.com', endsOn: '2027-10-31' })

await $.Lease.update('4b', { renter: 'dana.whitfield@northgate.example' })

Run a Verb

A Verb's method takes the record's id and the Verb's input, and returns the record after the change. Each call carries an idempotency key, so a retried renew after a dropped connection renews 4B once. end takes no input:

await $.Lease.end('2a')

Preview and undo

Pass { preview: true } to see what a call would change. Nothing is saved, and no customer is emailed or charged.

const preview = await $.Lease.renew('4b', { endsOn: '2027-11-30' }, { preview: true })

preview.changes // { endsOn: ['2026-11-30', '2027-11-30'] }

Every write is kept in the record's history, and undo reverses the latest one:

await $.Lease.undo('4b') // endsOn is 2026-11-30 again

Handle errors

Your editor catches wrong names and wrong types. What depends on the data, such as a lease's state or a plan's limit, comes back as a LeaseclockError with a status, a code and the fix:

renew.ts
import { Leaseclock, LeaseclockError } from 'leaseclock'

try {
  await $.Lease.renew('5a', { endsOn: '2027-09-30' })
} catch (err) {
  if (err instanceof LeaseclockError) {
    err.status  // 422
    err.code    // 'wrong-state'
    err.message // 'renew runs from active, and Lease 5A is ended'
    err.docs    // 'https://leaseclock.com/docs/errors#wrong_state'
  }
}

Every code is listed with its cause and fix in Errors.

Types and schemas

The package exports a type for every Noun and every Verb's input, so your own functions can take a Lease:

import type { Lease } from 'leaseclock'

function reminder(lease: Lease) {
  return `${lease.renter}: your lease on ${lease.unit.toUpperCase()} ends ${lease.endsOn}.`
}
leaseclock/index.d.ts
export interface Lease {
  $id: string
  $type: 'Lease'
  unit: string                  // the Unit's id
  renter: string                // an email address
  endsOn: string                // a date, YYYY-MM-DD
  status?: 'active' | 'ended'
}

leaseclock/schemas holds the same definition as JSON Schema, for checking a form or a file before it is sent. The says: line becomes each property's description:

import { schemas } from 'leaseclock/schemas'

schemas['Lease.renew']
schemas['Lease.renew']
{
  "type": "object",
  "properties": {
    "endsOn": { "type": "string", "format": "date", "description": "the new end date" }
  },
  "required": ["endsOn"],
  "additionalProperties": false
}

The API's OpenAPI file at leaseclock.com/api/openapi.json carries the same schemas, so a client in another language agrees with this one.

New versions

Each push that changes a Noun, a field or a Verb publishes a new version of leaseclock, and the changelog entry for that change names it.

  • Adding a field, a Verb or a Noun publishes a minor version. When Lease gained rent, the new version added rent: number to the Lease type, beside the changelog entry "Leases now track rent".
  • Renaming or removing one publishes a major version, so a project pinned to ^1 keeps building until its owner upgrades.

Questions

Do I write or maintain the SDK? No. It is generated from your definition on each push and published for you. Overrides you make to the app's screens or theme leave it untouched.

Where does it run? Node, Bun, Deno, edge workers and the browser. In the browser, it uses the signed-in person's session instead of a key, so no key ships to a page.

Can my customers' agents use it? Their agents connect to MCP, whose do tool runs the same calls. Agents that write code install leaseclock like any other package.

Does the SDK follow a renamed Noun? Yes. Rename Lease to Tenancy and the next major version has $.Tenancy, the Tenancy type and schemas['Tenancy.renew']. The CLI and API change in the same push.

Contents