@ai-matrx/records 0.57.3 → 0.58.1
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.
- package/CHANGELOG.md +122 -0
- package/dist/core/index.cjs +733 -154
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +295 -7
- package/dist/core/index.d.ts +295 -7
- package/dist/core/index.js +733 -154
- package/dist/core/index.js.map +1 -1
- package/dist/index.cjs +395 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +887 -197
- package/dist/index.d.ts +887 -197
- package/dist/index.js +395 -5
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +536 -33
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +1096 -191
- package/dist/react/index.d.ts +1096 -191
- package/dist/react/index.js +536 -33
- package/dist/react/index.js.map +1 -1
- package/dist/{types-BpBSMhYq.d.cts → types-NKSc_jZq.d.cts} +31 -3
- package/dist/{types-BpBSMhYq.d.ts → types-NKSc_jZq.d.ts} +31 -3
- package/dist/use-cases/index.cjs.map +1 -1
- package/dist/use-cases/index.d.cts +2 -2
- package/dist/use-cases/index.d.ts +2 -2
- package/dist/use-cases/index.js.map +1 -1
- package/package.json +1 -1
package/dist/core/index.js
CHANGED
|
@@ -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,161 @@ 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
|
+
}
|
|
2831
|
+
function countOf(value) {
|
|
2832
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
2833
|
+
}
|
|
2834
|
+
function wordsOf(value) {
|
|
2835
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
2836
|
+
}
|
|
2837
|
+
function parseTableHome(raw) {
|
|
2838
|
+
if (!raw || typeof raw !== "object") return null;
|
|
2839
|
+
const row = raw;
|
|
2840
|
+
const table = row.table;
|
|
2841
|
+
const org = row.organization;
|
|
2842
|
+
if (!table || !org || !wordsOf(table.id) || !wordsOf(org.id) || !wordsOf(org.name)) return null;
|
|
2843
|
+
const carries = row.carries ?? {};
|
|
2844
|
+
const destinations = Array.isArray(row.destinations) ? row.destinations : [];
|
|
2845
|
+
return {
|
|
2846
|
+
table: { id: String(table.id), name: wordsOf(table.name) ?? "This table", version: countOf(table.version) },
|
|
2847
|
+
organization: { id: String(org.id), name: String(org.name) },
|
|
2848
|
+
mayMove: row.may_move === true,
|
|
2849
|
+
whyNot: wordsOf(row.why_not),
|
|
2850
|
+
heldBy: Array.isArray(row.held_by) ? row.held_by.filter((s) => typeof s === "string") : [],
|
|
2851
|
+
destinations: destinations.filter((d) => !!d && typeof d === "object" && !!wordsOf(d.id)).map((d) => ({
|
|
2852
|
+
id: String(d.id),
|
|
2853
|
+
name: wordsOf(d.name) ?? "An organization",
|
|
2854
|
+
role: wordsOf(d.role) ?? "member",
|
|
2855
|
+
ok: d.ok === true,
|
|
2856
|
+
why: wordsOf(d.why)
|
|
2857
|
+
})),
|
|
2858
|
+
carries: {
|
|
2859
|
+
fields: countOf(carries.fields),
|
|
2860
|
+
records: countOf(carries.records),
|
|
2861
|
+
inTrash: countOf(carries.in_trash),
|
|
2862
|
+
choiceLists: countOf(carries.choice_lists),
|
|
2863
|
+
withIt: countOf(carries.with_it),
|
|
2864
|
+
tablesInside: countOf(carries.tables_inside),
|
|
2865
|
+
linksAcross: countOf(carries.links_across)
|
|
2866
|
+
}
|
|
2867
|
+
};
|
|
2868
|
+
}
|
|
2869
|
+
function parseTableMoved(raw) {
|
|
2870
|
+
const row = raw ?? {};
|
|
2871
|
+
const to = row.to ?? {};
|
|
2872
|
+
const from = row.from ?? {};
|
|
2873
|
+
const table = row.table ?? {};
|
|
2874
|
+
if (row.moved !== true || !wordsOf(to.id)) return null;
|
|
2875
|
+
return {
|
|
2876
|
+
to: { id: String(to.id), name: wordsOf(to.name) ?? "the new organization" },
|
|
2877
|
+
from: { id: wordsOf(from.id) ?? "", name: wordsOf(from.name) ?? "its old organization" },
|
|
2878
|
+
tableName: wordsOf(table.name) ?? "The table"
|
|
2879
|
+
};
|
|
2880
|
+
}
|
|
2881
|
+
function plural(n, one, many) {
|
|
2882
|
+
return `${n} ${n === 1 ? one : many}`;
|
|
2883
|
+
}
|
|
2884
|
+
function moveConsequence(home, to) {
|
|
2885
|
+
const c = home.carries;
|
|
2886
|
+
const parts = [plural(c.fields, "column", "columns"), plural(c.records, "row", "rows")];
|
|
2887
|
+
if (c.inTrash > 0) parts.push(`${c.inTrash} in the trash`);
|
|
2888
|
+
const extra = c.choiceLists + c.withIt;
|
|
2889
|
+
return `${home.table.name} moves to ${to.name} with its ${parts.join(", ")}` + (c.tablesInside > 0 ? `, the ${plural(c.tablesInside, "table", "tables")} that live${c.tablesInside === 1 ? "s" : ""} inside it or its rows (with their own rows)` : "") + (extra > 0 ? `, and ${plural(extra, "thing", "things")} built only on it (choice lists, rules, dashboards, templates)` : "") + `, its comments, forms, saved views and history. ` + (c.linksAcross > 0 ? `${plural(c.linksAcross, "link", "links")} to rows that stay in ${home.organization.name} keep${c.linksAcross === 1 ? "s" : ""} pointing there, across the two organizations. ` : "") + `People who see it only because they are in ${home.organization.name} stop seeing it; people it was shared with by name keep it. You can move it back from the same place.`;
|
|
2890
|
+
}
|
|
2699
2891
|
|
|
2700
2892
|
// src/capture-queue.ts
|
|
2701
2893
|
var DB_NAME = "matrx-capture";
|
|
@@ -2765,7 +2957,7 @@ function memoryStore() {
|
|
|
2765
2957
|
setMeta: async (key, value) => void meta.set(key, value)
|
|
2766
2958
|
};
|
|
2767
2959
|
}
|
|
2768
|
-
function
|
|
2960
|
+
function countOf2(items) {
|
|
2769
2961
|
const c = { waiting: 0, sending: 0, refused: 0, landed: 0 };
|
|
2770
2962
|
for (const i of items) c[i.state] += 1;
|
|
2771
2963
|
return c;
|
|
@@ -2782,7 +2974,7 @@ function openCaptureQueue(opts) {
|
|
|
2782
2974
|
const announce = async () => {
|
|
2783
2975
|
if (!opts.onChange) return;
|
|
2784
2976
|
const items = await store.all();
|
|
2785
|
-
opts.onChange(
|
|
2977
|
+
opts.onChange(countOf2(items), items);
|
|
2786
2978
|
};
|
|
2787
2979
|
const oldestFirst = (items) => items.slice().sort((a, b) => a.captured_at.localeCompare(b.captured_at));
|
|
2788
2980
|
const queue = {
|
|
@@ -2811,7 +3003,7 @@ function openCaptureQueue(opts) {
|
|
|
2811
3003
|
return key;
|
|
2812
3004
|
},
|
|
2813
3005
|
list: () => store.all().then(oldestFirst),
|
|
2814
|
-
counts: () => store.all().then(
|
|
3006
|
+
counts: () => store.all().then(countOf2),
|
|
2815
3007
|
async sync() {
|
|
2816
3008
|
if (syncing) return syncing;
|
|
2817
3009
|
syncing = (async () => {
|
|
@@ -2983,6 +3175,194 @@ function isRecordsErr(result) {
|
|
|
2983
3175
|
var ok = (data) => ({ ok: true, data });
|
|
2984
3176
|
var err = (error) => ({ ok: false, error });
|
|
2985
3177
|
|
|
3178
|
+
// src/core/pgError.ts
|
|
3179
|
+
var BY_SQLSTATE = {
|
|
3180
|
+
PT409: "stale_write",
|
|
3181
|
+
"42501": "door",
|
|
3182
|
+
"23514": "refused_by_rule",
|
|
3183
|
+
"22004": "invalid_argument",
|
|
3184
|
+
"22023": "invalid_argument",
|
|
3185
|
+
// 22P02 — a value does not fit the type the store holds for it. The class is
|
|
3186
|
+
// real and was hit live: an organization whose copy of a package table grew
|
|
3187
|
+
// `status` as a uuid-valued Field was sent the text "open", and the store
|
|
3188
|
+
// answered `invalid input syntax for type uuid: "open"`. It is the CALL that
|
|
3189
|
+
// is wrong for the shape the store holds, which is what `invalid_argument`
|
|
3190
|
+
// already means — the store's own sentence names the value and the type.
|
|
3191
|
+
"22P02": "invalid_argument",
|
|
3192
|
+
// 42703 — a column that is not there. The store raises it from
|
|
3193
|
+
// `custom.entity_table`, which is asked for a column by NAME, so the class is
|
|
3194
|
+
// exactly "the call named something the shape does not have": the same thing
|
|
3195
|
+
// `invalid_argument` already means, and the store's own sentence names the
|
|
3196
|
+
// column. Added 2026-09-20 (lane FORMS) because the package's live-door suite
|
|
3197
|
+
// caught it raised by a door this package did not know about — which is the
|
|
3198
|
+
// suite working, and is why the map is a census rather than a guess.
|
|
3199
|
+
"42703": "invalid_argument",
|
|
3200
|
+
// 22007 — a date or a time the store could not read as one. It is raised by
|
|
3201
|
+
// `custom.dashboard_window_sql`, which is handed a window a caller wrote
|
|
3202
|
+
// ("last 30 days", a literal date), so the class is again "the call named
|
|
3203
|
+
// something the shape does not accept" and the store's own sentence names the
|
|
3204
|
+
// text it could not read. Added 2026-09-20 (lane SCREEN-BREAK) after the
|
|
3205
|
+
// live-door census caught it — the same way 42703 arrived, which is the
|
|
3206
|
+
// census working rather than a guess.
|
|
3207
|
+
"22007": "invalid_argument",
|
|
3208
|
+
// 22003 — a number the store could not hold at the size it was handed. It is
|
|
3209
|
+
// raised by `custom.context_resolve`, which is given a window or a depth a
|
|
3210
|
+
// caller wrote, so the class is the same one 22P02, 42703 and 22007 already
|
|
3211
|
+
// are: the CALL named a value the shape does not accept, and the store's own
|
|
3212
|
+
// sentence names the number and the range. Added 2026-09-20 (lane GRID-TWO)
|
|
3213
|
+
// when the live-door census caught the release gate red on it — which is the
|
|
3214
|
+
// census working, and the fourth time it has been the thing that found a new
|
|
3215
|
+
// refusal class rather than a person guessing one.
|
|
3216
|
+
"22003": "invalid_argument",
|
|
3217
|
+
"02000": "not_found",
|
|
3218
|
+
"23503": "missing_reference",
|
|
3219
|
+
"23505": "already_exists",
|
|
3220
|
+
"0A000": "not_supported",
|
|
3221
|
+
"53400": "store_limit",
|
|
3222
|
+
"22012": "store_limit",
|
|
3223
|
+
// 54001 — `custom.record_delete` stops a cascade that nests past sixty-four
|
|
3224
|
+
// levels of containment, because that is a loop somebody built rather than a
|
|
3225
|
+
// hierarchy, and saying so beats recursing until the server runs out of
|
|
3226
|
+
// stack. It is a LIMIT of the store, and the store's own sentence names what
|
|
3227
|
+
// to do about it, so it reads as one.
|
|
3228
|
+
"54001": "store_limit",
|
|
3229
|
+
// 54000 — the grid primitives' organization ceilings (GRID-PRIMITIVES, 2026-09-23):
|
|
3230
|
+
// `custom.action_run` on a selection past its knob, `custom.table_decorate` past the
|
|
3231
|
+
// rule count, `custom.table_from_example` past `custom/example_rows_max`,
|
|
3232
|
+
// `custom.table_webhook_declare` past `custom/table_webhooks_max`, and
|
|
3233
|
+
// `custom.hub_changed_by`. Each names its knob in the hint and writes nothing, so it
|
|
3234
|
+
// reads exactly like 54001: a limit of the store, in the store's own sentence.
|
|
3235
|
+
"54000": "store_limit",
|
|
3236
|
+
// VIS: `custom.has_visibility_at` refuses to answer from a snapshot older
|
|
3237
|
+
// than a write this reader has already seen, rather than returning something
|
|
3238
|
+
// they just lost access to. It is retryable on a fresher connection, which is
|
|
3239
|
+
// a different instruction from every other refusal here.
|
|
3240
|
+
"40001": "stale_read",
|
|
3241
|
+
// 57014 — the store cancelled the statement at its own timeout. Like
|
|
3242
|
+
// `stale_read` and unlike every other refusal here, the call was never
|
|
3243
|
+
// refused on its merits: the same call on a smaller page, or once the
|
|
3244
|
+
// organization's kernel is not being rewritten, can succeed. It gets its own
|
|
3245
|
+
// code so a screen can offer "try again" honestly instead of showing a
|
|
3246
|
+
// failure sentence the caller cannot act on.
|
|
3247
|
+
"57014": "timed_out",
|
|
3248
|
+
// PostgREST's own two: the store is not being SERVED, which is a different
|
|
3249
|
+
// thing from the store refusing. `custom` absent from `pgrst.db_schemas`, or
|
|
3250
|
+
// the grants not copied, land here.
|
|
3251
|
+
PGRST202: "store_not_served",
|
|
3252
|
+
PGRST205: "store_not_served",
|
|
3253
|
+
PGRST301: "door",
|
|
3254
|
+
// PT428 — NOT A FAILURE, and the release gate's live census is what found it
|
|
3255
|
+
// missing: the store raises it and this package answered `internal`, which is
|
|
3256
|
+
// the one classification that tells a screen nothing. A stage gate whose
|
|
3257
|
+
// `on_fail` is `require_approval` files the request and leaves the record
|
|
3258
|
+
// where it was; the transaction is rolled back to the pipeline's own block, so
|
|
3259
|
+
// "nothing was written" is a fact rather than a promise. Landed by lane
|
|
3260
|
+
// STAGE-RULES; mapped here by STAGE-RULES-2 the day after, when the census
|
|
3261
|
+
// caught it — the sixth time that census has been the thing that found a
|
|
3262
|
+
// refusal class rather than a person guessing one.
|
|
3263
|
+
PT428: "needs_approval"
|
|
3264
|
+
};
|
|
3265
|
+
var MAPPED_SQLSTATES = Object.keys(BY_SQLSTATE);
|
|
3266
|
+
function parseDetail(details) {
|
|
3267
|
+
if (!details) return void 0;
|
|
3268
|
+
const trimmed = details.trim();
|
|
3269
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return details;
|
|
3270
|
+
try {
|
|
3271
|
+
return JSON.parse(trimmed);
|
|
3272
|
+
} catch {
|
|
3273
|
+
return details;
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
function isNotASqlstate(code) {
|
|
3277
|
+
if (!code) return true;
|
|
3278
|
+
return /^[0-9]{3}$/.test(code);
|
|
3279
|
+
}
|
|
3280
|
+
function mapPgError(error, where) {
|
|
3281
|
+
const raw = error.code ?? void 0;
|
|
3282
|
+
const sqlstate = raw && raw !== "" ? raw : void 0;
|
|
3283
|
+
const mapped = {
|
|
3284
|
+
code: "internal",
|
|
3285
|
+
message: error.message,
|
|
3286
|
+
...error.hint ? { hint: error.hint } : {},
|
|
3287
|
+
...sqlstate ? { sqlstate } : {}
|
|
3288
|
+
};
|
|
3289
|
+
if (isNotASqlstate(sqlstate)) {
|
|
3290
|
+
mapped.code = "unreachable";
|
|
3291
|
+
} else {
|
|
3292
|
+
mapped.code = BY_SQLSTATE[sqlstate] ?? "internal";
|
|
3293
|
+
}
|
|
3294
|
+
const detail = parseDetail(error.details);
|
|
3295
|
+
if (detail !== void 0) mapped.detail = detail;
|
|
3296
|
+
if (mapped.code === "internal") {
|
|
3297
|
+
mapped.unmapped = true;
|
|
3298
|
+
mapped.diagnostic = `@ai-matrx/records did not recognise SQLSTATE ${sqlstate ?? "(none)"} from ${where}; the store's own message is the thing to act on.`;
|
|
3299
|
+
} else if (mapped.code === "unreachable") {
|
|
3300
|
+
mapped.unmapped = true;
|
|
3301
|
+
mapped.diagnostic = `@ai-matrx/records never reached the store from ${where}: ${raw ? `the transport answered ${raw}` : "no response"} \u2014 ${error.message}`;
|
|
3302
|
+
mapped.message = "We could not reach your data.";
|
|
3303
|
+
delete mapped.hint;
|
|
3304
|
+
}
|
|
3305
|
+
return mapped;
|
|
3306
|
+
}
|
|
3307
|
+
function staleWriteDetail(error) {
|
|
3308
|
+
if (error.code !== "stale_write") return null;
|
|
3309
|
+
const detail = error.detail;
|
|
3310
|
+
if (!detail || typeof detail.expected_version !== "number" || typeof detail.current_version !== "number") {
|
|
3311
|
+
return null;
|
|
3312
|
+
}
|
|
3313
|
+
return {
|
|
3314
|
+
expected_version: detail.expected_version,
|
|
3315
|
+
current_version: detail.current_version,
|
|
3316
|
+
contested_fields: detail.contested_fields ?? {}
|
|
3317
|
+
};
|
|
3318
|
+
}
|
|
3319
|
+
|
|
3320
|
+
// src/relation.ts
|
|
3321
|
+
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;
|
|
3322
|
+
function isRefLike(value) {
|
|
3323
|
+
return typeof value === "object" && value !== null && "token" in value && "id" in value;
|
|
3324
|
+
}
|
|
3325
|
+
function entityRefsOf(value) {
|
|
3326
|
+
const items = Array.isArray(value) ? value : value == null ? [] : [value];
|
|
3327
|
+
const out = [];
|
|
3328
|
+
for (const item of items) {
|
|
3329
|
+
if (!isRefLike(item)) continue;
|
|
3330
|
+
const token = String(item.token ?? "").trim().toLowerCase();
|
|
3331
|
+
const id = String(item.id ?? "").trim();
|
|
3332
|
+
if (!token || !ENTITY_REF_ID.test(id)) continue;
|
|
3333
|
+
const label = item.label;
|
|
3334
|
+
out.push({ token, id: id.toLowerCase(), label: typeof label === "string" ? label : null });
|
|
3335
|
+
}
|
|
3336
|
+
return out;
|
|
3337
|
+
}
|
|
3338
|
+
function entityRefsToWrite(value, multi) {
|
|
3339
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3340
|
+
const refs = [];
|
|
3341
|
+
for (const ref of entityRefsOf(value)) {
|
|
3342
|
+
const k = `${ref.token}:${ref.id}`;
|
|
3343
|
+
if (seen.has(k)) continue;
|
|
3344
|
+
seen.add(k);
|
|
3345
|
+
refs.push({ token: ref.token, id: ref.id });
|
|
3346
|
+
}
|
|
3347
|
+
if (multi) return refs;
|
|
3348
|
+
return refs[0] ?? null;
|
|
3349
|
+
}
|
|
3350
|
+
async function entityReferenceKinds(source) {
|
|
3351
|
+
const { data, error } = await source.rpc("entity_reference_kinds", {}, { schema: "custom" });
|
|
3352
|
+
if (error) return err(mapPgError(error, "entityReferenceKinds"));
|
|
3353
|
+
return ok(Array.isArray(data) ? data : []);
|
|
3354
|
+
}
|
|
3355
|
+
async function entityReferenceWords(source, organizationId, refs) {
|
|
3356
|
+
if (refs.length === 0) return ok([]);
|
|
3357
|
+
const { data, error } = await source.rpc(
|
|
3358
|
+
"entity_reference_words",
|
|
3359
|
+
{ p_organization_id: organizationId, p_refs: refs.map((r) => ({ token: r.token, id: r.id })) },
|
|
3360
|
+
{ schema: "custom" }
|
|
3361
|
+
);
|
|
3362
|
+
if (error) return err(mapPgError(error, "entityReferenceWords"));
|
|
3363
|
+
return ok(entityRefsOf(data));
|
|
3364
|
+
}
|
|
3365
|
+
|
|
2986
3366
|
// src/ops.ts
|
|
2987
3367
|
var REMEMBER_MS = 3e4;
|
|
2988
3368
|
var REMEMBER_MAX = 500;
|
|
@@ -3696,6 +4076,7 @@ var STORE_DOORS = [
|
|
|
3696
4076
|
{ name: "table_work_kind", args: "p_organization_id uuid, p_table_id uuid", returns: "text", security: "invoker" },
|
|
3697
4077
|
{ 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
4078
|
{ name: "tables_described_without_asking", args: "", returns: "TABLE(function_name text, identity_args text, why text)", security: "invoker" },
|
|
4079
|
+
{ 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
4080
|
{ 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
4081
|
{ name: "trg_associations_bump_visibility", args: "", returns: "trigger", security: "definer" },
|
|
3701
4082
|
{ name: "undeclared_keys", args: "p_organization_id uuid, p_table_id uuid, p_data jsonb", returns: "text[]", security: "invoker" },
|
|
@@ -3720,6 +4101,7 @@ var STORE_DOORS = [
|
|
|
3720
4101
|
{ 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
4102
|
{ name: "visible_record_ids", args: "p_user_id uuid, p_required permission_level DEFAULT 'viewer'::permission_level", returns: "TABLE(id uuid)", security: "definer" },
|
|
3722
4103
|
{ 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" },
|
|
4104
|
+
{ name: "where_id_opens", args: "p_id uuid", returns: "jsonb", security: "definer" },
|
|
3723
4105
|
{ name: "widget_kernel_id", args: "", returns: "uuid", security: "invoker" },
|
|
3724
4106
|
{ name: "withheld_marker", args: "p_mask jsonb, p_key text", returns: "jsonb", security: "invoker" },
|
|
3725
4107
|
{ name: "withheld_sentence", args: "p_mask jsonb, p_key text", returns: "text", security: "invoker" },
|
|
@@ -3826,7 +4208,7 @@ var STORE_REFUSAL_CODES = [
|
|
|
3826
4208
|
{ 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
4209
|
{ 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
4210
|
{ 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" },
|
|
4211
|
+
{ 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
4212
|
{ code: "42703", raised_by: "entity_table,portal_declare" },
|
|
3831
4213
|
{ code: "53400", raised_by: "anon_rate_take,delete_cascade_closure,enrich_due,enrich_land,promote_field" },
|
|
3832
4214
|
{ code: "54000", raised_by: "action_run,hub_changed_by,table_decorate,table_from_example,table_webhook_declare" },
|
|
@@ -4871,6 +5253,8 @@ function scopeChip(scope) {
|
|
|
4871
5253
|
|
|
4872
5254
|
// src/core/doors.ts
|
|
4873
5255
|
var OWED_BY = {
|
|
5256
|
+
table_home: "SC-1' \u2014 matrx-frontend migrations/campaign/sc1p_a_table_says_where_it_lives_and_its_owner_can_move_it.sql (and its grant file), applied by the chair",
|
|
5257
|
+
table_move: "SC-1' / SC-1-TAILS \u2014 sc1p_a_table_says_where_it_lives_and_its_owner_can_move_it.sql, then sc1t_a_table_moves_with_the_tables_inside_it.sql, applied by the chair",
|
|
4874
5258
|
metadata_search: "W5-SRV / W5-AGENT \u2014 the agent surface's eight verbs",
|
|
4875
5259
|
record_aggregate: "W5-SRV / W5-AGENT \u2014 `record_aggregate` is one of the eight verbs",
|
|
4876
5260
|
field_propose: "W5-AGENT \u2014 AGT-4/AGT-8, the proposal and its approvers",
|
|
@@ -4892,6 +5276,20 @@ var OWED_BY = {
|
|
|
4892
5276
|
table_webhook_archive: "GRID-PRIMITIVES G4 \u2014 gridprim_every_row_change_reaches_the_webhook.sql (chair's production apply)",
|
|
4893
5277
|
table_from_example: "GRID-PRIMITIVES G5 \u2014 gridprim_a_table_can_start_from_a_real_example.sql (chair's production apply)",
|
|
4894
5278
|
autonumber_backfill: "GRID-PRIMITIVES G5 \u2014 gridprim_a_table_can_start_from_a_real_example.sql (chair's production apply)",
|
|
5279
|
+
table_list_everywhere: "GRID-PRIMITIVES G9 \u2014 gridprim_every_picker_lists_both_stores.sql (chair's production apply)",
|
|
5280
|
+
table_sync: "GRID-PRIMITIVES G10 \u2014 gridprim_a_synced_sheet_is_one_table_that_refreshes.sql (chair's production apply)",
|
|
5281
|
+
scope_table_provision: "GRID-PRIMITIVES G11 \u2014 gridprim_a_scope_provisions_its_table_in_the_store.sql (chair's production apply)",
|
|
5282
|
+
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)",
|
|
5283
|
+
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)",
|
|
5284
|
+
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)",
|
|
5285
|
+
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)",
|
|
5286
|
+
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)",
|
|
5287
|
+
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)",
|
|
5288
|
+
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)",
|
|
5289
|
+
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)",
|
|
5290
|
+
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)",
|
|
5291
|
+
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)",
|
|
5292
|
+
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
5293
|
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
5294
|
};
|
|
4897
5295
|
var TRUST_SCHEMA = "iam";
|
|
@@ -5095,6 +5493,8 @@ var DOORS = {
|
|
|
5095
5493
|
pipelineBoard: "pipeline_board",
|
|
5096
5494
|
pipelineTransitionRefusal: "pipeline_transition_refusal",
|
|
5097
5495
|
pipelineMove: "pipeline_move",
|
|
5496
|
+
/** Lane S4: many cards, one call, a verdict and a version per card. */
|
|
5497
|
+
pipelineMoveMany: "pipeline_move_many",
|
|
5098
5498
|
tableStageField: "table_stage_field",
|
|
5099
5499
|
// STAGE-RULES. A gate can refuse or it can hand the change to the approvals queue; the
|
|
5100
5500
|
// closed list is the store's, the knob is the organization's, and what is WAITING on a
|
|
@@ -5105,6 +5505,7 @@ var DOORS = {
|
|
|
5105
5505
|
pipelinePending: "pipeline_pending",
|
|
5106
5506
|
pipelineGatePreview: "pipeline_gate_preview",
|
|
5107
5507
|
ruleMembers: "rule_members",
|
|
5508
|
+
ruleMembersVisible: "rule_members_visible",
|
|
5108
5509
|
ruleMembership: "rule_membership",
|
|
5109
5510
|
ruleRun: "rule_run",
|
|
5110
5511
|
ruleEval: "rule_eval",
|
|
@@ -5119,6 +5520,12 @@ var DOORS = {
|
|
|
5119
5520
|
// are server-lane doors and a browser never calls them.
|
|
5120
5521
|
forms: "forms",
|
|
5121
5522
|
formDeclare: "form_declare",
|
|
5523
|
+
/**
|
|
5524
|
+
* WHICH QUESTIONS THE OWNER'S PREVIEW ASKS NEXT (lane FORMS-FIX-1). The same store answer a
|
|
5525
|
+
* stranger's form gets (`custom._form_questions_asked` → `custom.rule_eval`), behind the
|
|
5526
|
+
* organization wall and the caller's own rung on the Table — never `rule_eval` from a browser.
|
|
5527
|
+
*/
|
|
5528
|
+
formPreviewAsks: "form_preview_asks",
|
|
5122
5529
|
// PRODUCTS row 14 — "let clients book a 30-minute consult". A booking page IS an
|
|
5123
5530
|
// `anon_form`, so it is opened and closed by `anonPublish` like any other; this is
|
|
5124
5531
|
// the owner's list of them with the numbers that make it worth opening. The
|
|
@@ -5173,11 +5580,22 @@ var DOORS = {
|
|
|
5173
5580
|
// ask, under the same wall and visibility gate as `portals` itself.
|
|
5174
5581
|
portalTables: "portal_tables",
|
|
5175
5582
|
tableFacts: "table_facts",
|
|
5583
|
+
// WHERE A TABLE LIVES, AND MOVING IT (SC-1' / SC-1-TAILS). `table_home` answers only when the
|
|
5584
|
+
// caller may open the Table (custom.where_id_opens) and names its organization FROM THE TABLE;
|
|
5585
|
+
// `table_move` moves it into another organization the caller belongs to, with every Table
|
|
5586
|
+
// inside it, or refuses with one sentence.
|
|
5587
|
+
tableHome: "table_home",
|
|
5588
|
+
tableMove: "table_move",
|
|
5176
5589
|
portalCard: "portal_card",
|
|
5177
5590
|
portalDeclare: "portal_declare",
|
|
5178
5591
|
portalInvite: "portal_invite",
|
|
5179
5592
|
portalRevoke: "portal_revoke",
|
|
5180
5593
|
portalPreview: "portal_preview",
|
|
5594
|
+
// S6 — the CLIENT's two form doors. They are store doors (schema `custom`), called by a
|
|
5595
|
+
// signed-in portal principal, and each refuses anybody who is not a live principal of that
|
|
5596
|
+
// live portal — so, unlike the sign-in pair above, they belong in this table.
|
|
5597
|
+
portalForm: "portal_form",
|
|
5598
|
+
portalFormSubmit: "portal_form_submit",
|
|
5181
5599
|
// ORG-CLEANUP, 2026-09-22 — A PORTAL IS ARCHIVED, NEVER DELETED.
|
|
5182
5600
|
// `portals` keeps its signature and HIDES archived portals (the law's default
|
|
5183
5601
|
// is to hide); `list_portals(org, 'active'|'archived'|'all')` is the
|
|
@@ -5373,6 +5791,8 @@ var DOORS = {
|
|
|
5373
5791
|
workApprovalRequest: "work_approval_request",
|
|
5374
5792
|
workApprovalMayDecide: "work_approval_may_decide",
|
|
5375
5793
|
workApprovalDecide: "work_approval_decide",
|
|
5794
|
+
/** Lane S4: many inbox decisions, one call, a verdict per item. */
|
|
5795
|
+
workDecideMany: "work_decide_many",
|
|
5376
5796
|
workApprovalRead: "work_approval_read",
|
|
5377
5797
|
// PRODUCTS row 13 — checklists and SOP runs.
|
|
5378
5798
|
checklistRefusal: "checklist_refusal",
|
|
@@ -5398,6 +5818,14 @@ var DOORS = {
|
|
|
5398
5818
|
tableFromExample: "table_from_example",
|
|
5399
5819
|
autonumberBackfill: "autonumber_backfill",
|
|
5400
5820
|
ioExport: "io_export",
|
|
5821
|
+
tableListEverywhere: "table_list_everywhere",
|
|
5822
|
+
tableSync: "table_sync",
|
|
5823
|
+
scopeTableProvision: "scope_table_provision",
|
|
5824
|
+
tableRowCounts: "table_row_counts",
|
|
5825
|
+
recordHeaders: "record_headers",
|
|
5826
|
+
viewRecordOrderSet: "view_record_order_set",
|
|
5827
|
+
readRecordsInViewOrder: "read_records_in_view_order",
|
|
5828
|
+
tableShareOutsideGrant: "table_share_outside_grant",
|
|
5401
5829
|
// Declared here BECAUSE they do not exist yet: naming them is what makes the
|
|
5402
5830
|
// absence visible to `pnpm check:store-registry` and to the suite.
|
|
5403
5831
|
metadataSearch: "metadata_search",
|
|
@@ -5415,148 +5843,6 @@ function doorCensus() {
|
|
|
5415
5843
|
return { present: present.sort(), absent: absent.sort() };
|
|
5416
5844
|
}
|
|
5417
5845
|
|
|
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
5846
|
// src/core/recordChanges.ts
|
|
5561
5847
|
var listeners = /* @__PURE__ */ new Set();
|
|
5562
5848
|
function announceRecordChanged(recordId) {
|
|
@@ -5822,6 +6108,40 @@ function createRecordsClient(config) {
|
|
|
5822
6108
|
if (!doorExists(DOORS.tableFacts)) return ok(null);
|
|
5823
6109
|
return callDoor(DOORS.tableFacts, { p_organization_id: org }, "tableFacts");
|
|
5824
6110
|
},
|
|
6111
|
+
async tableHome({ table_id }) {
|
|
6112
|
+
const answered = await callDoor(DOORS.tableHome, { p_table_id: table_id }, "tableHome");
|
|
6113
|
+
if (!answered.ok) return answered;
|
|
6114
|
+
if (answered.data === null || answered.data === void 0) return ok(null);
|
|
6115
|
+
const home = parseTableHome(answered.data);
|
|
6116
|
+
if (!home) {
|
|
6117
|
+
const refusal = {
|
|
6118
|
+
code: "not_supported",
|
|
6119
|
+
message: "The record store answered where this table lives in a shape this client cannot read, so nothing is claimed about it.",
|
|
6120
|
+
hint: "Regenerate @ai-matrx/records against the store (pnpm records:generate) \u2014 the door and this client disagree."
|
|
6121
|
+
};
|
|
6122
|
+
scream({ code: refusal.code, message: refusal.message, ...refusal.hint ? { hint: refusal.hint } : {}, where: "tableHome" });
|
|
6123
|
+
return err(refusal);
|
|
6124
|
+
}
|
|
6125
|
+
return ok(home);
|
|
6126
|
+
},
|
|
6127
|
+
async tableMove({ table_id, to_organization_id, expected_version }) {
|
|
6128
|
+
const answered = await callDoor(
|
|
6129
|
+
DOORS.tableMove,
|
|
6130
|
+
{ p_table_id: table_id, p_to_organization_id: to_organization_id, p_expected_version: expected_version ?? null },
|
|
6131
|
+
"tableMove"
|
|
6132
|
+
);
|
|
6133
|
+
if (!answered.ok) return answered;
|
|
6134
|
+
const moved = parseTableMoved(answered.data);
|
|
6135
|
+
if (!moved) {
|
|
6136
|
+
const refusal = {
|
|
6137
|
+
code: "not_supported",
|
|
6138
|
+
message: "The record store answered the move in a shape this client cannot read. Look where the table lives before trying again."
|
|
6139
|
+
};
|
|
6140
|
+
scream({ code: refusal.code, message: refusal.message, where: "tableMove" });
|
|
6141
|
+
return err(refusal);
|
|
6142
|
+
}
|
|
6143
|
+
return ok(moved);
|
|
6144
|
+
},
|
|
5825
6145
|
async tableArchived({ table_id }) {
|
|
5826
6146
|
const kernel = await callDoor(DOORS.tableKernelId, {}, "tableArchived.kernel");
|
|
5827
6147
|
if (!kernel.ok) return err(kernel.error);
|
|
@@ -6198,10 +6518,11 @@ function createRecordsClient(config) {
|
|
|
6198
6518
|
"pipelineRead"
|
|
6199
6519
|
);
|
|
6200
6520
|
},
|
|
6201
|
-
async pipelineBoard({ table_id, measure }) {
|
|
6521
|
+
async pipelineBoard({ table_id, measure, filter }) {
|
|
6522
|
+
const asked = filter && stableFilterKey(filter) !== "{}";
|
|
6202
6523
|
return callDoor(
|
|
6203
6524
|
DOORS.pipelineBoard,
|
|
6204
|
-
{ p_organization_id: org, p_table_id: table_id, p_measure: measure ?? null },
|
|
6525
|
+
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
6526
|
"pipelineBoard"
|
|
6206
6527
|
);
|
|
6207
6528
|
},
|
|
@@ -6225,6 +6546,13 @@ function createRecordsClient(config) {
|
|
|
6225
6546
|
"pipelineMove"
|
|
6226
6547
|
);
|
|
6227
6548
|
},
|
|
6549
|
+
async pipelineMoveMany({ moves }) {
|
|
6550
|
+
return callDoor(
|
|
6551
|
+
DOORS.pipelineMoveMany,
|
|
6552
|
+
{ p_organization_id: org, p_moves: moves },
|
|
6553
|
+
"pipelineMoveMany"
|
|
6554
|
+
);
|
|
6555
|
+
},
|
|
6228
6556
|
async pipelineDeclare({ table_id, spec }) {
|
|
6229
6557
|
return callDoor(
|
|
6230
6558
|
DOORS.pipelineDeclare,
|
|
@@ -6274,6 +6602,18 @@ function createRecordsClient(config) {
|
|
|
6274
6602
|
"pipelineGatePreview"
|
|
6275
6603
|
);
|
|
6276
6604
|
},
|
|
6605
|
+
async ruleMembersVisible({ rule_id, limit = 200, offset = 0 }) {
|
|
6606
|
+
const door = await callDoor(
|
|
6607
|
+
DOORS.ruleMembersVisible,
|
|
6608
|
+
{ p_organization_id: org, p_rule_id: rule_id, p_limit: limit, p_offset: offset },
|
|
6609
|
+
"ruleMembersVisible"
|
|
6610
|
+
);
|
|
6611
|
+
if (!door.ok) return err(door.error);
|
|
6612
|
+
return ok((door.data ?? []).map((row) => {
|
|
6613
|
+
const { document, hidden } = unfold(row.document);
|
|
6614
|
+
return { id: row.id, document, level: row.level, hidden };
|
|
6615
|
+
}));
|
|
6616
|
+
},
|
|
6277
6617
|
async ruleMembers({ rule_id }) {
|
|
6278
6618
|
return callDoor(
|
|
6279
6619
|
DOORS.ruleMembers,
|
|
@@ -6326,6 +6666,13 @@ function createRecordsClient(config) {
|
|
|
6326
6666
|
"bookingDeclare"
|
|
6327
6667
|
);
|
|
6328
6668
|
},
|
|
6669
|
+
async formPreviewAsks({ table_id, questions, values }) {
|
|
6670
|
+
return callDoor(
|
|
6671
|
+
DOORS.formPreviewAsks,
|
|
6672
|
+
{ p_organization_id: org, p_table_id: table_id, p_questions: questions, p_values: values },
|
|
6673
|
+
"formPreviewAsks"
|
|
6674
|
+
);
|
|
6675
|
+
},
|
|
6329
6676
|
async formDeclare(args) {
|
|
6330
6677
|
return callDoor(
|
|
6331
6678
|
DOORS.formDeclare,
|
|
@@ -6501,11 +6848,33 @@ function createRecordsClient(config) {
|
|
|
6501
6848
|
p_tables: args.tables,
|
|
6502
6849
|
p_portal_id: args.portal_id ?? null,
|
|
6503
6850
|
p_slug: args.slug ?? null,
|
|
6504
|
-
p_sign_in_method: args.sign_in_method ?? "magic_link"
|
|
6851
|
+
p_sign_in_method: args.sign_in_method ?? "magic_link",
|
|
6852
|
+
// S6: sent only when given, so a caller that says nothing about the look keeps it.
|
|
6853
|
+
...args.config !== void 0 ? { p_config: args.config } : {}
|
|
6505
6854
|
},
|
|
6506
6855
|
"portalDeclare"
|
|
6507
6856
|
);
|
|
6508
6857
|
},
|
|
6858
|
+
async portalForm({ portal_id, form_id }) {
|
|
6859
|
+
return callDoor(
|
|
6860
|
+
DOORS.portalForm,
|
|
6861
|
+
{ p_organization_id: org, p_portal_id: portal_id, p_form_id: form_id },
|
|
6862
|
+
"portalForm"
|
|
6863
|
+
);
|
|
6864
|
+
},
|
|
6865
|
+
async portalFormSubmit({ portal_id, form_id, answers, client_key }) {
|
|
6866
|
+
return callDoor(
|
|
6867
|
+
DOORS.portalFormSubmit,
|
|
6868
|
+
{
|
|
6869
|
+
p_organization_id: org,
|
|
6870
|
+
p_portal_id: portal_id,
|
|
6871
|
+
p_form_id: form_id,
|
|
6872
|
+
p_payload: answers,
|
|
6873
|
+
p_client_key: client_key ?? null
|
|
6874
|
+
},
|
|
6875
|
+
"portalFormSubmit"
|
|
6876
|
+
);
|
|
6877
|
+
},
|
|
6509
6878
|
async portalInvite(args) {
|
|
6510
6879
|
return callDoor(
|
|
6511
6880
|
DOORS.portalInvite,
|
|
@@ -6704,10 +7073,17 @@ function createRecordsClient(config) {
|
|
|
6704
7073
|
"dashboardDeclare"
|
|
6705
7074
|
);
|
|
6706
7075
|
},
|
|
6707
|
-
async dashboardRun({ dashboard_id, filter }) {
|
|
7076
|
+
async dashboardRun({ dashboard_id, filter, compare, grain }) {
|
|
6708
7077
|
return callDoor(
|
|
6709
7078
|
DOORS.dashboardRun,
|
|
6710
|
-
{
|
|
7079
|
+
{
|
|
7080
|
+
p_organization_id: org,
|
|
7081
|
+
p_dashboard_id: dashboard_id,
|
|
7082
|
+
p_filter: filter ?? {},
|
|
7083
|
+
// Sent only when asked, so a caller that never compares calls exactly what it did.
|
|
7084
|
+
...compare ? { p_compare: compare } : {},
|
|
7085
|
+
...grain ? { p_grain: grain } : {}
|
|
7086
|
+
},
|
|
6711
7087
|
"dashboardRun"
|
|
6712
7088
|
);
|
|
6713
7089
|
},
|
|
@@ -6732,7 +7108,7 @@ function createRecordsClient(config) {
|
|
|
6732
7108
|
"dashboardDelete"
|
|
6733
7109
|
);
|
|
6734
7110
|
},
|
|
6735
|
-
async recordAggregate({ table_id, groupBy, measures, bucket, filter, limit }) {
|
|
7111
|
+
async recordAggregate({ table_id, groupBy, measures, bucket, filter, limit, compare }) {
|
|
6736
7112
|
return callDoor(
|
|
6737
7113
|
DOORS.recordAggregate,
|
|
6738
7114
|
{
|
|
@@ -6742,7 +7118,8 @@ function createRecordsClient(config) {
|
|
|
6742
7118
|
p_measures: measures ?? [],
|
|
6743
7119
|
p_bucket: bucket ?? null,
|
|
6744
7120
|
p_filter: filter ?? {},
|
|
6745
|
-
p_limit: limit ?? 200
|
|
7121
|
+
p_limit: limit ?? 200,
|
|
7122
|
+
...compare ? { p_compare: compare } : {}
|
|
6746
7123
|
},
|
|
6747
7124
|
"recordAggregate"
|
|
6748
7125
|
);
|
|
@@ -7470,6 +7847,13 @@ function createRecordsClient(config) {
|
|
|
7470
7847
|
"workApprovalDecide"
|
|
7471
7848
|
);
|
|
7472
7849
|
},
|
|
7850
|
+
async workDecideMany({ decisions }) {
|
|
7851
|
+
return callDoor(
|
|
7852
|
+
DOORS.workDecideMany,
|
|
7853
|
+
{ p_organization_id: org, p_decisions: decisions },
|
|
7854
|
+
"workDecideMany"
|
|
7855
|
+
);
|
|
7856
|
+
},
|
|
7473
7857
|
async workApprovalRead({ approval_id }) {
|
|
7474
7858
|
return callDoor(
|
|
7475
7859
|
DOORS.workApprovalRead,
|
|
@@ -7673,6 +8057,69 @@ function createRecordsClient(config) {
|
|
|
7673
8057
|
"tableFromExample"
|
|
7674
8058
|
);
|
|
7675
8059
|
},
|
|
8060
|
+
async tableListEverywhere() {
|
|
8061
|
+
return callDoor(
|
|
8062
|
+
DOORS.tableListEverywhere,
|
|
8063
|
+
{ p_organization_id: org },
|
|
8064
|
+
"tableListEverywhere"
|
|
8065
|
+
);
|
|
8066
|
+
},
|
|
8067
|
+
async tableSync({ home_id, spec }) {
|
|
8068
|
+
return callDoor(
|
|
8069
|
+
DOORS.tableSync,
|
|
8070
|
+
{ p_organization_id: org, p_home_id: home_id, p_spec: spec },
|
|
8071
|
+
"tableSync"
|
|
8072
|
+
);
|
|
8073
|
+
},
|
|
8074
|
+
async scopeTableProvision({ home_id, item_id, scope_id }) {
|
|
8075
|
+
return callDoor(
|
|
8076
|
+
DOORS.scopeTableProvision,
|
|
8077
|
+
{ p_organization_id: org, p_item_id: item_id, p_scope_id: scope_id, p_home_id: home_id ?? null },
|
|
8078
|
+
"scopeTableProvision"
|
|
8079
|
+
);
|
|
8080
|
+
},
|
|
8081
|
+
async tableRowCounts({ table_ids }) {
|
|
8082
|
+
return callDoor(
|
|
8083
|
+
DOORS.tableRowCounts,
|
|
8084
|
+
{ p_organization_id: org, p_table_ids: table_ids },
|
|
8085
|
+
"tableRowCounts"
|
|
8086
|
+
);
|
|
8087
|
+
},
|
|
8088
|
+
async recordHeaders({ ids }) {
|
|
8089
|
+
return callDoor(
|
|
8090
|
+
DOORS.recordHeaders,
|
|
8091
|
+
{ p_organization_id: org, p_ids: ids },
|
|
8092
|
+
"recordHeaders"
|
|
8093
|
+
);
|
|
8094
|
+
},
|
|
8095
|
+
async tableShareOutsideGrant({ table_id, person, level }) {
|
|
8096
|
+
return callDoor(
|
|
8097
|
+
DOORS.tableShareOutsideGrant,
|
|
8098
|
+
{ p_organization_id: org, p_table_id: table_id, p_person: person, p_level: level ?? "viewer" },
|
|
8099
|
+
"tableShareOutsideGrant"
|
|
8100
|
+
);
|
|
8101
|
+
},
|
|
8102
|
+
async viewRecordOrderSet({ view_id, record_ids }) {
|
|
8103
|
+
return callDoor(
|
|
8104
|
+
DOORS.viewRecordOrderSet,
|
|
8105
|
+
{ p_organization_id: org, p_view_id: view_id, p_record_ids: record_ids },
|
|
8106
|
+
"viewRecordOrderSet"
|
|
8107
|
+
);
|
|
8108
|
+
},
|
|
8109
|
+
async readInViewOrder({ view_id, limit, offset }) {
|
|
8110
|
+
const door = await callDoor(
|
|
8111
|
+
DOORS.readRecordsInViewOrder,
|
|
8112
|
+
{ p_organization_id: org, p_view_id: view_id, p_by_id: false, p_limit: limit ?? 200, p_offset: offset ?? 0 },
|
|
8113
|
+
"readInViewOrder"
|
|
8114
|
+
);
|
|
8115
|
+
if (!door.ok) return err(door.error);
|
|
8116
|
+
return ok(
|
|
8117
|
+
(door.data ?? []).map((row) => {
|
|
8118
|
+
const { document, hidden } = unfold(row.document);
|
|
8119
|
+
return { id: row.id, document, level: row.level, hidden, position: row.position ?? null };
|
|
8120
|
+
})
|
|
8121
|
+
);
|
|
8122
|
+
},
|
|
7676
8123
|
async autonumberBackfill({ field_id }) {
|
|
7677
8124
|
return callDoor(
|
|
7678
8125
|
DOORS.autonumberBackfill,
|
|
@@ -7728,6 +8175,113 @@ function asWriteConflict(error, recordId) {
|
|
|
7728
8175
|
};
|
|
7729
8176
|
}
|
|
7730
8177
|
|
|
8178
|
+
// src/core/declareTable.ts
|
|
8179
|
+
function tokenFor(name) {
|
|
8180
|
+
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 48) || "table";
|
|
8181
|
+
}
|
|
8182
|
+
var DEFAULT_FIELDS = [
|
|
8183
|
+
{ key: "title", label: "Title", type: "text", sort: 10, required: false }
|
|
8184
|
+
];
|
|
8185
|
+
function fieldDeclarationFor(field) {
|
|
8186
|
+
return {
|
|
8187
|
+
key: field.key,
|
|
8188
|
+
label: field.label,
|
|
8189
|
+
sort: field.sort ?? 100,
|
|
8190
|
+
required: field.required ?? false,
|
|
8191
|
+
multi: field.multi ?? false,
|
|
8192
|
+
...declaredType(field),
|
|
8193
|
+
...field.config ? { config: field.config } : {},
|
|
8194
|
+
...field.options ? { options: field.options } : {},
|
|
8195
|
+
...field.relationTarget ? { relation_target: field.relationTarget } : {},
|
|
8196
|
+
...field.unit ? { unit: field.unit } : {},
|
|
8197
|
+
...field.kind ? { kind: field.kind } : {}
|
|
8198
|
+
};
|
|
8199
|
+
}
|
|
8200
|
+
async function declareTable(client, spec) {
|
|
8201
|
+
const fields = spec.fields && spec.fields.length > 0 ? spec.fields : DEFAULT_FIELDS;
|
|
8202
|
+
const slug = spec.slug ?? tokenFor(spec.name);
|
|
8203
|
+
const titleField = spec.titleField ?? fields[0].key;
|
|
8204
|
+
const personKernel = await client.personKernelId();
|
|
8205
|
+
if (!personKernel.ok) return personKernel;
|
|
8206
|
+
const home = await client.recordWrite({
|
|
8207
|
+
table_id: personKernel.data,
|
|
8208
|
+
data: { name: `${spec.name} Home` }
|
|
8209
|
+
});
|
|
8210
|
+
if (!home.ok) return home;
|
|
8211
|
+
const table = await client.tableDeclare({
|
|
8212
|
+
homeId: home.data,
|
|
8213
|
+
spec: {
|
|
8214
|
+
name: spec.name,
|
|
8215
|
+
slug,
|
|
8216
|
+
type: "entity",
|
|
8217
|
+
label_singular: spec.labelSingular ?? spec.name,
|
|
8218
|
+
label_plural: spec.labelPlural ?? spec.name,
|
|
8219
|
+
display: "list",
|
|
8220
|
+
weight: "light",
|
|
8221
|
+
ordered: false,
|
|
8222
|
+
row_order: "manual",
|
|
8223
|
+
title_field: titleField,
|
|
8224
|
+
retention_days: 365,
|
|
8225
|
+
// AGT-1: a table a person made is agent-writable by declaration; the
|
|
8226
|
+
// organization turns it off, never this package.
|
|
8227
|
+
agent_writable: true,
|
|
8228
|
+
default_sort: [{ field: titleField, direction: "asc" }],
|
|
8229
|
+
fields: fields.map((f) => ({ name: f.key }))
|
|
8230
|
+
}
|
|
8231
|
+
});
|
|
8232
|
+
if (!table.ok) {
|
|
8233
|
+
await client.recordDelete({ record_id: home.data });
|
|
8234
|
+
return table;
|
|
8235
|
+
}
|
|
8236
|
+
const written = await addFields(client, table.data, fields);
|
|
8237
|
+
if (!written.ok) return undo(client, table.data, home.data, spec.name, written.error);
|
|
8238
|
+
return { ok: true, data: table.data, homeId: home.data };
|
|
8239
|
+
}
|
|
8240
|
+
async function undo(client, tableId, homeId, name, refusal) {
|
|
8241
|
+
const removed = await client.recordDelete({ record_id: tableId });
|
|
8242
|
+
if (removed.ok) {
|
|
8243
|
+
await client.recordDelete({ record_id: homeId });
|
|
8244
|
+
return { ok: false, error: refusal };
|
|
8245
|
+
}
|
|
8246
|
+
return {
|
|
8247
|
+
ok: false,
|
|
8248
|
+
error: {
|
|
8249
|
+
...refusal,
|
|
8250
|
+
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.`
|
|
8251
|
+
}
|
|
8252
|
+
};
|
|
8253
|
+
}
|
|
8254
|
+
async function addFields(client, tableId, fields) {
|
|
8255
|
+
for (const field of fields) {
|
|
8256
|
+
const result = await client.fieldDeclare({
|
|
8257
|
+
table_id: tableId,
|
|
8258
|
+
spec: fieldDeclarationFor(field)
|
|
8259
|
+
});
|
|
8260
|
+
if (!result.ok) return result;
|
|
8261
|
+
}
|
|
8262
|
+
return { ok: true, data: tableId };
|
|
8263
|
+
}
|
|
8264
|
+
var PLAIN_WORDS = {
|
|
8265
|
+
text: "text",
|
|
8266
|
+
string: "text",
|
|
8267
|
+
long_text: "long_text",
|
|
8268
|
+
longtext: "long_text",
|
|
8269
|
+
paragraph: "long_text",
|
|
8270
|
+
number: "number",
|
|
8271
|
+
range: "number",
|
|
8272
|
+
integer: "number",
|
|
8273
|
+
decimal: "number",
|
|
8274
|
+
float: "number"
|
|
8275
|
+
};
|
|
8276
|
+
function declaredType(field) {
|
|
8277
|
+
const word = (field.type ?? "text").toLowerCase();
|
|
8278
|
+
const plain = PLAIN_WORDS[word];
|
|
8279
|
+
if (plain) {
|
|
8280
|
+
return { plain: plain === "text" && field.config?.["multiline"] === true ? "long_text" : plain };
|
|
8281
|
+
}
|
|
8282
|
+
return { parity_type: word };
|
|
8283
|
+
}
|
|
8284
|
+
|
|
7731
8285
|
// src/core/supabase.ts
|
|
7732
8286
|
function refuse(code, message, hint) {
|
|
7733
8287
|
return { data: null, error: { code, message, hint, details: "" } };
|
|
@@ -8223,6 +8777,7 @@ export {
|
|
|
8223
8777
|
DECLARABLE_FIELD_KINDS,
|
|
8224
8778
|
DEFAULT_ARCHIVE_LANE,
|
|
8225
8779
|
DEFAULT_DOCUMENT_MAX_BYTES,
|
|
8780
|
+
DEFAULT_FIELDS,
|
|
8226
8781
|
DEFAULT_PAGE_SIZE,
|
|
8227
8782
|
DEFAULT_PORTAL_ARCHIVE_FILTER,
|
|
8228
8783
|
DEFAULT_VALUE_MAX_BYTES,
|
|
@@ -8244,6 +8799,7 @@ export {
|
|
|
8244
8799
|
PERMISSION_LEVELS,
|
|
8245
8800
|
PER_VALUE_ACCESS_WORDS,
|
|
8246
8801
|
PIPELINE_RULE_KINDS,
|
|
8802
|
+
PORTAL_ACCENTS,
|
|
8247
8803
|
PORTAL_ARCHIVE_FILTERS,
|
|
8248
8804
|
REFUSALS_WORTH_REPEATING,
|
|
8249
8805
|
RELATION_BINDINGS,
|
|
@@ -8264,6 +8820,7 @@ export {
|
|
|
8264
8820
|
VALUE_ALTERNATE_KEYS,
|
|
8265
8821
|
VALUE_ENVELOPE_KEYS,
|
|
8266
8822
|
actionRefusals,
|
|
8823
|
+
addFields,
|
|
8267
8824
|
allowsOtherValues,
|
|
8268
8825
|
announceRecordChanged,
|
|
8269
8826
|
archivedByLine,
|
|
@@ -8280,16 +8837,23 @@ export {
|
|
|
8280
8837
|
coerceTypedAnswers,
|
|
8281
8838
|
coercionLabel,
|
|
8282
8839
|
createRecordsClient,
|
|
8840
|
+
declareTable,
|
|
8283
8841
|
doorAbsent,
|
|
8284
8842
|
doorCensus,
|
|
8285
8843
|
doorExists,
|
|
8286
8844
|
emptyArchiveLine,
|
|
8287
8845
|
emptyPortalArchiveLine,
|
|
8846
|
+
entityReferenceKinds,
|
|
8847
|
+
entityReferenceKindsOf,
|
|
8848
|
+
entityReferenceWords,
|
|
8849
|
+
entityRefsOf,
|
|
8850
|
+
entityRefsToWrite,
|
|
8288
8851
|
err,
|
|
8289
8852
|
exampleSpec,
|
|
8290
8853
|
exportCsv,
|
|
8291
8854
|
exportPageXlsx,
|
|
8292
8855
|
exportXlsx,
|
|
8856
|
+
fieldDeclarationFor,
|
|
8293
8857
|
fieldKindFor,
|
|
8294
8858
|
forgetAllOps,
|
|
8295
8859
|
highestLevel,
|
|
@@ -8301,10 +8865,16 @@ export {
|
|
|
8301
8865
|
isAPersonsRefusal,
|
|
8302
8866
|
isArchiveLane,
|
|
8303
8867
|
isDecided,
|
|
8868
|
+
isEntityReferenceConfig,
|
|
8304
8869
|
isOwnOp,
|
|
8870
|
+
isPortalAccent,
|
|
8305
8871
|
isPortalArchiveFilter,
|
|
8306
8872
|
isRecordsErr,
|
|
8873
|
+
isRuleFilter,
|
|
8307
8874
|
isTheChosen,
|
|
8875
|
+
keptGroups,
|
|
8876
|
+
keptOutOfSight,
|
|
8877
|
+
keptTableHref,
|
|
8308
8878
|
looksLikeAnId,
|
|
8309
8879
|
mapPgError,
|
|
8310
8880
|
mayDrag,
|
|
@@ -8312,12 +8882,17 @@ export {
|
|
|
8312
8882
|
memoryStore,
|
|
8313
8883
|
mintClientKey,
|
|
8314
8884
|
mintOpId,
|
|
8885
|
+
moveConsequence,
|
|
8886
|
+
moveInOrder,
|
|
8887
|
+
offeredAsContext,
|
|
8315
8888
|
ok,
|
|
8316
8889
|
onRecordChanged,
|
|
8317
8890
|
openCaptureQueue,
|
|
8318
8891
|
optionKey,
|
|
8319
8892
|
optionLabel,
|
|
8320
8893
|
parseCsv,
|
|
8894
|
+
parseTableHome,
|
|
8895
|
+
parseTableMoved,
|
|
8321
8896
|
planQuery,
|
|
8322
8897
|
portalArchiveConsequence,
|
|
8323
8898
|
portalConfirmLine,
|
|
@@ -8332,10 +8907,14 @@ export {
|
|
|
8332
8907
|
rememberedOpCount,
|
|
8333
8908
|
sameDocument,
|
|
8334
8909
|
scopeChip,
|
|
8910
|
+
sheetSyncSpec,
|
|
8335
8911
|
stableFilterKey,
|
|
8336
8912
|
staleWriteDetail,
|
|
8337
8913
|
supabaseDataSource,
|
|
8338
8914
|
toAoa,
|
|
8339
|
-
|
|
8915
|
+
tokenFor,
|
|
8916
|
+
withRowCounts,
|
|
8917
|
+
withTableFacts,
|
|
8918
|
+
withTablePlacement
|
|
8340
8919
|
};
|
|
8341
8920
|
//# sourceMappingURL=index.js.map
|