export type Row = { id: string; created_at?: string; updated_at?: string; created_by?: string; /** The row's place in its environment's change sequence (raised by every write). */ _seq?: number; [column: string]: unknown; }; /** * A row of a view with joins (CONTRACT.md "View joins"): each joined row is * nested under its join's name, e.g. `db.view>("my_answers")` * then `row.question.text`. The joined table's column read rules apply to * the nested row too: a column the caller may not read is absent. */ export type JoinedRow> = Row & J; export type Where = Record; /** * An atomic operator in update values (CONTRACT.md "Concurrency-safe writes"), * computed by the server from the stored value. Build them with `db.inc(n)`, * `db.dec(n)`, `db.min(v)`, `db.max(v)`, `db.append(v)`, `db.remove(v)`. */ export type UpdateOperator = { $inc: number; } | { $dec: number; } | { $min: number | string; } | { $max: number | string; } | { $append: unknown; } | { $remove: unknown; } | { $set: unknown; }; /** A comparison in `expect` (a plain value means equality). */ export type ExpectComparison = { $eq: unknown; } | { $ne: unknown; } | { $gt: number | string; } | { $gte: number | string; } | { $lt: number | string; } | { $lte: number | string; }; /** What must hold on the row at write time: column -> value (equality) or a comparison. */ export type Expect = Record; /** One op of `db.transact`. `id` (and a column value) may be `{ $ref: "" }`. */ export type TransactOp = { op: "insert"; table: string; values: Record; ifAbsent?: Where; ref?: string; } | { op: "update"; table: string; id: string | { $ref: string; }; values: Record; expect?: Expect; } | { op: "delete"; table: string; id: string | { $ref: string; }; expect?: Expect; } | { op: "check"; table: string; id: string | { $ref: string; }; expect: Expect; }; /** The model call of an action with `ai` (CONTRACT.md "AI calls in actions"). */ export type ActionAiResult = /** * Waited for: the answer. `stopReason` "max_tokens" = the text was cut. * With `tools.webSearch`: `sources` (the pages it cites - show them) and `usage.webSearches`. */ { status: "done"; text: string; stopReason: string; usage: { inputTokens: number | null; outputTokens: number | null; webSearches?: number | null; }; sources?: { url: string; title: string; }[]; traceId: number; } /** The call failed (the steps, if any, committed). `error` names the cause. */ | { status: "failed"; error: string; traceId: number; } /** With `saveTo`: running in the background; the row changes when it ends. */ | { status: "queued"; id: string; }; /** What `db.action` resolves with (CONTRACT.md "Actions"). */ export interface ActionResult { /** One entry per step, in order: its row, or null when your read rule does not let you see it. */ results: { row: R | null; }[]; /** The action's own trace row. */ traceId: number; /** The steps' trace rows, in step order. */ traceIds: number[]; /** The model call, when the action has `ai`. */ ai?: ActionAiResult; } export interface TransactResult { /** One entry per op, in order: the row written (or checked; the deleted row for a delete). */ results: { row: R; }[]; traceIds: number[]; } export interface UpdateWithRetryOptions { /** Attempts in all (default 5). */ attempts?: number; /** Milliseconds to wait after the n-th conflict (default: random, up to 200 ms x 2^n; the row is read again after a wait). */ delayMs?: (attempt: number) => number; } export interface QueryOptions { /** * Column -> value (equality) or operators: `$eq $ne $gt $gte $lt $lte $in * $nin $like $ilike $null`, `$contains` (json: an array holding a value, an * object holding a sub-object); `$and` / `$or` nest filters. A location * column: `$near: { column, lat, lng, km }` (top level; rows then carry * `_distance` in km, and `orderBy: "_distance"` sorts by it). */ where?: Where; /** A column, or several: `"due,-created_at"` or `["due", "-created_at"]` (`-` = descending). */ orderBy?: string | string[]; desc?: boolean; limit?: number; } export interface SubscribeOptions { where?: Where; } /** A view's query (CONTRACT.md "Views"): its params, a `where` on its filter columns, a sort on its sort columns, a limit. */ export interface ViewOptions { params?: Record; where?: Where; orderBy?: string | string[]; desc?: boolean; limit?: number; } export interface ViewResult { rows: R[]; traceId: number; } /** What `subscribe` takes besides a table name: a view and its params. */ export interface ViewTarget { view: string; params?: Record; } /** `resyncing`: the realtime tier is catching up after lost history or a `resync` (rows are reloaded). */ export type ConnectionStatus = "connecting" | "open" | "reconnecting" | "resyncing" | "closed"; /** Which socket live data uses: the server's /v1/live, or the realtime tier (from GET /v1/meta). */ export type Transport = "inproc" | "centrifugo"; /** Someone connected to a table's live lane (`db.presence`), or who sent a live message. */ export interface PresenceEntry { id: string; name?: string; role: string; } export interface Change { op: "insert" | "update" | "delete"; /** For deletes the server may send only `{ id }`. */ row: R; } export interface SubscribeHandlers { /** Full result set. Sent first, and again after every reconnect: treat it as a reset. */ onRows?(rows: R[]): void; onChange?(change: Change): void; onError?(error: DabadataError): void; onStatus?(status: ConnectionStatus): void; } export interface Subscription { readonly id: string; /** The table, or for a view subscription the view's name. */ readonly table: string; /** Set for a view subscription. */ readonly view?: string; close(): void; } /** A token pair from POST /v1/users/token or /v1/users/refresh. */ export interface TokenSet { token: string; expiresAt: string; refreshToken: string; refreshExpiresAt: string; } /** Where `db.auth` keeps the signed-in user's tokens. */ export type AuthStorage = "local" | "memory"; /** A hosted end user (`GET /v1/auth/users/me`). */ export interface AuthUser { id: string; email: string; name: string | null; role: string; emailVerified: boolean; /** False until the account has a password (after a sign-in link, an invite, or a social sign-up): offer `update({ password })`. */ hasPassword: boolean; createdAt: string; } /** A pending invite the signed-in user sent (`GET /v1/auth/users/invites`). */ export interface Invite { /** The invited user's id (what `cancelInvite` takes). */ id: string; email: string; name: string | null; role: string; /** When the invite link was made. */ createdAt: string; expiresAt: string; } /** A sign-in provider of hosted users (the browser flow through the hosted pages). */ export type AuthProvider = "google" | "apple" | "microsoft" | "github" | "gitlab" | "discord" | "linkedin" | "slack"; /** The providers whose ID tokens `signInWithIdToken` takes. */ export type IdTokenProvider = "google" | "apple"; /** `GET /v1/auth/users/providers`: the providers whose browser flow is on, and the hosted pages' base URL. */ export interface AuthProviders { /** In the server's order; ids this client does not know are left out. */ providers: AuthProvider[]; /** `/auth//`, ending in "/". */ page: string; } /** Why `onAuthChange` fired. */ export type AuthChangeEvent = "signIn" | "signOut" | "refresh" | "update"; /** A user as the project (and an app admin) sees it: the end-user shape plus its state. */ export interface ManagedUser extends AuthUser { disabled: boolean; lastSignInAt: string | null; /** The social providers linked to the user (google, github, ...). */ providers: AuthProvider[]; } /** One page of `db.auth.admin.listUsers`: newest first; `next` is the `after` of the next page, or null. */ export interface UserList { users: ManagedUser[]; next: string | null; } /** * Managing the project's users from the app (`db.auth.admin`), for signed-in * users who pass the project's `auth.users.manage` rule. A manager may not * change their own role, disable or delete themselves, and only touches (and * gives) the roles of `auth.users.manageRoles`. Rejects with DabadataError * (`unauthorized`, `manage_not_allowed`, `role_not_allowed`, * `self_not_allowed`, `not found`, `validation`). */ export interface AuthAdminApi { /** The users, newest first. `query`: part of an email or name; `limit` 1..1000 (default 100); `after`: the last id of the previous page. */ listUsers(input?: { query?: string; limit?: number; after?: string; }): Promise; /** Gives the user a role (one of `auth.users.manageRoles`). */ setRole(id: string, role: string): Promise; /** Renames the user (null clears the name). */ setName(id: string, name: string | null): Promise; /** Disables the user: signed out everywhere, sign-in refused. */ disable(id: string): Promise; /** Lets a disabled user sign in again. */ enable(id: string): Promise; /** Deletes the user and signs them out everywhere. */ remove(id: string): Promise; } export interface SignUpResult { /** The new user when the project does not ask for email verification; null otherwise. */ user: AuthUser | null; /** True when a confirmation link was mailed and sign-in waits for it. */ verify: boolean; /** * With `verify`: the one-time secret of this sign-up. The client keeps it * (per its `storage`) and `completeLink()` sends it with the confirmation * link: only then do the password and name given here survive (a link * completed elsewhere still verifies, without them). Null otherwise. */ signupSecret: string | null; } /** Hosted end-user accounts of the project (CONTRACT.md "End-user accounts"). Needs `key`. */ export interface AuthApi { /** Creates an account. With email verification on, a link is mailed and `verify` is true. */ signUp(input: { email: string; password: string; name?: string; redirectUrl?: string; }): Promise; /** Email + password. Rejects with DabadataError (`invalid_login`, `email_unverified`, `user_disabled`). */ signIn(input: { email: string; password: string; }): Promise; /** Mails a one-time sign-in link. `redirectUrl` (one of the project's) + `?mode=app` sends it straight to the app. */ signInWithLink(input: { email: string; redirectUrl?: string; }): Promise; /** * Which providers have the browser flow on (`GET /v1/auth/users/providers`), * and `page`, the hosted pages' base (`/auth//`). */ providers(): Promise; /** * Leaves the page for a provider (Google, Apple, Microsoft, GitHub ...) through the hosted pages * (`?redirect=`). `redirectUrl` (one of the * project's) defaults to the current URL without its fragment; call * `completeLink()` there on return. Rejects with DabadataError * `provider_not_enabled` when the provider is not in `providers()`, and * `unsupported` outside a browser (use `signInWithIdToken` in an app). */ signInWithProvider(provider: AuthProvider, input?: { redirectUrl?: string; }): Promise; /** * Signs in with an ID token the app got from Google or Apple itself * (Google Identity Services, Sign in with Apple on iOS): `POST * /v1/auth/users/idtoken`, then like `signIn`. `nonce` is the raw nonce * when the token carries one; `name` is used only when a user is created. * Rejects with DabadataError (`invalid_token`, `provider_not_enabled`, * `email_missing`, `email_unverified`, `signup_closed`, `user_disabled`, * `on_signup_failed`, `validation`, `rate_limited`). */ signInWithIdToken(input: { provider: IdTokenProvider; idToken: string; nonce?: string; name?: string; }): Promise; /** * Finishes a link: reads `#token=...` from the page URL (or `url`), signs in * and clears the fragment. Handles the hosted pages' redirect * (`#token&refreshToken&expiresAt&refreshExpiresAt`) and app-mode links * (`#token=...&type=verify|magic|reset|invite`; reset and invite need * `password`). Null when the URL carries no token. A confirmation link * sends the kept `signupSecret` (or the one given here) along. */ completeLink(input?: { url?: string; password?: string; signupSecret?: string; }): Promise; /** Mails a password reset link. */ resetPassword(input: { email: string; redirectUrl?: string; }): Promise; /** Signs out (this session, or every session with `everywhere`), clears the tokens, reconnects as anonymous. */ signOut(input?: { everywhere?: boolean; }): Promise; /** The signed-in user (cached `GET /v1/auth/users/me`), or null. */ user(input?: { force?: boolean; }): Promise; /** Called on sign-in, sign-out and token refresh. Returns an unsubscribe function. */ onAuthChange(cb: (user: AuthUser | null, event: AuthChangeEvent) => void): () => void; /** * Invites someone as the signed-in user, under the project's * `auth.users.invite` rule. Resolves the same whether or not the address * already has an account. `role` must be one of `auth.users.invite.roles`; * `redirectUrl` (one of the project's, `?mode=app` for the app itself) is * where the invite link sends the person. Rejects with DabadataError * (`unauthorized`, `invite_not_allowed`, `role_not_allowed`, `validation`, * `rate_limited`). */ invite(input: { email: string; name?: string; role?: string; redirectUrl?: string; }): Promise; /** The signed-in user's pending invites, newest first. */ invites(): Promise; /** Cancels one of the signed-in user's pending invites (`id` from `invites()`). */ cancelInvite(id: string): Promise; /** * Changes the signed-in user's name and / or password (`PATCH * /v1/auth/users/me`). A new password needs `currentPassword` when the user * has one, signs every other session out and answers a new token pair, * which the client stores like a sign-in. Resolves with the user; * `onAuthChange` fires with "update". Rejects with DabadataError * (`unauthorized`, `invalid_login` for a wrong current password, * `validation`, `rate_limited`). */ update(input: { name?: string | null; password?: string; currentPassword?: string; }): Promise; /** Managing the project's users from the app (the `auth.users.manage` rule). */ readonly admin: AuthAdminApi; } /** A row of `files_` (CONTRACT.md "File storage"). */ export interface FileRow { id: string; name: string; /** Bytes. */ size: number; /** MIME type. */ type: string; /** The uploader's user id (null for anonymous uploads). */ owner: string | null; created_at: string; updated_at?: string; created_by?: string | null; _seq?: number; } export interface UploadProgress { loaded: number; total: number; } export interface UploadOptions { /** The file's name. Default: the File's own name, else "file". */ name?: string; /** MIME type. Default: the Blob's type, else application/octet-stream. */ type?: string; /** Upload progress (uses XMLHttpRequest when it exists; fetch otherwise, which reports only the end). */ onProgress?: (p: UploadProgress) => void; /** * Then set a file column of a row to the new file (`update` with the row's * rules). The file it replaces is deleted by the server (the column's * `onReplace`). When the update is refused, the upload stays and the error * is thrown with the uploaded file as `error.file` (retry the attach, or * remove it). */ attachTo?: { table: string; id: string; column: string; }; } export interface FileUrl { /** Signed, valid until `expiresAt` (5 minutes). */ url: string; expiresAt: string; /** Seconds the signed URL is valid (300). Absent from older servers. */ expiresIn?: number; /** Buckets anyone may read: a stable URL (cacheable, fine for ). */ publicUrl?: string; } /** What `watchUrl` and `bindImage` take. */ export interface RenewOptions { /** A failed renewal (or, for `bindImage`, a file that fails to load with a fresh URL). */ onError?: (error: unknown) => void; } /** Anything with a `src` that fires `load` / `error`: an , a