tapedb
Generated from the public tapedb entry point’s TypeScript declarations. Signatures and comments are the API contract for this preview.
Functions
Section titled “Functions”A predicate over the caller and the row. Throwing denies.
declare function check<F extends Fields = Fields>(test: (context: CheckContext<F>) => boolean): CheckDecision<F>;createClient
Section titled “createClient”Creates a client for one workspace.
declare function createClient<M extends Models>(config: ClientConfig<M>, deps?: ClientDeps): Client<M>;filter
Section titled “filter”read only: the rows the caller may see, as a filter compiled into SQL. An
array is OR of its filters.
declare function filter<F extends Fields = Fields>(build: (context: { auth: AccessAuth;}) => Where<F> | Where<F>[]): FilterDecision<F>;hasRole
Section titled “hasRole”declare function hasRole(claim: RoleClaim, minimum: WorkspaceRole): boolean;hasRoleLevel
Section titled “hasRoleLevel”declare function hasRoleLevel(role: WorkspaceRole, minimum: WorkspaceRole): boolean;Returns a UUIDv7 (or a random UUID when the platform cannot do better).
Ids from one client sort in the order they were made, even within one millisecond: the 12 bits after the version carry a counter (RFC 9562, method 1), seeded randomly in the lower half each millisecond so there is room to count up. When the counter runs out the timestamp moves forward by one, which keeps order at the cost of a millisecond of drift.
declare function id(): string;declare function model<F extends Fields>(fields: F, options?: ModelOptions): Model<F>;A minimum role, checked before any SQL.
declare function role(name: RoleName): RoleDecision;schema
Section titled “schema”Compiles models into the engine schema plus the client’s relation graph. Throws on anything the engine would only discover later: a relation to a model that does not exist, an ambiguous inverse, a reserved name.
declare function schema<M extends Models>(models: M): Schema<M>;Classes
Section titled “Classes”BulkWriteError
Section titled “BulkWriteError”A bulk write that failed after some of its batches committed. written
rows are already changed; cause is the error that stopped the next batch.
Rerunning the same filter continues where it stopped.
declare class BulkWriteError extends Error { readonly written: number; readonly cause: unknown; readonly code: string; constructor(written: number, cause: unknown);}PageQuery
Section titled “PageQuery”declare class PageQuery<R> extends Query<Page<R>> { /** Live only: appends the next page to this subscription. False when there is none. */ loadMore(): Promise<boolean>;}declare class Query<T> implements PromiseLike<T> { protected readonly runtime: Runtime; readonly document: QueryDocument; private readonly project; /** Identifies the query: equal keys share one live subscription. */ readonly key: string; constructor(runtime: Runtime, document: QueryDocument, project: Project<T>, kind: string); then<R1 = T, R2 = never>(onFulfilled?: ((value: T) => R1 | PromiseLike<R1>) | null, onRejected?: ((reason: unknown) => R2 | PromiseLike<R2>) | null): Promise<R1 | R2>; catch<R = never>(onRejected?: ((reason: unknown) => R | PromiseLike<R>) | null): Promise<T | R>; finally(onFinally?: (() => void) | null): Promise<T>; /** Live results. The listener runs now with the current state, then on every change. */ subscribe(listener: (state: LiveState<T>) => void): () => void; /** * The current live state. Referentially stable while nothing changed, so it * can back `useSyncExternalStore`. */ snapshot(): LiveState<T>;}declare class Write implements PromiseLike<WriteResult> { private readonly runtime; readonly ops: TxOp[]; readonly id: string; private sent; constructor(runtime: Runtime, ops: TxOp[], id: string); then<R1 = WriteResult, R2 = never>(onFulfilled?: ((value: WriteResult) => R1 | PromiseLike<R1>) | null, onRejected?: ((reason: unknown) => R2 | PromiseLike<R2>) | null): Promise<R1 | R2>; catch<R = never>(onRejected?: ((reason: unknown) => R | PromiseLike<R>) | null): Promise<WriteResult | R>; finally(onFinally?: (() => void) | null): Promise<WriteResult>;}Interfaces
Section titled “Interfaces”Access
Section titled “Access”interface Access<F extends Fields = Fields> { read?: RoleDecision | FilterDecision<F> | CheckDecision<F>; create?: RoleDecision | CheckDecision<F>; update?: RoleDecision | CheckDecision<F>; delete?: RoleDecision | CheckDecision<F>;}AccessAuth
Section titled “AccessAuth”interface AccessAuth { /** The caller's subject, from your identity provider. */ sub: string; role: WorkspaceRole; workspaceId: string;}BroadcastTopic
Section titled “BroadcastTopic”A broadcast topic: fire-and-forget messages between clients of a workspace.
interface BroadcastTopic<T = unknown> { /** * Sends to every other client listening on the topic. Returns false, and * drops the message, when the socket is not open: a broadcast is never * delivered late. */ send(message: T): boolean; /** * Listens until the returned function is called. Your own messages are not * delivered back to you. `onError` hears refusals, such as an `authorize` * hook saying no. */ subscribe(listener: (message: T, from: TopicSender) => void, options?: { onError?: (error: QueryError) => void; }): () => void;}CheckContext
Section titled “CheckContext”interface CheckContext<F extends Fields = Fields> { auth: AccessAuth; /** The stored row, for update, delete and read. */ row?: Partial<Row<F>> & { id: string; }; /** The incoming values, for create and update. */ data?: Record<string, unknown>; /** Field names this write sets. */ changed?: string[];}ClientConfig
Section titled “ClientConfig”interface ClientConfig<M extends Models> { schema: Schema<M>; /** The Worker's origin, e.g. https://api.example.com or http://localhost:8787. */ url: string; /** Workspace id (a UUID): which database this client talks to. */ workspace: string; /** Returns a short-lived claim for every request. Omit in `tapedb dev`. */ getToken?: () => Promise<string>; /** A queued write the engine refused for good during a background flush. */ onWriteRejected?: (rejection: WriteRejection) => void;}Worker bindings and vars. tapedb init writes exactly these.
Three resources: one Durable Object namespace, two R2 buckets. There is no D1 and no identity database: the engine consumes a verified claim and never reads a session.
interface Env { /** The database room. One instance per workspace. */ WORKSPACE: DurableObjectNamespace<WorkspaceRoom>; /** Transaction log segments and snapshots. */ DATA: R2Bucket; /** Uploaded files. Optional unless `db.$files` is used. */ FILES?: R2Bucket; ENVIRONMENT?: string; SERVICE_VERSION?: string; ENABLE_SMOKE?: string; ALLOWED_ORIGINS?: string; /** Shared HS256 secret used to verify role claims. */ TAPEDB_CLAIM_SECRET: string; /** Optional expected `iss` claim; checked only when set. */ TAPEDB_CLAIM_ISSUER?: string; /** Optional expected `aud` claim; checked only when set. */ TAPEDB_CLAIM_AUDIENCE?: string; /** * Enables the development claim issuer. Set to `"true"` by `tapedb dev` on * the command line and never written to `wrangler.jsonc`, so a plain * `wrangler deploy` has no dev claim route. */ TAPEDB_DEV_CLAIMS?: string;}FieldFilter
Section titled “FieldFilter”interface FieldFilter<T = Scalar> { equals?: T; not?: T | FieldFilter<T>; in?: T[]; notIn?: T[]; gt?: T; gte?: T; lt?: T; lte?: T;}FilesClient
Section titled “FilesClient”interface FilesClient { upload(file: Blob, options: { path: string; }): Promise<StorageObject>; download(path: string): Promise<Blob>; delete(path: string): Promise<void>; list(options?: { prefix?: string; }): Promise<StoredFile[]>;}FindArgs
Section titled “FindArgs”interface FindArgs<M extends Models, F extends Fields> { where?: Where<F, M>; orderBy?: OrderBy<F>; take?: number; include?: Include<M, F>; /** * Return only these fields. The engine reads and sends only them, plus the * id and sequence it needs for live updates; the result type narrows to * match. Relations named here are included. Not combined with `include`. */ select?: Select<M, F>;}LiveState
Section titled “LiveState”createClient(): one accessor per model, plus $transaction, $files and
$presence.
Reads return a Query: await it for one result over HTTP, or subscribe to
it for live results over the workspace socket. Writes return a Write:
await it to send it alone, or pass several to $transaction to send them as
one log entry. Both are lazy, so building one costs nothing.
Everything below translates into the engine’s query documents and ops; the socket, cache, outbox and retry logic are the same ones the engine client has always used.
interface LiveState<T> { /** Undefined until the first result arrives. */ data: T | undefined; isLoading: boolean; error: QueryError | null;}ModelClient
Section titled “ModelClient”interface ModelClient<M extends Models, F extends Fields> { findMany<A extends FindArgs<M, F> = FindArgs<M, F>>(args?: A): Query<Result<M, F, A>[]>; findFirst<A extends FindArgs<M, F> = FindArgs<M, F>>(args?: A): Query<Result<M, F, A> | null>; findUnique<A extends { where: { id: string; }; include?: Include<M, F>; select?: Select<M, F>; }>(args: A): Query<Result<M, F, A> | null>; /** One page in a stable order. Defaults to `orderBy: { id: "asc" }` (ids are time-ordered). */ findPage<A extends PageArgs<M, F> = PageArgs<M, F>>(args?: A): PageQuery<Result<M, F, A>>; count(args?: { where?: Where<F, M>; }): Query<number>; /** * Count, sum, average, minimum and maximum over the rows matching `where`, * as the caller's read rules see them. Await it or subscribe to it. */ aggregate<A extends AggregateArgs<M, F>>(args: A): Query<AggregateOutput<F, A>>; /** The same measures, one row per distinct value of the `by` fields. */ groupBy<A extends GroupByArgs<M, F>>(args: A): Query<GroupRow<F, A>[]>; create(args: { data: CreateData<F, M>; }): Write; /** * Writes every row matching `where`. Sent straight to the engine, which * resolves the rows: unlike single-row writes it is not applied * optimistically and not queued offline, because which rows match is the * engine's answer, not the cache's. * * Up to 1000 rows commit as one transaction. Past that the rows are written * in batches of 1000, in id order, each its own transaction; if one fails, * a `BulkWriteError` reports how many rows the earlier batches wrote, and * running the same call again continues with the rows still matching. */ updateMany(args: { where: Where<F, M>; data: UpdateData<F, M>; }): Promise<BulkResult>; /** Deletes every row matching `where`. Batched like `updateMany`. */ deleteMany(args: { where: Where<F, M>; }): Promise<BulkResult>; update(args: { where: { id: string; }; data: UpdateData<F, M>; }): Write; upsert(args: { where: { id: string; }; create: CreateData<F, M>; update: UpdateData<F, M>; }): Write; delete(args: { where: { id: string; }; }): Write;}interface Page<R> { rows: R[]; hasMore: boolean; /** Pass back as `after` for the next page; null on the last page. */ cursor: string | null;}PageArgs
Section titled “PageArgs”interface PageArgs<M extends Models, F extends Fields> extends FindArgs<M, F> { /** A previous page's `cursor`. */ after?: string | null;}PresencePeer
Section titled “PresencePeer”interface PresencePeer { clientId: string; presence: unknown; cursor?: unknown; drag?: unknown;}PresenceRoom
Section titled “PresenceRoom”interface PresenceRoom { set(presence: unknown): void; cursor(cursor: unknown): void; drag(drag: unknown): void; clearDrag(): void; subscribe(listener: (peers: PresencePeer[]) => void): () => void; leave(): void;}QueryError
Section titled “QueryError”interface QueryError { code: string; message: string;}RoleClaim
Section titled “RoleClaim”interface RoleClaim { /** Platform user id. */ sub: string; /** Platform workspace id (the DO name). */ workspaceId: string; /** Numeric workspace role level. */ role: WorkspaceRole; /** Expiry, seconds since epoch. */ exp: number; /** Issued at, seconds since epoch. */ iat: number; iss?: string; aud?: string;}Schema
Section titled “Schema”interface Schema<M extends Models = Models> extends TapeDBSchema { readonly models: M; readonly graph: Record<string, ModelInfo>;}StorageObject
Section titled “StorageObject”interface StorageObject { id: string; key: string; size: number; etag: string; contentType: string;}StoredFile
Section titled “StoredFile”One row of the workspace’s file index.
interface StoredFile { id: string; path: string; r2Key: string; size: number; contentType: string | null; createdAt: number; updatedAt: number;}TopicSender
Section titled “TopicSender”Who sent a broadcast message.
interface TopicSender { /** The sending socket's client id: one per open client. */ clientId: string; /** The claim subject (user id) the sender connected with. */ userId: string;}WriteRejection
Section titled “WriteRejection”A queued write the server refused for good and the outbox dropped.
interface WriteRejection { clientTxId: string; ops: TxOp[]; code: string; message: string;}WriteResult
Section titled “WriteResult”interface WriteResult { id: string; /** "queued" when the send failed transiently and the outbox will retry. */ status: "applied" | "queued";}Client
Section titled “Client”type Client<M extends Models> = { [K in keyof M & string]: ModelClient<M, FieldsOf<M[K]>>;} & Omit<ClientExtras, "$export"> & { $export(): AsyncIterable<ExportRecord<M>>;};CreateData
Section titled “CreateData”type CreateData<F, M extends Models = Models> = { id?: string;} & RequiredParents<F> & { [K in RequiredScalarKeys<F>]: InputOf<F[K]>;} & { [K in OptionalScalarKeys<F>]?: InputOf<F[K]>;} & { [K in BelongsToKeys<F>]?: BelongsToInput<F[K], M>;} & { [K in BelongsToKeys<F> as `${K}Id`]?: string | null;} & { [K in HasManyKeys<F>]?: HasManyInput<F[K], M>;};DeleteBehaviour
Section titled “DeleteBehaviour”type DeleteBehaviour = "setNull" | "cascade" | "restrict";ExportRecord
Section titled “ExportRecord”One line of $export().
type ExportRecord<M extends Models = Models> = { type: "header"; format: "tapedb-export"; version: 1; workspaceId: string; /** Workspace sequence when the export started. */ seq: number; exportedAt: number; schemaVersion: string | null; /** Models exported, in order. */ models: string[]; /** Models the caller may not read, and why. */ skipped: Array<{ model: string; reason: string; }>;} | { [K in keyof M & string]: { type: "row"; model: K; data: Row<FieldsOf<M[K]>>; };}[keyof M & string] | { type: "file"; file: StoredFile & { id: string; r2Key: string; createdAt: number; updatedAt: number; };} | { type: "end"; startSeq: number; /** Workspace sequence when the export finished. */ seq: number; rows: number; counts: Record<string, number>; /** True when no write landed during the export: it is the workspace exactly at `seq`. */ consistent: boolean;} | { type: "error"; code: string; message: string;};Include
Section titled “Include”type Include<M extends Models, F extends Fields> = { [K in RelationKeys<F>]?: true | IncludeArgs<M, TargetFields<F[K], M>>;};OrderBy
Section titled “OrderBy”type OrderBy<F> = Partial<Record<FilterKeys<F>, "asc" | "desc">> | Partial<Record<FilterKeys<F>, "asc" | "desc">>[];Result
Section titled “Result”type Result<M extends Models, F extends Fields, A> = A extends { select: infer S;} ? Selected<M, F, S> : Row<F> & (A extends { include: infer I;} ? WithInclude<M, F, I> : unknown);A stored row: its fields, the foreign keys of its belongsTo relations, and the system fields.
type Row<F> = { id: string; createdAt: number; updatedAt: number;} & { [K in ScalarKeys<F>]: OutputOf<F[K]>;} & { [K in BelongsToKeys<F> as `${K}Id`]: ForeignKeyOf<F[K]>;};ScalarKind
Section titled “ScalarKind”Field and relation builders: the t in model({ title: t.string() }).
Every builder is immutable: a modifier returns a new builder, so a shared
base field cannot be changed by one model’s .optional(). The type
parameters carry what the client needs to infer rows and inputs.
type ScalarKind = "string" | "number" | "boolean" | "date" | "json";UpdateData
Section titled “UpdateData”type UpdateData<F, M extends Models = Models> = { [K in ScalarKeys<F>]?: InputOf<F[K]>;} & { [K in BelongsToKeys<F>]?: BelongsToInput<F[K], M>;} & { [K in BelongsToKeys<F> as `${K}Id`]?: string | null;} & { [K in HasManyKeys<F>]?: HasManyInput<F[K], M>;};type Where<F extends Fields = Fields, M extends Models = Models> = Scalars<F> & { [K in RelationKeys<F>]?: F[K] extends HasMany ? ManyFilter<WhereLeaf<TargetFieldsOf<F[K], M>>> : WhereLeaf<TargetFieldsOf<F[K], M>> | null;} & { AND?: Where<F, M> | Where<F, M>[]; OR?: Where<F, M>[]; NOT?: Where<F, M> | Where<F, M>[];};WorkspaceRole
Section titled “WorkspaceRole”type WorkspaceRole = (typeof WORKSPACE_ROLE_LEVELS)[keyof typeof WORKSPACE_ROLE_LEVELS];Constants
Section titled “Constants”declare const t: { string: () => Field<"string", false, false>; number: () => Field<"number", false, false>; boolean: () => Field<"boolean", false, false>; /** Stored as epoch milliseconds; accepts a `Date`, milliseconds or an ISO string. */ date: () => Field<"date", false, false>; /** Any JSON value. Not filterable or sortable. */ json: () => Field<"json", false, false>; belongsTo: <T extends string>(target: T, options?: BelongsToOptions) => BelongsTo<T, false>; hasMany: <T extends string>(target: T, options?: HasManyOptions) => HasMany<T>;};WORKSPACE_ROLE_LEVELS
Section titled “WORKSPACE_ROLE_LEVELS”Shared role-claim contract between the Worker (JWT verification) and the workspace engine (permission checks). consumes this shape.
declare const WORKSPACE_ROLE_LEVELS: { readonly GUEST: 0; readonly USER: 1; readonly MEMBER: 2; readonly EDITOR: 3; readonly MODERATOR: 4; readonly ADMIN: 5; readonly OWNER: 6; readonly FOUNDER: 7; readonly DEVELOPER: 8;};