import { F as Field } from './field-IlJ97y8H.js'; import { U as Uuid, A as Actor, P as PgError, l as RecordDocument } from './record-C3thPhpR.js'; /** `public.permission_level`, in the store's own order. */ /** * `edit_content` (Notion's "Can edit content") sits between `commenter` and `editor`: it may change * the rows and page content of a thing, never its structure (columns, views, settings) or its * sharing — those stay with `editor` and `admin`. */ type PermissionLevel = "viewer" | "commenter" | "edit_content" | "editor" | "admin"; /** The order the store writes them in, lowest first — `public.permission_level` itself, retired `edit_content` included (the code ladder that labels and offers levels is `@ai-matrx/data/access` PERMISSION_LEVELS). */ declare const STORE_PERMISSION_LEVELS: readonly PermissionLevel[]; /** Whether `held` is the same rung as `needed` or a higher one. Nothing held is never enough. */ declare function atLeast(held: PermissionLevel | null | undefined, needed: PermissionLevel): boolean; /** A level word the store sent, or null when it sent something this package does not know. */ declare function asPermissionLevel(word: unknown): PermissionLevel | null; /** The higher of two levels — what a person holds when two ways in both let them through. */ declare function highestLevel(left: PermissionLevel | null | undefined, right: PermissionLevel | null | undefined): PermissionLevel | null; /** * What one subject's level is, as `custom.my_levels` answers it. `null` is an * ANSWER — "you hold nothing on this" — and never a missing row: a screen that * could not tell "no access" from "not asked yet" would draw one of them as the * other. */ interface MyLevel { id: string; level: PermissionLevel | null; } /** * One client's memory of its own operations. `mint()` names a write and remembers it; `isOwn(opId)` * answers whether a notice is that write's echo. * * OWN-WRITE DETECTION IS PER CLIENT INSTANCE. Two setups in ONE process (a server holding two * identities, a worker beside a UI) must hear each other: a realm-wide ledger made A's write "ours" to * B as well, and B dropped it as an echo (2026-10-08, the README proof). `createRecords` gives every * setup its own ledger and hands it to its client, its entity port and its live-changes port. */ interface OpLedger { mint(): Uuid; isOwn(opId: string | null | undefined): boolean; /** Test seam: forget everything. */ clear(): void; /** Test seam: how many operations are currently remembered. */ readonly size: number; } /** A ledger of one client's own: hand the SAME one to the client that writes and the port that listens. */ declare function createOpLedger(): OpLedger; /** * THE REALM'S DEFAULT LEDGER, IN A GLOBAL SLOT — for hosts that mount one client per realm (a browser * tab) and never name a ledger. Every section of the built package (root, core, react, realtime, data, * typed-table) carries its own copy of this module, and the ESM and CJS builds are two more graphs; a * module-level Map split it so `records/core` minted in one and `records/realtime` checked another — own * writes came back as strangers' changes (2026-10-08). Guarded on the packed tarball by * scripts/check-shared-state.mjs. */ declare const defaultOpLedger: OpLedger; /** * Mint the id for one client operation and remember it as ours (the realm's default ledger). * * ONE CALL IS ONE OPERATION. A fifty-row paste through `recordWriteMany` is ONE op id, because * the store announces one statement once — and it REFUSES a batch carrying two different ones * by name, for exactly that reason. */ declare function mintOpId(): Uuid; /** * Is this notice the echo of a write this realm's default ledger made? * * `undefined`/`null` is NOT ours and never has been: a notice with no `op_id` came from a * server-side write, an agent tool, another client, or a store older than this mechanism — * all of which are changes this browser has not seen and must read. */ declare function isOwnOp(opId: string | null | undefined): boolean; /** Test seam: forget everything. Never called by product code. */ declare function forgetAllOps(): void; /** Test seam: how many operations are currently remembered. */ declare function rememberedOpCount(): number; /** * The data seam: a structural subset of `SupabaseClient`. The package never * opens a database connection of its own and never holds a key — the HOST * supplies a client that already carries the operating person's session, which * is what makes DOOR-5 and AGT-N-4 true (the store sees the person, not us). */ interface RecordsDataSource { /** * Call a door. The name is always a door the live catalog has; `/core` * refuses `door_absent` before it ever reaches here for one that does not. */ rpc(fn: string, args: Record, /** * THE SCHEMA THE DOOR LIVES IN, always supplied: `custom` for the store's * own doors, `iam` for the trust doors (the external principal and the * publish binding, which are the PLATFORM's). A host binding supabase-js * routes this to `client.schema(options.schema).rpc(fn, args)`; a host that * ignores it calls its own default PostgREST profile — `public` — and is * refused by name, which is the failure we want rather than a silent answer * from the wrong system. (Before 2026-09-18 this was described as "only ever * `iam`" and the store's doors went unqualified; the extension's first real * turn proved that asks `public.store_is_open` and finds nothing.) */ options?: { schema?: string; }): PromiseLike<{ data: unknown; error: PgError | null; }>; /** * OPTIONAL. True for the real supabase adapters: the client may open a table page with ONE * `custom.table_page_bundle` call instead of a dozen shape doors one after another (PAGE-BUNDLE). * Leave it off for a test or replay source that does not model the bundle door. */ opensInOneCall?: boolean; /** Reads of `custom.*` go through the schema-qualified builder, never a bare `from`. */ schema(name: string): { from(table: string): RecordsTableQuery; }; } /** The read surface the list and query paths use. A real supabase-js client satisfies it. */ interface RecordsTableQuery { select(columns: string, options?: { count?: "exact" | "planned" | "estimated"; head?: boolean; }): RecordsFilter; } interface RecordsFilter extends PromiseLike { eq(column: string, value: unknown): RecordsFilter; neq(column: string, value: unknown): RecordsFilter; in(column: string, values: readonly unknown[]): RecordsFilter; is(column: string, value: null | boolean): RecordsFilter; gte(column: string, value: unknown): RecordsFilter; lte(column: string, value: unknown): RecordsFilter; order(column: string, options?: { ascending?: boolean; }): RecordsFilter; range(from: number, to: number): RecordsFilter; limit(count: number): RecordsFilter; } interface RecordsResponse { data: unknown; error: PgError | null; count?: number | null; } /** * WHO IS ACTING. AGT-N-4: an agent carries the exact authority of the person * operating it — there is no agent identity and there are no service roles. So * the actor word and the person are two separate facts, and `on_behalf_of` is * meaningful only when the actor is `agent`. */ interface RecordsActor { actor: Actor; /** The signed-in person. The store reads this from the session, not from us; this is for the client's own mirror. */ user_id?: Uuid; /** Only an agent acts on behalf of a person (the store refuses it otherwise). */ on_behalf_of?: Uuid; } /** * OPTIONAL. A live-updates seam. The default degradation is stated rather than * silent: with no port bound, `useRecords` reports `live: false` and the screen * can say "not live" instead of quietly going stale. * * A host binds this with `@ai-matrx/data/react`'s channel hygiene — the package * deliberately does NOT depend on it, so the headless core stays free of a * realtime client and a host that has one keeps using the one it already has. */ interface RecordsRealtimePort { /** * Watch one organization's records of one table. Returns an unsubscribe. * * THE HOST NUDGES THROUGH THIS SINK AND REACHES AROUND NOTHING. Until 0.43.0 the callback * was `(recordId: Uuid) => void` and a host that also wanted to say "the SHAPE moved" * imported `tableShapeChanged` from `@ai-matrx/records/react` and called it directly — * which worked, and which meant the port contract did not describe half of what a port * actually does. A column appearing is not a row changing, and the package owns the * difference. * * NOTHING THAT ARRIVES HERE IS DATA. A nudge has not passed the read door, so the hook * re-reads — by id set when the nudge names ids, whole-page when it cannot. */ subscribeRecords(args: { organization_id: Uuid; table_id: Uuid; }, sink: RecordsChangeSink): () => void; } /** What a realtime port may tell the package. Two calls, because a column is not a row. */ interface RecordsChangeSink { /** * THESE RECORDS MOVED. `null` means "I cannot tell you which" — a reconnect, a tab wake, or * a change above the store's own id cap — and is the one case that costs a whole-page read. * An EMPTY array is not the same thing and is not a nudge at all. */ records(ids: readonly Uuid[] | null): void; /** * THIS TABLE'S SHAPE MOVED — a column added, retired or changed. Re-reading the rows would * redraw the same table without the new column in it, which is a screen that is out of date * while looking healthy. */ shape(): void; } /** * OPTIONAL. Where a refusal that nobody asked about goes — the loud funnel. * Default degradation: `console.error` with the store's own words, never * silence. */ type RecordsErrorSink = (error: { code: string; message: string; hint?: string; where: string; }) => void; /** * OPTIONAL. The merge-field resolver's server half (`W5-MERGE-B`). While it is * not bound, `useMergeField` answers `pending` WITH THE REASON — never a blank, * never a literal placeholder left in the text (DYN-14). */ interface RecordsResolverPort { resolve(args: { organization_id: Uuid; merge_field_keys: string[]; /** The live selection the `state` source reads (DYNAMIC-VALUES §C). */ state?: Record; }): Promise; } /** The one config a host binds. */ interface RecordsConfig { /** REQUIRED. */ dataSource: RecordsDataSource; /** REQUIRED. */ actor: RecordsActor; /** * REQUIRED. REC-29: organizations are hard walls, and the store is keyed * `(organization_id, id)`. There is no "current organization" this package * discovers for itself — the host says which one, every time. * * `null` is the ALL-ORGANIZATIONS mode (access belongs to the person, not the selected * organization): a host whose page lists across every organization the person belongs to * mounts with none. Doors that answer for the person (`custom.data_home*`) take it; a door that * needs one is sent NULL and refuses in its own words, never a guessed organization. */ organizationId: Uuid | null; /** * EVERY organization the person belongs to (active-org-is-never-a-list-filter.md). * `organizationId` above is where NEW things are written and which org a write * runs in; it never decides what a READ shows. With this set, `tableList` / * `tableFacts` span all of these organizations (or one, when the page passes * its `organization_id` filter), and `tableRead` / `recordRead` / * `recordValuesVersioned` open a record in ITS OWN organization. Absent ⇒ the * client reads only `organizationId`, exactly as before. */ organizationIds?: readonly Uuid[]; realtime?: RecordsRealtimePort; /** * This client's own-write memory (`createOpLedger()`). Hand the SAME ledger to the live-changes port * that listens for this client. Absent ⇒ the realm's default ledger (one client per browser tab). */ opLedger?: OpLedger; onError?: RecordsErrorSink; resolver?: RecordsResolverPort; /** * The schema the record store lives in. Exists so a rehearsal branch and * production can differ without a fork; it is `custom` everywhere today. */ schema?: string; /** * Doors the generated catalog (`STORE_DOORS`) does not list YET but this store is known to have — * the nightly clone after a rehearsal, a test store. A screen reading an owed door asks * `doorReady(config, name)` first, so production (which lacks the door until the chair applies it) * is never asked and logs nothing. Leave it out in a shipped host: the regenerated catalog is * the switch. */ knownDoors?: readonly string[]; } /** * RUN AN AGENT ON THIS ROW — a PORT (step 10, 2026-10-08). A table's agent button ("Draft follow-up") hands * the row to an agent with the action's own prompt. The store ships no agent: the host binds the run (the * agent run itself belongs to `@ai-matrx/agents`; the website starts it through the `data.row_action` * mandate). Everything the run needs rides the target, read as THIS reader sees it — no second read. */ interface RowAgentTarget { tableId: Uuid; recordId: Uuid; title: string; action: string; prompt: string; /** The table's name, as the reader sees it. */ tableName: string; /** The table's columns. */ fields: readonly Field[]; /** The row, masked for this reader. */ document: RecordDocument; /** This reader's level on the row. */ level: PermissionLevel | null; } /** The host's agent runner — `createRecords({ rowAgent })`; records-ui's grid calls it for `kind: "agent"` actions. */ interface RowAgentPort { run(target: RowAgentTarget): void | Promise; } export { STORE_PERMISSION_LEVELS as S, asPermissionLevel as l, atLeast as m, createOpLedger as n, defaultOpLedger as o, forgetAllOps as p, highestLevel as q, isOwnOp as r, mintOpId as s, rememberedOpCount as t }; export type { MyLevel as M, OpLedger as O, PermissionLevel as P, RecordsConfig as R, RowAgentPort as a, RecordsErrorSink as b, RecordsDataSource as c, RecordsChangeSink as d, RowAgentTarget as e, RecordsRealtimePort as f, RecordsActor as g, RecordsFilter as h, RecordsResolverPort as i, RecordsResponse as j, RecordsTableQuery as k };