@ai-matrx/records 0.57.3 → 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.
@@ -2347,6 +2347,7 @@ var FIELD_KINDS = [
2347
2347
  "currency",
2348
2348
  "datetime",
2349
2349
  "email",
2350
+ "entity_reference",
2350
2351
  "formula",
2351
2352
  "json",
2352
2353
  "long_text",
@@ -2385,6 +2386,7 @@ function fieldKindFor(field, kernel) {
2385
2386
  case "list":
2386
2387
  return field.multi ? "multi_select" : "select";
2387
2388
  case "relation": {
2389
+ if (isEntityReferenceConfig(config)) return "entity_reference";
2388
2390
  const parity = String(config["parity"] ?? "");
2389
2391
  if (parity === "attachment" || parity === "member") return parity;
2390
2392
  if (kernel?.file && field.relation_target === kernel.file) return "attachment";
@@ -2402,6 +2404,14 @@ function fieldKindFor(field, kernel) {
2402
2404
  }
2403
2405
  }
2404
2406
  var UUID_TEXT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2407
+ function isEntityReferenceConfig(config) {
2408
+ const allowed = config?.["allowed_types"];
2409
+ return Array.isArray(allowed) && allowed.length > 0;
2410
+ }
2411
+ function entityReferenceKindsOf(field) {
2412
+ const allowed = field.config?.["allowed_types"];
2413
+ return Array.isArray(allowed) ? allowed.map((k) => String(k).trim().toLowerCase()).filter(Boolean) : [];
2414
+ }
2405
2415
  var TRUE_WORDS = /* @__PURE__ */ new Set(["true", "yes", "y", "1", "\u2713", "x", "checked", "on"]);
2406
2416
  var FALSE_WORDS = /* @__PURE__ */ new Set(["false", "no", "n", "0", "", "unchecked", "off"]);
2407
2417
  function coercionLabel(field) {
@@ -2517,6 +2527,23 @@ function coerceTypedAnswer(field, raw, opts = {}) {
2517
2527
  return {
2518
2528
  refusal: `${label} points at another record, so it is chosen rather than typed. Open it and pick the record.`
2519
2529
  };
2530
+ // SC-R / P12. An entity reference names a platform thing by kind and id.
2531
+ // An import or an agent may hand over `note:<id>` — or a bare id when the
2532
+ // column allows exactly one kind; the store then decides whether the thing
2533
+ // is there and whether the writer may open it. Words are chosen, not typed.
2534
+ case "entity_reference": {
2535
+ const allowed = entityReferenceKindsOf(field);
2536
+ const [maybeToken, maybeId] = text.includes(":") ? text.split(/:(.*)/s, 2) : ["", text];
2537
+ const token = (maybeToken ?? "").trim().toLowerCase() || (allowed.length === 1 ? allowed[0] : "");
2538
+ const id = (maybeId ?? "").trim();
2539
+ if (token && UUID_TEXT.test(id)) {
2540
+ const ref = { token, id: id.toLowerCase() };
2541
+ return { value: field.multi ? [ref] : ref };
2542
+ }
2543
+ return {
2544
+ refusal: `${label} points at something on the platform, so it is chosen rather than typed. Open it and pick it.`
2545
+ };
2546
+ }
2520
2547
  case "formula":
2521
2548
  case "lookup":
2522
2549
  case "rollup":
@@ -2573,6 +2600,10 @@ function publicFormPath(formId) {
2573
2600
  }
2574
2601
 
2575
2602
  // src/portal.ts
2603
+ var PORTAL_ACCENTS = ["slate", "green", "amber", "red", "blue", "violet", "teal"];
2604
+ function isPortalAccent(word) {
2605
+ return typeof word === "string" && PORTAL_ACCENTS.includes(word);
2606
+ }
2576
2607
  function portalPath(slug) {
2577
2608
  return `/portal/c/${slug}`;
2578
2609
  }
@@ -2596,11 +2627,17 @@ function emptyPortalArchiveLine() {
2596
2627
  }
2597
2628
 
2598
2629
  // src/aggregate.ts
2630
+ function isRuleFilter(filter) {
2631
+ if (!filter || typeof filter !== "object" || Array.isArray(filter)) return false;
2632
+ const node = filter;
2633
+ return typeof node.op === "string" && Array.isArray(node.args);
2634
+ }
2599
2635
  function measureKey(measure) {
2600
2636
  return measure.op === "count" ? "count" : `${measure.op}_${measure.key ?? ""}`;
2601
2637
  }
2602
2638
  function stableFilterKey(filter) {
2603
2639
  if (!filter) return "{}";
2640
+ if (isRuleFilter(filter)) return JSON.stringify(filter);
2604
2641
  const keys = Object.keys(filter).sort();
2605
2642
  if (keys.length === 0) return "{}";
2606
2643
  const parts = keys.map((key) => {
@@ -2696,6 +2733,101 @@ function recordChangeTrigger(tableId, options = {}) {
2696
2733
  ...options.changedFields && options.changedFields.length > 0 ? { changed_fields: options.changedFields } : {}
2697
2734
  };
2698
2735
  }
2736
+ function sheetSyncSpec(args) {
2737
+ const [header = [], ...body] = args.cells;
2738
+ const columns = header.map((c, i) => {
2739
+ const name = c == null ? "" : String(c).trim();
2740
+ return name === "" ? `Column ${i + 1}` : name;
2741
+ });
2742
+ if (columns.length === 0) {
2743
+ throw new Error("The sheet has no header row, so there are no columns to sync. Nothing was sent.");
2744
+ }
2745
+ const seenCol = /* @__PURE__ */ new Set();
2746
+ for (const c of columns) {
2747
+ if (seenCol.has(c)) throw new Error(`The sheet has two columns named "${c}". Rename one and sync again.`);
2748
+ seenCol.add(c);
2749
+ }
2750
+ const refIdx = args.refColumn == null ? -1 : columns.indexOf(args.refColumn);
2751
+ if (args.refColumn != null && refIdx < 0) {
2752
+ throw new Error(`The id column "${args.refColumn}" is not in the sheet's header. Nothing was sent.`);
2753
+ }
2754
+ const seen = /* @__PURE__ */ new Set();
2755
+ const rows = [];
2756
+ body.forEach((cells, i) => {
2757
+ if (cells.every((v) => v == null || String(v).trim() === "")) return;
2758
+ const ref = refIdx >= 0 ? String(cells[refIdx] ?? "").trim() : `row-${i + 2}`;
2759
+ if (ref === "") throw new Error(`Sheet row ${i + 2} has no value in the id column "${args.refColumn}".`);
2760
+ if (seen.has(ref)) throw new Error(`Two sheet rows share the id "${ref}"; each row needs its own.`);
2761
+ seen.add(ref);
2762
+ const values = {};
2763
+ columns.forEach((c, j) => {
2764
+ const v = cells[j];
2765
+ values[c] = v === void 0 || v === "" ? null : v;
2766
+ });
2767
+ rows.push({ ref, values });
2768
+ });
2769
+ return { source: args.source, table_name: args.tableName, columns, rows };
2770
+ }
2771
+ function withRowCounts(tables, counts) {
2772
+ if (!counts) return [...tables];
2773
+ const byId = new Map(counts.map((c) => [c.table_id, Number(c.visible_rows)]));
2774
+ return tables.map((t) => byId.has(t.id) ? { ...t, row_count: byId.get(t.id) } : t);
2775
+ }
2776
+ function moveInOrder(order, moved, toIndex) {
2777
+ const rest = order.filter((id) => id !== moved);
2778
+ const at = Math.max(0, Math.min(Math.trunc(toIndex), rest.length));
2779
+ return [...rest.slice(0, at), moved, ...rest.slice(at)];
2780
+ }
2781
+
2782
+ // src/placement.ts
2783
+ function keptOutOfSight(placement) {
2784
+ return placement?.kept_by_the_app === true;
2785
+ }
2786
+ function offeredAsContext(placement) {
2787
+ return placement?.offered_as_context === true;
2788
+ }
2789
+ function withTablePlacement(tables, facts) {
2790
+ if (!facts) return [...tables];
2791
+ const byId = new Map(facts.map((f) => [f.table_id, f]));
2792
+ return tables.map((table) => {
2793
+ const fact = byId.get(table.id);
2794
+ if (!fact || fact.kept_by_the_app === void 0) return table;
2795
+ return {
2796
+ ...table,
2797
+ kept_by_the_app: fact.kept_by_the_app,
2798
+ kept_for: fact.kept_for ?? null,
2799
+ offered_as_context: fact.offered_as_context ?? false,
2800
+ keeper_group: fact.keeper_group ?? null,
2801
+ keeper_says: fact.keeper_says ?? null,
2802
+ used_in_kind: fact.used_in_kind ?? null,
2803
+ used_in_id: fact.used_in_id ?? null,
2804
+ used_in_table_id: fact.used_in_table_id ?? null
2805
+ };
2806
+ });
2807
+ }
2808
+ function keptGroups(tables) {
2809
+ const out = [];
2810
+ const at = /* @__PURE__ */ new Map();
2811
+ for (const table of tables) {
2812
+ if (!keptOutOfSight(table)) continue;
2813
+ const group = table.keeper_group?.trim() || "Kept by the app";
2814
+ let i = at.get(group);
2815
+ if (i === void 0) {
2816
+ i = out.length;
2817
+ at.set(group, i);
2818
+ out.push({ group, tables: [] });
2819
+ }
2820
+ out[i].tables.push(table);
2821
+ }
2822
+ return out;
2823
+ }
2824
+ function keptTableHref(fact, hrefs) {
2825
+ if (fact.used_in_kind === "scope" && fact.used_in_id) return hrefs.scope(fact.used_in_id);
2826
+ if (fact.used_in_kind === "table" && (fact.used_in_table_id || fact.used_in_id)) {
2827
+ return hrefs.table(fact.used_in_table_id || fact.used_in_id);
2828
+ }
2829
+ return hrefs.table(fact.table_id);
2830
+ }
2699
2831
 
2700
2832
  // src/capture-queue.ts
2701
2833
  var DB_NAME = "matrx-capture";
@@ -2983,6 +3115,194 @@ function isRecordsErr(result) {
2983
3115
  var ok = (data) => ({ ok: true, data });
2984
3116
  var err = (error) => ({ ok: false, error });
2985
3117
 
3118
+ // src/core/pgError.ts
3119
+ var BY_SQLSTATE = {
3120
+ PT409: "stale_write",
3121
+ "42501": "door",
3122
+ "23514": "refused_by_rule",
3123
+ "22004": "invalid_argument",
3124
+ "22023": "invalid_argument",
3125
+ // 22P02 — a value does not fit the type the store holds for it. The class is
3126
+ // real and was hit live: an organization whose copy of a package table grew
3127
+ // `status` as a uuid-valued Field was sent the text "open", and the store
3128
+ // answered `invalid input syntax for type uuid: "open"`. It is the CALL that
3129
+ // is wrong for the shape the store holds, which is what `invalid_argument`
3130
+ // already means — the store's own sentence names the value and the type.
3131
+ "22P02": "invalid_argument",
3132
+ // 42703 — a column that is not there. The store raises it from
3133
+ // `custom.entity_table`, which is asked for a column by NAME, so the class is
3134
+ // exactly "the call named something the shape does not have": the same thing
3135
+ // `invalid_argument` already means, and the store's own sentence names the
3136
+ // column. Added 2026-09-20 (lane FORMS) because the package's live-door suite
3137
+ // caught it raised by a door this package did not know about — which is the
3138
+ // suite working, and is why the map is a census rather than a guess.
3139
+ "42703": "invalid_argument",
3140
+ // 22007 — a date or a time the store could not read as one. It is raised by
3141
+ // `custom.dashboard_window_sql`, which is handed a window a caller wrote
3142
+ // ("last 30 days", a literal date), so the class is again "the call named
3143
+ // something the shape does not accept" and the store's own sentence names the
3144
+ // text it could not read. Added 2026-09-20 (lane SCREEN-BREAK) after the
3145
+ // live-door census caught it — the same way 42703 arrived, which is the
3146
+ // census working rather than a guess.
3147
+ "22007": "invalid_argument",
3148
+ // 22003 — a number the store could not hold at the size it was handed. It is
3149
+ // raised by `custom.context_resolve`, which is given a window or a depth a
3150
+ // caller wrote, so the class is the same one 22P02, 42703 and 22007 already
3151
+ // are: the CALL named a value the shape does not accept, and the store's own
3152
+ // sentence names the number and the range. Added 2026-09-20 (lane GRID-TWO)
3153
+ // when the live-door census caught the release gate red on it — which is the
3154
+ // census working, and the fourth time it has been the thing that found a new
3155
+ // refusal class rather than a person guessing one.
3156
+ "22003": "invalid_argument",
3157
+ "02000": "not_found",
3158
+ "23503": "missing_reference",
3159
+ "23505": "already_exists",
3160
+ "0A000": "not_supported",
3161
+ "53400": "store_limit",
3162
+ "22012": "store_limit",
3163
+ // 54001 — `custom.record_delete` stops a cascade that nests past sixty-four
3164
+ // levels of containment, because that is a loop somebody built rather than a
3165
+ // hierarchy, and saying so beats recursing until the server runs out of
3166
+ // stack. It is a LIMIT of the store, and the store's own sentence names what
3167
+ // to do about it, so it reads as one.
3168
+ "54001": "store_limit",
3169
+ // 54000 — the grid primitives' organization ceilings (GRID-PRIMITIVES, 2026-09-23):
3170
+ // `custom.action_run` on a selection past its knob, `custom.table_decorate` past the
3171
+ // rule count, `custom.table_from_example` past `custom/example_rows_max`,
3172
+ // `custom.table_webhook_declare` past `custom/table_webhooks_max`, and
3173
+ // `custom.hub_changed_by`. Each names its knob in the hint and writes nothing, so it
3174
+ // reads exactly like 54001: a limit of the store, in the store's own sentence.
3175
+ "54000": "store_limit",
3176
+ // VIS: `custom.has_visibility_at` refuses to answer from a snapshot older
3177
+ // than a write this reader has already seen, rather than returning something
3178
+ // they just lost access to. It is retryable on a fresher connection, which is
3179
+ // a different instruction from every other refusal here.
3180
+ "40001": "stale_read",
3181
+ // 57014 — the store cancelled the statement at its own timeout. Like
3182
+ // `stale_read` and unlike every other refusal here, the call was never
3183
+ // refused on its merits: the same call on a smaller page, or once the
3184
+ // organization's kernel is not being rewritten, can succeed. It gets its own
3185
+ // code so a screen can offer "try again" honestly instead of showing a
3186
+ // failure sentence the caller cannot act on.
3187
+ "57014": "timed_out",
3188
+ // PostgREST's own two: the store is not being SERVED, which is a different
3189
+ // thing from the store refusing. `custom` absent from `pgrst.db_schemas`, or
3190
+ // the grants not copied, land here.
3191
+ PGRST202: "store_not_served",
3192
+ PGRST205: "store_not_served",
3193
+ PGRST301: "door",
3194
+ // PT428 — NOT A FAILURE, and the release gate's live census is what found it
3195
+ // missing: the store raises it and this package answered `internal`, which is
3196
+ // the one classification that tells a screen nothing. A stage gate whose
3197
+ // `on_fail` is `require_approval` files the request and leaves the record
3198
+ // where it was; the transaction is rolled back to the pipeline's own block, so
3199
+ // "nothing was written" is a fact rather than a promise. Landed by lane
3200
+ // STAGE-RULES; mapped here by STAGE-RULES-2 the day after, when the census
3201
+ // caught it — the sixth time that census has been the thing that found a
3202
+ // refusal class rather than a person guessing one.
3203
+ PT428: "needs_approval"
3204
+ };
3205
+ var MAPPED_SQLSTATES = Object.keys(BY_SQLSTATE);
3206
+ function parseDetail(details) {
3207
+ if (!details) return void 0;
3208
+ const trimmed = details.trim();
3209
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return details;
3210
+ try {
3211
+ return JSON.parse(trimmed);
3212
+ } catch {
3213
+ return details;
3214
+ }
3215
+ }
3216
+ function isNotASqlstate(code) {
3217
+ if (!code) return true;
3218
+ return /^[0-9]{3}$/.test(code);
3219
+ }
3220
+ function mapPgError(error, where) {
3221
+ const raw = error.code ?? void 0;
3222
+ const sqlstate = raw && raw !== "" ? raw : void 0;
3223
+ const mapped = {
3224
+ code: "internal",
3225
+ message: error.message,
3226
+ ...error.hint ? { hint: error.hint } : {},
3227
+ ...sqlstate ? { sqlstate } : {}
3228
+ };
3229
+ if (isNotASqlstate(sqlstate)) {
3230
+ mapped.code = "unreachable";
3231
+ } else {
3232
+ mapped.code = BY_SQLSTATE[sqlstate] ?? "internal";
3233
+ }
3234
+ const detail = parseDetail(error.details);
3235
+ if (detail !== void 0) mapped.detail = detail;
3236
+ if (mapped.code === "internal") {
3237
+ mapped.unmapped = true;
3238
+ mapped.diagnostic = `@ai-matrx/records did not recognise SQLSTATE ${sqlstate ?? "(none)"} from ${where}; the store's own message is the thing to act on.`;
3239
+ } else if (mapped.code === "unreachable") {
3240
+ mapped.unmapped = true;
3241
+ mapped.diagnostic = `@ai-matrx/records never reached the store from ${where}: ${raw ? `the transport answered ${raw}` : "no response"} \u2014 ${error.message}`;
3242
+ mapped.message = "We could not reach your data.";
3243
+ delete mapped.hint;
3244
+ }
3245
+ return mapped;
3246
+ }
3247
+ function staleWriteDetail(error) {
3248
+ if (error.code !== "stale_write") return null;
3249
+ const detail = error.detail;
3250
+ if (!detail || typeof detail.expected_version !== "number" || typeof detail.current_version !== "number") {
3251
+ return null;
3252
+ }
3253
+ return {
3254
+ expected_version: detail.expected_version,
3255
+ current_version: detail.current_version,
3256
+ contested_fields: detail.contested_fields ?? {}
3257
+ };
3258
+ }
3259
+
3260
+ // src/relation.ts
3261
+ 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;
3262
+ function isRefLike(value) {
3263
+ return typeof value === "object" && value !== null && "token" in value && "id" in value;
3264
+ }
3265
+ function entityRefsOf(value) {
3266
+ const items = Array.isArray(value) ? value : value == null ? [] : [value];
3267
+ const out = [];
3268
+ for (const item of items) {
3269
+ if (!isRefLike(item)) continue;
3270
+ const token = String(item.token ?? "").trim().toLowerCase();
3271
+ const id = String(item.id ?? "").trim();
3272
+ if (!token || !ENTITY_REF_ID.test(id)) continue;
3273
+ const label = item.label;
3274
+ out.push({ token, id: id.toLowerCase(), label: typeof label === "string" ? label : null });
3275
+ }
3276
+ return out;
3277
+ }
3278
+ function entityRefsToWrite(value, multi) {
3279
+ const seen = /* @__PURE__ */ new Set();
3280
+ const refs = [];
3281
+ for (const ref of entityRefsOf(value)) {
3282
+ const k = `${ref.token}:${ref.id}`;
3283
+ if (seen.has(k)) continue;
3284
+ seen.add(k);
3285
+ refs.push({ token: ref.token, id: ref.id });
3286
+ }
3287
+ if (multi) return refs;
3288
+ return refs[0] ?? null;
3289
+ }
3290
+ async function entityReferenceKinds(source) {
3291
+ const { data, error } = await source.rpc("entity_reference_kinds", {}, { schema: "custom" });
3292
+ if (error) return err(mapPgError(error, "entityReferenceKinds"));
3293
+ return ok(Array.isArray(data) ? data : []);
3294
+ }
3295
+ async function entityReferenceWords(source, organizationId, refs) {
3296
+ if (refs.length === 0) return ok([]);
3297
+ const { data, error } = await source.rpc(
3298
+ "entity_reference_words",
3299
+ { p_organization_id: organizationId, p_refs: refs.map((r) => ({ token: r.token, id: r.id })) },
3300
+ { schema: "custom" }
3301
+ );
3302
+ if (error) return err(mapPgError(error, "entityReferenceWords"));
3303
+ return ok(entityRefsOf(data));
3304
+ }
3305
+
2986
3306
  // src/ops.ts
2987
3307
  var REMEMBER_MS = 3e4;
2988
3308
  var REMEMBER_MAX = 500;
@@ -3696,6 +4016,7 @@ var STORE_DOORS = [
3696
4016
  { name: "table_work_kind", args: "p_organization_id uuid, p_table_id uuid", returns: "text", security: "invoker" },
3697
4017
  { 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" },
3698
4018
  { name: "tables_described_without_asking", args: "", returns: "TABLE(function_name text, identity_args text, why text)", security: "invoker" },
4019
+ { 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" },
3699
4020
  { 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" },
3700
4021
  { name: "trg_associations_bump_visibility", args: "", returns: "trigger", security: "definer" },
3701
4022
  { name: "undeclared_keys", args: "p_organization_id uuid, p_table_id uuid, p_data jsonb", returns: "text[]", security: "invoker" },
@@ -3720,6 +4041,7 @@ var STORE_DOORS = [
3720
4041
  { 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" },
3721
4042
  { name: "visible_record_ids", args: "p_user_id uuid, p_required permission_level DEFAULT 'viewer'::permission_level", returns: "TABLE(id uuid)", security: "definer" },
3722
4043
  { 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" },
4044
+ { name: "where_id_opens", args: "p_id uuid", returns: "jsonb", security: "definer" },
3723
4045
  { name: "widget_kernel_id", args: "", returns: "uuid", security: "invoker" },
3724
4046
  { name: "withheld_marker", args: "p_mask jsonb, p_key text", returns: "jsonb", security: "invoker" },
3725
4047
  { name: "withheld_sentence", args: "p_mask jsonb, p_key text", returns: "text", security: "invoker" },
@@ -3826,7 +4148,7 @@ var STORE_REFUSAL_CODES = [
3826
4148
  { 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" },
3827
4149
  { 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" },
3828
4150
  { code: "40001", raised_by: "has_visibility_at" },
3829
- { 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" },
4151
+ { 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" },
3830
4152
  { code: "42703", raised_by: "entity_table,portal_declare" },
3831
4153
  { code: "53400", raised_by: "anon_rate_take,delete_cascade_closure,enrich_due,enrich_land,promote_field" },
3832
4154
  { code: "54000", raised_by: "action_run,hub_changed_by,table_decorate,table_from_example,table_webhook_declare" },
@@ -4892,6 +5214,20 @@ var OWED_BY = {
4892
5214
  table_webhook_archive: "GRID-PRIMITIVES G4 \u2014 gridprim_every_row_change_reaches_the_webhook.sql (chair's production apply)",
4893
5215
  table_from_example: "GRID-PRIMITIVES G5 \u2014 gridprim_a_table_can_start_from_a_real_example.sql (chair's production apply)",
4894
5216
  autonumber_backfill: "GRID-PRIMITIVES G5 \u2014 gridprim_a_table_can_start_from_a_real_example.sql (chair's production apply)",
5217
+ table_list_everywhere: "GRID-PRIMITIVES G9 \u2014 gridprim_every_picker_lists_both_stores.sql (chair's production apply)",
5218
+ table_sync: "GRID-PRIMITIVES G10 \u2014 gridprim_a_synced_sheet_is_one_table_that_refreshes.sql (chair's production apply)",
5219
+ scope_table_provision: "GRID-PRIMITIVES G11 \u2014 gridprim_a_scope_provisions_its_table_in_the_store.sql (chair's production apply)",
5220
+ 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)",
5221
+ 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)",
5222
+ 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)",
5223
+ 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)",
5224
+ 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)",
5225
+ 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)",
5226
+ 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)",
5227
+ 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)",
5228
+ 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)",
5229
+ 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)",
5230
+ 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)",
4895
5231
  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"
4896
5232
  };
4897
5233
  var TRUST_SCHEMA = "iam";
@@ -5095,6 +5431,8 @@ var DOORS = {
5095
5431
  pipelineBoard: "pipeline_board",
5096
5432
  pipelineTransitionRefusal: "pipeline_transition_refusal",
5097
5433
  pipelineMove: "pipeline_move",
5434
+ /** Lane S4: many cards, one call, a verdict and a version per card. */
5435
+ pipelineMoveMany: "pipeline_move_many",
5098
5436
  tableStageField: "table_stage_field",
5099
5437
  // STAGE-RULES. A gate can refuse or it can hand the change to the approvals queue; the
5100
5438
  // closed list is the store's, the knob is the organization's, and what is WAITING on a
@@ -5105,6 +5443,7 @@ var DOORS = {
5105
5443
  pipelinePending: "pipeline_pending",
5106
5444
  pipelineGatePreview: "pipeline_gate_preview",
5107
5445
  ruleMembers: "rule_members",
5446
+ ruleMembersVisible: "rule_members_visible",
5108
5447
  ruleMembership: "rule_membership",
5109
5448
  ruleRun: "rule_run",
5110
5449
  ruleEval: "rule_eval",
@@ -5119,6 +5458,12 @@ var DOORS = {
5119
5458
  // are server-lane doors and a browser never calls them.
5120
5459
  forms: "forms",
5121
5460
  formDeclare: "form_declare",
5461
+ /**
5462
+ * WHICH QUESTIONS THE OWNER'S PREVIEW ASKS NEXT (lane FORMS-FIX-1). The same store answer a
5463
+ * stranger's form gets (`custom._form_questions_asked` → `custom.rule_eval`), behind the
5464
+ * organization wall and the caller's own rung on the Table — never `rule_eval` from a browser.
5465
+ */
5466
+ formPreviewAsks: "form_preview_asks",
5122
5467
  // PRODUCTS row 14 — "let clients book a 30-minute consult". A booking page IS an
5123
5468
  // `anon_form`, so it is opened and closed by `anonPublish` like any other; this is
5124
5469
  // the owner's list of them with the numbers that make it worth opening. The
@@ -5178,6 +5523,11 @@ var DOORS = {
5178
5523
  portalInvite: "portal_invite",
5179
5524
  portalRevoke: "portal_revoke",
5180
5525
  portalPreview: "portal_preview",
5526
+ // S6 — the CLIENT's two form doors. They are store doors (schema `custom`), called by a
5527
+ // signed-in portal principal, and each refuses anybody who is not a live principal of that
5528
+ // live portal — so, unlike the sign-in pair above, they belong in this table.
5529
+ portalForm: "portal_form",
5530
+ portalFormSubmit: "portal_form_submit",
5181
5531
  // ORG-CLEANUP, 2026-09-22 — A PORTAL IS ARCHIVED, NEVER DELETED.
5182
5532
  // `portals` keeps its signature and HIDES archived portals (the law's default
5183
5533
  // is to hide); `list_portals(org, 'active'|'archived'|'all')` is the
@@ -5373,6 +5723,8 @@ var DOORS = {
5373
5723
  workApprovalRequest: "work_approval_request",
5374
5724
  workApprovalMayDecide: "work_approval_may_decide",
5375
5725
  workApprovalDecide: "work_approval_decide",
5726
+ /** Lane S4: many inbox decisions, one call, a verdict per item. */
5727
+ workDecideMany: "work_decide_many",
5376
5728
  workApprovalRead: "work_approval_read",
5377
5729
  // PRODUCTS row 13 — checklists and SOP runs.
5378
5730
  checklistRefusal: "checklist_refusal",
@@ -5398,6 +5750,14 @@ var DOORS = {
5398
5750
  tableFromExample: "table_from_example",
5399
5751
  autonumberBackfill: "autonumber_backfill",
5400
5752
  ioExport: "io_export",
5753
+ tableListEverywhere: "table_list_everywhere",
5754
+ tableSync: "table_sync",
5755
+ scopeTableProvision: "scope_table_provision",
5756
+ tableRowCounts: "table_row_counts",
5757
+ recordHeaders: "record_headers",
5758
+ viewRecordOrderSet: "view_record_order_set",
5759
+ readRecordsInViewOrder: "read_records_in_view_order",
5760
+ tableShareOutsideGrant: "table_share_outside_grant",
5401
5761
  // Declared here BECAUSE they do not exist yet: naming them is what makes the
5402
5762
  // absence visible to `pnpm check:store-registry` and to the suite.
5403
5763
  metadataSearch: "metadata_search",
@@ -5415,148 +5775,6 @@ function doorCensus() {
5415
5775
  return { present: present.sort(), absent: absent.sort() };
5416
5776
  }
5417
5777
 
5418
- // src/core/pgError.ts
5419
- var BY_SQLSTATE = {
5420
- PT409: "stale_write",
5421
- "42501": "door",
5422
- "23514": "refused_by_rule",
5423
- "22004": "invalid_argument",
5424
- "22023": "invalid_argument",
5425
- // 22P02 — a value does not fit the type the store holds for it. The class is
5426
- // real and was hit live: an organization whose copy of a package table grew
5427
- // `status` as a uuid-valued Field was sent the text "open", and the store
5428
- // answered `invalid input syntax for type uuid: "open"`. It is the CALL that
5429
- // is wrong for the shape the store holds, which is what `invalid_argument`
5430
- // already means — the store's own sentence names the value and the type.
5431
- "22P02": "invalid_argument",
5432
- // 42703 — a column that is not there. The store raises it from
5433
- // `custom.entity_table`, which is asked for a column by NAME, so the class is
5434
- // exactly "the call named something the shape does not have": the same thing
5435
- // `invalid_argument` already means, and the store's own sentence names the
5436
- // column. Added 2026-09-20 (lane FORMS) because the package's live-door suite
5437
- // caught it raised by a door this package did not know about — which is the
5438
- // suite working, and is why the map is a census rather than a guess.
5439
- "42703": "invalid_argument",
5440
- // 22007 — a date or a time the store could not read as one. It is raised by
5441
- // `custom.dashboard_window_sql`, which is handed a window a caller wrote
5442
- // ("last 30 days", a literal date), so the class is again "the call named
5443
- // something the shape does not accept" and the store's own sentence names the
5444
- // text it could not read. Added 2026-09-20 (lane SCREEN-BREAK) after the
5445
- // live-door census caught it — the same way 42703 arrived, which is the
5446
- // census working rather than a guess.
5447
- "22007": "invalid_argument",
5448
- // 22003 — a number the store could not hold at the size it was handed. It is
5449
- // raised by `custom.context_resolve`, which is given a window or a depth a
5450
- // caller wrote, so the class is the same one 22P02, 42703 and 22007 already
5451
- // are: the CALL named a value the shape does not accept, and the store's own
5452
- // sentence names the number and the range. Added 2026-09-20 (lane GRID-TWO)
5453
- // when the live-door census caught the release gate red on it — which is the
5454
- // census working, and the fourth time it has been the thing that found a new
5455
- // refusal class rather than a person guessing one.
5456
- "22003": "invalid_argument",
5457
- "02000": "not_found",
5458
- "23503": "missing_reference",
5459
- "23505": "already_exists",
5460
- "0A000": "not_supported",
5461
- "53400": "store_limit",
5462
- "22012": "store_limit",
5463
- // 54001 — `custom.record_delete` stops a cascade that nests past sixty-four
5464
- // levels of containment, because that is a loop somebody built rather than a
5465
- // hierarchy, and saying so beats recursing until the server runs out of
5466
- // stack. It is a LIMIT of the store, and the store's own sentence names what
5467
- // to do about it, so it reads as one.
5468
- "54001": "store_limit",
5469
- // 54000 — the grid primitives' organization ceilings (GRID-PRIMITIVES, 2026-09-23):
5470
- // `custom.action_run` on a selection past its knob, `custom.table_decorate` past the
5471
- // rule count, `custom.table_from_example` past `custom/example_rows_max`,
5472
- // `custom.table_webhook_declare` past `custom/table_webhooks_max`, and
5473
- // `custom.hub_changed_by`. Each names its knob in the hint and writes nothing, so it
5474
- // reads exactly like 54001: a limit of the store, in the store's own sentence.
5475
- "54000": "store_limit",
5476
- // VIS: `custom.has_visibility_at` refuses to answer from a snapshot older
5477
- // than a write this reader has already seen, rather than returning something
5478
- // they just lost access to. It is retryable on a fresher connection, which is
5479
- // a different instruction from every other refusal here.
5480
- "40001": "stale_read",
5481
- // 57014 — the store cancelled the statement at its own timeout. Like
5482
- // `stale_read` and unlike every other refusal here, the call was never
5483
- // refused on its merits: the same call on a smaller page, or once the
5484
- // organization's kernel is not being rewritten, can succeed. It gets its own
5485
- // code so a screen can offer "try again" honestly instead of showing a
5486
- // failure sentence the caller cannot act on.
5487
- "57014": "timed_out",
5488
- // PostgREST's own two: the store is not being SERVED, which is a different
5489
- // thing from the store refusing. `custom` absent from `pgrst.db_schemas`, or
5490
- // the grants not copied, land here.
5491
- PGRST202: "store_not_served",
5492
- PGRST205: "store_not_served",
5493
- PGRST301: "door",
5494
- // PT428 — NOT A FAILURE, and the release gate's live census is what found it
5495
- // missing: the store raises it and this package answered `internal`, which is
5496
- // the one classification that tells a screen nothing. A stage gate whose
5497
- // `on_fail` is `require_approval` files the request and leaves the record
5498
- // where it was; the transaction is rolled back to the pipeline's own block, so
5499
- // "nothing was written" is a fact rather than a promise. Landed by lane
5500
- // STAGE-RULES; mapped here by STAGE-RULES-2 the day after, when the census
5501
- // caught it — the sixth time that census has been the thing that found a
5502
- // refusal class rather than a person guessing one.
5503
- PT428: "needs_approval"
5504
- };
5505
- var MAPPED_SQLSTATES = Object.keys(BY_SQLSTATE);
5506
- function parseDetail(details) {
5507
- if (!details) return void 0;
5508
- const trimmed = details.trim();
5509
- if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return details;
5510
- try {
5511
- return JSON.parse(trimmed);
5512
- } catch {
5513
- return details;
5514
- }
5515
- }
5516
- function isNotASqlstate(code) {
5517
- if (!code) return true;
5518
- return /^[0-9]{3}$/.test(code);
5519
- }
5520
- function mapPgError(error, where) {
5521
- const raw = error.code ?? void 0;
5522
- const sqlstate = raw && raw !== "" ? raw : void 0;
5523
- const mapped = {
5524
- code: "internal",
5525
- message: error.message,
5526
- ...error.hint ? { hint: error.hint } : {},
5527
- ...sqlstate ? { sqlstate } : {}
5528
- };
5529
- if (isNotASqlstate(sqlstate)) {
5530
- mapped.code = "unreachable";
5531
- } else {
5532
- mapped.code = BY_SQLSTATE[sqlstate] ?? "internal";
5533
- }
5534
- const detail = parseDetail(error.details);
5535
- if (detail !== void 0) mapped.detail = detail;
5536
- if (mapped.code === "internal") {
5537
- mapped.unmapped = true;
5538
- mapped.diagnostic = `@ai-matrx/records did not recognise SQLSTATE ${sqlstate ?? "(none)"} from ${where}; the store's own message is the thing to act on.`;
5539
- } else if (mapped.code === "unreachable") {
5540
- mapped.unmapped = true;
5541
- mapped.diagnostic = `@ai-matrx/records never reached the store from ${where}: ${raw ? `the transport answered ${raw}` : "no response"} \u2014 ${error.message}`;
5542
- mapped.message = "We could not reach your data.";
5543
- delete mapped.hint;
5544
- }
5545
- return mapped;
5546
- }
5547
- function staleWriteDetail(error) {
5548
- if (error.code !== "stale_write") return null;
5549
- const detail = error.detail;
5550
- if (!detail || typeof detail.expected_version !== "number" || typeof detail.current_version !== "number") {
5551
- return null;
5552
- }
5553
- return {
5554
- expected_version: detail.expected_version,
5555
- current_version: detail.current_version,
5556
- contested_fields: detail.contested_fields ?? {}
5557
- };
5558
- }
5559
-
5560
5778
  // src/core/recordChanges.ts
5561
5779
  var listeners = /* @__PURE__ */ new Set();
5562
5780
  function announceRecordChanged(recordId) {
@@ -6198,10 +6416,11 @@ function createRecordsClient(config) {
6198
6416
  "pipelineRead"
6199
6417
  );
6200
6418
  },
6201
- async pipelineBoard({ table_id, measure }) {
6419
+ async pipelineBoard({ table_id, measure, filter }) {
6420
+ const asked = filter && stableFilterKey(filter) !== "{}";
6202
6421
  return callDoor(
6203
6422
  DOORS.pipelineBoard,
6204
- { p_organization_id: org, p_table_id: table_id, p_measure: measure ?? null },
6423
+ 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 },
6205
6424
  "pipelineBoard"
6206
6425
  );
6207
6426
  },
@@ -6225,6 +6444,13 @@ function createRecordsClient(config) {
6225
6444
  "pipelineMove"
6226
6445
  );
6227
6446
  },
6447
+ async pipelineMoveMany({ moves }) {
6448
+ return callDoor(
6449
+ DOORS.pipelineMoveMany,
6450
+ { p_organization_id: org, p_moves: moves },
6451
+ "pipelineMoveMany"
6452
+ );
6453
+ },
6228
6454
  async pipelineDeclare({ table_id, spec }) {
6229
6455
  return callDoor(
6230
6456
  DOORS.pipelineDeclare,
@@ -6274,6 +6500,18 @@ function createRecordsClient(config) {
6274
6500
  "pipelineGatePreview"
6275
6501
  );
6276
6502
  },
6503
+ async ruleMembersVisible({ rule_id, limit = 200, offset = 0 }) {
6504
+ const door = await callDoor(
6505
+ DOORS.ruleMembersVisible,
6506
+ { p_organization_id: org, p_rule_id: rule_id, p_limit: limit, p_offset: offset },
6507
+ "ruleMembersVisible"
6508
+ );
6509
+ if (!door.ok) return err(door.error);
6510
+ return ok((door.data ?? []).map((row) => {
6511
+ const { document, hidden } = unfold(row.document);
6512
+ return { id: row.id, document, level: row.level, hidden };
6513
+ }));
6514
+ },
6277
6515
  async ruleMembers({ rule_id }) {
6278
6516
  return callDoor(
6279
6517
  DOORS.ruleMembers,
@@ -6326,6 +6564,13 @@ function createRecordsClient(config) {
6326
6564
  "bookingDeclare"
6327
6565
  );
6328
6566
  },
6567
+ async formPreviewAsks({ table_id, questions, values }) {
6568
+ return callDoor(
6569
+ DOORS.formPreviewAsks,
6570
+ { p_organization_id: org, p_table_id: table_id, p_questions: questions, p_values: values },
6571
+ "formPreviewAsks"
6572
+ );
6573
+ },
6329
6574
  async formDeclare(args) {
6330
6575
  return callDoor(
6331
6576
  DOORS.formDeclare,
@@ -6501,11 +6746,33 @@ function createRecordsClient(config) {
6501
6746
  p_tables: args.tables,
6502
6747
  p_portal_id: args.portal_id ?? null,
6503
6748
  p_slug: args.slug ?? null,
6504
- p_sign_in_method: args.sign_in_method ?? "magic_link"
6749
+ p_sign_in_method: args.sign_in_method ?? "magic_link",
6750
+ // S6: sent only when given, so a caller that says nothing about the look keeps it.
6751
+ ...args.config !== void 0 ? { p_config: args.config } : {}
6505
6752
  },
6506
6753
  "portalDeclare"
6507
6754
  );
6508
6755
  },
6756
+ async portalForm({ portal_id, form_id }) {
6757
+ return callDoor(
6758
+ DOORS.portalForm,
6759
+ { p_organization_id: org, p_portal_id: portal_id, p_form_id: form_id },
6760
+ "portalForm"
6761
+ );
6762
+ },
6763
+ async portalFormSubmit({ portal_id, form_id, answers, client_key }) {
6764
+ return callDoor(
6765
+ DOORS.portalFormSubmit,
6766
+ {
6767
+ p_organization_id: org,
6768
+ p_portal_id: portal_id,
6769
+ p_form_id: form_id,
6770
+ p_payload: answers,
6771
+ p_client_key: client_key ?? null
6772
+ },
6773
+ "portalFormSubmit"
6774
+ );
6775
+ },
6509
6776
  async portalInvite(args) {
6510
6777
  return callDoor(
6511
6778
  DOORS.portalInvite,
@@ -6704,10 +6971,17 @@ function createRecordsClient(config) {
6704
6971
  "dashboardDeclare"
6705
6972
  );
6706
6973
  },
6707
- async dashboardRun({ dashboard_id, filter }) {
6974
+ async dashboardRun({ dashboard_id, filter, compare, grain }) {
6708
6975
  return callDoor(
6709
6976
  DOORS.dashboardRun,
6710
- { p_organization_id: org, p_dashboard_id: dashboard_id, p_filter: filter ?? {} },
6977
+ {
6978
+ p_organization_id: org,
6979
+ p_dashboard_id: dashboard_id,
6980
+ p_filter: filter ?? {},
6981
+ // Sent only when asked, so a caller that never compares calls exactly what it did.
6982
+ ...compare ? { p_compare: compare } : {},
6983
+ ...grain ? { p_grain: grain } : {}
6984
+ },
6711
6985
  "dashboardRun"
6712
6986
  );
6713
6987
  },
@@ -6732,7 +7006,7 @@ function createRecordsClient(config) {
6732
7006
  "dashboardDelete"
6733
7007
  );
6734
7008
  },
6735
- async recordAggregate({ table_id, groupBy, measures, bucket, filter, limit }) {
7009
+ async recordAggregate({ table_id, groupBy, measures, bucket, filter, limit, compare }) {
6736
7010
  return callDoor(
6737
7011
  DOORS.recordAggregate,
6738
7012
  {
@@ -6742,7 +7016,8 @@ function createRecordsClient(config) {
6742
7016
  p_measures: measures ?? [],
6743
7017
  p_bucket: bucket ?? null,
6744
7018
  p_filter: filter ?? {},
6745
- p_limit: limit ?? 200
7019
+ p_limit: limit ?? 200,
7020
+ ...compare ? { p_compare: compare } : {}
6746
7021
  },
6747
7022
  "recordAggregate"
6748
7023
  );
@@ -7470,6 +7745,13 @@ function createRecordsClient(config) {
7470
7745
  "workApprovalDecide"
7471
7746
  );
7472
7747
  },
7748
+ async workDecideMany({ decisions }) {
7749
+ return callDoor(
7750
+ DOORS.workDecideMany,
7751
+ { p_organization_id: org, p_decisions: decisions },
7752
+ "workDecideMany"
7753
+ );
7754
+ },
7473
7755
  async workApprovalRead({ approval_id }) {
7474
7756
  return callDoor(
7475
7757
  DOORS.workApprovalRead,
@@ -7673,6 +7955,69 @@ function createRecordsClient(config) {
7673
7955
  "tableFromExample"
7674
7956
  );
7675
7957
  },
7958
+ async tableListEverywhere() {
7959
+ return callDoor(
7960
+ DOORS.tableListEverywhere,
7961
+ { p_organization_id: org },
7962
+ "tableListEverywhere"
7963
+ );
7964
+ },
7965
+ async tableSync({ home_id, spec }) {
7966
+ return callDoor(
7967
+ DOORS.tableSync,
7968
+ { p_organization_id: org, p_home_id: home_id, p_spec: spec },
7969
+ "tableSync"
7970
+ );
7971
+ },
7972
+ async scopeTableProvision({ home_id, item_id, scope_id }) {
7973
+ return callDoor(
7974
+ DOORS.scopeTableProvision,
7975
+ { p_organization_id: org, p_item_id: item_id, p_scope_id: scope_id, p_home_id: home_id ?? null },
7976
+ "scopeTableProvision"
7977
+ );
7978
+ },
7979
+ async tableRowCounts({ table_ids }) {
7980
+ return callDoor(
7981
+ DOORS.tableRowCounts,
7982
+ { p_organization_id: org, p_table_ids: table_ids },
7983
+ "tableRowCounts"
7984
+ );
7985
+ },
7986
+ async recordHeaders({ ids }) {
7987
+ return callDoor(
7988
+ DOORS.recordHeaders,
7989
+ { p_organization_id: org, p_ids: ids },
7990
+ "recordHeaders"
7991
+ );
7992
+ },
7993
+ async tableShareOutsideGrant({ table_id, person, level }) {
7994
+ return callDoor(
7995
+ DOORS.tableShareOutsideGrant,
7996
+ { p_organization_id: org, p_table_id: table_id, p_person: person, p_level: level ?? "viewer" },
7997
+ "tableShareOutsideGrant"
7998
+ );
7999
+ },
8000
+ async viewRecordOrderSet({ view_id, record_ids }) {
8001
+ return callDoor(
8002
+ DOORS.viewRecordOrderSet,
8003
+ { p_organization_id: org, p_view_id: view_id, p_record_ids: record_ids },
8004
+ "viewRecordOrderSet"
8005
+ );
8006
+ },
8007
+ async readInViewOrder({ view_id, limit, offset }) {
8008
+ const door = await callDoor(
8009
+ DOORS.readRecordsInViewOrder,
8010
+ { p_organization_id: org, p_view_id: view_id, p_by_id: false, p_limit: limit ?? 200, p_offset: offset ?? 0 },
8011
+ "readInViewOrder"
8012
+ );
8013
+ if (!door.ok) return err(door.error);
8014
+ return ok(
8015
+ (door.data ?? []).map((row) => {
8016
+ const { document, hidden } = unfold(row.document);
8017
+ return { id: row.id, document, level: row.level, hidden, position: row.position ?? null };
8018
+ })
8019
+ );
8020
+ },
7676
8021
  async autonumberBackfill({ field_id }) {
7677
8022
  return callDoor(
7678
8023
  DOORS.autonumberBackfill,
@@ -7728,6 +8073,113 @@ function asWriteConflict(error, recordId) {
7728
8073
  };
7729
8074
  }
7730
8075
 
8076
+ // src/core/declareTable.ts
8077
+ function tokenFor(name) {
8078
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 48) || "table";
8079
+ }
8080
+ var DEFAULT_FIELDS = [
8081
+ { key: "title", label: "Title", type: "text", sort: 10, required: false }
8082
+ ];
8083
+ function fieldDeclarationFor(field) {
8084
+ return {
8085
+ key: field.key,
8086
+ label: field.label,
8087
+ sort: field.sort ?? 100,
8088
+ required: field.required ?? false,
8089
+ multi: field.multi ?? false,
8090
+ ...declaredType(field),
8091
+ ...field.config ? { config: field.config } : {},
8092
+ ...field.options ? { options: field.options } : {},
8093
+ ...field.relationTarget ? { relation_target: field.relationTarget } : {},
8094
+ ...field.unit ? { unit: field.unit } : {},
8095
+ ...field.kind ? { kind: field.kind } : {}
8096
+ };
8097
+ }
8098
+ async function declareTable(client, spec) {
8099
+ const fields = spec.fields && spec.fields.length > 0 ? spec.fields : DEFAULT_FIELDS;
8100
+ const slug = spec.slug ?? tokenFor(spec.name);
8101
+ const titleField = spec.titleField ?? fields[0].key;
8102
+ const personKernel = await client.personKernelId();
8103
+ if (!personKernel.ok) return personKernel;
8104
+ const home = await client.recordWrite({
8105
+ table_id: personKernel.data,
8106
+ data: { name: `${spec.name} Home` }
8107
+ });
8108
+ if (!home.ok) return home;
8109
+ const table = await client.tableDeclare({
8110
+ homeId: home.data,
8111
+ spec: {
8112
+ name: spec.name,
8113
+ slug,
8114
+ type: "entity",
8115
+ label_singular: spec.labelSingular ?? spec.name,
8116
+ label_plural: spec.labelPlural ?? spec.name,
8117
+ display: "list",
8118
+ weight: "light",
8119
+ ordered: false,
8120
+ row_order: "manual",
8121
+ title_field: titleField,
8122
+ retention_days: 365,
8123
+ // AGT-1: a table a person made is agent-writable by declaration; the
8124
+ // organization turns it off, never this package.
8125
+ agent_writable: true,
8126
+ default_sort: [{ field: titleField, direction: "asc" }],
8127
+ fields: fields.map((f) => ({ name: f.key }))
8128
+ }
8129
+ });
8130
+ if (!table.ok) {
8131
+ await client.recordDelete({ record_id: home.data });
8132
+ return table;
8133
+ }
8134
+ const written = await addFields(client, table.data, fields);
8135
+ if (!written.ok) return undo(client, table.data, home.data, spec.name, written.error);
8136
+ return { ok: true, data: table.data, homeId: home.data };
8137
+ }
8138
+ async function undo(client, tableId, homeId, name, refusal) {
8139
+ const removed = await client.recordDelete({ record_id: tableId });
8140
+ if (removed.ok) {
8141
+ await client.recordDelete({ record_id: homeId });
8142
+ return { ok: false, error: refusal };
8143
+ }
8144
+ return {
8145
+ ok: false,
8146
+ error: {
8147
+ ...refusal,
8148
+ 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.`
8149
+ }
8150
+ };
8151
+ }
8152
+ async function addFields(client, tableId, fields) {
8153
+ for (const field of fields) {
8154
+ const result = await client.fieldDeclare({
8155
+ table_id: tableId,
8156
+ spec: fieldDeclarationFor(field)
8157
+ });
8158
+ if (!result.ok) return result;
8159
+ }
8160
+ return { ok: true, data: tableId };
8161
+ }
8162
+ var PLAIN_WORDS = {
8163
+ text: "text",
8164
+ string: "text",
8165
+ long_text: "long_text",
8166
+ longtext: "long_text",
8167
+ paragraph: "long_text",
8168
+ number: "number",
8169
+ range: "number",
8170
+ integer: "number",
8171
+ decimal: "number",
8172
+ float: "number"
8173
+ };
8174
+ function declaredType(field) {
8175
+ const word = (field.type ?? "text").toLowerCase();
8176
+ const plain = PLAIN_WORDS[word];
8177
+ if (plain) {
8178
+ return { plain: plain === "text" && field.config?.["multiline"] === true ? "long_text" : plain };
8179
+ }
8180
+ return { parity_type: word };
8181
+ }
8182
+
7731
8183
  // src/core/supabase.ts
7732
8184
  function refuse(code, message, hint) {
7733
8185
  return { data: null, error: { code, message, hint, details: "" } };
@@ -8223,6 +8675,7 @@ export {
8223
8675
  DECLARABLE_FIELD_KINDS,
8224
8676
  DEFAULT_ARCHIVE_LANE,
8225
8677
  DEFAULT_DOCUMENT_MAX_BYTES,
8678
+ DEFAULT_FIELDS,
8226
8679
  DEFAULT_PAGE_SIZE,
8227
8680
  DEFAULT_PORTAL_ARCHIVE_FILTER,
8228
8681
  DEFAULT_VALUE_MAX_BYTES,
@@ -8244,6 +8697,7 @@ export {
8244
8697
  PERMISSION_LEVELS,
8245
8698
  PER_VALUE_ACCESS_WORDS,
8246
8699
  PIPELINE_RULE_KINDS,
8700
+ PORTAL_ACCENTS,
8247
8701
  PORTAL_ARCHIVE_FILTERS,
8248
8702
  REFUSALS_WORTH_REPEATING,
8249
8703
  RELATION_BINDINGS,
@@ -8264,6 +8718,7 @@ export {
8264
8718
  VALUE_ALTERNATE_KEYS,
8265
8719
  VALUE_ENVELOPE_KEYS,
8266
8720
  actionRefusals,
8721
+ addFields,
8267
8722
  allowsOtherValues,
8268
8723
  announceRecordChanged,
8269
8724
  archivedByLine,
@@ -8280,16 +8735,23 @@ export {
8280
8735
  coerceTypedAnswers,
8281
8736
  coercionLabel,
8282
8737
  createRecordsClient,
8738
+ declareTable,
8283
8739
  doorAbsent,
8284
8740
  doorCensus,
8285
8741
  doorExists,
8286
8742
  emptyArchiveLine,
8287
8743
  emptyPortalArchiveLine,
8744
+ entityReferenceKinds,
8745
+ entityReferenceKindsOf,
8746
+ entityReferenceWords,
8747
+ entityRefsOf,
8748
+ entityRefsToWrite,
8288
8749
  err,
8289
8750
  exampleSpec,
8290
8751
  exportCsv,
8291
8752
  exportPageXlsx,
8292
8753
  exportXlsx,
8754
+ fieldDeclarationFor,
8293
8755
  fieldKindFor,
8294
8756
  forgetAllOps,
8295
8757
  highestLevel,
@@ -8301,10 +8763,16 @@ export {
8301
8763
  isAPersonsRefusal,
8302
8764
  isArchiveLane,
8303
8765
  isDecided,
8766
+ isEntityReferenceConfig,
8304
8767
  isOwnOp,
8768
+ isPortalAccent,
8305
8769
  isPortalArchiveFilter,
8306
8770
  isRecordsErr,
8771
+ isRuleFilter,
8307
8772
  isTheChosen,
8773
+ keptGroups,
8774
+ keptOutOfSight,
8775
+ keptTableHref,
8308
8776
  looksLikeAnId,
8309
8777
  mapPgError,
8310
8778
  mayDrag,
@@ -8312,6 +8780,8 @@ export {
8312
8780
  memoryStore,
8313
8781
  mintClientKey,
8314
8782
  mintOpId,
8783
+ moveInOrder,
8784
+ offeredAsContext,
8315
8785
  ok,
8316
8786
  onRecordChanged,
8317
8787
  openCaptureQueue,
@@ -8332,10 +8802,14 @@ export {
8332
8802
  rememberedOpCount,
8333
8803
  sameDocument,
8334
8804
  scopeChip,
8805
+ sheetSyncSpec,
8335
8806
  stableFilterKey,
8336
8807
  staleWriteDetail,
8337
8808
  supabaseDataSource,
8338
8809
  toAoa,
8339
- withTableFacts
8810
+ tokenFor,
8811
+ withRowCounts,
8812
+ withTableFacts,
8813
+ withTablePlacement
8340
8814
  };
8341
8815
  //# sourceMappingURL=index.js.map