@ai-matrx/entity-data 0.3.44 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/port/port.ts","../../src/core/client.ts","../../src/core/errors.ts","../../src/core/realtime.ts","../../src/core/wire.ts","../../src/port/rows.ts"],"sourcesContent":["// src/port/port.ts — @ai-matrx/entity-data/port\n//\n// THE HOST'S DATAPORT FOR ENTITY ALIASES (Applets CONTRACTS §4; PHASE-B-APPLETS U5, read-only).\n//\n// One port per mounted Applet, scoped to the aliases its record declares: `{kind:\"entity\", token}`.\n// Every call goes through the store's entity doors under the signed-in person's seat — the store\n// decides what she may see; nothing here widens or narrows it.\n//\n// · sources — canCreate / canUpdate are `entity_columns`' own answer for the person.\n// · columns — live: platform columns plus the organizations' custom fields (`origin`), the identity\n// columns left to `_id` / `_version` / `_organization_id`.\n// · list — KEYSET: the cursor is the door's `next_after`; the lane defaults to the type's own\n// landing lane (decided by the door, never here); `org` is a filter, never a lock.\n// · get — by id, through the same door every list row opens through. Every id asked in the same\n// tick (the engine re-reading the rows a notice named) is ONE `entity_get(ids)`.\n// · create — `entity_insert` into the organization the HOST names (`activeOrganizationId()`), never\n// guessed; with none, refused `organization_required` before anything is sent.\n// · update — `entity_update` with the `baseVersion` the person saw; a link (`EntityRef[]`) goes back\n// as its id, `_custom` as `{key: value}`. Stale → `stale_write` with the store's sentence.\n// · archive / restore — `entity_archive` / `entity_unarchive` (on these types, the trash).\n// Every write mints an op id (`mintOpId`) and sends it as header `x-matrx-op-id`, so the\n// notice of the writer's own change comes back marked and is dropped.\n// · subscribe — LIVE (U6): `rows:<alias>` hears the organization topics `mx:entity-org:<org>:<token>`\n// through the host's `realtime` (core/realtime.ts). An alias with an organization hears\n// that one; otherwise the active organization, then the organizations of the rows this\n// port has handed out (most rows first), then the person's other memberships — capped by\n// knob entity_data.realtime_max_org_topics. `{ids}` names rows to re-read; `{ids: null}`\n// re-reads all. With no `realtime` bound it says so out loud and stays a no-op.\n\nimport { isUuidShape } from \"@ai-matrx/kit/uuid\";\nimport type {\n Column,\n DataPortEventName,\n DataSource,\n HostDataPort,\n Page,\n Query,\n Row,\n RowsEvent,\n Uuid,\n Values,\n} from \"@ai-matrx/records/data\";\nimport { mintOpId, type RecordsResult } from \"@ai-matrx/records\";\nimport { createEntityClient, type EntityClient } from \"../core/client\";\nimport { refusal } from \"../core/errors\";\nimport { DEFAULT_MAX_ORG_TOPICS, orderOrganizationTopics, type EntityRealtime } from \"../core/realtime\";\nimport type { EntityColumnsAnswer, EntityGetAnswer, EntityWriteAnswer, EntityWireRow } from \"../core/types\";\nimport { toWire } from \"../core/wire\";\nimport { toColumns, toRow } from \"./rows\";\n\n/** An alias as the Applet record declares it for an entity: one token (a `platform.entity_types` word). */\nexport interface EntityAlias {\n kind: \"entity\";\n token: string;\n /** Whose custom fields `columns()` reads. Left out: every organization the person belongs to. */\n organizationId?: Uuid;\n /** Shown in `sources()`; the type's own name when left out. */\n label?: string;\n}\n\nexport interface CreateEntityDataPortOptions {\n /** A client from `createEntityClient` … */\n client?: EntityClient;\n /** … or the supabase-js client (or `rpc` source) to build one from. Give one of the two. */\n dataSource?: unknown;\n aliases: Readonly<Record<string, EntityAlias>>;\n /**\n * TOKEN-FIRST (the `/react` layer): an alias nobody declared is read as a type token —\n * `list(\"party\")` is `{kind:\"entity\", token:\"party\"}`. `sources()` still lists only the declared\n * aliases. Off by default: an Applet's port answers only what its record declares.\n */\n tokensAreAliases?: boolean;\n /**\n * The active organization (CONTRACTS §4: `viewer().activeOrganizationId`), read at the moment it is\n * needed: where `create` saves, and the first live topic joined. The host's own answer, never guessed;\n * null/undefined means none is chosen and `create` is refused `organization_required`.\n */\n activeOrganizationId?: () => Uuid | null | undefined;\n /** A fixed active organization, for a host whose port lives no longer than its choice. `activeOrganizationId` wins. */\n organization?: Uuid | null;\n /** The host's live-notice layer (`createEntityRealtime`). Left out, lists are as fresh as their last read. */\n realtime?: EntityRealtime;\n}\n\n/** A value as rows carry it → as the write doors take it: a link (`EntityRef[]` or one ref) becomes its id. */\nexport function toWireValue(value: unknown): unknown {\n if (isRef(value)) return value.id;\n if (Array.isArray(value) && value.length > 0 && value.every(isRef)) {\n // One link is one id; none is null; several are sent as ids and the door refuses a one-record column by name.\n return value.length === 1 ? (value[0] as { id: string }).id : value.map((ref) => (ref as { id: string }).id);\n }\n return value;\n}\n\nfunction isRef(value: unknown): value is { token: string; id: string } {\n return value !== null && typeof value === \"object\" && !Array.isArray(value)\n && typeof (value as { token?: unknown }).token === \"string\" && typeof (value as { id?: unknown }).id === \"string\";\n}\n\n/** `Values` → the doors' `{column: value, _custom: {key: value}}`: links back to ids at both levels. */\nexport function toWireValues(values: Values): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(values)) {\n if (value === undefined) continue;\n if (key === \"_custom\" && value && typeof value === \"object\" && !Array.isArray(value)) {\n out._custom = Object.fromEntries(Object.entries(value as Record<string, unknown>).filter(([, v]) => v !== undefined).map(([k, v]) => [k, toWireValue(v)]));\n continue;\n }\n // A link column emptied in a grid arrives as [] and means \"no link\".\n out[key] = Array.isArray(value) && value.length === 0 && key !== \"_custom\" ? null : toWireValue(value);\n }\n return out;\n}\n\n/** A type as the person may use it: its name and whether she may add or change rows (`entity_columns`' own answer). */\nexport interface EntityTypeFacts {\n token: string;\n label: string;\n canCreate: boolean;\n canUpdate: boolean;\n}\n\n/** The host's entity port: the DataPort, plus `describe` for a token-first screen that has no `sources()` entry. */\nexport interface EntityDataPort extends HostDataPort {\n /** The type behind an alias, from the same one `entity_columns` read `columns()` uses. */\n describe(alias: string): Promise<RecordsResult<EntityTypeFacts>>;\n}\n\nexport function createEntityDataPort(options: CreateEntityDataPortOptions): EntityDataPort {\n const client: EntityClient = options.client ?? createEntityClient({ dataSource: options.dataSource });\n const activeOrganization = (): Uuid | null => (options.activeOrganizationId ? options.activeOrganizationId() : options.organization) ?? null;\n /** Ids asked by `get` in this tick, per alias: answered by one `entity_get`. */\n const gets = new Map<string, Map<Uuid, ((answer: RecordsResult<Row | null>) => void)[]>>();\n const aliases = options.aliases;\n const shapes = new Map<string, Promise<RecordsResult<EntityColumnsAnswer>>>();\n let warnedNotLive = false;\n /** Which organization each row this port handed out belongs to, per alias: the \"held rows\" topic order. */\n const held = new Map<string, Map<Uuid, Uuid>>();\n const replans = new Map<string, Set<() => void>>();\n let memberships: Promise<Uuid[] | null> | null = null;\n let maxOrgTopics: Promise<number> | null = null;\n\n function hold(alias: string, rows: readonly { id?: unknown; organization_id?: unknown }[]) {\n const byId = held.get(alias) ?? new Map<Uuid, Uuid>();\n held.set(alias, byId);\n const before = new Set(byId.values());\n for (const r of rows) {\n if (typeof r.id === \"string\" && typeof r.organization_id === \"string\") byId.set(r.id, r.organization_id);\n }\n // A row from an organization this alias did not hold yet may move it into the joined topics.\n if ([...byId.values()].some((org) => !before.has(org))) for (const replan of replans.get(alias) ?? []) replan();\n }\n\n function heldCounts(alias: string): Map<Uuid, number> {\n const counts = new Map<Uuid, number>();\n for (const org of held.get(alias)?.values() ?? []) counts.set(org, (counts.get(org) ?? 0) + 1);\n return counts;\n }\n\n function membershipsOnce(): Promise<Uuid[] | null> {\n memberships ??= client.memberOrganizations().then((answer) => {\n if (answer.ok) return answer.data;\n memberships = null; // a refusal is never kept for the next asker\n console.warn(`[@ai-matrx/entity-data] Could not read your organizations for live updates: ${answer.error.message}`);\n return null;\n });\n return memberships;\n }\n\n function maxOrgTopicsOnce(): Promise<number> {\n maxOrgTopics ??= client.knob({ key: \"realtime_max_org_topics\", organizationId: activeOrganization() }).then((answer) => {\n const value = answer.ok ? Number(answer.data) : Number.NaN;\n if (Number.isFinite(value) && value >= 0) return value;\n console.warn(`[@ai-matrx/entity-data] knob entity_data.realtime_max_org_topics did not resolve; joining at most ${DEFAULT_MAX_ORG_TOPICS} organizations.`);\n return DEFAULT_MAX_ORG_TOPICS;\n });\n return maxOrgTopics;\n }\n\n const tokenBindings = new Map<string, EntityAlias>();\n function bindingOf(alias: string): EntityAlias | null {\n if (Object.prototype.hasOwnProperty.call(aliases, alias)) return aliases[alias]!;\n if (!options.tokensAreAliases || !/^[a-z][a-z0-9_]*$/.test(alias)) return null;\n let bound = tokenBindings.get(alias);\n if (!bound) {\n bound = { kind: \"entity\", token: alias };\n tokenBindings.set(alias, bound);\n }\n return bound;\n }\n\n function noSuchAlias(alias: string) {\n return refusal(\"not_found\", `This Applet has no data source called \"${alias}\".`, \"Use one of the aliases sources() lists.\");\n }\n\n /** One read of a type's columns per port; a refusal is never kept for the next asker. */\n function shapeOf(alias: string, binding: EntityAlias): Promise<RecordsResult<EntityColumnsAnswer>> {\n let held = shapes.get(alias);\n if (!held) {\n held = client.columns({ token: binding.token, organizationId: binding.organizationId ?? null }).then((answer) => {\n if (!answer.ok) shapes.delete(alias);\n return answer;\n });\n shapes.set(alias, held);\n }\n return held;\n }\n\n /** Ask for one row; every id asked for the alias before the tick ends goes in the same `entity_get`. */\n function getCoalesced(alias: string, token: string, id: Uuid): Promise<RecordsResult<Row | null>> {\n return new Promise((resolve) => {\n let batch = gets.get(alias);\n if (!batch) {\n batch = new Map();\n gets.set(alias, batch);\n const asked = batch;\n queueMicrotask(() => {\n gets.delete(alias);\n void flushGets(alias, token, asked);\n });\n }\n const waiting = batch.get(id) ?? [];\n waiting.push(resolve);\n batch.set(id, waiting);\n });\n }\n\n async function flushGets(alias: string, token: string, asked: Map<Uuid, ((answer: RecordsResult<Row | null>) => void)[]>) {\n const answer: RecordsResult<EntityGetAnswer> = await client.get({ token, ids: [...asked.keys()] });\n if (answer.ok) hold(alias, answer.data.rows);\n const byId = new Map<string, EntityWireRow>(answer.ok ? answer.data.rows.map((r) => [r.id, r]) : []);\n for (const [id, waiting] of asked) {\n // A row in the trash is not a row of this port: the DataPort's lists are active rows only (a Query has no\n // archived option), so a re-read that finds one trashed answers null and the engine takes it off the list.\n // `entity_get` itself opens trashed rows by id (`_state.archived`) for screens that read the doors directly.\n const found = byId.get(id);\n const wire = found && (found[\"_state\"] as { archived?: unknown } | undefined)?.archived === true ? undefined : found;\n const result: RecordsResult<Row | null> = answer.ok ? { ok: true, data: wire ? toRow(wire) : null } : answer;\n for (const resolve of waiting) resolve(result);\n }\n }\n\n /** The one row a write door read back (with its new `version`). */\n function readBack(alias: string, answer: RecordsResult<EntityWriteAnswer>, what: string): RecordsResult<Row> {\n if (!answer.ok) return answer;\n hold(alias, answer.data.rows);\n const wire = answer.data.rows[0];\n if (!wire) {\n return refusal(\n \"not_found\",\n `${what} was saved, but you cannot open it now, so it is not shown here.`,\n \"The row is in the store; who may open it decides whether it appears in this list.\",\n );\n }\n return { ok: true, data: toRow(wire) };\n }\n\n function done(answer: RecordsResult<EntityWriteAnswer>): RecordsResult<void> {\n return answer.ok ? { ok: true, data: undefined } : answer;\n }\n\n return {\n async sources() {\n const out: DataSource[] = [];\n for (const [alias, binding] of Object.entries(aliases)) {\n const shape = await shapeOf(alias, binding);\n if (!shape.ok) return shape;\n out.push({\n alias,\n label: binding.label ?? shape.data.label ?? alias,\n kind: \"entity\",\n canCreate: Boolean(shape.data.can_create),\n canUpdate: Boolean(shape.data.can_update),\n });\n }\n return { ok: true, data: out };\n },\n\n async describe(alias) {\n const binding = bindingOf(alias);\n if (!binding) return noSuchAlias(alias);\n const shape = await shapeOf(alias, binding);\n if (!shape.ok) return shape;\n return {\n ok: true,\n data: {\n token: binding.token,\n label: binding.label ?? shape.data.label ?? binding.token,\n canCreate: Boolean(shape.data.can_create),\n canUpdate: Boolean(shape.data.can_update),\n },\n };\n },\n\n async columns(alias) {\n const binding = bindingOf(alias);\n if (!binding) return noSuchAlias(alias);\n const shape = await shapeOf(alias, binding);\n return shape.ok ? { ok: true, data: toColumns(shape.data.columns) satisfies Column[] } : shape;\n },\n\n async list(alias, q: Query = {}, cursor) {\n const binding = bindingOf(alias);\n if (!binding) return noSuchAlias(alias);\n // A custom field's key is only known from the columns; ask for them only when the query names a column.\n let customKeys: ReadonlySet<string> = new Set();\n if ((q.where && q.where.length > 0) || (q.sort && q.sort.length > 0)) {\n const shape = await shapeOf(alias, binding);\n if (!shape.ok) return shape;\n customKeys = new Set(shape.data.columns.filter((c) => c.origin === \"custom\").map((c) => c.key));\n }\n const wire = toWire(q, customKeys);\n // A SOURCE DECLARED IN ONE ORGANIZATION reads that organization only (an Applet's own source scope,\n // CONTRACTS v2.11): the alias's organization replaces any per-query org filter — it is the source's\n // definition, not the person's list filter. Without one, the query's filter (or none) stands.\n if (binding.organizationId) wire.scope = { ...wire.scope, organization_id: binding.organizationId };\n const answer = await client.list({ token: binding.token, ...wire, after: cursor ?? null });\n if (!answer.ok) return answer;\n const { data } = answer;\n if (data.sort_ignored.length > 0) {\n // SAID OUT LOUD: the door keeps one sort key today; the rest were dropped, not silently reordered.\n console.warn(`[@ai-matrx/entity-data] \"${alias}\" sorts by one column for now; ignored: ${data.sort_ignored.join(\", \")}.`);\n }\n hold(alias, data.rows);\n const page: Page = {\n rows: data.rows.map(toRow),\n // A count that stopped at the store's cap is a floor, not a count: unknown beats a wrong number.\n total: data.total === null || data.total === undefined || data.total_is_capped ? null : data.total,\n cursor: data.has_more ? data.next_after : null,\n };\n return { ok: true, data: page };\n },\n\n async get(alias, id: Uuid) {\n const binding = bindingOf(alias);\n if (!binding) return noSuchAlias(alias);\n if (!isUuidShape(id)) return refusal(\"invalid_argument\", `\"${String(id)}\" is not a row id.`);\n return getCoalesced(alias, binding.token, id);\n },\n\n async create(alias, values: Values) {\n const binding = bindingOf(alias);\n if (!binding) return noSuchAlias(alias);\n // A source declared in one organization saves there; otherwise the host's active organization.\n const organizationId = binding.organizationId ?? activeOrganization();\n if (!organizationId) {\n // MX002, said before anything is sent: a new row's organization is the person's choice, never ours.\n return refusal(\n \"organization_required\",\n \"Choose the organization this new record belongs to.\",\n \"Pick an organization, then add the row again. Nothing was saved.\",\n );\n }\n const answer = await client.insert({ token: binding.token, organizationId, rows: [toWireValues(values)], opId: mintOpId() });\n return readBack(alias, answer, \"The new row\");\n },\n\n async update(alias, id: Uuid, patch: Values, baseVersion: number) {\n const binding = bindingOf(alias);\n if (!binding) return noSuchAlias(alias);\n if (!isUuidShape(id)) return refusal(\"invalid_argument\", `\"${String(id)}\" is not a row id.`);\n const answer = await client.update({ token: binding.token, id, baseVersion, patch: toWireValues(patch), opId: mintOpId() });\n return readBack(alias, answer, \"The change\");\n },\n\n async archive(alias, id: Uuid) {\n const binding = bindingOf(alias);\n if (!binding) return noSuchAlias(alias);\n if (!isUuidShape(id)) return refusal(\"invalid_argument\", `\"${String(id)}\" is not a row id.`);\n return done(await client.archive({ token: binding.token, ids: [id], opId: mintOpId() }));\n },\n\n async restore(alias, id: Uuid) {\n const binding = bindingOf(alias);\n if (!binding) return noSuchAlias(alias);\n if (!isUuidShape(id)) return refusal(\"invalid_argument\", `\"${String(id)}\" is not a row id.`);\n return done(await client.unarchive({ token: binding.token, ids: [id], opId: mintOpId() }));\n },\n\n subscribe(event: DataPortEventName, listener: (event: RowsEvent) => void) {\n const alias = event.startsWith(\"rows:\") ? event.slice(5) : null;\n const binding = alias ? bindingOf(alias) : null;\n if (!alias || !binding) {\n throw new Error(`DataPort.subscribe: \"${event}\" is not an event this Applet's port carries. Use rows:<alias>.`);\n }\n const realtime = options.realtime;\n if (!realtime) {\n // NOT LIVE, SAID OUT LOUD: a host that bound no realtime layer goes stale, and the console says why.\n if (!warnedNotLive) {\n warnedNotLive = true;\n console.warn(\"[@ai-matrx/entity-data] This host bound no realtime layer, so other people's changes appear on the next read.\");\n }\n return () => {};\n }\n\n let stopped = false;\n let joined: string[] = [];\n let stopLive: (() => void) | null = null;\n const sink = { rows: (ids: string[] | null) => listener({ ids: ids === null ? null : [...ids] }) };\n\n const plan = async () => {\n const organizationIds = binding.organizationId\n ? [binding.organizationId]\n : orderOrganizationTopics({\n active: activeOrganization(),\n held: heldCounts(alias),\n members: await membershipsOnce(),\n max: await maxOrgTopicsOnce(),\n });\n if (stopped) return;\n if (organizationIds.length === joined.length && organizationIds.every((org, i) => org === joined[i])) return;\n // Join the new set before leaving the old, so a topic in both never closes and reopens.\n const previous = stopLive;\n joined = organizationIds;\n stopLive = realtime.subscribe({ token: binding.token, organizationIds }, sink);\n previous?.();\n };\n const replan = () => void plan();\n const set = replans.get(alias) ?? new Set<() => void>();\n replans.set(alias, set);\n set.add(replan);\n replan();\n\n return () => {\n stopped = true;\n set.delete(replan);\n stopLive?.();\n stopLive = null;\n };\n },\n };\n}\n\n/**\n * THE PORT A SERVER ASKS THROUGH, AND THE PAGE READS THROUGH WHILE IT HYDRATES (lane SEED-ENTITY).\n * One construction for both, so the doors' arguments are the same on either side: a token is its own\n * alias, nothing is bound to an organization, and nothing is live. `dataSource` is the person's own\n * (server), a recording one, or one that answers from a page seed.\n */\nexport function createSeedPort(dataSource: unknown): EntityDataPort {\n return createEntityDataPort({ client: createEntityClient({ dataSource }), aliases: {}, tokensAreAliases: true, activeOrganizationId: () => null });\n}\n","// src/core/client.ts — @ai-matrx/entity-data/core\n//\n// THE THIN TYPED CLIENT over the live INVOKER doors in `platform`: reads (`entity_list_scoped`,\n// `entity_get`, `entity_columns`) and, since 0.3, writes (`entity_insert`, `entity_update`,\n// `entity_archive`, `entity_unarchive`). Every call answers a `RecordsResult` and\n// never throws; every refusal is the store's own sentence (see `errors.ts`). The caller's seat is the\n// one on the supabase client handed in — nothing here widens or narrows what the store lets her see.\n\nimport { asRecordsDataSource } from \"@ai-matrx/records/core\";\nimport type { RecordsDataSource, RecordsResult, Uuid } from \"@ai-matrx/records\";\nimport { mapEntityError } from \"./errors\";\nimport type { EntityColumnsAnswer, EntityGetAnswer, EntityListAnswer, EntityWireQuery, EntityWriteAnswer } from \"./types\";\n\n/**\n * THE ECHO HEADER (U6). The entity doors take no `_op_id` argument: the writer's op id rides the request\n * as this header, PostgREST hands it to `request.headers`, and `platform._entity_org_notice` puts it on\n * the notice as `op_id`, so the browser that wrote drops its own echo (`@ai-matrx/records` `isOwnOp`).\n */\nexport const OP_ID_HEADER = \"x-matrx-op-id\";\n\n/** What a write takes beyond its arguments: the op id minted for it (`mintOpId`). */\nexport interface EntityWriteOptions {\n opId?: string;\n}\n\n/** The schema every entity door lives in. */\nexport const ENTITY_DOOR_SCHEMA = \"platform\";\n\nexport interface CreateEntityClientOptions {\n /** A supabase-js v2 client for the signed-in person, or anything with `rpc(fn, args, {schema})`. */\n dataSource?: unknown;\n /** Same thing, spelled the way the plan names it. Give one of the two. */\n supabase?: unknown;\n}\n\nexport interface EntityListRequest extends EntityWireQuery {\n token: string;\n /** Keyset cursor from the previous answer's `next_after`. */\n after?: string | null;\n /** Ask the door to skip the count. Default: counted. */\n withTotal?: boolean;\n archived?: \"active\" | \"archived\" | \"all\";\n}\n\nexport interface EntityClient {\n list(request: EntityListRequest): Promise<RecordsResult<EntityListAnswer>>;\n get(request: { token: string; ids: readonly Uuid[] }): Promise<RecordsResult<EntityGetAnswer>>;\n /** `organizationId` null reads the custom fields of every organization the person belongs to. */\n columns(request: { token: string; organizationId?: Uuid | null }): Promise<RecordsResult<EntityColumnsAnswer>>;\n /** One `entity_data` knob as `platform.knob_resolve` answers it for an organization (live updates read three). */\n knob(request: { key: string; organizationId?: Uuid | null }): Promise<RecordsResult<unknown>>;\n /** `platform.entity_insert`: rows into the organization the host names (never guessed). */\n insert(request: { token: string; organizationId: Uuid; rows: readonly Record<string, unknown>[] } & EntityWriteOptions): Promise<RecordsResult<EntityWriteAnswer>>;\n /** `platform.entity_update`: one row, refused as stale (`MX001`) unless `baseVersion` is still current. */\n update(request: { token: string; id: Uuid; baseVersion: number; patch: Record<string, unknown> } & EntityWriteOptions): Promise<RecordsResult<EntityWriteAnswer>>;\n /** `platform.entity_archive` (on these types, the trash). */\n archive(request: { token: string; ids: readonly Uuid[] } & EntityWriteOptions): Promise<RecordsResult<EntityWriteAnswer>>;\n /** `platform.entity_unarchive`. */\n unarchive(request: { token: string; ids: readonly Uuid[] } & EntityWriteOptions): Promise<RecordsResult<EntityWriteAnswer>>;\n /** The organizations the person belongs to, as row security counts them (`iam.my_orgs()`: archived ones closed). */\n memberOrganizations(): Promise<RecordsResult<Uuid[]>>;\n}\n\nexport function createEntityClient(options: CreateEntityClientOptions): EntityClient {\n const source: RecordsDataSource = asRecordsDataSource(options.dataSource ?? options.supabase);\n\n let warnedHeaderless = false;\n\n /**\n * A per-call header rides the request builder supabase-js hands back (`PostgrestBuilder.setHeader`, set\n * before the builder is awaited). `@ai-matrx/records`' adapter returns that builder unwrapped, so the\n * header reaches PostgREST on this one call only — never on the client every other caller shares.\n * A data seam whose answer has no `setHeader` (a host's own adapter, a test double) cannot carry it:\n * the write still lands, the writer re-reads its own echo once, and the console says why.\n */\n function withHeaders(pending: PromiseLike<unknown>, headers: Record<string, string> | undefined): void {\n if (!headers) return;\n const builder = pending as { setHeader?: (name: string, value: string) => unknown };\n if (typeof builder.setHeader === \"function\") {\n for (const [name, value] of Object.entries(headers)) builder.setHeader(name, value);\n return;\n }\n if (!warnedHeaderless) {\n warnedHeaderless = true;\n console.warn(\n `[@ai-matrx/entity-data] This data source cannot carry a per-call header (${Object.keys(headers).join(\", \")}), so this browser re-reads the notice of its own writes once.`,\n );\n }\n }\n\n async function call<T>(\n fn: string,\n args: Record<string, unknown>,\n schema: string = ENTITY_DOOR_SCHEMA,\n headers?: Record<string, string>,\n ): Promise<RecordsResult<T>> {\n try {\n const pending = source.rpc(fn, args, { schema });\n withHeaders(pending, headers);\n const answer = await pending;\n if (answer.error) return { ok: false, error: mapEntityError(answer.error, `${schema}.${fn}`) };\n return { ok: true, data: answer.data as T };\n } catch (thrown) {\n // supabase-js answers failures; a throw here is a transport that never reached the store.\n return {\n ok: false,\n error: mapEntityError({ code: \"\", message: thrown instanceof Error ? thrown.message : String(thrown) }, `${schema}.${fn}`),\n };\n }\n }\n\n return {\n list(request) {\n const args: Record<string, unknown> = {\n p_entity_type: request.token,\n p_scope: request.scope ?? null,\n p_filter: request.filter ?? {},\n p_search: request.search ?? null,\n p_sort: request.sort ?? null,\n p_archived: request.archived ?? \"active\",\n p_page_size: request.pageSize ?? null,\n p_after: request.after ?? null,\n p_with_total: request.withTotal ?? true,\n };\n return call<EntityListAnswer>(\"entity_list_scoped\", args);\n },\n get(request) {\n return call<EntityGetAnswer>(\"entity_get\", { p_entity_type: request.token, p_ids: [...request.ids] });\n },\n columns(request) {\n return call<EntityColumnsAnswer>(\"entity_columns\", { p_organization_id: request.organizationId ?? null, p_token: request.token });\n },\n insert(request) {\n return call<EntityWriteAnswer>(\n \"entity_insert\",\n { p_entity_type: request.token, p_organization_id: request.organizationId, p_rows: request.rows.map((r) => ({ ...r })) },\n ENTITY_DOOR_SCHEMA,\n opHeader(request.opId),\n );\n },\n update(request) {\n return call<EntityWriteAnswer>(\n \"entity_update\",\n { p_entity_type: request.token, p_id: request.id, p_base_version: request.baseVersion, p_patch: request.patch },\n ENTITY_DOOR_SCHEMA,\n opHeader(request.opId),\n );\n },\n archive(request) {\n return call<EntityWriteAnswer>(\"entity_archive\", { p_entity_type: request.token, p_ids: [...request.ids] }, ENTITY_DOOR_SCHEMA, opHeader(request.opId));\n },\n unarchive(request) {\n return call<EntityWriteAnswer>(\"entity_unarchive\", { p_entity_type: request.token, p_ids: [...request.ids] }, ENTITY_DOOR_SCHEMA, opHeader(request.opId));\n },\n knob(request) {\n return call<unknown>(\"knob_resolve\", { p_feature: \"entity_data\", p_key: request.key, p_organization_id: request.organizationId ?? null });\n },\n async memberOrganizations() {\n const answer = await call<unknown>(\"my_orgs\", {}, \"iam\");\n if (!answer.ok) return answer;\n const rows = Array.isArray(answer.data) ? answer.data : [];\n // A SETOF uuid comes back as bare strings; a row shape (`{my_orgs}`) is read the same way.\n const ids = rows\n .map((row) => (typeof row === \"string\" ? row : row && typeof row === \"object\" ? Object.values(row as Record<string, unknown>)[0] : null))\n .filter((id): id is string => typeof id === \"string\");\n return { ok: true, data: ids };\n },\n };\n}\n\nfunction opHeader(opId: string | undefined): Record<string, string> | undefined {\n return opId ? { [OP_ID_HEADER]: opId } : undefined;\n}\n","// src/core/errors.ts — @ai-matrx/entity-data/core\n//\n// THE ERROR MAP (PHASE-B-APPLETS §4). The entity doors raise their own `MX0xx` class; this turns it\n// into the `RecordsError` every other door of the records package answers, carrying THE STORE'S\n// SENTENCE VERBATIM (message and hint are never rewritten here). Anything that is not an `MX` code\n// (a timeout, a dropped connection, a revoked schema) goes through records' own `mapPgError`.\n\nimport { mapPgError } from \"@ai-matrx/records/core\";\nimport type { PgError, RecordsError, RecordsErrorCode } from \"@ai-matrx/records\";\n\n/**\n * Every MX code a read door raises, and the engine code a screen branches on. A code the doors learn\n * later and this map lacks comes back `internal` with `unmapped: true` (never silently \"handled\").\n */\nexport const ENTITY_REFUSALS: Readonly<Record<string, RecordsErrorCode>> = {\n /** conflict_version: somebody else's write won. */\n MX001: \"stale_write\",\n /** organization_required: the call needs an organization and carried none. */\n MX002: \"organization_required\",\n /** forbidden. */\n MX003: \"door\",\n /** not_found (also: not visible to this person). */\n MX004: \"not_found\",\n /** validation_refused (detail.reason required / bad_value / ambiguous_word / constraint; `not_supported` is read apart below). */\n MX005: \"invalid_argument\",\n /** system_column: a write named a column the store owns. */\n MX008: \"invalid_argument\",\n /** unknown_identifier: a column that is not there or not usable here. */\n MX012: \"invalid_argument\",\n /** cursor_invalid: the page cursor belongs to another list; the engine restarts the page. */\n MX013: \"invalid_argument\",\n /** view_not_offered. */\n MX015: \"invalid_argument\",\n /** invalid_argument: a parameter the door refuses by name. */\n MX016: \"invalid_argument\",\n};\n\nexport function mapEntityError(error: PgError, where: string): RecordsError {\n const mapped = mapPgError(error, where);\n const state = mapped.sqlstate;\n if (state && /^MX\\d{3}$/.test(state)) {\n const code = ENTITY_REFUSALS[state];\n if (code) {\n // MX005 that says the store does not offer this yet is a different question for a screen than a bad value.\n const reason = (mapped.detail as { reason?: unknown } | undefined)?.reason;\n const known: RecordsError = { ...mapped, code: state === \"MX005\" && reason === \"not_supported\" ? \"not_supported\" : code };\n delete known.unmapped;\n delete known.diagnostic;\n return known;\n }\n }\n return mapped;\n}\n\n/** A refusal this package itself raises (an alias nobody declared, a write not offered yet). */\nexport function refusal(code: RecordsErrorCode, message: string, hint?: string): { ok: false; error: RecordsError } {\n return { ok: false, error: { code, message, ...(hint ? { hint } : {}) } };\n}\n","// src/core/realtime.ts — @ai-matrx/entity-data/core\n//\n// LIVE NOTICES FOR PLATFORM LISTS (PHASE-B-APPLETS R4 / U6).\n//\n// The database announces every statement on the five Table-API tables (party, crm_deal, project, task,\n// hr_employee) with ONE broadcast per organization it touched, on the PRIVATE topic\n// `mx:entity-org:<organization>:<token>`, event `entity_bump`, payload\n// `{id, entity_type, organization_id, op, op_id, ids | null, at}` — ids, never values. It is written by\n// `platform.entity_org_notice_stmt` and admitted by `platform.entity_org_topic_admits` through the one RLS\n// policy on `realtime.messages`: a signed-in member of that organization (her own memberships, never the\n// admin route) whose role may read the table. The subscriber re-reads the ids through the read door, so\n// row security decides what she sees at read time.\n//\n// This file joins those topics through `@ai-matrx/realtime` — the ONE realtime layer, never a hand-written\n// `.channel(` — so the socket, `setAuth` before a private join, backoff, dedupe and wake backfill are the\n// manager's.\n//\n// · ONE CHANNEL PER (organization, token), HOWEVER MANY READERS — opened by the first, closed by the last.\n// · BURSTS COALESCED per reader: the first notice starts a `batchMs` timer, later ones join it, and\n// `maxWaitMs` caps how long a steady stream can hold a re-read back (knobs entity_data.realtime_batch_ms\n// / realtime_max_wait_ms).\n// · `ids: null` PASSES THROUGH — above knob entity_data.realtime_max_ids the database stops naming ids.\n// · ECHO DROPPED — a write that carried header `x-matrx-op-id` comes back with that `op_id`; a notice whose\n// op id this client minted (`@ai-matrx/records` `isOwnOp`) is our own write and is dropped.\n// · NO REPLAY ON A WAKE OR RECONNECT — the answer is `ids: null` (read again).\n\nimport {\n defineChannelNamespace,\n subscribeToRealtimeManager,\n type ChannelSpec,\n type RealtimeManager,\n} from \"@ai-matrx/realtime\";\nimport { isOwnOp } from \"@ai-matrx/records\";\n\nimport type { EntityClient } from \"./client\";\n\n/** The topic is a CONTRACT WITH THE DATABASE (a trigger writes it, an RLS policy parses it): built once, here. */\nexport const entityOrgChannel = defineChannelNamespace({\n namespace: \"entity-org\",\n foreignTopic: \"mx:entity-org\",\n parts: [\"organizationId\", \"token\"],\n description:\n \"One organization's changes to one platform type (contacts, deals, projects, tasks, employees). Carries NOTICES written by platform.entity_org_notice_stmt — which ids moved, never a value. Admitted by the one RLS policy on realtime.messages to members of that organization.\",\n});\n\n/** The broadcast event the database sends on an organization topic. */\nexport const ENTITY_NOTICE_EVENT = \"entity_bump\";\n\n/** Starting values of the knobs the runtime coalesces with (entity_data.realtime_batch_ms / _max_wait_ms). */\nexport const DEFAULT_BATCH_MS = 150;\nexport const DEFAULT_MAX_WAIT_MS = 1000;\n/** Starting value of knob entity_data.realtime_max_org_topics. */\nexport const DEFAULT_MAX_ORG_TOPICS = 20;\n\nexport interface EntityNotice {\n id?: string;\n entity_type?: string;\n organization_id?: string;\n op?: \"created\" | \"updated\" | \"deleted\";\n /** The client operation that caused it (header `x-matrx-op-id`), or null for any other writer. */\n op_id?: string | null;\n /** `null` MEANS \"more changed than the database lists: re-read\". */\n ids?: string[] | null;\n at?: string;\n}\n\n/** A redelivered notice is the same notice: the database mints one id per send. */\nexport function entityNoticeKey(notice: EntityNotice): string | undefined {\n return notice.id ?? (notice.at ? `${notice.organization_id ?? \"\"}:${notice.entity_type ?? \"\"}:${notice.at}` : undefined);\n}\n\nexport function entityOrgTopic(organizationId: string, token: string): string {\n return entityOrgChannel.topic({ organizationId, token });\n}\n\n/** The channel spec for one (organization, token): private, echo-guarded, deduped, backfilled. */\nexport function entityOrgChannelSpec(\n organizationId: string,\n token: string,\n onNotice: (notice: EntityNotice) => void,\n onBackfill: () => void,\n): ChannelSpec {\n return {\n topic: entityOrgTopic(organizationId, token),\n // Database Broadcast is authorized by RLS on realtime.messages; the manager awaits setAuth() first.\n private: true,\n // The sender is Postgres: no Matrx envelope. Our own write is known by the op id it carried.\n wire: { mode: \"raw\", isOwnMessage: (payload) => isOwnOp((payload as EntityNotice | null)?.op_id) },\n eventKey: (_source, payload) => entityNoticeKey((payload ?? {}) as EntityNotice),\n broadcast: [\n {\n event: ENTITY_NOTICE_EVENT,\n onMessage: (message) => {\n const notice = (message.data ?? {}) as EntityNotice;\n if (isOwnOp(notice.op_id)) return; // belt and braces: the manager's test ran first\n onNotice(notice);\n },\n },\n ],\n // Realtime has no replay: a channel back from a gap re-reads.\n onBackfill: () => onBackfill(),\n };\n}\n\n/**\n * Which organization topics to join, in order, until `max`: the active organization, the alias's `org`\n * filter, then the organizations of held rows by number of held rows (descending; organization id\n * ascending on ties), then the person's remaining memberships by id. Only memberships are kept when they\n * are known — any other topic is refused by the database anyway. Deterministic for the same input.\n */\nexport function orderOrganizationTopics(input: {\n active?: string | null;\n filter?: string | null;\n held?: ReadonlyMap<string, number>;\n members?: readonly string[] | null;\n max: number;\n}): string[] {\n const members = input.members ? new Set(input.members) : null;\n const out: string[] = [];\n const push = (org: string | null | undefined) => {\n if (!org || out.includes(org)) return;\n if (members && !members.has(org)) return;\n out.push(org);\n };\n push(input.active);\n push(input.filter);\n const held = [...(input.held ?? new Map<string, number>()).entries()].sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));\n for (const [org] of held) push(org);\n for (const org of [...(input.members ?? [])].sort()) push(org);\n return out.slice(0, Math.max(0, input.max));\n}\n\nexport interface EntityNoticeSink {\n /** `ids` to re-read, or `null` to re-read everything this reader shows. */\n rows(ids: string[] | null): void;\n}\n\nexport interface EntityRealtime {\n /** Hear `token` across `organizationIds`; the sink gets one coalesced call per burst. Returns the stop. */\n subscribe(target: { token: string; organizationIds: readonly string[] }, sink: EntityNoticeSink): () => void;\n}\n\nexport interface CreateEntityRealtimeOptions {\n /** The host's realtime manager. Left out, the AMBIENT manager the app mounted — never a socket of our own. */\n manager?: RealtimeManager;\n /** Knob entity_data.realtime_batch_ms (starting value 150). */\n batchMs?: number;\n /** Knob entity_data.realtime_max_wait_ms (starting value 1000). */\n maxWaitMs?: number;\n}\n\ninterface OrgListener {\n notice(notice: EntityNotice): void;\n backfill(): void;\n}\n\ninterface OrgChannel {\n listeners: Set<OrgListener>;\n stop: (() => void) | null;\n}\n\n/** The live-notice layer for platform lists, bound to a realtime manager. One per host is enough. */\nexport function createEntityRealtime(options: CreateEntityRealtimeOptions = {}): EntityRealtime {\n const batchMs = options.batchMs ?? DEFAULT_BATCH_MS;\n const maxWaitMs = options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS;\n const channels = new Map<string, OrgChannel>();\n\n const open = (organizationId: string, token: string): OrgChannel => {\n const channel: OrgChannel = { listeners: new Set(), stop: null };\n const spec = entityOrgChannelSpec(\n organizationId,\n token,\n (notice) => {\n for (const listener of channel.listeners) listener.notice(notice);\n },\n () => {\n for (const listener of channel.listeners) listener.backfill();\n },\n );\n if (options.manager) {\n const handle = options.manager.open(spec);\n channel.stop = () => handle.close();\n } else {\n channel.stop = subscribeToRealtimeManager(() => spec);\n }\n return channel;\n };\n\n return {\n subscribe({ token, organizationIds }, sink) {\n let timer: ReturnType<typeof setTimeout> | null = null;\n let firstAt = 0;\n let pending = new Set<string>();\n let wholeList = false;\n\n const flush = () => {\n if (timer !== null) clearTimeout(timer);\n timer = null;\n const ids = [...pending];\n const all = wholeList;\n pending = new Set();\n wholeList = false;\n // THE IDS ARE USED AS IDS. `null` costs a whole re-read; three ids re-read three rows.\n if (all) sink.rows(null);\n else if (ids.length > 0) sink.rows(ids);\n };\n const nudge = () => {\n const now = Date.now();\n if (timer === null) firstAt = now;\n else clearTimeout(timer);\n // Wait `batchMs` after the latest notice, never past `maxWaitMs` after the first one.\n const wait = Math.max(0, Math.min(batchMs, firstAt + maxWaitMs - now));\n timer = setTimeout(flush, wait);\n };\n\n const listener: OrgListener = {\n notice(notice) {\n if (notice.entity_type && notice.entity_type !== token) return;\n if (notice.ids == null) wholeList = true;\n else for (const id of notice.ids) pending.add(id);\n nudge();\n },\n backfill() {\n wholeList = true;\n flush();\n },\n };\n\n const keys = [...new Set(organizationIds)].map((org) => {\n const key = entityOrgTopic(org, token);\n const channel = channels.get(key) ?? open(org, token);\n channels.set(key, channel);\n channel.listeners.add(listener);\n return key;\n });\n\n return () => {\n if (timer !== null) clearTimeout(timer);\n for (const key of keys) {\n const channel = channels.get(key);\n if (!channel) continue;\n channel.listeners.delete(listener);\n if (channel.listeners.size > 0) continue;\n // The last reader of this (organization, token) left: its one subscription goes with it.\n channel.stop?.();\n channels.delete(key);\n }\n };\n },\n };\n}\n\n/** The three live-update knobs as the store answers them for an organization (starting values when unset). */\nexport async function readEntityRealtimeKnobs(\n client: Pick<EntityClient, \"knob\">,\n organizationId?: string | null,\n): Promise<{ batchMs: number; maxWaitMs: number; maxOrgTopics: number }> {\n const read = async (key: string, fallback: number) => {\n const answer = await client.knob({ key, organizationId: organizationId ?? null });\n const value = answer.ok ? Number(answer.data) : Number.NaN;\n if (!Number.isFinite(value)) {\n // SAID OUT LOUD: the starting value stands in for a knob the store did not answer.\n console.warn(`[@ai-matrx/entity-data] knob entity_data.${key} did not resolve; using its starting value ${fallback}.`);\n return fallback;\n }\n return value;\n };\n const [batchMs, maxWaitMs, maxOrgTopics] = await Promise.all([\n read(\"realtime_batch_ms\", DEFAULT_BATCH_MS),\n read(\"realtime_max_wait_ms\", DEFAULT_MAX_WAIT_MS),\n read(\"realtime_max_org_topics\", DEFAULT_MAX_ORG_TOPICS),\n ]);\n return { batchMs, maxWaitMs, maxOrgTopics };\n}\n","// src/core/wire.ts — @ai-matrx/entity-data/core\n//\n// `toWire`: the Applets `Query` (§4) → the door's arguments (scope, filter, sort). Pure.\n//\n// What the doors do not offer yet is NOT filtered, reordered or emulated here: the clause is sent as\n// asked and the door refuses it BY NAME (`\"contains\" filters are not offered yet.`), so the day the\n// door learns it the port already speaks it, and until then the person reads the store's sentence.\n// A browser-side filter would silently drop everything the browser was not shown.\n\nimport type { Query, WhereClause } from \"@ai-matrx/records/data\";\nimport type { EntityWirePredicate, EntityWireQuery, EntityWireSort } from \"./types\";\n\n/** The runtime's underscore names for the three identity columns the doors call `id`, `version`, `organization_id`. */\nconst IDENTITY: Readonly<Record<string, string>> = {\n _id: \"id\",\n _version: \"version\",\n _organization_id: \"organization_id\",\n};\n\nexport const CUSTOM_PREFIX = \"_custom.\";\n\n/**\n * Applets column key → the door's identifier. A custom field is `_custom.<key>` (the key as\n * `columns()` answered it — the same plain key for every viewer; `<key>@<organization_id>` is accepted, never produced).\n */\nexport function wireColumn(column: string, customKeys: ReadonlySet<string>): string {\n const identity = IDENTITY[column];\n if (identity) return identity;\n if (column.startsWith(CUSTOM_PREFIX)) return column;\n return customKeys.has(column) ? `${CUSTOM_PREFIX}${column}` : column;\n}\n\nfunction predicate(clause: WhereClause, customKeys: ReadonlySet<string>): EntityWirePredicate {\n const column = wireColumn(clause.column, customKeys);\n const v = clause.value;\n switch (clause.op) {\n case \"eq\":\n // The store reads \"equals nothing\" as empty (the table port's rule, so both kinds of source agree).\n return v === null || v === undefined ? { column, op: \"empty\" } : { column, op: \"eq\", value: v };\n case \"neq\":\n return v === null || v === undefined ? { column, op: \"empty\", negated: true } : { column, op: \"neq\", value: v };\n case \"lt\":\n case \"lte\":\n case \"gt\":\n case \"gte\":\n return { column, op: clause.op, value: v };\n case \"in\":\n return { column, op: \"in\", values: Array.isArray(v) ? v : [] };\n case \"empty\":\n return v === false ? { column, op: \"empty\", negated: true } : { column, op: \"empty\" };\n case \"contains\":\n return { column, op: \"contains\", value: v };\n default:\n // An op this version does not know is still SENT, so the door names it, instead of vanishing here.\n return { column, op: String((clause as WhereClause).op), value: v };\n }\n}\n\nexport function toWire(query: Query | undefined, customKeys: ReadonlySet<string> = new Set()): EntityWireQuery {\n const q = query ?? {};\n const out: EntityWireQuery = {};\n const scope: NonNullable<EntityWireQuery[\"scope\"]> = {};\n if (q.lane) scope.kind = q.lane; // absent = the type's own landing lane, decided by the door\n if (q.org && q.org !== \"all\") scope.organization_id = q.org; // a filter, never a lock\n if (scope.kind || scope.organization_id) out.scope = scope;\n if (q.where && q.where.length > 0) out.filter = { all: q.where.map((c) => predicate(c, customKeys)) };\n const search = q.search?.trim();\n if (search) out.search = search;\n if (q.sort && q.sort.length > 0) {\n out.sort = q.sort.map<EntityWireSort>((s) => ({\n column: wireColumn(s.column, customKeys),\n dir: s.direction === \"desc\" ? \"desc\" : \"asc\",\n nulls: \"last\",\n }));\n }\n if (typeof q.pageSize === \"number\" && Number.isFinite(q.pageSize)) out.pageSize = Math.max(1, Math.floor(q.pageSize));\n return out;\n}\n","// src/port/rows.ts — @ai-matrx/entity-data/port\n//\n// ROW AND COLUMN SHAPING (Applets CONTRACTS §0): the door's wire row → `Row`, its columns → `Column`.\n// Pure; every case is pinned against recorded door answers (`__fixtures__`).\n\nimport type { Column, EntityRef, Row } from \"@ai-matrx/records/data\";\nimport type { EntityColumnWire, EntityWireRow } from \"../core/types\";\n\n/** The door's identity columns become the runtime's own underscore keys on a Row; they are never plain columns. */\nconst IDENTITY_COLUMNS = new Set([\"id\", \"version\", \"organization_id\"]);\n/** The compiler's own presentation markers; not data. */\nconst PRESENTATION_KEYS = new Set([\"_state\", \"_k\"]);\n\nfunction isRefList(value: unknown): value is { token: string; id: string; label?: unknown }[] {\n return (\n Array.isArray(value) &&\n value.length > 0 &&\n value.every((item) => item !== null && typeof item === \"object\" && typeof (item as { token?: unknown }).token === \"string\" && typeof (item as { id?: unknown }).id === \"string\")\n );\n}\n\n/** Every link reads as `EntityRef[]` — `{token, id, label}`, the label null when the store had no words for it. */\nexport function toRefs(value: unknown): EntityRef[] {\n if (!isRefList(value)) return [];\n return value.map<EntityRef>((item) => ({ token: item.token, id: item.id, label: typeof item.label === \"string\" ? item.label : null }));\n}\n\nexport function toRow(wire: EntityWireRow): Row {\n const row: Row = {\n _id: wire.id,\n _version: wire.version,\n _organization_id: wire.organization_id,\n _custom: wire._custom && typeof wire._custom === \"object\" ? { ...wire._custom } : {},\n };\n for (const [key, value] of Object.entries(wire)) {\n if (IDENTITY_COLUMNS.has(key) || PRESENTATION_KEYS.has(key) || key === \"_custom\") continue;\n row[key] = isRefList(value) ? toRefs(value) : value === undefined ? null : value;\n }\n return row;\n}\n\nexport function toColumn(wire: EntityColumnWire): Column {\n const column: Column = {\n key: wire.key,\n label: wire.label || wire.key,\n type: wire.type,\n origin: wire.origin === \"custom\" ? \"custom\" : \"platform\",\n required: Boolean(wire.required),\n readOnly: Boolean(wire.readOnly),\n };\n if (wire.choices && wire.choices.length > 0) column.choices = [...wire.choices];\n if (wire.linksTo && wire.linksTo.length > 0) column.linksTo = [...wire.linksTo];\n return column;\n}\n\n/** The columns a person sees: the door's own list minus the identity columns a Row carries as `_id` / `_version` / `_organization_id`. */\nexport function toColumns(wire: readonly EntityColumnWire[]): Column[] {\n return wire.filter((c) => !IDENTITY_COLUMNS.has(c.key)).map(toColumn);\n}\n"],"mappings":"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","names":["held"]}
1
+ {"version":3,"sources":["../../src/port/index.ts"],"sourcesContent":["// @ai-matrx/entity-data — DEPRECATED: this package now lives inside records as `@ai-matrx/records/entity`.\n// A thin re-export until no published @ai-matrx package imports it (check-tarball-sibling-imports).\nexport * from \"@ai-matrx/records/entity\";\n"],"mappings":";AAEA,cAAc;","names":[]}