@ai-matrx/records 0.57.2 → 0.58.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.
@@ -2205,6 +2205,7 @@ __export(core_exports, {
2205
2205
  DECLARABLE_FIELD_KINDS: () => DECLARABLE_FIELD_KINDS,
2206
2206
  DEFAULT_ARCHIVE_LANE: () => DEFAULT_ARCHIVE_LANE,
2207
2207
  DEFAULT_DOCUMENT_MAX_BYTES: () => DEFAULT_DOCUMENT_MAX_BYTES,
2208
+ DEFAULT_FIELDS: () => DEFAULT_FIELDS,
2208
2209
  DEFAULT_PAGE_SIZE: () => DEFAULT_PAGE_SIZE,
2209
2210
  DEFAULT_PORTAL_ARCHIVE_FILTER: () => DEFAULT_PORTAL_ARCHIVE_FILTER,
2210
2211
  DEFAULT_VALUE_MAX_BYTES: () => DEFAULT_VALUE_MAX_BYTES,
@@ -2226,6 +2227,7 @@ __export(core_exports, {
2226
2227
  PERMISSION_LEVELS: () => PERMISSION_LEVELS,
2227
2228
  PER_VALUE_ACCESS_WORDS: () => PER_VALUE_ACCESS_WORDS,
2228
2229
  PIPELINE_RULE_KINDS: () => PIPELINE_RULE_KINDS,
2230
+ PORTAL_ACCENTS: () => PORTAL_ACCENTS,
2229
2231
  PORTAL_ARCHIVE_FILTERS: () => PORTAL_ARCHIVE_FILTERS,
2230
2232
  REFUSALS_WORTH_REPEATING: () => REFUSALS_WORTH_REPEATING,
2231
2233
  RELATION_BINDINGS: () => RELATION_BINDINGS,
@@ -2246,6 +2248,7 @@ __export(core_exports, {
2246
2248
  VALUE_ALTERNATE_KEYS: () => VALUE_ALTERNATE_KEYS,
2247
2249
  VALUE_ENVELOPE_KEYS: () => VALUE_ENVELOPE_KEYS,
2248
2250
  actionRefusals: () => actionRefusals,
2251
+ addFields: () => addFields,
2249
2252
  allowsOtherValues: () => allowsOtherValues,
2250
2253
  announceRecordChanged: () => announceRecordChanged,
2251
2254
  archivedByLine: () => archivedByLine,
@@ -2262,16 +2265,23 @@ __export(core_exports, {
2262
2265
  coerceTypedAnswers: () => coerceTypedAnswers,
2263
2266
  coercionLabel: () => coercionLabel,
2264
2267
  createRecordsClient: () => createRecordsClient,
2268
+ declareTable: () => declareTable,
2265
2269
  doorAbsent: () => doorAbsent,
2266
2270
  doorCensus: () => doorCensus,
2267
2271
  doorExists: () => doorExists,
2268
2272
  emptyArchiveLine: () => emptyArchiveLine,
2269
2273
  emptyPortalArchiveLine: () => emptyPortalArchiveLine,
2274
+ entityReferenceKinds: () => entityReferenceKinds,
2275
+ entityReferenceKindsOf: () => entityReferenceKindsOf,
2276
+ entityReferenceWords: () => entityReferenceWords,
2277
+ entityRefsOf: () => entityRefsOf,
2278
+ entityRefsToWrite: () => entityRefsToWrite,
2270
2279
  err: () => err,
2271
2280
  exampleSpec: () => exampleSpec,
2272
2281
  exportCsv: () => exportCsv,
2273
2282
  exportPageXlsx: () => exportPageXlsx,
2274
2283
  exportXlsx: () => exportXlsx,
2284
+ fieldDeclarationFor: () => fieldDeclarationFor,
2275
2285
  fieldKindFor: () => fieldKindFor,
2276
2286
  forgetAllOps: () => forgetAllOps,
2277
2287
  highestLevel: () => highestLevel,
@@ -2283,10 +2293,16 @@ __export(core_exports, {
2283
2293
  isAPersonsRefusal: () => isAPersonsRefusal,
2284
2294
  isArchiveLane: () => isArchiveLane,
2285
2295
  isDecided: () => isDecided,
2296
+ isEntityReferenceConfig: () => isEntityReferenceConfig,
2286
2297
  isOwnOp: () => isOwnOp,
2298
+ isPortalAccent: () => isPortalAccent,
2287
2299
  isPortalArchiveFilter: () => isPortalArchiveFilter,
2288
2300
  isRecordsErr: () => isRecordsErr,
2301
+ isRuleFilter: () => isRuleFilter,
2289
2302
  isTheChosen: () => isTheChosen,
2303
+ keptGroups: () => keptGroups,
2304
+ keptOutOfSight: () => keptOutOfSight,
2305
+ keptTableHref: () => keptTableHref,
2290
2306
  looksLikeAnId: () => looksLikeAnId,
2291
2307
  mapPgError: () => mapPgError,
2292
2308
  mayDrag: () => mayDrag,
@@ -2294,6 +2310,8 @@ __export(core_exports, {
2294
2310
  memoryStore: () => memoryStore,
2295
2311
  mintClientKey: () => mintClientKey,
2296
2312
  mintOpId: () => mintOpId,
2313
+ moveInOrder: () => moveInOrder,
2314
+ offeredAsContext: () => offeredAsContext,
2297
2315
  ok: () => ok,
2298
2316
  onRecordChanged: () => onRecordChanged,
2299
2317
  openCaptureQueue: () => openCaptureQueue,
@@ -2314,10 +2332,15 @@ __export(core_exports, {
2314
2332
  rememberedOpCount: () => rememberedOpCount,
2315
2333
  sameDocument: () => sameDocument,
2316
2334
  scopeChip: () => scopeChip,
2335
+ sheetSyncSpec: () => sheetSyncSpec,
2317
2336
  stableFilterKey: () => stableFilterKey,
2318
2337
  staleWriteDetail: () => staleWriteDetail,
2319
2338
  supabaseDataSource: () => supabaseDataSource,
2320
- toAoa: () => toAoa
2339
+ toAoa: () => toAoa,
2340
+ tokenFor: () => tokenFor,
2341
+ withRowCounts: () => withRowCounts,
2342
+ withTableFacts: () => withTableFacts,
2343
+ withTablePlacement: () => withTablePlacement
2321
2344
  });
2322
2345
  module.exports = __toCommonJS(core_exports);
2323
2346
 
@@ -2499,6 +2522,7 @@ var FIELD_KINDS = [
2499
2522
  "currency",
2500
2523
  "datetime",
2501
2524
  "email",
2525
+ "entity_reference",
2502
2526
  "formula",
2503
2527
  "json",
2504
2528
  "long_text",
@@ -2537,6 +2561,7 @@ function fieldKindFor(field, kernel) {
2537
2561
  case "list":
2538
2562
  return field.multi ? "multi_select" : "select";
2539
2563
  case "relation": {
2564
+ if (isEntityReferenceConfig(config)) return "entity_reference";
2540
2565
  const parity = String(config["parity"] ?? "");
2541
2566
  if (parity === "attachment" || parity === "member") return parity;
2542
2567
  if (kernel?.file && field.relation_target === kernel.file) return "attachment";
@@ -2554,6 +2579,14 @@ function fieldKindFor(field, kernel) {
2554
2579
  }
2555
2580
  }
2556
2581
  var UUID_TEXT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2582
+ function isEntityReferenceConfig(config) {
2583
+ const allowed = config?.["allowed_types"];
2584
+ return Array.isArray(allowed) && allowed.length > 0;
2585
+ }
2586
+ function entityReferenceKindsOf(field) {
2587
+ const allowed = field.config?.["allowed_types"];
2588
+ return Array.isArray(allowed) ? allowed.map((k) => String(k).trim().toLowerCase()).filter(Boolean) : [];
2589
+ }
2557
2590
  var TRUE_WORDS = /* @__PURE__ */ new Set(["true", "yes", "y", "1", "\u2713", "x", "checked", "on"]);
2558
2591
  var FALSE_WORDS = /* @__PURE__ */ new Set(["false", "no", "n", "0", "", "unchecked", "off"]);
2559
2592
  function coercionLabel(field) {
@@ -2669,6 +2702,23 @@ function coerceTypedAnswer(field, raw, opts = {}) {
2669
2702
  return {
2670
2703
  refusal: `${label} points at another record, so it is chosen rather than typed. Open it and pick the record.`
2671
2704
  };
2705
+ // SC-R / P12. An entity reference names a platform thing by kind and id.
2706
+ // An import or an agent may hand over `note:<id>` — or a bare id when the
2707
+ // column allows exactly one kind; the store then decides whether the thing
2708
+ // is there and whether the writer may open it. Words are chosen, not typed.
2709
+ case "entity_reference": {
2710
+ const allowed = entityReferenceKindsOf(field);
2711
+ const [maybeToken, maybeId] = text.includes(":") ? text.split(/:(.*)/s, 2) : ["", text];
2712
+ const token = (maybeToken ?? "").trim().toLowerCase() || (allowed.length === 1 ? allowed[0] : "");
2713
+ const id = (maybeId ?? "").trim();
2714
+ if (token && UUID_TEXT.test(id)) {
2715
+ const ref = { token, id: id.toLowerCase() };
2716
+ return { value: field.multi ? [ref] : ref };
2717
+ }
2718
+ return {
2719
+ refusal: `${label} points at something on the platform, so it is chosen rather than typed. Open it and pick it.`
2720
+ };
2721
+ }
2672
2722
  case "formula":
2673
2723
  case "lookup":
2674
2724
  case "rollup":
@@ -2725,6 +2775,10 @@ function publicFormPath(formId) {
2725
2775
  }
2726
2776
 
2727
2777
  // src/portal.ts
2778
+ var PORTAL_ACCENTS = ["slate", "green", "amber", "red", "blue", "violet", "teal"];
2779
+ function isPortalAccent(word) {
2780
+ return typeof word === "string" && PORTAL_ACCENTS.includes(word);
2781
+ }
2728
2782
  function portalPath(slug) {
2729
2783
  return `/portal/c/${slug}`;
2730
2784
  }
@@ -2748,11 +2802,17 @@ function emptyPortalArchiveLine() {
2748
2802
  }
2749
2803
 
2750
2804
  // src/aggregate.ts
2805
+ function isRuleFilter(filter) {
2806
+ if (!filter || typeof filter !== "object" || Array.isArray(filter)) return false;
2807
+ const node = filter;
2808
+ return typeof node.op === "string" && Array.isArray(node.args);
2809
+ }
2751
2810
  function measureKey(measure) {
2752
2811
  return measure.op === "count" ? "count" : `${measure.op}_${measure.key ?? ""}`;
2753
2812
  }
2754
2813
  function stableFilterKey(filter) {
2755
2814
  if (!filter) return "{}";
2815
+ if (isRuleFilter(filter)) return JSON.stringify(filter);
2756
2816
  const keys = Object.keys(filter).sort();
2757
2817
  if (keys.length === 0) return "{}";
2758
2818
  const parts = keys.map((key) => {
@@ -2848,6 +2908,101 @@ function recordChangeTrigger(tableId, options = {}) {
2848
2908
  ...options.changedFields && options.changedFields.length > 0 ? { changed_fields: options.changedFields } : {}
2849
2909
  };
2850
2910
  }
2911
+ function sheetSyncSpec(args) {
2912
+ const [header = [], ...body] = args.cells;
2913
+ const columns = header.map((c, i) => {
2914
+ const name = c == null ? "" : String(c).trim();
2915
+ return name === "" ? `Column ${i + 1}` : name;
2916
+ });
2917
+ if (columns.length === 0) {
2918
+ throw new Error("The sheet has no header row, so there are no columns to sync. Nothing was sent.");
2919
+ }
2920
+ const seenCol = /* @__PURE__ */ new Set();
2921
+ for (const c of columns) {
2922
+ if (seenCol.has(c)) throw new Error(`The sheet has two columns named "${c}". Rename one and sync again.`);
2923
+ seenCol.add(c);
2924
+ }
2925
+ const refIdx = args.refColumn == null ? -1 : columns.indexOf(args.refColumn);
2926
+ if (args.refColumn != null && refIdx < 0) {
2927
+ throw new Error(`The id column "${args.refColumn}" is not in the sheet's header. Nothing was sent.`);
2928
+ }
2929
+ const seen = /* @__PURE__ */ new Set();
2930
+ const rows = [];
2931
+ body.forEach((cells, i) => {
2932
+ if (cells.every((v) => v == null || String(v).trim() === "")) return;
2933
+ const ref = refIdx >= 0 ? String(cells[refIdx] ?? "").trim() : `row-${i + 2}`;
2934
+ if (ref === "") throw new Error(`Sheet row ${i + 2} has no value in the id column "${args.refColumn}".`);
2935
+ if (seen.has(ref)) throw new Error(`Two sheet rows share the id "${ref}"; each row needs its own.`);
2936
+ seen.add(ref);
2937
+ const values = {};
2938
+ columns.forEach((c, j) => {
2939
+ const v = cells[j];
2940
+ values[c] = v === void 0 || v === "" ? null : v;
2941
+ });
2942
+ rows.push({ ref, values });
2943
+ });
2944
+ return { source: args.source, table_name: args.tableName, columns, rows };
2945
+ }
2946
+ function withRowCounts(tables, counts) {
2947
+ if (!counts) return [...tables];
2948
+ const byId = new Map(counts.map((c) => [c.table_id, Number(c.visible_rows)]));
2949
+ return tables.map((t) => byId.has(t.id) ? { ...t, row_count: byId.get(t.id) } : t);
2950
+ }
2951
+ function moveInOrder(order, moved, toIndex) {
2952
+ const rest = order.filter((id) => id !== moved);
2953
+ const at = Math.max(0, Math.min(Math.trunc(toIndex), rest.length));
2954
+ return [...rest.slice(0, at), moved, ...rest.slice(at)];
2955
+ }
2956
+
2957
+ // src/placement.ts
2958
+ function keptOutOfSight(placement) {
2959
+ return placement?.kept_by_the_app === true;
2960
+ }
2961
+ function offeredAsContext(placement) {
2962
+ return placement?.offered_as_context === true;
2963
+ }
2964
+ function withTablePlacement(tables, facts) {
2965
+ if (!facts) return [...tables];
2966
+ const byId = new Map(facts.map((f) => [f.table_id, f]));
2967
+ return tables.map((table) => {
2968
+ const fact = byId.get(table.id);
2969
+ if (!fact || fact.kept_by_the_app === void 0) return table;
2970
+ return {
2971
+ ...table,
2972
+ kept_by_the_app: fact.kept_by_the_app,
2973
+ kept_for: fact.kept_for ?? null,
2974
+ offered_as_context: fact.offered_as_context ?? false,
2975
+ keeper_group: fact.keeper_group ?? null,
2976
+ keeper_says: fact.keeper_says ?? null,
2977
+ used_in_kind: fact.used_in_kind ?? null,
2978
+ used_in_id: fact.used_in_id ?? null,
2979
+ used_in_table_id: fact.used_in_table_id ?? null
2980
+ };
2981
+ });
2982
+ }
2983
+ function keptGroups(tables) {
2984
+ const out = [];
2985
+ const at = /* @__PURE__ */ new Map();
2986
+ for (const table of tables) {
2987
+ if (!keptOutOfSight(table)) continue;
2988
+ const group = table.keeper_group?.trim() || "Kept by the app";
2989
+ let i = at.get(group);
2990
+ if (i === void 0) {
2991
+ i = out.length;
2992
+ at.set(group, i);
2993
+ out.push({ group, tables: [] });
2994
+ }
2995
+ out[i].tables.push(table);
2996
+ }
2997
+ return out;
2998
+ }
2999
+ function keptTableHref(fact, hrefs) {
3000
+ if (fact.used_in_kind === "scope" && fact.used_in_id) return hrefs.scope(fact.used_in_id);
3001
+ if (fact.used_in_kind === "table" && (fact.used_in_table_id || fact.used_in_id)) {
3002
+ return hrefs.table(fact.used_in_table_id || fact.used_in_id);
3003
+ }
3004
+ return hrefs.table(fact.table_id);
3005
+ }
2851
3006
 
2852
3007
  // src/capture-queue.ts
2853
3008
  var DB_NAME = "matrx-capture";
@@ -3135,6 +3290,194 @@ function isRecordsErr(result) {
3135
3290
  var ok = (data) => ({ ok: true, data });
3136
3291
  var err = (error) => ({ ok: false, error });
3137
3292
 
3293
+ // src/core/pgError.ts
3294
+ var BY_SQLSTATE = {
3295
+ PT409: "stale_write",
3296
+ "42501": "door",
3297
+ "23514": "refused_by_rule",
3298
+ "22004": "invalid_argument",
3299
+ "22023": "invalid_argument",
3300
+ // 22P02 — a value does not fit the type the store holds for it. The class is
3301
+ // real and was hit live: an organization whose copy of a package table grew
3302
+ // `status` as a uuid-valued Field was sent the text "open", and the store
3303
+ // answered `invalid input syntax for type uuid: "open"`. It is the CALL that
3304
+ // is wrong for the shape the store holds, which is what `invalid_argument`
3305
+ // already means — the store's own sentence names the value and the type.
3306
+ "22P02": "invalid_argument",
3307
+ // 42703 — a column that is not there. The store raises it from
3308
+ // `custom.entity_table`, which is asked for a column by NAME, so the class is
3309
+ // exactly "the call named something the shape does not have": the same thing
3310
+ // `invalid_argument` already means, and the store's own sentence names the
3311
+ // column. Added 2026-09-20 (lane FORMS) because the package's live-door suite
3312
+ // caught it raised by a door this package did not know about — which is the
3313
+ // suite working, and is why the map is a census rather than a guess.
3314
+ "42703": "invalid_argument",
3315
+ // 22007 — a date or a time the store could not read as one. It is raised by
3316
+ // `custom.dashboard_window_sql`, which is handed a window a caller wrote
3317
+ // ("last 30 days", a literal date), so the class is again "the call named
3318
+ // something the shape does not accept" and the store's own sentence names the
3319
+ // text it could not read. Added 2026-09-20 (lane SCREEN-BREAK) after the
3320
+ // live-door census caught it — the same way 42703 arrived, which is the
3321
+ // census working rather than a guess.
3322
+ "22007": "invalid_argument",
3323
+ // 22003 — a number the store could not hold at the size it was handed. It is
3324
+ // raised by `custom.context_resolve`, which is given a window or a depth a
3325
+ // caller wrote, so the class is the same one 22P02, 42703 and 22007 already
3326
+ // are: the CALL named a value the shape does not accept, and the store's own
3327
+ // sentence names the number and the range. Added 2026-09-20 (lane GRID-TWO)
3328
+ // when the live-door census caught the release gate red on it — which is the
3329
+ // census working, and the fourth time it has been the thing that found a new
3330
+ // refusal class rather than a person guessing one.
3331
+ "22003": "invalid_argument",
3332
+ "02000": "not_found",
3333
+ "23503": "missing_reference",
3334
+ "23505": "already_exists",
3335
+ "0A000": "not_supported",
3336
+ "53400": "store_limit",
3337
+ "22012": "store_limit",
3338
+ // 54001 — `custom.record_delete` stops a cascade that nests past sixty-four
3339
+ // levels of containment, because that is a loop somebody built rather than a
3340
+ // hierarchy, and saying so beats recursing until the server runs out of
3341
+ // stack. It is a LIMIT of the store, and the store's own sentence names what
3342
+ // to do about it, so it reads as one.
3343
+ "54001": "store_limit",
3344
+ // 54000 — the grid primitives' organization ceilings (GRID-PRIMITIVES, 2026-09-23):
3345
+ // `custom.action_run` on a selection past its knob, `custom.table_decorate` past the
3346
+ // rule count, `custom.table_from_example` past `custom/example_rows_max`,
3347
+ // `custom.table_webhook_declare` past `custom/table_webhooks_max`, and
3348
+ // `custom.hub_changed_by`. Each names its knob in the hint and writes nothing, so it
3349
+ // reads exactly like 54001: a limit of the store, in the store's own sentence.
3350
+ "54000": "store_limit",
3351
+ // VIS: `custom.has_visibility_at` refuses to answer from a snapshot older
3352
+ // than a write this reader has already seen, rather than returning something
3353
+ // they just lost access to. It is retryable on a fresher connection, which is
3354
+ // a different instruction from every other refusal here.
3355
+ "40001": "stale_read",
3356
+ // 57014 — the store cancelled the statement at its own timeout. Like
3357
+ // `stale_read` and unlike every other refusal here, the call was never
3358
+ // refused on its merits: the same call on a smaller page, or once the
3359
+ // organization's kernel is not being rewritten, can succeed. It gets its own
3360
+ // code so a screen can offer "try again" honestly instead of showing a
3361
+ // failure sentence the caller cannot act on.
3362
+ "57014": "timed_out",
3363
+ // PostgREST's own two: the store is not being SERVED, which is a different
3364
+ // thing from the store refusing. `custom` absent from `pgrst.db_schemas`, or
3365
+ // the grants not copied, land here.
3366
+ PGRST202: "store_not_served",
3367
+ PGRST205: "store_not_served",
3368
+ PGRST301: "door",
3369
+ // PT428 — NOT A FAILURE, and the release gate's live census is what found it
3370
+ // missing: the store raises it and this package answered `internal`, which is
3371
+ // the one classification that tells a screen nothing. A stage gate whose
3372
+ // `on_fail` is `require_approval` files the request and leaves the record
3373
+ // where it was; the transaction is rolled back to the pipeline's own block, so
3374
+ // "nothing was written" is a fact rather than a promise. Landed by lane
3375
+ // STAGE-RULES; mapped here by STAGE-RULES-2 the day after, when the census
3376
+ // caught it — the sixth time that census has been the thing that found a
3377
+ // refusal class rather than a person guessing one.
3378
+ PT428: "needs_approval"
3379
+ };
3380
+ var MAPPED_SQLSTATES = Object.keys(BY_SQLSTATE);
3381
+ function parseDetail(details) {
3382
+ if (!details) return void 0;
3383
+ const trimmed = details.trim();
3384
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return details;
3385
+ try {
3386
+ return JSON.parse(trimmed);
3387
+ } catch {
3388
+ return details;
3389
+ }
3390
+ }
3391
+ function isNotASqlstate(code) {
3392
+ if (!code) return true;
3393
+ return /^[0-9]{3}$/.test(code);
3394
+ }
3395
+ function mapPgError(error, where) {
3396
+ const raw = error.code ?? void 0;
3397
+ const sqlstate = raw && raw !== "" ? raw : void 0;
3398
+ const mapped = {
3399
+ code: "internal",
3400
+ message: error.message,
3401
+ ...error.hint ? { hint: error.hint } : {},
3402
+ ...sqlstate ? { sqlstate } : {}
3403
+ };
3404
+ if (isNotASqlstate(sqlstate)) {
3405
+ mapped.code = "unreachable";
3406
+ } else {
3407
+ mapped.code = BY_SQLSTATE[sqlstate] ?? "internal";
3408
+ }
3409
+ const detail = parseDetail(error.details);
3410
+ if (detail !== void 0) mapped.detail = detail;
3411
+ if (mapped.code === "internal") {
3412
+ mapped.unmapped = true;
3413
+ mapped.diagnostic = `@ai-matrx/records did not recognise SQLSTATE ${sqlstate ?? "(none)"} from ${where}; the store's own message is the thing to act on.`;
3414
+ } else if (mapped.code === "unreachable") {
3415
+ mapped.unmapped = true;
3416
+ mapped.diagnostic = `@ai-matrx/records never reached the store from ${where}: ${raw ? `the transport answered ${raw}` : "no response"} \u2014 ${error.message}`;
3417
+ mapped.message = "We could not reach your data.";
3418
+ delete mapped.hint;
3419
+ }
3420
+ return mapped;
3421
+ }
3422
+ function staleWriteDetail(error) {
3423
+ if (error.code !== "stale_write") return null;
3424
+ const detail = error.detail;
3425
+ if (!detail || typeof detail.expected_version !== "number" || typeof detail.current_version !== "number") {
3426
+ return null;
3427
+ }
3428
+ return {
3429
+ expected_version: detail.expected_version,
3430
+ current_version: detail.current_version,
3431
+ contested_fields: detail.contested_fields ?? {}
3432
+ };
3433
+ }
3434
+
3435
+ // src/relation.ts
3436
+ var ENTITY_REF_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3437
+ function isRefLike(value) {
3438
+ return typeof value === "object" && value !== null && "token" in value && "id" in value;
3439
+ }
3440
+ function entityRefsOf(value) {
3441
+ const items = Array.isArray(value) ? value : value == null ? [] : [value];
3442
+ const out = [];
3443
+ for (const item of items) {
3444
+ if (!isRefLike(item)) continue;
3445
+ const token = String(item.token ?? "").trim().toLowerCase();
3446
+ const id = String(item.id ?? "").trim();
3447
+ if (!token || !ENTITY_REF_ID.test(id)) continue;
3448
+ const label = item.label;
3449
+ out.push({ token, id: id.toLowerCase(), label: typeof label === "string" ? label : null });
3450
+ }
3451
+ return out;
3452
+ }
3453
+ function entityRefsToWrite(value, multi) {
3454
+ const seen = /* @__PURE__ */ new Set();
3455
+ const refs = [];
3456
+ for (const ref of entityRefsOf(value)) {
3457
+ const k = `${ref.token}:${ref.id}`;
3458
+ if (seen.has(k)) continue;
3459
+ seen.add(k);
3460
+ refs.push({ token: ref.token, id: ref.id });
3461
+ }
3462
+ if (multi) return refs;
3463
+ return refs[0] ?? null;
3464
+ }
3465
+ async function entityReferenceKinds(source) {
3466
+ const { data, error } = await source.rpc("entity_reference_kinds", {}, { schema: "custom" });
3467
+ if (error) return err(mapPgError(error, "entityReferenceKinds"));
3468
+ return ok(Array.isArray(data) ? data : []);
3469
+ }
3470
+ async function entityReferenceWords(source, organizationId, refs) {
3471
+ if (refs.length === 0) return ok([]);
3472
+ const { data, error } = await source.rpc(
3473
+ "entity_reference_words",
3474
+ { p_organization_id: organizationId, p_refs: refs.map((r) => ({ token: r.token, id: r.id })) },
3475
+ { schema: "custom" }
3476
+ );
3477
+ if (error) return err(mapPgError(error, "entityReferenceWords"));
3478
+ return ok(entityRefsOf(data));
3479
+ }
3480
+
3138
3481
  // src/ops.ts
3139
3482
  var REMEMBER_MS = 3e4;
3140
3483
  var REMEMBER_MAX = 500;
@@ -3848,6 +4191,7 @@ var STORE_DOORS = [
3848
4191
  { name: "table_work_kind", args: "p_organization_id uuid, p_table_id uuid", returns: "text", security: "invoker" },
3849
4192
  { name: "tables_at_home", args: "p_organization_id uuid, p_home_ids uuid[]", returns: "TABLE(table_id uuid, home_record_id uuid, kind text)", security: "definer" },
3850
4193
  { name: "tables_described_without_asking", args: "", returns: "TABLE(function_name text, identity_args text, why text)", security: "invoker" },
4194
+ { name: "tables_i_can_open", args: "", returns: "TABLE(table_id uuid, table_name text, organization_id uuid, organization_name text, member boolean, visibility text, updated_at timestamp with time zone)", security: "definer" },
3851
4195
  { name: "tables_shared_with_me", args: "", returns: "TABLE(table_id uuid, organization_id uuid, organization text, table_name text, level text, level_label text, shared_at timestamp with time zone, opens boolean, say text)", security: "definer" },
3852
4196
  { name: "trg_associations_bump_visibility", args: "", returns: "trigger", security: "definer" },
3853
4197
  { name: "undeclared_keys", args: "p_organization_id uuid, p_table_id uuid, p_data jsonb", returns: "text[]", security: "invoker" },
@@ -3872,6 +4216,7 @@ var STORE_DOORS = [
3872
4216
  { name: "visible_predicate_sql", args: "p_user uuid, p_organization_id uuid, p_table_id uuid, p_required permission_level DEFAULT 'viewer'::permission_level, p_alias text DEFAULT 'r'::text", returns: "text", security: "definer" },
3873
4217
  { name: "visible_record_ids", args: "p_user_id uuid, p_required permission_level DEFAULT 'viewer'::permission_level", returns: "TABLE(id uuid)", security: "definer" },
3874
4218
  { name: "visible_set", args: "p_user uuid, p_organization_id uuid, p_table_id uuid, p_required permission_level DEFAULT 'viewer'::permission_level, OUT o_all_visible boolean, OUT o_true_visibility platform.visibility[], OUT o_granted_all uuid[], OUT o_granted_visible uuid[], OUT o_carried_visible uuid[], OUT o_ladder_calls integer, OUT o_fallback boolean, OUT o_note text", returns: "record", security: "definer" },
4219
+ { name: "where_id_opens", args: "p_id uuid", returns: "jsonb", security: "definer" },
3875
4220
  { name: "widget_kernel_id", args: "", returns: "uuid", security: "invoker" },
3876
4221
  { name: "withheld_marker", args: "p_mask jsonb, p_key text", returns: "jsonb", security: "invoker" },
3877
4222
  { name: "withheld_sentence", args: "p_mask jsonb, p_key text", returns: "text", security: "invoker" },
@@ -3978,7 +4323,7 @@ var STORE_REFUSAL_CODES = [
3978
4323
  { code: "23505", raised_by: "_share_write_person,_unique_rule_holds,doc_sign,doc_signature_write,entity_field_declare,field_declare,home_add,inbound_declare,io_proposal_accept,relation_carry,relation_own,sign_request_cancel,sign_request_create,table_share_outside_resend,work_approval_decide,work_slot_hold" },
3979
4324
  { code: "23514", raised_by: "_checklist_instantiate,_checklist_step_guard,_containment_guard,_dated_values_guard,_derived_fields,_display_format_check,_display_spec_for,_entity_custom_fields_guard,_field_class_guard,_field_document_for,_field_shape_guard,_field_type_parity_guard,_merge_field_shape_guard,_merge_field_temporal_guard,_promoted_field_cap_guard,_record_rule_uses,_relation_halves_agree,_relation_kernel_targets,_resolve_choice_words,_rule_shape_guard,_rule_topology_guard,_store_relation_edge_names_its_field,_table_columns_word_guard,_table_shape_guard,_undeclared_key_guard,_value_envelope,_with_display,_work_shape_guard,_workdoors_approval_guard,action_run,assert_columns_are_defined,assert_entity_is_organization_scoped,booking_declare,capture_publish,capture_sheet_declare,checklist_declare,checklist_step_complete,doc_sign,doc_signature_write,doc_template_save,entity_field_declare,entity_field_retire,entity_field_update,entity_records_find,entity_table,field_declare,field_retire,field_update,home_add,io_comment_write,io_import_rows,migrate_merge,migrate_purge_hard,portal_archive,portal_restore,record_write_graph,relation_carry,relation_halves_repair,relation_own,sign_request_create,sign_request_sign,table_declare,table_from_example,validate_value_envelope,validate_values,work_set_state,work_slots_declare,work_take_assignment,work_template_instantiate" },
3980
4325
  { code: "40001", raised_by: "has_visibility_at" },
3981
- { code: "42501", raised_by: "_doc_render_immutable,_doc_signature_immutable,_field_definition_write,_field_write_door,_rule_definition_write,_store_door,agent_table_claim,anon_capture,anon_inbound_land,anon_publish,anon_token_issue,anon_token_verify,anon_write,assert_client_may_change,assert_client_may_open,assert_client_may_reach,assert_may_know_table,assert_organization_admin,assert_store_door,booking_confirm,booking_declare,booking_hold,capture_submit,checklist_run,comment_write,conversation_scope_bind,conversation_scope_unbind,doc_sign,enrich_declare,enrich_due,enrich_land,enrich_normalize,entity_field_declare,entity_field_retire,entity_field_update,entity_value_write,external_write_through,form_declare,form_submit,inbound_mail_land,io_comment_resolve,io_comment_write,io_import_begin,io_import_finish,io_restore,migrate_purge,migrate_purge_hard,migrate_purge_hard_request,migrate_reclass,my_levels,organization_clear,portal_declare,portal_invitation,portal_invite,portal_invite_accept,portal_principal_bind,promote_table,query_visibility_parity,read_record,read_records,read_records_archived,read_records_by_ids,read_records_matching,record_table,record_write_graph,relation_halves_disagreements,relation_halves_repair,share_grant,subscription_mute,subscription_preview,table_share_outside_accept,table_share_outside_invite,table_share_outside_resend,table_webhook_declare,visibility_as_of,work_approval_decide,work_approval_read,work_approval_request,work_person" },
4326
+ { code: "42501", raised_by: "_doc_render_immutable,_doc_signature_immutable,_field_definition_write,_field_write_door,_rule_definition_write,_store_door,agent_table_claim,anon_capture,anon_inbound_land,anon_publish,anon_token_issue,anon_token_verify,anon_write,assert_client_may_change,assert_client_may_open,assert_client_may_reach,assert_may_know_table,assert_organization_admin,assert_store_door,booking_confirm,booking_declare,booking_hold,capture_submit,checklist_run,comment_write,conversation_scope_bind,conversation_scope_unbind,doc_sign,enrich_declare,enrich_due,enrich_land,enrich_normalize,entity_field_declare,entity_field_retire,entity_field_update,entity_value_write,external_write_through,form_declare,form_submit,inbound_mail_land,io_comment_resolve,io_comment_write,io_import_begin,io_import_finish,io_restore,migrate_purge,migrate_purge_hard,migrate_purge_hard_request,migrate_reclass,my_levels,organization_clear,organization_contents,portal_declare,portal_invitation,portal_invite,portal_invite_accept,portal_principal_bind,promote_table,query_visibility_parity,read_record,read_records,read_records_archived,read_records_by_ids,read_records_matching,record_table,record_write_graph,relation_halves_disagreements,relation_halves_repair,share_grant,subscription_mute,subscription_preview,table_share_outside_accept,table_share_outside_invite,table_share_outside_resend,table_webhook_declare,visibility_as_of,work_approval_decide,work_approval_read,work_approval_request,work_person" },
3982
4327
  { code: "42703", raised_by: "entity_table,portal_declare" },
3983
4328
  { code: "53400", raised_by: "anon_rate_take,delete_cascade_closure,enrich_due,enrich_land,promote_field" },
3984
4329
  { code: "54000", raised_by: "action_run,hub_changed_by,table_decorate,table_from_example,table_webhook_declare" },
@@ -5044,6 +5389,20 @@ var OWED_BY = {
5044
5389
  table_webhook_archive: "GRID-PRIMITIVES G4 \u2014 gridprim_every_row_change_reaches_the_webhook.sql (chair's production apply)",
5045
5390
  table_from_example: "GRID-PRIMITIVES G5 \u2014 gridprim_a_table_can_start_from_a_real_example.sql (chair's production apply)",
5046
5391
  autonumber_backfill: "GRID-PRIMITIVES G5 \u2014 gridprim_a_table_can_start_from_a_real_example.sql (chair's production apply)",
5392
+ table_list_everywhere: "GRID-PRIMITIVES G9 \u2014 gridprim_every_picker_lists_both_stores.sql (chair's production apply)",
5393
+ table_sync: "GRID-PRIMITIVES G10 \u2014 gridprim_a_synced_sheet_is_one_table_that_refreshes.sql (chair's production apply)",
5394
+ scope_table_provision: "GRID-PRIMITIVES G11 \u2014 gridprim_a_scope_provisions_its_table_in_the_store.sql (chair's production apply)",
5395
+ table_row_counts: "GRID-PRIMITIVES G12 \u2014 gridprim_every_table_counts_its_rows_and_every_row_shows_its_header.sql + its grant file (chair's production apply)",
5396
+ view_record_order_set: "GRID-PRIMITIVES G13 \u2014 gridprim_a_view_keeps_the_order_a_person_dragged.sql + its grant file (chair's production apply)",
5397
+ table_share_outside_grant: "GRID-PRIMITIVES G14 \u2014 gridprim_a_known_outsider_is_granted_at_once.sql + gridprim_a_signed_in_person_may_share_with_a_known_outsider.sql (chair's production apply)",
5398
+ read_records_in_view_order: "GRID-PRIMITIVES G13 \u2014 gridprim_a_view_keeps_the_order_a_person_dragged.sql + its grant file (chair's production apply)",
5399
+ rule_members_visible: "S2-PRIME FILTER-GROUPS \u2014 filtergroups_a_views_nested_question_is_one_where_clause.sql + its grant file (chair's production apply)",
5400
+ record_headers: "GRID-PRIMITIVES G12 \u2014 gridprim_every_table_counts_its_rows_and_every_row_shows_its_header.sql + its grant file (chair's production apply)",
5401
+ pipeline_move_many: "UI-CHAMPIONS S4 \u2014 uichamp_s4_a_selection_moves_and_decides_in_one_call.sql + its grant file (chair's production apply)",
5402
+ work_decide_many: "UI-CHAMPIONS S4 \u2014 uichamp_s4_a_selection_moves_and_decides_in_one_call.sql + its grant file (chair's production apply)",
5403
+ portal_form: "UI-CHAMPIONS S6 \u2014 uichamp_s6_a_portal_carries_its_look_and_its_forms.sql + uichamp_s6_a_signed_in_person_may_open_a_portals_look_and_forms.sql (chair's production apply)",
5404
+ portal_form_submit: "UI-CHAMPIONS S6 \u2014 uichamp_s6_a_portal_carries_its_look_and_its_forms.sql + uichamp_s6_a_signed_in_person_may_open_a_portals_look_and_forms.sql (chair's production apply)",
5405
+ form_preview_asks: "FORMS-FIX-1 \u2014 formsfix1_the_owners_preview_asks_the_store_the_same_question.sql + its grant file (chair's production apply)",
5047
5406
  rule_declare: "W1-RULE \u2014 `custom.record_write` writes `data_class = 'record'`, so the ONE write door cannot make a Rule record; declaring a Rule (and therefore a DOOR-18 subscription) needs a door of its own"
5048
5407
  };
5049
5408
  var TRUST_SCHEMA = "iam";
@@ -5247,6 +5606,8 @@ var DOORS = {
5247
5606
  pipelineBoard: "pipeline_board",
5248
5607
  pipelineTransitionRefusal: "pipeline_transition_refusal",
5249
5608
  pipelineMove: "pipeline_move",
5609
+ /** Lane S4: many cards, one call, a verdict and a version per card. */
5610
+ pipelineMoveMany: "pipeline_move_many",
5250
5611
  tableStageField: "table_stage_field",
5251
5612
  // STAGE-RULES. A gate can refuse or it can hand the change to the approvals queue; the
5252
5613
  // closed list is the store's, the knob is the organization's, and what is WAITING on a
@@ -5257,6 +5618,7 @@ var DOORS = {
5257
5618
  pipelinePending: "pipeline_pending",
5258
5619
  pipelineGatePreview: "pipeline_gate_preview",
5259
5620
  ruleMembers: "rule_members",
5621
+ ruleMembersVisible: "rule_members_visible",
5260
5622
  ruleMembership: "rule_membership",
5261
5623
  ruleRun: "rule_run",
5262
5624
  ruleEval: "rule_eval",
@@ -5271,6 +5633,12 @@ var DOORS = {
5271
5633
  // are server-lane doors and a browser never calls them.
5272
5634
  forms: "forms",
5273
5635
  formDeclare: "form_declare",
5636
+ /**
5637
+ * WHICH QUESTIONS THE OWNER'S PREVIEW ASKS NEXT (lane FORMS-FIX-1). The same store answer a
5638
+ * stranger's form gets (`custom._form_questions_asked` → `custom.rule_eval`), behind the
5639
+ * organization wall and the caller's own rung on the Table — never `rule_eval` from a browser.
5640
+ */
5641
+ formPreviewAsks: "form_preview_asks",
5274
5642
  // PRODUCTS row 14 — "let clients book a 30-minute consult". A booking page IS an
5275
5643
  // `anon_form`, so it is opened and closed by `anonPublish` like any other; this is
5276
5644
  // the owner's list of them with the numbers that make it worth opening. The
@@ -5330,6 +5698,11 @@ var DOORS = {
5330
5698
  portalInvite: "portal_invite",
5331
5699
  portalRevoke: "portal_revoke",
5332
5700
  portalPreview: "portal_preview",
5701
+ // S6 — the CLIENT's two form doors. They are store doors (schema `custom`), called by a
5702
+ // signed-in portal principal, and each refuses anybody who is not a live principal of that
5703
+ // live portal — so, unlike the sign-in pair above, they belong in this table.
5704
+ portalForm: "portal_form",
5705
+ portalFormSubmit: "portal_form_submit",
5333
5706
  // ORG-CLEANUP, 2026-09-22 — A PORTAL IS ARCHIVED, NEVER DELETED.
5334
5707
  // `portals` keeps its signature and HIDES archived portals (the law's default
5335
5708
  // is to hide); `list_portals(org, 'active'|'archived'|'all')` is the
@@ -5525,6 +5898,8 @@ var DOORS = {
5525
5898
  workApprovalRequest: "work_approval_request",
5526
5899
  workApprovalMayDecide: "work_approval_may_decide",
5527
5900
  workApprovalDecide: "work_approval_decide",
5901
+ /** Lane S4: many inbox decisions, one call, a verdict per item. */
5902
+ workDecideMany: "work_decide_many",
5528
5903
  workApprovalRead: "work_approval_read",
5529
5904
  // PRODUCTS row 13 — checklists and SOP runs.
5530
5905
  checklistRefusal: "checklist_refusal",
@@ -5550,6 +5925,14 @@ var DOORS = {
5550
5925
  tableFromExample: "table_from_example",
5551
5926
  autonumberBackfill: "autonumber_backfill",
5552
5927
  ioExport: "io_export",
5928
+ tableListEverywhere: "table_list_everywhere",
5929
+ tableSync: "table_sync",
5930
+ scopeTableProvision: "scope_table_provision",
5931
+ tableRowCounts: "table_row_counts",
5932
+ recordHeaders: "record_headers",
5933
+ viewRecordOrderSet: "view_record_order_set",
5934
+ readRecordsInViewOrder: "read_records_in_view_order",
5935
+ tableShareOutsideGrant: "table_share_outside_grant",
5553
5936
  // Declared here BECAUSE they do not exist yet: naming them is what makes the
5554
5937
  // absence visible to `pnpm check:store-registry` and to the suite.
5555
5938
  metadataSearch: "metadata_search",
@@ -5567,148 +5950,6 @@ function doorCensus() {
5567
5950
  return { present: present.sort(), absent: absent.sort() };
5568
5951
  }
5569
5952
 
5570
- // src/core/pgError.ts
5571
- var BY_SQLSTATE = {
5572
- PT409: "stale_write",
5573
- "42501": "door",
5574
- "23514": "refused_by_rule",
5575
- "22004": "invalid_argument",
5576
- "22023": "invalid_argument",
5577
- // 22P02 — a value does not fit the type the store holds for it. The class is
5578
- // real and was hit live: an organization whose copy of a package table grew
5579
- // `status` as a uuid-valued Field was sent the text "open", and the store
5580
- // answered `invalid input syntax for type uuid: "open"`. It is the CALL that
5581
- // is wrong for the shape the store holds, which is what `invalid_argument`
5582
- // already means — the store's own sentence names the value and the type.
5583
- "22P02": "invalid_argument",
5584
- // 42703 — a column that is not there. The store raises it from
5585
- // `custom.entity_table`, which is asked for a column by NAME, so the class is
5586
- // exactly "the call named something the shape does not have": the same thing
5587
- // `invalid_argument` already means, and the store's own sentence names the
5588
- // column. Added 2026-09-20 (lane FORMS) because the package's live-door suite
5589
- // caught it raised by a door this package did not know about — which is the
5590
- // suite working, and is why the map is a census rather than a guess.
5591
- "42703": "invalid_argument",
5592
- // 22007 — a date or a time the store could not read as one. It is raised by
5593
- // `custom.dashboard_window_sql`, which is handed a window a caller wrote
5594
- // ("last 30 days", a literal date), so the class is again "the call named
5595
- // something the shape does not accept" and the store's own sentence names the
5596
- // text it could not read. Added 2026-09-20 (lane SCREEN-BREAK) after the
5597
- // live-door census caught it — the same way 42703 arrived, which is the
5598
- // census working rather than a guess.
5599
- "22007": "invalid_argument",
5600
- // 22003 — a number the store could not hold at the size it was handed. It is
5601
- // raised by `custom.context_resolve`, which is given a window or a depth a
5602
- // caller wrote, so the class is the same one 22P02, 42703 and 22007 already
5603
- // are: the CALL named a value the shape does not accept, and the store's own
5604
- // sentence names the number and the range. Added 2026-09-20 (lane GRID-TWO)
5605
- // when the live-door census caught the release gate red on it — which is the
5606
- // census working, and the fourth time it has been the thing that found a new
5607
- // refusal class rather than a person guessing one.
5608
- "22003": "invalid_argument",
5609
- "02000": "not_found",
5610
- "23503": "missing_reference",
5611
- "23505": "already_exists",
5612
- "0A000": "not_supported",
5613
- "53400": "store_limit",
5614
- "22012": "store_limit",
5615
- // 54001 — `custom.record_delete` stops a cascade that nests past sixty-four
5616
- // levels of containment, because that is a loop somebody built rather than a
5617
- // hierarchy, and saying so beats recursing until the server runs out of
5618
- // stack. It is a LIMIT of the store, and the store's own sentence names what
5619
- // to do about it, so it reads as one.
5620
- "54001": "store_limit",
5621
- // 54000 — the grid primitives' organization ceilings (GRID-PRIMITIVES, 2026-09-23):
5622
- // `custom.action_run` on a selection past its knob, `custom.table_decorate` past the
5623
- // rule count, `custom.table_from_example` past `custom/example_rows_max`,
5624
- // `custom.table_webhook_declare` past `custom/table_webhooks_max`, and
5625
- // `custom.hub_changed_by`. Each names its knob in the hint and writes nothing, so it
5626
- // reads exactly like 54001: a limit of the store, in the store's own sentence.
5627
- "54000": "store_limit",
5628
- // VIS: `custom.has_visibility_at` refuses to answer from a snapshot older
5629
- // than a write this reader has already seen, rather than returning something
5630
- // they just lost access to. It is retryable on a fresher connection, which is
5631
- // a different instruction from every other refusal here.
5632
- "40001": "stale_read",
5633
- // 57014 — the store cancelled the statement at its own timeout. Like
5634
- // `stale_read` and unlike every other refusal here, the call was never
5635
- // refused on its merits: the same call on a smaller page, or once the
5636
- // organization's kernel is not being rewritten, can succeed. It gets its own
5637
- // code so a screen can offer "try again" honestly instead of showing a
5638
- // failure sentence the caller cannot act on.
5639
- "57014": "timed_out",
5640
- // PostgREST's own two: the store is not being SERVED, which is a different
5641
- // thing from the store refusing. `custom` absent from `pgrst.db_schemas`, or
5642
- // the grants not copied, land here.
5643
- PGRST202: "store_not_served",
5644
- PGRST205: "store_not_served",
5645
- PGRST301: "door",
5646
- // PT428 — NOT A FAILURE, and the release gate's live census is what found it
5647
- // missing: the store raises it and this package answered `internal`, which is
5648
- // the one classification that tells a screen nothing. A stage gate whose
5649
- // `on_fail` is `require_approval` files the request and leaves the record
5650
- // where it was; the transaction is rolled back to the pipeline's own block, so
5651
- // "nothing was written" is a fact rather than a promise. Landed by lane
5652
- // STAGE-RULES; mapped here by STAGE-RULES-2 the day after, when the census
5653
- // caught it — the sixth time that census has been the thing that found a
5654
- // refusal class rather than a person guessing one.
5655
- PT428: "needs_approval"
5656
- };
5657
- var MAPPED_SQLSTATES = Object.keys(BY_SQLSTATE);
5658
- function parseDetail(details) {
5659
- if (!details) return void 0;
5660
- const trimmed = details.trim();
5661
- if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return details;
5662
- try {
5663
- return JSON.parse(trimmed);
5664
- } catch {
5665
- return details;
5666
- }
5667
- }
5668
- function isNotASqlstate(code) {
5669
- if (!code) return true;
5670
- return /^[0-9]{3}$/.test(code);
5671
- }
5672
- function mapPgError(error, where) {
5673
- const raw = error.code ?? void 0;
5674
- const sqlstate = raw && raw !== "" ? raw : void 0;
5675
- const mapped = {
5676
- code: "internal",
5677
- message: error.message,
5678
- ...error.hint ? { hint: error.hint } : {},
5679
- ...sqlstate ? { sqlstate } : {}
5680
- };
5681
- if (isNotASqlstate(sqlstate)) {
5682
- mapped.code = "unreachable";
5683
- } else {
5684
- mapped.code = BY_SQLSTATE[sqlstate] ?? "internal";
5685
- }
5686
- const detail = parseDetail(error.details);
5687
- if (detail !== void 0) mapped.detail = detail;
5688
- if (mapped.code === "internal") {
5689
- mapped.unmapped = true;
5690
- mapped.diagnostic = `@ai-matrx/records did not recognise SQLSTATE ${sqlstate ?? "(none)"} from ${where}; the store's own message is the thing to act on.`;
5691
- } else if (mapped.code === "unreachable") {
5692
- mapped.unmapped = true;
5693
- mapped.diagnostic = `@ai-matrx/records never reached the store from ${where}: ${raw ? `the transport answered ${raw}` : "no response"} \u2014 ${error.message}`;
5694
- mapped.message = "We could not reach your data.";
5695
- delete mapped.hint;
5696
- }
5697
- return mapped;
5698
- }
5699
- function staleWriteDetail(error) {
5700
- if (error.code !== "stale_write") return null;
5701
- const detail = error.detail;
5702
- if (!detail || typeof detail.expected_version !== "number" || typeof detail.current_version !== "number") {
5703
- return null;
5704
- }
5705
- return {
5706
- expected_version: detail.expected_version,
5707
- current_version: detail.current_version,
5708
- contested_fields: detail.contested_fields ?? {}
5709
- };
5710
- }
5711
-
5712
5953
  // src/core/recordChanges.ts
5713
5954
  var listeners = /* @__PURE__ */ new Set();
5714
5955
  function announceRecordChanged(recordId) {
@@ -5783,6 +6024,19 @@ function sink(config) {
5783
6024
  else console.error(line);
5784
6025
  });
5785
6026
  }
6027
+ function withTableFacts(tables, facts, me) {
6028
+ if (!facts) return [...tables];
6029
+ const byId = new Map(facts.map((f) => [f.table_id, f]));
6030
+ return tables.map((table) => {
6031
+ const fact = byId.get(table.id);
6032
+ if (!fact) return table;
6033
+ return {
6034
+ ...table,
6035
+ visibility: fact.visibility,
6036
+ ...fact.mine && me ? { created_by: me } : {}
6037
+ };
6038
+ });
6039
+ }
5786
6040
  function createRecordsClient(config) {
5787
6041
  const schema = config.schema ?? DEFAULT_SCHEMA;
5788
6042
  const org = config.organizationId;
@@ -5957,6 +6211,10 @@ function createRecordsClient(config) {
5957
6211
  })
5958
6212
  );
5959
6213
  },
6214
+ async tableFacts() {
6215
+ if (!doorExists(DOORS.tableFacts)) return ok(null);
6216
+ return callDoor(DOORS.tableFacts, { p_organization_id: org }, "tableFacts");
6217
+ },
5960
6218
  async tableArchived({ table_id }) {
5961
6219
  const kernel = await callDoor(DOORS.tableKernelId, {}, "tableArchived.kernel");
5962
6220
  if (!kernel.ok) return err(kernel.error);
@@ -6140,23 +6398,6 @@ function createRecordsClient(config) {
6140
6398
  const { document } = unfold(row.document);
6141
6399
  return { ...document, id: row.id, organization_id: org };
6142
6400
  });
6143
- if (doorExists(DOORS.tableFacts)) {
6144
- const facts = await callDoor(
6145
- DOORS.tableFacts,
6146
- { p_organization_id: org },
6147
- "tableList.facts"
6148
- );
6149
- if (facts.ok) {
6150
- const byId = new Map((facts.data ?? []).map((f) => [f.table_id, f]));
6151
- const me = config.actor.user_id ?? null;
6152
- for (const table of tables) {
6153
- const fact = byId.get(table.id);
6154
- if (!fact) continue;
6155
- table.visibility = fact.visibility;
6156
- if (fact.mine && me) table.created_by = me;
6157
- }
6158
- }
6159
- }
6160
6401
  tables.sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
6161
6402
  return ok(tables);
6162
6403
  },
@@ -6350,10 +6591,11 @@ function createRecordsClient(config) {
6350
6591
  "pipelineRead"
6351
6592
  );
6352
6593
  },
6353
- async pipelineBoard({ table_id, measure }) {
6594
+ async pipelineBoard({ table_id, measure, filter }) {
6595
+ const asked = filter && stableFilterKey(filter) !== "{}";
6354
6596
  return callDoor(
6355
6597
  DOORS.pipelineBoard,
6356
- { p_organization_id: org, p_table_id: table_id, p_measure: measure ?? null },
6598
+ asked ? { p_organization_id: org, p_table_id: table_id, p_measure: measure ?? null, p_filter: filter } : { p_organization_id: org, p_table_id: table_id, p_measure: measure ?? null },
6357
6599
  "pipelineBoard"
6358
6600
  );
6359
6601
  },
@@ -6377,6 +6619,13 @@ function createRecordsClient(config) {
6377
6619
  "pipelineMove"
6378
6620
  );
6379
6621
  },
6622
+ async pipelineMoveMany({ moves }) {
6623
+ return callDoor(
6624
+ DOORS.pipelineMoveMany,
6625
+ { p_organization_id: org, p_moves: moves },
6626
+ "pipelineMoveMany"
6627
+ );
6628
+ },
6380
6629
  async pipelineDeclare({ table_id, spec }) {
6381
6630
  return callDoor(
6382
6631
  DOORS.pipelineDeclare,
@@ -6426,6 +6675,18 @@ function createRecordsClient(config) {
6426
6675
  "pipelineGatePreview"
6427
6676
  );
6428
6677
  },
6678
+ async ruleMembersVisible({ rule_id, limit = 200, offset = 0 }) {
6679
+ const door = await callDoor(
6680
+ DOORS.ruleMembersVisible,
6681
+ { p_organization_id: org, p_rule_id: rule_id, p_limit: limit, p_offset: offset },
6682
+ "ruleMembersVisible"
6683
+ );
6684
+ if (!door.ok) return err(door.error);
6685
+ return ok((door.data ?? []).map((row) => {
6686
+ const { document, hidden } = unfold(row.document);
6687
+ return { id: row.id, document, level: row.level, hidden };
6688
+ }));
6689
+ },
6429
6690
  async ruleMembers({ rule_id }) {
6430
6691
  return callDoor(
6431
6692
  DOORS.ruleMembers,
@@ -6478,6 +6739,13 @@ function createRecordsClient(config) {
6478
6739
  "bookingDeclare"
6479
6740
  );
6480
6741
  },
6742
+ async formPreviewAsks({ table_id, questions, values }) {
6743
+ return callDoor(
6744
+ DOORS.formPreviewAsks,
6745
+ { p_organization_id: org, p_table_id: table_id, p_questions: questions, p_values: values },
6746
+ "formPreviewAsks"
6747
+ );
6748
+ },
6481
6749
  async formDeclare(args) {
6482
6750
  return callDoor(
6483
6751
  DOORS.formDeclare,
@@ -6653,11 +6921,33 @@ function createRecordsClient(config) {
6653
6921
  p_tables: args.tables,
6654
6922
  p_portal_id: args.portal_id ?? null,
6655
6923
  p_slug: args.slug ?? null,
6656
- p_sign_in_method: args.sign_in_method ?? "magic_link"
6924
+ p_sign_in_method: args.sign_in_method ?? "magic_link",
6925
+ // S6: sent only when given, so a caller that says nothing about the look keeps it.
6926
+ ...args.config !== void 0 ? { p_config: args.config } : {}
6657
6927
  },
6658
6928
  "portalDeclare"
6659
6929
  );
6660
6930
  },
6931
+ async portalForm({ portal_id, form_id }) {
6932
+ return callDoor(
6933
+ DOORS.portalForm,
6934
+ { p_organization_id: org, p_portal_id: portal_id, p_form_id: form_id },
6935
+ "portalForm"
6936
+ );
6937
+ },
6938
+ async portalFormSubmit({ portal_id, form_id, answers, client_key }) {
6939
+ return callDoor(
6940
+ DOORS.portalFormSubmit,
6941
+ {
6942
+ p_organization_id: org,
6943
+ p_portal_id: portal_id,
6944
+ p_form_id: form_id,
6945
+ p_payload: answers,
6946
+ p_client_key: client_key ?? null
6947
+ },
6948
+ "portalFormSubmit"
6949
+ );
6950
+ },
6661
6951
  async portalInvite(args) {
6662
6952
  return callDoor(
6663
6953
  DOORS.portalInvite,
@@ -6856,10 +7146,17 @@ function createRecordsClient(config) {
6856
7146
  "dashboardDeclare"
6857
7147
  );
6858
7148
  },
6859
- async dashboardRun({ dashboard_id, filter }) {
7149
+ async dashboardRun({ dashboard_id, filter, compare, grain }) {
6860
7150
  return callDoor(
6861
7151
  DOORS.dashboardRun,
6862
- { p_organization_id: org, p_dashboard_id: dashboard_id, p_filter: filter ?? {} },
7152
+ {
7153
+ p_organization_id: org,
7154
+ p_dashboard_id: dashboard_id,
7155
+ p_filter: filter ?? {},
7156
+ // Sent only when asked, so a caller that never compares calls exactly what it did.
7157
+ ...compare ? { p_compare: compare } : {},
7158
+ ...grain ? { p_grain: grain } : {}
7159
+ },
6863
7160
  "dashboardRun"
6864
7161
  );
6865
7162
  },
@@ -6884,7 +7181,7 @@ function createRecordsClient(config) {
6884
7181
  "dashboardDelete"
6885
7182
  );
6886
7183
  },
6887
- async recordAggregate({ table_id, groupBy, measures, bucket, filter, limit }) {
7184
+ async recordAggregate({ table_id, groupBy, measures, bucket, filter, limit, compare }) {
6888
7185
  return callDoor(
6889
7186
  DOORS.recordAggregate,
6890
7187
  {
@@ -6894,7 +7191,8 @@ function createRecordsClient(config) {
6894
7191
  p_measures: measures ?? [],
6895
7192
  p_bucket: bucket ?? null,
6896
7193
  p_filter: filter ?? {},
6897
- p_limit: limit ?? 200
7194
+ p_limit: limit ?? 200,
7195
+ ...compare ? { p_compare: compare } : {}
6898
7196
  },
6899
7197
  "recordAggregate"
6900
7198
  );
@@ -7622,6 +7920,13 @@ function createRecordsClient(config) {
7622
7920
  "workApprovalDecide"
7623
7921
  );
7624
7922
  },
7923
+ async workDecideMany({ decisions }) {
7924
+ return callDoor(
7925
+ DOORS.workDecideMany,
7926
+ { p_organization_id: org, p_decisions: decisions },
7927
+ "workDecideMany"
7928
+ );
7929
+ },
7625
7930
  async workApprovalRead({ approval_id }) {
7626
7931
  return callDoor(
7627
7932
  DOORS.workApprovalRead,
@@ -7825,6 +8130,69 @@ function createRecordsClient(config) {
7825
8130
  "tableFromExample"
7826
8131
  );
7827
8132
  },
8133
+ async tableListEverywhere() {
8134
+ return callDoor(
8135
+ DOORS.tableListEverywhere,
8136
+ { p_organization_id: org },
8137
+ "tableListEverywhere"
8138
+ );
8139
+ },
8140
+ async tableSync({ home_id, spec }) {
8141
+ return callDoor(
8142
+ DOORS.tableSync,
8143
+ { p_organization_id: org, p_home_id: home_id, p_spec: spec },
8144
+ "tableSync"
8145
+ );
8146
+ },
8147
+ async scopeTableProvision({ home_id, item_id, scope_id }) {
8148
+ return callDoor(
8149
+ DOORS.scopeTableProvision,
8150
+ { p_organization_id: org, p_item_id: item_id, p_scope_id: scope_id, p_home_id: home_id ?? null },
8151
+ "scopeTableProvision"
8152
+ );
8153
+ },
8154
+ async tableRowCounts({ table_ids }) {
8155
+ return callDoor(
8156
+ DOORS.tableRowCounts,
8157
+ { p_organization_id: org, p_table_ids: table_ids },
8158
+ "tableRowCounts"
8159
+ );
8160
+ },
8161
+ async recordHeaders({ ids }) {
8162
+ return callDoor(
8163
+ DOORS.recordHeaders,
8164
+ { p_organization_id: org, p_ids: ids },
8165
+ "recordHeaders"
8166
+ );
8167
+ },
8168
+ async tableShareOutsideGrant({ table_id, person, level }) {
8169
+ return callDoor(
8170
+ DOORS.tableShareOutsideGrant,
8171
+ { p_organization_id: org, p_table_id: table_id, p_person: person, p_level: level ?? "viewer" },
8172
+ "tableShareOutsideGrant"
8173
+ );
8174
+ },
8175
+ async viewRecordOrderSet({ view_id, record_ids }) {
8176
+ return callDoor(
8177
+ DOORS.viewRecordOrderSet,
8178
+ { p_organization_id: org, p_view_id: view_id, p_record_ids: record_ids },
8179
+ "viewRecordOrderSet"
8180
+ );
8181
+ },
8182
+ async readInViewOrder({ view_id, limit, offset }) {
8183
+ const door = await callDoor(
8184
+ DOORS.readRecordsInViewOrder,
8185
+ { p_organization_id: org, p_view_id: view_id, p_by_id: false, p_limit: limit ?? 200, p_offset: offset ?? 0 },
8186
+ "readInViewOrder"
8187
+ );
8188
+ if (!door.ok) return err(door.error);
8189
+ return ok(
8190
+ (door.data ?? []).map((row) => {
8191
+ const { document, hidden } = unfold(row.document);
8192
+ return { id: row.id, document, level: row.level, hidden, position: row.position ?? null };
8193
+ })
8194
+ );
8195
+ },
7828
8196
  async autonumberBackfill({ field_id }) {
7829
8197
  return callDoor(
7830
8198
  DOORS.autonumberBackfill,
@@ -7880,6 +8248,113 @@ function asWriteConflict(error, recordId) {
7880
8248
  };
7881
8249
  }
7882
8250
 
8251
+ // src/core/declareTable.ts
8252
+ function tokenFor(name) {
8253
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 48) || "table";
8254
+ }
8255
+ var DEFAULT_FIELDS = [
8256
+ { key: "title", label: "Title", type: "text", sort: 10, required: false }
8257
+ ];
8258
+ function fieldDeclarationFor(field) {
8259
+ return {
8260
+ key: field.key,
8261
+ label: field.label,
8262
+ sort: field.sort ?? 100,
8263
+ required: field.required ?? false,
8264
+ multi: field.multi ?? false,
8265
+ ...declaredType(field),
8266
+ ...field.config ? { config: field.config } : {},
8267
+ ...field.options ? { options: field.options } : {},
8268
+ ...field.relationTarget ? { relation_target: field.relationTarget } : {},
8269
+ ...field.unit ? { unit: field.unit } : {},
8270
+ ...field.kind ? { kind: field.kind } : {}
8271
+ };
8272
+ }
8273
+ async function declareTable(client, spec) {
8274
+ const fields = spec.fields && spec.fields.length > 0 ? spec.fields : DEFAULT_FIELDS;
8275
+ const slug = spec.slug ?? tokenFor(spec.name);
8276
+ const titleField = spec.titleField ?? fields[0].key;
8277
+ const personKernel = await client.personKernelId();
8278
+ if (!personKernel.ok) return personKernel;
8279
+ const home = await client.recordWrite({
8280
+ table_id: personKernel.data,
8281
+ data: { name: `${spec.name} Home` }
8282
+ });
8283
+ if (!home.ok) return home;
8284
+ const table = await client.tableDeclare({
8285
+ homeId: home.data,
8286
+ spec: {
8287
+ name: spec.name,
8288
+ slug,
8289
+ type: "entity",
8290
+ label_singular: spec.labelSingular ?? spec.name,
8291
+ label_plural: spec.labelPlural ?? spec.name,
8292
+ display: "list",
8293
+ weight: "light",
8294
+ ordered: false,
8295
+ row_order: "manual",
8296
+ title_field: titleField,
8297
+ retention_days: 365,
8298
+ // AGT-1: a table a person made is agent-writable by declaration; the
8299
+ // organization turns it off, never this package.
8300
+ agent_writable: true,
8301
+ default_sort: [{ field: titleField, direction: "asc" }],
8302
+ fields: fields.map((f) => ({ name: f.key }))
8303
+ }
8304
+ });
8305
+ if (!table.ok) {
8306
+ await client.recordDelete({ record_id: home.data });
8307
+ return table;
8308
+ }
8309
+ const written = await addFields(client, table.data, fields);
8310
+ if (!written.ok) return undo(client, table.data, home.data, spec.name, written.error);
8311
+ return { ok: true, data: table.data, homeId: home.data };
8312
+ }
8313
+ async function undo(client, tableId, homeId, name, refusal) {
8314
+ const removed = await client.recordDelete({ record_id: tableId });
8315
+ if (removed.ok) {
8316
+ await client.recordDelete({ record_id: homeId });
8317
+ return { ok: false, error: refusal };
8318
+ }
8319
+ return {
8320
+ ok: false,
8321
+ error: {
8322
+ ...refusal,
8323
+ hint: `${refusal.hint ? `${refusal.hint} ` : ""}"${name}" was created before this happened and could not be removed again, so it is in your tables with no columns \u2014 open it and add the first column, or delete it from its own Settings.`
8324
+ }
8325
+ };
8326
+ }
8327
+ async function addFields(client, tableId, fields) {
8328
+ for (const field of fields) {
8329
+ const result = await client.fieldDeclare({
8330
+ table_id: tableId,
8331
+ spec: fieldDeclarationFor(field)
8332
+ });
8333
+ if (!result.ok) return result;
8334
+ }
8335
+ return { ok: true, data: tableId };
8336
+ }
8337
+ var PLAIN_WORDS = {
8338
+ text: "text",
8339
+ string: "text",
8340
+ long_text: "long_text",
8341
+ longtext: "long_text",
8342
+ paragraph: "long_text",
8343
+ number: "number",
8344
+ range: "number",
8345
+ integer: "number",
8346
+ decimal: "number",
8347
+ float: "number"
8348
+ };
8349
+ function declaredType(field) {
8350
+ const word = (field.type ?? "text").toLowerCase();
8351
+ const plain = PLAIN_WORDS[word];
8352
+ if (plain) {
8353
+ return { plain: plain === "text" && field.config?.["multiline"] === true ? "long_text" : plain };
8354
+ }
8355
+ return { parity_type: word };
8356
+ }
8357
+
7883
8358
  // src/core/supabase.ts
7884
8359
  function refuse(code, message, hint) {
7885
8360
  return { data: null, error: { code, message, hint, details: "" } };