import * as _tanstack_react_query from '@tanstack/react-query'; import { QueryClient } from '@tanstack/react-query'; import * as react from 'react'; import { ReactNode } from 'react'; /** A `uuid` as the store writes it. */ type Uuid = string; /** * What KIND of refusal it was. The mapping from SQLSTATE to code lives in * `/core`, and it is checked against `STORE_REFUSAL_CODES` — the list of every * SQLSTATE the store's own bodies raise, generated from the live catalog. A * code the store can raise and this union cannot name is a hole, and the * real-door suite fails on it. */ type RecordsErrorCode = /** `PT409` — somebody else's write won. The record clock moved under you. */ "stale_write" /** * The call needs an organization and none was carried (the platform's `organization_required`; the entity * doors' MX002). Never resolved by guessing one: the person is shown their memberships and picks. */ | "organization_required" /** `42501` — a door. Either the schema is revoked, or the store is switched off, or this principal may not pass. */ | "door" /** `23514` — a Rule, a shape guard, or a validation the store enforces refused it. */ | "refused_by_rule" /** * `22004` / `22023` / `22P02` — the call itself was malformed: a missing id, a * patch that is not an object, or a value that does not fit the type the * store holds for that Field. The store's own message names the value and the * type it could not be read as. */ | "invalid_argument" /** `02000` — the record is not here (deleted, never existed, or another organization's). */ | "not_found" /** `23503` — something it points at is not there. */ | "missing_reference" /** `23505` — that thing is already here. */ | "already_exists" /** `0A000` — the store does not do this yet, and says which part. */ | "not_supported" /** `53400` / `22012` — the store ran out of something, or the arithmetic could not be done. */ | "store_limit" /** * `40001` — the read-access snapshot this connection carries is older than a * write the reader has already observed, so the store refused to answer at * all rather than hand back something they just lost. Retry on a fresh * connection; it is the one refusal in this list that is worth repeating. */ | "stale_read" /** * `57014` — the store cancelled the statement at its own timeout. Nothing was * refused on its merits and nothing was written; the same call can succeed on * a smaller page or a quieter moment. With `stale_read`, one of the only two * refusals in this list worth repeating. */ | "timed_out" /** * THE DOOR THAT DOES NOT EXIST YET. Not an error condition in the database — * this package asked for a door that is not in the live catalogue, so it * refused to guess. `door_absent` always names the door and the lane that * owns it; it is never a silent no-op and never an empty list. */ | "door_absent" /** * `PGRST202` / `PGRST205` — PostgREST could not find the function or the * schema. The database this client is pointed at is not serving the record * store: the schema is not exposed, or the grants are not there. */ | "store_not_served" /** * `PT428` — the move was not refused and it did not happen: a stage gate whose * `on_fail` is `require_approval` filed a request with the people who can * answer it, and left the record exactly where it was. The store's own * sentence is the gate's own sentence, and the DETAIL carries the approval. * * It is the one code in this list that is not a failure. A screen that drew it * as one would tell somebody their work was rejected when it is being * considered; a screen that drew it as success would move the card while * somebody is still deciding. `custom.pipeline_move` answers an object on this * path rather than raising, so a caller only meets PT428 when the move went * through the ORDINARY write door — which is most of them. */ | "needs_approval" /** * THE CALL NEVER REACHED THE STORE. Not a refusal — a transport failure: the * browser lost the network, the gateway answered with an HTTP status and no * body, the request was cancelled. supabase-js hands these back with an EMPTY * `code` (a `FetchError`) or with the HTTP status in it (`"404"`, `"502"`), * neither of which is a SQLSTATE, and until 2026-09-22 they all landed in * `internal` — which is the one classification that tells a screen nothing, * and which made the package print a sentence about ITSELF to the person * (lane TAILS-6). Nothing was written, and the same call can simply be made * again, which is what separates it from `internal`. */ | "unreachable" /** Anything the mapper could not place. It still carries the store's own words. */ | "internal"; /** A refusal, in the store's own words. */ interface RecordsError { code: RecordsErrorCode; /** THE STORE'S message, verbatim. Never paraphrased. */ message: string; /** THE STORE'S hint, verbatim — this is where the remedy is written. */ hint?: string; /** The SQLSTATE, so a caller can be exact when a code is not exact enough. */ sqlstate?: string; /** Whatever the store put in DETAIL, parsed when it was json. */ detail?: unknown; /** * TRUE when this package did not recognize the class it was handed, so a * caller can say so instead of reading the silence as "handled". * * 🚨 IT IS A FLAG AND A SEPARATE SENTENCE, NEVER AN APPENDIX TO `hint` * (lane TAILS-6, 2026-09-22). `mapPgError` used to append "@ai-matrx/records * did not recognize SQLSTATE … from checklistTemplates" ONTO `hint`, and * `hint` is the field every screen treats as THE REMEDY — the sentence a * person is told to act on. So a dropped connection on the Checklists tab * showed a plumbing company "Something went wrong at our end" over a line * about a TypeScript package and a SQLSTATE. A note this package writes about * ITSELF is for whoever debugs it and belongs nowhere a person reads. */ unmapped?: boolean; /** * THIS PACKAGE'S OWN NOTE, for an engineer. Never the store's words and never * a person's: screens render it out of sight (a `title` attribute, a * screen-reader line) or not at all. */ diagnostic?: string; } type RecordsResult = { ok: true; data: T; } | { ok: false; error: RecordsError; }; /** §0. One column, read live — never from a generated file. */ interface Column { /** The store's stable field key — never derived from the label. No platform key starts with "_". */ key: string; label: string; /** The store's word for what the value is: text, number, date, datetime, boolean, list, relation, formula, … */ type: string; origin: "platform" | "custom"; required: boolean; /** For a choice column: the words a person picks from (what a cell reads as and a write takes). */ choices?: string[]; /** For a link column: what it may point at (a custom table's id, or entity tokens). */ linksTo?: string[]; /** * For a link column: the alias, in the SAME port, that serves the table it points at — so a form can list * the linked rows to pick from. Absent when no source of this Applet serves that table. */ linksToSource?: string; /** * The store's own answer to "does this column hold more than one value" (`custom.field.multi`). A choice * column's type is `list` whether it takes one word or many — only this tells them apart (Status, one * value, drew as four switches and the store refused the list it sent: social planner, 2026-10-08). */ multi?: boolean; /** The column a row is named by (the table's `title_field`) — what a link to the row reads as. */ title?: boolean; readOnly: boolean; } /** §0. One row, keyed by `Column.key`. Underscore keys are the runtime's own. */ interface Row { _id: Uuid; _version: number; _organization_id: Uuid; /** On a platform row only: the organization's custom fields (a real column may be named `custom`). */ _custom?: Record; /** On a row read with `archived: true`: when it was archived (ISO). */ _archived_at?: string; /** On a row read with `archived: true`: who archived it, or null when the store cannot say. */ _archived_by_name?: string | null; [key: string]: unknown; } /** §0. What a write carries, keyed by `Column.key`; `_custom` on platform sources. */ type Values = Record & { _custom?: Record; }; type WhereOp = "eq" | "neq" | "lt" | "lte" | "gt" | "gte" | "contains" | "in" | "empty"; interface WhereClause { column: string; op: WhereOp; value?: unknown; } type Lane = "all" | "mine" | "team" | "orgs" | "shared" | "public" | "system"; /** §4. A question of one source. Every clause must hold. */ interface Query { where?: WhereClause[]; search?: string; sort?: { column: string; direction?: "asc" | "desc"; }[]; pageSize?: number; /** Default: the type's "Shown to by default" knob. A custom table has one lane: what row security shows. */ lane?: Lane; /** A filter, never a lock. Default "all". */ org?: Uuid | "all"; /** * THE ARCHIVE (the archived-items law): `true` answers the rows that were archived instead of the live * ones, newest-archived first, each carrying `_archived_at` and `_archived_by_name`, so a screen can offer * Restore. The archive door takes no `where` / `search` / `sort`: they are not applied to it. */ archived?: boolean; } /** §4. One page; `cursor` is null on the last one. */ interface Page { rows: Row[]; total: number | null; cursor: string | null; } interface DataSource { alias: string; label: string; kind: "table" | "entity"; canCreate: boolean; canUpdate: boolean; } /** The one event stream a source has: these ids moved — re-read only them. `null`: cannot tell which. */ interface RowsEvent { ids: Uuid[] | null; /** The table's columns moved (one added, retired or changed): re-read `columns()` too. */ shape?: true; } type DataPortEventName = `rows:${string}`; /** §4. The host's data port, scoped to ONE mounted Applet's aliases. Every call answers, never throws. */ interface DataPort { sources(): Promise>; columns(alias: string): Promise>; list(alias: string, q: Query, cursor?: string): Promise>; get(alias: string, id: Uuid): Promise>; create(alias: string, values: Values): Promise>; update(alias: string, id: Uuid, patch: Values, baseVersion: number): Promise>; archive(alias: string, id: Uuid): Promise>; restore(alias: string, id: Uuid): Promise>; } /** * The frame's side of the §1 event stream `rows:` (the AppletHost's `host.events`). The listener * stays in the frame; only the event payload `{ids, shape?}` crosses the channel. */ interface DataEvents { on(event: DataPortEventName, listener: (event: RowsEvent) => void): () => void; } declare const RECORDS_QUERY_ROOT = "@ai-matrx/records"; /** How long a live subscription outlives the last hook reading its alias (a remount costs nothing). */ declare const DATA_LIVE_GRACE_MS = 30000; declare const dataKeys: { all: (portKey: string) => readonly ["@ai-matrx/records", "data", string]; sources: (portKey: string) => readonly ["@ai-matrx/records", "data", string, "sources"]; alias: (portKey: string, alias: string) => readonly ["@ai-matrx/records", "data", string, "alias", string]; columns: (portKey: string, alias: string) => readonly ["@ai-matrx/records", "data", string, "alias", string, "columns"]; rowsPrefix: (portKey: string, alias: string) => readonly ["@ai-matrx/records", "data", string, "alias", string, "rows"]; rows: (portKey: string, alias: string, query: Query) => readonly ["@ai-matrx/records", "data", string, "alias", string, "rows", string]; row: (portKey: string, alias: string, id: Uuid) => readonly ["@ai-matrx/records", "data", string, "alias", string, "row", string]; }; /** A store refusal carried through the query engine; hooks hand back `.error`, never the wrapper. */ declare class DataRefusal extends Error { readonly error: RecordsError; constructor(error: RecordsError); } interface PortContext { port: DataPort; portKey: string; /** The `rows:` streams. Absent: not live, and the console says so once. */ events?: DataEvents; } interface DataPortProviderProps { port: DataPort; /** The frame's event streams (`host.events`); a host-side mount passes `hostEvents(port)`. */ events?: DataEvents; /** Unique per mounted Applet (its instance id): two ports never share a cache entry. */ portKey: string; /** The frame's QueryClient. Left out, the nearest `QueryClientProvider` above is used. */ queryClient?: QueryClient; children?: ReactNode; } declare function DataPortProvider({ port, events, portKey, queryClient, children }: DataPortProviderProps): react.FunctionComponentElement> | react.FunctionComponentElement<_tanstack_react_query.QueryClientProviderProps>; /** A host hook that owns its own port (no provider above it) passes it explicitly. */ interface DataScope { port: DataPort; portKey: string; events?: DataEvents; } /** A host-side mount (no frame between): the host port's own producer, as the frame's event streams. */ declare function hostEvents(port: { subscribe: DataEvents["on"]; }): DataEvents; declare function useDataPort(scope?: DataScope): PortContext; /** * The frame's QueryClient — the nearest provider's. A host with none (a plain page using a records hook) * gets ONE shared client for the page, held on globalThis so a dual-loaded graph still shares it. */ declare function useRecordsQueryClient(): QueryClient; interface DataRead { data: T | undefined; loading: boolean; /** The store's own sentence when the read was refused. */ error: RecordsError | null; refresh: () => void; } declare function useDataSources(scope?: DataScope): DataRead; /** A server's answer to start from (lane SEED-ENTITY): drawn on the first frame, re-read once it is older than the cache's stale time. */ interface DataSeed { data: T; /** Epoch ms the server asked. */ at: number; } declare function useDataColumns(alias: string, scope?: DataScope, seed?: DataSeed | undefined): DataRead; interface DataRows { rows: Row[]; total: number | null; loading: boolean; /** A next page is being read. */ loadingMore: boolean; hasMore: boolean; error: RecordsError | null; /** Read the page after the last row (keyset): never skips or repeats a row, whatever is written meanwhile. */ loadMore: () => void; /** Rows written by others that belong somewhere in this (deep, filtered or sorted) list; `refresh()` reads them in. */ newer: number; refresh: () => void; } declare function useDataRows(alias: string, query?: Query, scope?: DataScope, seed?: DataSeed | undefined): DataRows; declare function useDataRow(alias: string, id: Uuid | null, scope?: DataScope): DataRead; interface DataMutations { /** Drawn at once (marked `_pending`), replaced by the stored row; removed with the store's sentence on refusal. */ create(values: Values): Promise>; /** Sends the drawn row's `_version` as baseVersion (or `options.baseVersion`). A stale write is refused and the row re-read. */ update(id: Uuid, patch: Values, options?: { baseVersion?: number; }): Promise>; archive(id: Uuid): Promise>; restore(id: Uuid): Promise>; } /** * The row as an optimistic update draws it: the patch over the row, with `_custom` MERGED (a patch naming one * custom field never blanks the others), and the runtime's own keys kept as they were. */ declare function drawPatch(before: Row, patch: Values): Row; declare function useDataMutations(alias: string, scope?: DataScope): DataMutations; export { DATA_LIVE_GRACE_MS, DataPortProvider, DataRefusal, RECORDS_QUERY_ROOT, dataKeys, drawPatch, hostEvents, useDataColumns, useDataMutations, useDataPort, useDataRow, useDataRows, useDataSources, useRecordsQueryClient }; export type { DataMutations, DataPortProviderProps, DataRead, DataRows, DataScope, DataSeed };