import * as _tanstack_react_query 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; } /** * The HOST's DataPort (contracts amendment 2.2): the port plus the producer of its event streams. * `subscribe` never crosses the frame/host channel — the AppletHost turns it into `rows:`. */ interface HostDataPort extends DataPort { subscribe(event: DataPortEventName, listener: (event: RowsEvent) => void): () => void; } /** A host hook that owns its own port (no provider above it) passes it explicitly. */ interface DataScope { port: DataPort; portKey: string; events?: DataEvents; } interface DataRead { data: T | undefined; loading: boolean; /** The store's own sentence when the read was refused. */ error: RecordsError | null; refresh: () => void; } 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; } 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>; } /** One row exactly as `platform.entity_list_scoped` / `entity_get` answer it. */ interface EntityWireRow { id: Uuid; version: number; organization_id: Uuid; /** The row's own organization's custom fields, already masked by the store (a hidden field is null). */ _custom?: Record; /** The token's own columns; link columns are `EntityWireRef[]`. */ [column: string]: unknown; } interface EntitySortApplied { column: string; dir: "asc" | "desc"; nulls: "first" | "last"; } interface EntityListAnswer { rows: EntityWireRow[]; /** null when not asked for. */ total: number | null; /** The total stopped at the store's count cap; `total` is then a floor, not a count. */ total_is_capped: boolean; page_size: number; /** The keyset cursor of the next page; opaque. */ next_after: string | null; has_more: boolean; sort_applied: EntitySortApplied[]; /** Sort columns the door could not use (it keeps one key today). Said back, never hidden. */ sort_ignored: string[]; feed_cursor: null; } interface EntityGetAnswer { rows: EntityWireRow[]; /** Ids that are not there or that this person cannot open. */ missing: Uuid[]; feed_cursor: null; } interface EntityColumnWire { key: string; label: string; type: string; origin: "platform" | "custom"; required: boolean; readOnly: boolean; linksTo?: string[]; choices?: string[]; /** Custom columns only. */ field_id?: Uuid; organization?: string; organization_id?: Uuid; } interface EntityColumnsAnswer { label: string; token: string; columns: EntityColumnWire[]; can_create: boolean; can_update: boolean; /** The type's landing lane (`platform.entity_default_list_scope`). */ default_scope: Lane; } /** One filter predicate as the door reads it (CONTRACT §3.2). */ interface EntityWirePredicate { column: string; op: string; value?: unknown; values?: unknown[]; negated?: boolean; } interface EntityWireSort { column: string; dir: "asc" | "desc"; nulls: "last"; } interface EntityWireQuery { /** Absent = the type's own landing lane. `organization_id` narrows any lane and never locks it. */ scope?: { kind?: Lane; organization_id?: Uuid; }; filter?: { all: EntityWirePredicate[]; }; search?: string; sort?: EntityWireSort[]; pageSize?: number; } /** What every write door answers (`entity_insert` / `entity_update` / `entity_archive` / `entity_unarchive`). */ interface EntityWriteAnswer { status: string; /** The rows as the read door now reads them, each with its NEW `version`. */ rows: EntityWireRow[]; validation: unknown[]; feed_cursor: null; /** Archive and restore say what they did, in the store's words (trash words on these types). */ message?: string; } /** What a write takes beyond its arguments: the op id minted for it (`mintOpId`). */ interface EntityWriteOptions { opId?: string; } interface EntityListRequest extends EntityWireQuery { token: string; /** Keyset cursor from the previous answer's `next_after`. */ after?: string | null; /** Ask the door to skip the count. Default: counted. */ withTotal?: boolean; archived?: "active" | "archived" | "all"; } interface EntityClient { list(request: EntityListRequest): Promise>; get(request: { token: string; ids: readonly Uuid[]; }): Promise>; /** `organizationId` null reads the custom fields of every organization the person belongs to. */ columns(request: { token: string; organizationId?: Uuid | null; }): Promise>; /** One `entity_data` knob as `platform.knob_resolve` answers it for an organization (live updates read three). */ knob(request: { key: string; organizationId?: Uuid | null; }): Promise>; /** `platform.entity_insert`: rows into the organization the host names (never guessed). */ insert(request: { token: string; organizationId: Uuid; rows: readonly Record[]; } & EntityWriteOptions): Promise>; /** `platform.entity_update`: one row, refused as stale (`MX001`) unless `baseVersion` is still current. */ update(request: { token: string; id: Uuid; baseVersion: number; patch: Record; } & EntityWriteOptions): Promise>; /** `platform.entity_archive` (on these types, the trash). */ archive(request: { token: string; ids: readonly Uuid[]; } & EntityWriteOptions): Promise>; /** `platform.entity_unarchive`. */ unarchive(request: { token: string; ids: readonly Uuid[]; } & EntityWriteOptions): Promise>; /** The organizations the person belongs to, as row security counts them (`iam.my_orgs()`: archived ones closed). */ memberOrganizations(): Promise>; } interface EntityNoticeSink { /** `ids` to re-read, or `null` to re-read everything this reader shows. */ rows(ids: string[] | null): void; } interface EntityRealtime { /** Hear `token` across `organizationIds`; the sink gets one coalesced call per burst. Returns the stop. */ subscribe(target: { token: string; organizationIds: readonly string[]; }, sink: EntityNoticeSink): () => void; } /** A type as the person may use it: its name and whether she may add or change rows (`entity_columns`' own answer). */ interface EntityTypeFacts { token: string; label: string; canCreate: boolean; canUpdate: boolean; } /** The host's entity port: the DataPort, plus `describe` for a token-first screen that has no `sources()` entry. */ interface EntityDataPort extends HostDataPort { /** The type behind an alias, from the same one `entity_columns` read `columns()` uses. */ describe(alias: string): Promise>; } /** The cache prefix of the one entity port: `["@ai-matrx/records","data","entity-data",,…]`. */ declare const ENTITY_PORT_KEY = "entity-data"; interface EntityDataProviderProps { /** The supabase-js client carrying the signed-in person's session (or a records data seam). */ dataSource?: unknown; /** … or an entity client already built. Give one of the two. */ client?: EntityClient; /** * Where a NEW row is saved and which organization's topic is joined first: the host's active * organization, never guessed. `null` while none is chosen — `create` is then refused * `organization_required` and nothing is sent. Lists never narrow by it. */ activeOrganizationId: Uuid | null; /** * Live notices. Left out: the app's ambient realtime manager (`createEntityRealtime()`), never a socket * of our own. `false`: not live (the console says so once). */ realtime?: EntityRealtime | false; /** A second, separate cache (tests, two seats on one page). Default `entity-data`. */ portKey?: string; children?: ReactNode; } /** The engine's scope over the one entity port. */ interface EntityScope extends DataScope { port: EntityDataPort; } declare function EntityDataProvider({ dataSource, client, activeOrganizationId, realtime, portKey, children }: EntityDataProviderProps): react.FunctionComponentElement> | react.FunctionComponentElement<_tanstack_react_query.QueryClientProviderProps>; /** The entity port's scope when an `EntityDataProvider` is mounted above, else null (a host component deciding whether to mount one). */ declare function useOptionalEntityScope(): EntityScope | null; /** The entity port's scope, or a loud failure naming what to mount. */ declare function useEntityScope(): EntityScope; /** One type's list: keyset pages, `loadMore`, `newer`, live. `query.org` is a filter, never a lock. */ declare function useEntityList(token: string, query?: Query): DataRows; /** One type's columns: platform columns and the organizations' custom fields, with `origin`. */ declare function useEntityColumns(token: string): DataRead; /** * The type itself: its name and whether this person may add or change rows. Read from the same * `entity_columns` answer the columns come from (one request per type), cached under the type's own * key, so a shape notice refreshes it with the columns. */ declare function useEntityType(token: string): DataRead; /** One row by id (drawn at once from a loaded page, confirmed by the read). */ declare function useEntityRow(token: string, id: Uuid | null): DataRead; /** * Writes for one type, optimistic with rollback: `create` (into the active organization), `update` (with * the `_version` the person saw), `archive` / `restore`. Each answers `RecordsResult`; a refusal carries * the store's sentence verbatim. */ declare function useEntityWrite(token: string): DataMutations; export { ENTITY_PORT_KEY, EntityDataProvider, useEntityColumns, useEntityList, useEntityRow, useEntityScope, useEntityType, useEntityWrite, useOptionalEntityScope }; export type { DataMutations, DataRead, DataRows, EntityDataProviderProps, EntityScope, EntityTypeFacts };