@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.cjs
CHANGED
|
@@ -2205,6 +2205,7 @@ __export(core_exports, {
|
|
|
2205
2205
|
DECLARABLE_FIELD_KINDS: () => DECLARABLE_FIELD_KINDS,
|
|
2206
2206
|
DEFAULT_ARCHIVE_LANE: () => DEFAULT_ARCHIVE_LANE,
|
|
2207
2207
|
DEFAULT_DOCUMENT_MAX_BYTES: () => DEFAULT_DOCUMENT_MAX_BYTES,
|
|
2208
|
+
DEFAULT_FIELDS: () => DEFAULT_FIELDS,
|
|
2208
2209
|
DEFAULT_PAGE_SIZE: () => DEFAULT_PAGE_SIZE,
|
|
2209
2210
|
DEFAULT_PORTAL_ARCHIVE_FILTER: () => DEFAULT_PORTAL_ARCHIVE_FILTER,
|
|
2210
2211
|
DEFAULT_VALUE_MAX_BYTES: () => DEFAULT_VALUE_MAX_BYTES,
|
|
@@ -2226,6 +2227,7 @@ __export(core_exports, {
|
|
|
2226
2227
|
PERMISSION_LEVELS: () => PERMISSION_LEVELS,
|
|
2227
2228
|
PER_VALUE_ACCESS_WORDS: () => PER_VALUE_ACCESS_WORDS,
|
|
2228
2229
|
PIPELINE_RULE_KINDS: () => PIPELINE_RULE_KINDS,
|
|
2230
|
+
PORTAL_ACCENTS: () => PORTAL_ACCENTS,
|
|
2229
2231
|
PORTAL_ARCHIVE_FILTERS: () => PORTAL_ARCHIVE_FILTERS,
|
|
2230
2232
|
REFUSALS_WORTH_REPEATING: () => REFUSALS_WORTH_REPEATING,
|
|
2231
2233
|
RELATION_BINDINGS: () => RELATION_BINDINGS,
|
|
@@ -2246,6 +2248,7 @@ __export(core_exports, {
|
|
|
2246
2248
|
VALUE_ALTERNATE_KEYS: () => VALUE_ALTERNATE_KEYS,
|
|
2247
2249
|
VALUE_ENVELOPE_KEYS: () => VALUE_ENVELOPE_KEYS,
|
|
2248
2250
|
actionRefusals: () => actionRefusals,
|
|
2251
|
+
addFields: () => addFields,
|
|
2249
2252
|
allowsOtherValues: () => allowsOtherValues,
|
|
2250
2253
|
announceRecordChanged: () => announceRecordChanged,
|
|
2251
2254
|
archivedByLine: () => archivedByLine,
|
|
@@ -2262,16 +2265,23 @@ __export(core_exports, {
|
|
|
2262
2265
|
coerceTypedAnswers: () => coerceTypedAnswers,
|
|
2263
2266
|
coercionLabel: () => coercionLabel,
|
|
2264
2267
|
createRecordsClient: () => createRecordsClient,
|
|
2268
|
+
declareTable: () => declareTable,
|
|
2265
2269
|
doorAbsent: () => doorAbsent,
|
|
2266
2270
|
doorCensus: () => doorCensus,
|
|
2267
2271
|
doorExists: () => doorExists,
|
|
2268
2272
|
emptyArchiveLine: () => emptyArchiveLine,
|
|
2269
2273
|
emptyPortalArchiveLine: () => emptyPortalArchiveLine,
|
|
2274
|
+
entityReferenceKinds: () => entityReferenceKinds,
|
|
2275
|
+
entityReferenceKindsOf: () => entityReferenceKindsOf,
|
|
2276
|
+
entityReferenceWords: () => entityReferenceWords,
|
|
2277
|
+
entityRefsOf: () => entityRefsOf,
|
|
2278
|
+
entityRefsToWrite: () => entityRefsToWrite,
|
|
2270
2279
|
err: () => err,
|
|
2271
2280
|
exampleSpec: () => exampleSpec,
|
|
2272
2281
|
exportCsv: () => exportCsv,
|
|
2273
2282
|
exportPageXlsx: () => exportPageXlsx,
|
|
2274
2283
|
exportXlsx: () => exportXlsx,
|
|
2284
|
+
fieldDeclarationFor: () => fieldDeclarationFor,
|
|
2275
2285
|
fieldKindFor: () => fieldKindFor,
|
|
2276
2286
|
forgetAllOps: () => forgetAllOps,
|
|
2277
2287
|
highestLevel: () => highestLevel,
|
|
@@ -2283,10 +2293,16 @@ __export(core_exports, {
|
|
|
2283
2293
|
isAPersonsRefusal: () => isAPersonsRefusal,
|
|
2284
2294
|
isArchiveLane: () => isArchiveLane,
|
|
2285
2295
|
isDecided: () => isDecided,
|
|
2296
|
+
isEntityReferenceConfig: () => isEntityReferenceConfig,
|
|
2286
2297
|
isOwnOp: () => isOwnOp,
|
|
2298
|
+
isPortalAccent: () => isPortalAccent,
|
|
2287
2299
|
isPortalArchiveFilter: () => isPortalArchiveFilter,
|
|
2288
2300
|
isRecordsErr: () => isRecordsErr,
|
|
2301
|
+
isRuleFilter: () => isRuleFilter,
|
|
2289
2302
|
isTheChosen: () => isTheChosen,
|
|
2303
|
+
keptGroups: () => keptGroups,
|
|
2304
|
+
keptOutOfSight: () => keptOutOfSight,
|
|
2305
|
+
keptTableHref: () => keptTableHref,
|
|
2290
2306
|
looksLikeAnId: () => looksLikeAnId,
|
|
2291
2307
|
mapPgError: () => mapPgError,
|
|
2292
2308
|
mayDrag: () => mayDrag,
|
|
@@ -2294,12 +2310,17 @@ __export(core_exports, {
|
|
|
2294
2310
|
memoryStore: () => memoryStore,
|
|
2295
2311
|
mintClientKey: () => mintClientKey,
|
|
2296
2312
|
mintOpId: () => mintOpId,
|
|
2313
|
+
moveConsequence: () => moveConsequence,
|
|
2314
|
+
moveInOrder: () => moveInOrder,
|
|
2315
|
+
offeredAsContext: () => offeredAsContext,
|
|
2297
2316
|
ok: () => ok,
|
|
2298
2317
|
onRecordChanged: () => onRecordChanged,
|
|
2299
2318
|
openCaptureQueue: () => openCaptureQueue,
|
|
2300
2319
|
optionKey: () => optionKey,
|
|
2301
2320
|
optionLabel: () => optionLabel,
|
|
2302
2321
|
parseCsv: () => parseCsv,
|
|
2322
|
+
parseTableHome: () => parseTableHome,
|
|
2323
|
+
parseTableMoved: () => parseTableMoved,
|
|
2303
2324
|
planQuery: () => planQuery,
|
|
2304
2325
|
portalArchiveConsequence: () => portalArchiveConsequence,
|
|
2305
2326
|
portalConfirmLine: () => portalConfirmLine,
|
|
@@ -2314,11 +2335,15 @@ __export(core_exports, {
|
|
|
2314
2335
|
rememberedOpCount: () => rememberedOpCount,
|
|
2315
2336
|
sameDocument: () => sameDocument,
|
|
2316
2337
|
scopeChip: () => scopeChip,
|
|
2338
|
+
sheetSyncSpec: () => sheetSyncSpec,
|
|
2317
2339
|
stableFilterKey: () => stableFilterKey,
|
|
2318
2340
|
staleWriteDetail: () => staleWriteDetail,
|
|
2319
2341
|
supabaseDataSource: () => supabaseDataSource,
|
|
2320
2342
|
toAoa: () => toAoa,
|
|
2321
|
-
|
|
2343
|
+
tokenFor: () => tokenFor,
|
|
2344
|
+
withRowCounts: () => withRowCounts,
|
|
2345
|
+
withTableFacts: () => withTableFacts,
|
|
2346
|
+
withTablePlacement: () => withTablePlacement
|
|
2322
2347
|
});
|
|
2323
2348
|
module.exports = __toCommonJS(core_exports);
|
|
2324
2349
|
|
|
@@ -2500,6 +2525,7 @@ var FIELD_KINDS = [
|
|
|
2500
2525
|
"currency",
|
|
2501
2526
|
"datetime",
|
|
2502
2527
|
"email",
|
|
2528
|
+
"entity_reference",
|
|
2503
2529
|
"formula",
|
|
2504
2530
|
"json",
|
|
2505
2531
|
"long_text",
|
|
@@ -2538,6 +2564,7 @@ function fieldKindFor(field, kernel) {
|
|
|
2538
2564
|
case "list":
|
|
2539
2565
|
return field.multi ? "multi_select" : "select";
|
|
2540
2566
|
case "relation": {
|
|
2567
|
+
if (isEntityReferenceConfig(config)) return "entity_reference";
|
|
2541
2568
|
const parity = String(config["parity"] ?? "");
|
|
2542
2569
|
if (parity === "attachment" || parity === "member") return parity;
|
|
2543
2570
|
if (kernel?.file && field.relation_target === kernel.file) return "attachment";
|
|
@@ -2555,6 +2582,14 @@ function fieldKindFor(field, kernel) {
|
|
|
2555
2582
|
}
|
|
2556
2583
|
}
|
|
2557
2584
|
var UUID_TEXT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2585
|
+
function isEntityReferenceConfig(config) {
|
|
2586
|
+
const allowed = config?.["allowed_types"];
|
|
2587
|
+
return Array.isArray(allowed) && allowed.length > 0;
|
|
2588
|
+
}
|
|
2589
|
+
function entityReferenceKindsOf(field) {
|
|
2590
|
+
const allowed = field.config?.["allowed_types"];
|
|
2591
|
+
return Array.isArray(allowed) ? allowed.map((k) => String(k).trim().toLowerCase()).filter(Boolean) : [];
|
|
2592
|
+
}
|
|
2558
2593
|
var TRUE_WORDS = /* @__PURE__ */ new Set(["true", "yes", "y", "1", "\u2713", "x", "checked", "on"]);
|
|
2559
2594
|
var FALSE_WORDS = /* @__PURE__ */ new Set(["false", "no", "n", "0", "", "unchecked", "off"]);
|
|
2560
2595
|
function coercionLabel(field) {
|
|
@@ -2670,6 +2705,23 @@ function coerceTypedAnswer(field, raw, opts = {}) {
|
|
|
2670
2705
|
return {
|
|
2671
2706
|
refusal: `${label} points at another record, so it is chosen rather than typed. Open it and pick the record.`
|
|
2672
2707
|
};
|
|
2708
|
+
// SC-R / P12. An entity reference names a platform thing by kind and id.
|
|
2709
|
+
// An import or an agent may hand over `note:<id>` — or a bare id when the
|
|
2710
|
+
// column allows exactly one kind; the store then decides whether the thing
|
|
2711
|
+
// is there and whether the writer may open it. Words are chosen, not typed.
|
|
2712
|
+
case "entity_reference": {
|
|
2713
|
+
const allowed = entityReferenceKindsOf(field);
|
|
2714
|
+
const [maybeToken, maybeId] = text.includes(":") ? text.split(/:(.*)/s, 2) : ["", text];
|
|
2715
|
+
const token = (maybeToken ?? "").trim().toLowerCase() || (allowed.length === 1 ? allowed[0] : "");
|
|
2716
|
+
const id = (maybeId ?? "").trim();
|
|
2717
|
+
if (token && UUID_TEXT.test(id)) {
|
|
2718
|
+
const ref = { token, id: id.toLowerCase() };
|
|
2719
|
+
return { value: field.multi ? [ref] : ref };
|
|
2720
|
+
}
|
|
2721
|
+
return {
|
|
2722
|
+
refusal: `${label} points at something on the platform, so it is chosen rather than typed. Open it and pick it.`
|
|
2723
|
+
};
|
|
2724
|
+
}
|
|
2673
2725
|
case "formula":
|
|
2674
2726
|
case "lookup":
|
|
2675
2727
|
case "rollup":
|
|
@@ -2726,6 +2778,10 @@ function publicFormPath(formId) {
|
|
|
2726
2778
|
}
|
|
2727
2779
|
|
|
2728
2780
|
// src/portal.ts
|
|
2781
|
+
var PORTAL_ACCENTS = ["slate", "green", "amber", "red", "blue", "violet", "teal"];
|
|
2782
|
+
function isPortalAccent(word) {
|
|
2783
|
+
return typeof word === "string" && PORTAL_ACCENTS.includes(word);
|
|
2784
|
+
}
|
|
2729
2785
|
function portalPath(slug) {
|
|
2730
2786
|
return `/portal/c/${slug}`;
|
|
2731
2787
|
}
|
|
@@ -2749,11 +2805,17 @@ function emptyPortalArchiveLine() {
|
|
|
2749
2805
|
}
|
|
2750
2806
|
|
|
2751
2807
|
// src/aggregate.ts
|
|
2808
|
+
function isRuleFilter(filter) {
|
|
2809
|
+
if (!filter || typeof filter !== "object" || Array.isArray(filter)) return false;
|
|
2810
|
+
const node = filter;
|
|
2811
|
+
return typeof node.op === "string" && Array.isArray(node.args);
|
|
2812
|
+
}
|
|
2752
2813
|
function measureKey(measure) {
|
|
2753
2814
|
return measure.op === "count" ? "count" : `${measure.op}_${measure.key ?? ""}`;
|
|
2754
2815
|
}
|
|
2755
2816
|
function stableFilterKey(filter) {
|
|
2756
2817
|
if (!filter) return "{}";
|
|
2818
|
+
if (isRuleFilter(filter)) return JSON.stringify(filter);
|
|
2757
2819
|
const keys = Object.keys(filter).sort();
|
|
2758
2820
|
if (keys.length === 0) return "{}";
|
|
2759
2821
|
const parts = keys.map((key) => {
|
|
@@ -2849,6 +2911,161 @@ function recordChangeTrigger(tableId, options = {}) {
|
|
|
2849
2911
|
...options.changedFields && options.changedFields.length > 0 ? { changed_fields: options.changedFields } : {}
|
|
2850
2912
|
};
|
|
2851
2913
|
}
|
|
2914
|
+
function sheetSyncSpec(args) {
|
|
2915
|
+
const [header = [], ...body] = args.cells;
|
|
2916
|
+
const columns = header.map((c, i) => {
|
|
2917
|
+
const name = c == null ? "" : String(c).trim();
|
|
2918
|
+
return name === "" ? `Column ${i + 1}` : name;
|
|
2919
|
+
});
|
|
2920
|
+
if (columns.length === 0) {
|
|
2921
|
+
throw new Error("The sheet has no header row, so there are no columns to sync. Nothing was sent.");
|
|
2922
|
+
}
|
|
2923
|
+
const seenCol = /* @__PURE__ */ new Set();
|
|
2924
|
+
for (const c of columns) {
|
|
2925
|
+
if (seenCol.has(c)) throw new Error(`The sheet has two columns named "${c}". Rename one and sync again.`);
|
|
2926
|
+
seenCol.add(c);
|
|
2927
|
+
}
|
|
2928
|
+
const refIdx = args.refColumn == null ? -1 : columns.indexOf(args.refColumn);
|
|
2929
|
+
if (args.refColumn != null && refIdx < 0) {
|
|
2930
|
+
throw new Error(`The id column "${args.refColumn}" is not in the sheet's header. Nothing was sent.`);
|
|
2931
|
+
}
|
|
2932
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2933
|
+
const rows = [];
|
|
2934
|
+
body.forEach((cells, i) => {
|
|
2935
|
+
if (cells.every((v) => v == null || String(v).trim() === "")) return;
|
|
2936
|
+
const ref = refIdx >= 0 ? String(cells[refIdx] ?? "").trim() : `row-${i + 2}`;
|
|
2937
|
+
if (ref === "") throw new Error(`Sheet row ${i + 2} has no value in the id column "${args.refColumn}".`);
|
|
2938
|
+
if (seen.has(ref)) throw new Error(`Two sheet rows share the id "${ref}"; each row needs its own.`);
|
|
2939
|
+
seen.add(ref);
|
|
2940
|
+
const values = {};
|
|
2941
|
+
columns.forEach((c, j) => {
|
|
2942
|
+
const v = cells[j];
|
|
2943
|
+
values[c] = v === void 0 || v === "" ? null : v;
|
|
2944
|
+
});
|
|
2945
|
+
rows.push({ ref, values });
|
|
2946
|
+
});
|
|
2947
|
+
return { source: args.source, table_name: args.tableName, columns, rows };
|
|
2948
|
+
}
|
|
2949
|
+
function withRowCounts(tables, counts) {
|
|
2950
|
+
if (!counts) return [...tables];
|
|
2951
|
+
const byId = new Map(counts.map((c) => [c.table_id, Number(c.visible_rows)]));
|
|
2952
|
+
return tables.map((t) => byId.has(t.id) ? { ...t, row_count: byId.get(t.id) } : t);
|
|
2953
|
+
}
|
|
2954
|
+
function moveInOrder(order, moved, toIndex) {
|
|
2955
|
+
const rest = order.filter((id) => id !== moved);
|
|
2956
|
+
const at = Math.max(0, Math.min(Math.trunc(toIndex), rest.length));
|
|
2957
|
+
return [...rest.slice(0, at), moved, ...rest.slice(at)];
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
// src/placement.ts
|
|
2961
|
+
function keptOutOfSight(placement) {
|
|
2962
|
+
return placement?.kept_by_the_app === true;
|
|
2963
|
+
}
|
|
2964
|
+
function offeredAsContext(placement) {
|
|
2965
|
+
return placement?.offered_as_context === true;
|
|
2966
|
+
}
|
|
2967
|
+
function withTablePlacement(tables, facts) {
|
|
2968
|
+
if (!facts) return [...tables];
|
|
2969
|
+
const byId = new Map(facts.map((f) => [f.table_id, f]));
|
|
2970
|
+
return tables.map((table) => {
|
|
2971
|
+
const fact = byId.get(table.id);
|
|
2972
|
+
if (!fact || fact.kept_by_the_app === void 0) return table;
|
|
2973
|
+
return {
|
|
2974
|
+
...table,
|
|
2975
|
+
kept_by_the_app: fact.kept_by_the_app,
|
|
2976
|
+
kept_for: fact.kept_for ?? null,
|
|
2977
|
+
offered_as_context: fact.offered_as_context ?? false,
|
|
2978
|
+
keeper_group: fact.keeper_group ?? null,
|
|
2979
|
+
keeper_says: fact.keeper_says ?? null,
|
|
2980
|
+
used_in_kind: fact.used_in_kind ?? null,
|
|
2981
|
+
used_in_id: fact.used_in_id ?? null,
|
|
2982
|
+
used_in_table_id: fact.used_in_table_id ?? null
|
|
2983
|
+
};
|
|
2984
|
+
});
|
|
2985
|
+
}
|
|
2986
|
+
function keptGroups(tables) {
|
|
2987
|
+
const out = [];
|
|
2988
|
+
const at = /* @__PURE__ */ new Map();
|
|
2989
|
+
for (const table of tables) {
|
|
2990
|
+
if (!keptOutOfSight(table)) continue;
|
|
2991
|
+
const group = table.keeper_group?.trim() || "Kept by the app";
|
|
2992
|
+
let i = at.get(group);
|
|
2993
|
+
if (i === void 0) {
|
|
2994
|
+
i = out.length;
|
|
2995
|
+
at.set(group, i);
|
|
2996
|
+
out.push({ group, tables: [] });
|
|
2997
|
+
}
|
|
2998
|
+
out[i].tables.push(table);
|
|
2999
|
+
}
|
|
3000
|
+
return out;
|
|
3001
|
+
}
|
|
3002
|
+
function keptTableHref(fact, hrefs) {
|
|
3003
|
+
if (fact.used_in_kind === "scope" && fact.used_in_id) return hrefs.scope(fact.used_in_id);
|
|
3004
|
+
if (fact.used_in_kind === "table" && (fact.used_in_table_id || fact.used_in_id)) {
|
|
3005
|
+
return hrefs.table(fact.used_in_table_id || fact.used_in_id);
|
|
3006
|
+
}
|
|
3007
|
+
return hrefs.table(fact.table_id);
|
|
3008
|
+
}
|
|
3009
|
+
function countOf(value) {
|
|
3010
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
3011
|
+
}
|
|
3012
|
+
function wordsOf(value) {
|
|
3013
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
3014
|
+
}
|
|
3015
|
+
function parseTableHome(raw) {
|
|
3016
|
+
if (!raw || typeof raw !== "object") return null;
|
|
3017
|
+
const row = raw;
|
|
3018
|
+
const table = row.table;
|
|
3019
|
+
const org = row.organization;
|
|
3020
|
+
if (!table || !org || !wordsOf(table.id) || !wordsOf(org.id) || !wordsOf(org.name)) return null;
|
|
3021
|
+
const carries = row.carries ?? {};
|
|
3022
|
+
const destinations = Array.isArray(row.destinations) ? row.destinations : [];
|
|
3023
|
+
return {
|
|
3024
|
+
table: { id: String(table.id), name: wordsOf(table.name) ?? "This table", version: countOf(table.version) },
|
|
3025
|
+
organization: { id: String(org.id), name: String(org.name) },
|
|
3026
|
+
mayMove: row.may_move === true,
|
|
3027
|
+
whyNot: wordsOf(row.why_not),
|
|
3028
|
+
heldBy: Array.isArray(row.held_by) ? row.held_by.filter((s) => typeof s === "string") : [],
|
|
3029
|
+
destinations: destinations.filter((d) => !!d && typeof d === "object" && !!wordsOf(d.id)).map((d) => ({
|
|
3030
|
+
id: String(d.id),
|
|
3031
|
+
name: wordsOf(d.name) ?? "An organization",
|
|
3032
|
+
role: wordsOf(d.role) ?? "member",
|
|
3033
|
+
ok: d.ok === true,
|
|
3034
|
+
why: wordsOf(d.why)
|
|
3035
|
+
})),
|
|
3036
|
+
carries: {
|
|
3037
|
+
fields: countOf(carries.fields),
|
|
3038
|
+
records: countOf(carries.records),
|
|
3039
|
+
inTrash: countOf(carries.in_trash),
|
|
3040
|
+
choiceLists: countOf(carries.choice_lists),
|
|
3041
|
+
withIt: countOf(carries.with_it),
|
|
3042
|
+
tablesInside: countOf(carries.tables_inside),
|
|
3043
|
+
linksAcross: countOf(carries.links_across)
|
|
3044
|
+
}
|
|
3045
|
+
};
|
|
3046
|
+
}
|
|
3047
|
+
function parseTableMoved(raw) {
|
|
3048
|
+
const row = raw ?? {};
|
|
3049
|
+
const to = row.to ?? {};
|
|
3050
|
+
const from = row.from ?? {};
|
|
3051
|
+
const table = row.table ?? {};
|
|
3052
|
+
if (row.moved !== true || !wordsOf(to.id)) return null;
|
|
3053
|
+
return {
|
|
3054
|
+
to: { id: String(to.id), name: wordsOf(to.name) ?? "the new organization" },
|
|
3055
|
+
from: { id: wordsOf(from.id) ?? "", name: wordsOf(from.name) ?? "its old organization" },
|
|
3056
|
+
tableName: wordsOf(table.name) ?? "The table"
|
|
3057
|
+
};
|
|
3058
|
+
}
|
|
3059
|
+
function plural(n, one, many) {
|
|
3060
|
+
return `${n} ${n === 1 ? one : many}`;
|
|
3061
|
+
}
|
|
3062
|
+
function moveConsequence(home, to) {
|
|
3063
|
+
const c = home.carries;
|
|
3064
|
+
const parts = [plural(c.fields, "column", "columns"), plural(c.records, "row", "rows")];
|
|
3065
|
+
if (c.inTrash > 0) parts.push(`${c.inTrash} in the trash`);
|
|
3066
|
+
const extra = c.choiceLists + c.withIt;
|
|
3067
|
+
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.`;
|
|
3068
|
+
}
|
|
2852
3069
|
|
|
2853
3070
|
// src/capture-queue.ts
|
|
2854
3071
|
var DB_NAME = "matrx-capture";
|
|
@@ -2918,7 +3135,7 @@ function memoryStore() {
|
|
|
2918
3135
|
setMeta: async (key, value) => void meta.set(key, value)
|
|
2919
3136
|
};
|
|
2920
3137
|
}
|
|
2921
|
-
function
|
|
3138
|
+
function countOf2(items) {
|
|
2922
3139
|
const c = { waiting: 0, sending: 0, refused: 0, landed: 0 };
|
|
2923
3140
|
for (const i of items) c[i.state] += 1;
|
|
2924
3141
|
return c;
|
|
@@ -2935,7 +3152,7 @@ function openCaptureQueue(opts) {
|
|
|
2935
3152
|
const announce = async () => {
|
|
2936
3153
|
if (!opts.onChange) return;
|
|
2937
3154
|
const items = await store.all();
|
|
2938
|
-
opts.onChange(
|
|
3155
|
+
opts.onChange(countOf2(items), items);
|
|
2939
3156
|
};
|
|
2940
3157
|
const oldestFirst = (items) => items.slice().sort((a, b) => a.captured_at.localeCompare(b.captured_at));
|
|
2941
3158
|
const queue = {
|
|
@@ -2964,7 +3181,7 @@ function openCaptureQueue(opts) {
|
|
|
2964
3181
|
return key;
|
|
2965
3182
|
},
|
|
2966
3183
|
list: () => store.all().then(oldestFirst),
|
|
2967
|
-
counts: () => store.all().then(
|
|
3184
|
+
counts: () => store.all().then(countOf2),
|
|
2968
3185
|
async sync() {
|
|
2969
3186
|
if (syncing) return syncing;
|
|
2970
3187
|
syncing = (async () => {
|
|
@@ -3136,6 +3353,194 @@ function isRecordsErr(result) {
|
|
|
3136
3353
|
var ok = (data) => ({ ok: true, data });
|
|
3137
3354
|
var err = (error) => ({ ok: false, error });
|
|
3138
3355
|
|
|
3356
|
+
// src/core/pgError.ts
|
|
3357
|
+
var BY_SQLSTATE = {
|
|
3358
|
+
PT409: "stale_write",
|
|
3359
|
+
"42501": "door",
|
|
3360
|
+
"23514": "refused_by_rule",
|
|
3361
|
+
"22004": "invalid_argument",
|
|
3362
|
+
"22023": "invalid_argument",
|
|
3363
|
+
// 22P02 — a value does not fit the type the store holds for it. The class is
|
|
3364
|
+
// real and was hit live: an organization whose copy of a package table grew
|
|
3365
|
+
// `status` as a uuid-valued Field was sent the text "open", and the store
|
|
3366
|
+
// answered `invalid input syntax for type uuid: "open"`. It is the CALL that
|
|
3367
|
+
// is wrong for the shape the store holds, which is what `invalid_argument`
|
|
3368
|
+
// already means — the store's own sentence names the value and the type.
|
|
3369
|
+
"22P02": "invalid_argument",
|
|
3370
|
+
// 42703 — a column that is not there. The store raises it from
|
|
3371
|
+
// `custom.entity_table`, which is asked for a column by NAME, so the class is
|
|
3372
|
+
// exactly "the call named something the shape does not have": the same thing
|
|
3373
|
+
// `invalid_argument` already means, and the store's own sentence names the
|
|
3374
|
+
// column. Added 2026-09-20 (lane FORMS) because the package's live-door suite
|
|
3375
|
+
// caught it raised by a door this package did not know about — which is the
|
|
3376
|
+
// suite working, and is why the map is a census rather than a guess.
|
|
3377
|
+
"42703": "invalid_argument",
|
|
3378
|
+
// 22007 — a date or a time the store could not read as one. It is raised by
|
|
3379
|
+
// `custom.dashboard_window_sql`, which is handed a window a caller wrote
|
|
3380
|
+
// ("last 30 days", a literal date), so the class is again "the call named
|
|
3381
|
+
// something the shape does not accept" and the store's own sentence names the
|
|
3382
|
+
// text it could not read. Added 2026-09-20 (lane SCREEN-BREAK) after the
|
|
3383
|
+
// live-door census caught it — the same way 42703 arrived, which is the
|
|
3384
|
+
// census working rather than a guess.
|
|
3385
|
+
"22007": "invalid_argument",
|
|
3386
|
+
// 22003 — a number the store could not hold at the size it was handed. It is
|
|
3387
|
+
// raised by `custom.context_resolve`, which is given a window or a depth a
|
|
3388
|
+
// caller wrote, so the class is the same one 22P02, 42703 and 22007 already
|
|
3389
|
+
// are: the CALL named a value the shape does not accept, and the store's own
|
|
3390
|
+
// sentence names the number and the range. Added 2026-09-20 (lane GRID-TWO)
|
|
3391
|
+
// when the live-door census caught the release gate red on it — which is the
|
|
3392
|
+
// census working, and the fourth time it has been the thing that found a new
|
|
3393
|
+
// refusal class rather than a person guessing one.
|
|
3394
|
+
"22003": "invalid_argument",
|
|
3395
|
+
"02000": "not_found",
|
|
3396
|
+
"23503": "missing_reference",
|
|
3397
|
+
"23505": "already_exists",
|
|
3398
|
+
"0A000": "not_supported",
|
|
3399
|
+
"53400": "store_limit",
|
|
3400
|
+
"22012": "store_limit",
|
|
3401
|
+
// 54001 — `custom.record_delete` stops a cascade that nests past sixty-four
|
|
3402
|
+
// levels of containment, because that is a loop somebody built rather than a
|
|
3403
|
+
// hierarchy, and saying so beats recursing until the server runs out of
|
|
3404
|
+
// stack. It is a LIMIT of the store, and the store's own sentence names what
|
|
3405
|
+
// to do about it, so it reads as one.
|
|
3406
|
+
"54001": "store_limit",
|
|
3407
|
+
// 54000 — the grid primitives' organization ceilings (GRID-PRIMITIVES, 2026-09-23):
|
|
3408
|
+
// `custom.action_run` on a selection past its knob, `custom.table_decorate` past the
|
|
3409
|
+
// rule count, `custom.table_from_example` past `custom/example_rows_max`,
|
|
3410
|
+
// `custom.table_webhook_declare` past `custom/table_webhooks_max`, and
|
|
3411
|
+
// `custom.hub_changed_by`. Each names its knob in the hint and writes nothing, so it
|
|
3412
|
+
// reads exactly like 54001: a limit of the store, in the store's own sentence.
|
|
3413
|
+
"54000": "store_limit",
|
|
3414
|
+
// VIS: `custom.has_visibility_at` refuses to answer from a snapshot older
|
|
3415
|
+
// than a write this reader has already seen, rather than returning something
|
|
3416
|
+
// they just lost access to. It is retryable on a fresher connection, which is
|
|
3417
|
+
// a different instruction from every other refusal here.
|
|
3418
|
+
"40001": "stale_read",
|
|
3419
|
+
// 57014 — the store cancelled the statement at its own timeout. Like
|
|
3420
|
+
// `stale_read` and unlike every other refusal here, the call was never
|
|
3421
|
+
// refused on its merits: the same call on a smaller page, or once the
|
|
3422
|
+
// organization's kernel is not being rewritten, can succeed. It gets its own
|
|
3423
|
+
// code so a screen can offer "try again" honestly instead of showing a
|
|
3424
|
+
// failure sentence the caller cannot act on.
|
|
3425
|
+
"57014": "timed_out",
|
|
3426
|
+
// PostgREST's own two: the store is not being SERVED, which is a different
|
|
3427
|
+
// thing from the store refusing. `custom` absent from `pgrst.db_schemas`, or
|
|
3428
|
+
// the grants not copied, land here.
|
|
3429
|
+
PGRST202: "store_not_served",
|
|
3430
|
+
PGRST205: "store_not_served",
|
|
3431
|
+
PGRST301: "door",
|
|
3432
|
+
// PT428 — NOT A FAILURE, and the release gate's live census is what found it
|
|
3433
|
+
// missing: the store raises it and this package answered `internal`, which is
|
|
3434
|
+
// the one classification that tells a screen nothing. A stage gate whose
|
|
3435
|
+
// `on_fail` is `require_approval` files the request and leaves the record
|
|
3436
|
+
// where it was; the transaction is rolled back to the pipeline's own block, so
|
|
3437
|
+
// "nothing was written" is a fact rather than a promise. Landed by lane
|
|
3438
|
+
// STAGE-RULES; mapped here by STAGE-RULES-2 the day after, when the census
|
|
3439
|
+
// caught it — the sixth time that census has been the thing that found a
|
|
3440
|
+
// refusal class rather than a person guessing one.
|
|
3441
|
+
PT428: "needs_approval"
|
|
3442
|
+
};
|
|
3443
|
+
var MAPPED_SQLSTATES = Object.keys(BY_SQLSTATE);
|
|
3444
|
+
function parseDetail(details) {
|
|
3445
|
+
if (!details) return void 0;
|
|
3446
|
+
const trimmed = details.trim();
|
|
3447
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return details;
|
|
3448
|
+
try {
|
|
3449
|
+
return JSON.parse(trimmed);
|
|
3450
|
+
} catch {
|
|
3451
|
+
return details;
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3454
|
+
function isNotASqlstate(code) {
|
|
3455
|
+
if (!code) return true;
|
|
3456
|
+
return /^[0-9]{3}$/.test(code);
|
|
3457
|
+
}
|
|
3458
|
+
function mapPgError(error, where) {
|
|
3459
|
+
const raw = error.code ?? void 0;
|
|
3460
|
+
const sqlstate = raw && raw !== "" ? raw : void 0;
|
|
3461
|
+
const mapped = {
|
|
3462
|
+
code: "internal",
|
|
3463
|
+
message: error.message,
|
|
3464
|
+
...error.hint ? { hint: error.hint } : {},
|
|
3465
|
+
...sqlstate ? { sqlstate } : {}
|
|
3466
|
+
};
|
|
3467
|
+
if (isNotASqlstate(sqlstate)) {
|
|
3468
|
+
mapped.code = "unreachable";
|
|
3469
|
+
} else {
|
|
3470
|
+
mapped.code = BY_SQLSTATE[sqlstate] ?? "internal";
|
|
3471
|
+
}
|
|
3472
|
+
const detail = parseDetail(error.details);
|
|
3473
|
+
if (detail !== void 0) mapped.detail = detail;
|
|
3474
|
+
if (mapped.code === "internal") {
|
|
3475
|
+
mapped.unmapped = true;
|
|
3476
|
+
mapped.diagnostic = `@ai-matrx/records did not recognise SQLSTATE ${sqlstate ?? "(none)"} from ${where}; the store's own message is the thing to act on.`;
|
|
3477
|
+
} else if (mapped.code === "unreachable") {
|
|
3478
|
+
mapped.unmapped = true;
|
|
3479
|
+
mapped.diagnostic = `@ai-matrx/records never reached the store from ${where}: ${raw ? `the transport answered ${raw}` : "no response"} \u2014 ${error.message}`;
|
|
3480
|
+
mapped.message = "We could not reach your data.";
|
|
3481
|
+
delete mapped.hint;
|
|
3482
|
+
}
|
|
3483
|
+
return mapped;
|
|
3484
|
+
}
|
|
3485
|
+
function staleWriteDetail(error) {
|
|
3486
|
+
if (error.code !== "stale_write") return null;
|
|
3487
|
+
const detail = error.detail;
|
|
3488
|
+
if (!detail || typeof detail.expected_version !== "number" || typeof detail.current_version !== "number") {
|
|
3489
|
+
return null;
|
|
3490
|
+
}
|
|
3491
|
+
return {
|
|
3492
|
+
expected_version: detail.expected_version,
|
|
3493
|
+
current_version: detail.current_version,
|
|
3494
|
+
contested_fields: detail.contested_fields ?? {}
|
|
3495
|
+
};
|
|
3496
|
+
}
|
|
3497
|
+
|
|
3498
|
+
// src/relation.ts
|
|
3499
|
+
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;
|
|
3500
|
+
function isRefLike(value) {
|
|
3501
|
+
return typeof value === "object" && value !== null && "token" in value && "id" in value;
|
|
3502
|
+
}
|
|
3503
|
+
function entityRefsOf(value) {
|
|
3504
|
+
const items = Array.isArray(value) ? value : value == null ? [] : [value];
|
|
3505
|
+
const out = [];
|
|
3506
|
+
for (const item of items) {
|
|
3507
|
+
if (!isRefLike(item)) continue;
|
|
3508
|
+
const token = String(item.token ?? "").trim().toLowerCase();
|
|
3509
|
+
const id = String(item.id ?? "").trim();
|
|
3510
|
+
if (!token || !ENTITY_REF_ID.test(id)) continue;
|
|
3511
|
+
const label = item.label;
|
|
3512
|
+
out.push({ token, id: id.toLowerCase(), label: typeof label === "string" ? label : null });
|
|
3513
|
+
}
|
|
3514
|
+
return out;
|
|
3515
|
+
}
|
|
3516
|
+
function entityRefsToWrite(value, multi) {
|
|
3517
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3518
|
+
const refs = [];
|
|
3519
|
+
for (const ref of entityRefsOf(value)) {
|
|
3520
|
+
const k = `${ref.token}:${ref.id}`;
|
|
3521
|
+
if (seen.has(k)) continue;
|
|
3522
|
+
seen.add(k);
|
|
3523
|
+
refs.push({ token: ref.token, id: ref.id });
|
|
3524
|
+
}
|
|
3525
|
+
if (multi) return refs;
|
|
3526
|
+
return refs[0] ?? null;
|
|
3527
|
+
}
|
|
3528
|
+
async function entityReferenceKinds(source) {
|
|
3529
|
+
const { data, error } = await source.rpc("entity_reference_kinds", {}, { schema: "custom" });
|
|
3530
|
+
if (error) return err(mapPgError(error, "entityReferenceKinds"));
|
|
3531
|
+
return ok(Array.isArray(data) ? data : []);
|
|
3532
|
+
}
|
|
3533
|
+
async function entityReferenceWords(source, organizationId, refs) {
|
|
3534
|
+
if (refs.length === 0) return ok([]);
|
|
3535
|
+
const { data, error } = await source.rpc(
|
|
3536
|
+
"entity_reference_words",
|
|
3537
|
+
{ p_organization_id: organizationId, p_refs: refs.map((r) => ({ token: r.token, id: r.id })) },
|
|
3538
|
+
{ schema: "custom" }
|
|
3539
|
+
);
|
|
3540
|
+
if (error) return err(mapPgError(error, "entityReferenceWords"));
|
|
3541
|
+
return ok(entityRefsOf(data));
|
|
3542
|
+
}
|
|
3543
|
+
|
|
3139
3544
|
// src/ops.ts
|
|
3140
3545
|
var REMEMBER_MS = 3e4;
|
|
3141
3546
|
var REMEMBER_MAX = 500;
|
|
@@ -3849,6 +4254,7 @@ var STORE_DOORS = [
|
|
|
3849
4254
|
{ name: "table_work_kind", args: "p_organization_id uuid, p_table_id uuid", returns: "text", security: "invoker" },
|
|
3850
4255
|
{ 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" },
|
|
3851
4256
|
{ name: "tables_described_without_asking", args: "", returns: "TABLE(function_name text, identity_args text, why text)", security: "invoker" },
|
|
4257
|
+
{ 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" },
|
|
3852
4258
|
{ 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" },
|
|
3853
4259
|
{ name: "trg_associations_bump_visibility", args: "", returns: "trigger", security: "definer" },
|
|
3854
4260
|
{ name: "undeclared_keys", args: "p_organization_id uuid, p_table_id uuid, p_data jsonb", returns: "text[]", security: "invoker" },
|
|
@@ -3873,6 +4279,7 @@ var STORE_DOORS = [
|
|
|
3873
4279
|
{ 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" },
|
|
3874
4280
|
{ name: "visible_record_ids", args: "p_user_id uuid, p_required permission_level DEFAULT 'viewer'::permission_level", returns: "TABLE(id uuid)", security: "definer" },
|
|
3875
4281
|
{ 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" },
|
|
4282
|
+
{ name: "where_id_opens", args: "p_id uuid", returns: "jsonb", security: "definer" },
|
|
3876
4283
|
{ name: "widget_kernel_id", args: "", returns: "uuid", security: "invoker" },
|
|
3877
4284
|
{ name: "withheld_marker", args: "p_mask jsonb, p_key text", returns: "jsonb", security: "invoker" },
|
|
3878
4285
|
{ name: "withheld_sentence", args: "p_mask jsonb, p_key text", returns: "text", security: "invoker" },
|
|
@@ -3979,7 +4386,7 @@ var STORE_REFUSAL_CODES = [
|
|
|
3979
4386
|
{ 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" },
|
|
3980
4387
|
{ 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" },
|
|
3981
4388
|
{ code: "40001", raised_by: "has_visibility_at" },
|
|
3982
|
-
{ 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" },
|
|
4389
|
+
{ 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" },
|
|
3983
4390
|
{ code: "42703", raised_by: "entity_table,portal_declare" },
|
|
3984
4391
|
{ code: "53400", raised_by: "anon_rate_take,delete_cascade_closure,enrich_due,enrich_land,promote_field" },
|
|
3985
4392
|
{ code: "54000", raised_by: "action_run,hub_changed_by,table_decorate,table_from_example,table_webhook_declare" },
|
|
@@ -5024,6 +5431,8 @@ function scopeChip(scope) {
|
|
|
5024
5431
|
|
|
5025
5432
|
// src/core/doors.ts
|
|
5026
5433
|
var OWED_BY = {
|
|
5434
|
+
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",
|
|
5435
|
+
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",
|
|
5027
5436
|
metadata_search: "W5-SRV / W5-AGENT \u2014 the agent surface's eight verbs",
|
|
5028
5437
|
record_aggregate: "W5-SRV / W5-AGENT \u2014 `record_aggregate` is one of the eight verbs",
|
|
5029
5438
|
field_propose: "W5-AGENT \u2014 AGT-4/AGT-8, the proposal and its approvers",
|
|
@@ -5045,6 +5454,20 @@ var OWED_BY = {
|
|
|
5045
5454
|
table_webhook_archive: "GRID-PRIMITIVES G4 \u2014 gridprim_every_row_change_reaches_the_webhook.sql (chair's production apply)",
|
|
5046
5455
|
table_from_example: "GRID-PRIMITIVES G5 \u2014 gridprim_a_table_can_start_from_a_real_example.sql (chair's production apply)",
|
|
5047
5456
|
autonumber_backfill: "GRID-PRIMITIVES G5 \u2014 gridprim_a_table_can_start_from_a_real_example.sql (chair's production apply)",
|
|
5457
|
+
table_list_everywhere: "GRID-PRIMITIVES G9 \u2014 gridprim_every_picker_lists_both_stores.sql (chair's production apply)",
|
|
5458
|
+
table_sync: "GRID-PRIMITIVES G10 \u2014 gridprim_a_synced_sheet_is_one_table_that_refreshes.sql (chair's production apply)",
|
|
5459
|
+
scope_table_provision: "GRID-PRIMITIVES G11 \u2014 gridprim_a_scope_provisions_its_table_in_the_store.sql (chair's production apply)",
|
|
5460
|
+
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)",
|
|
5461
|
+
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)",
|
|
5462
|
+
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)",
|
|
5463
|
+
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)",
|
|
5464
|
+
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)",
|
|
5465
|
+
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)",
|
|
5466
|
+
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)",
|
|
5467
|
+
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)",
|
|
5468
|
+
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)",
|
|
5469
|
+
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)",
|
|
5470
|
+
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)",
|
|
5048
5471
|
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"
|
|
5049
5472
|
};
|
|
5050
5473
|
var TRUST_SCHEMA = "iam";
|
|
@@ -5248,6 +5671,8 @@ var DOORS = {
|
|
|
5248
5671
|
pipelineBoard: "pipeline_board",
|
|
5249
5672
|
pipelineTransitionRefusal: "pipeline_transition_refusal",
|
|
5250
5673
|
pipelineMove: "pipeline_move",
|
|
5674
|
+
/** Lane S4: many cards, one call, a verdict and a version per card. */
|
|
5675
|
+
pipelineMoveMany: "pipeline_move_many",
|
|
5251
5676
|
tableStageField: "table_stage_field",
|
|
5252
5677
|
// STAGE-RULES. A gate can refuse or it can hand the change to the approvals queue; the
|
|
5253
5678
|
// closed list is the store's, the knob is the organization's, and what is WAITING on a
|
|
@@ -5258,6 +5683,7 @@ var DOORS = {
|
|
|
5258
5683
|
pipelinePending: "pipeline_pending",
|
|
5259
5684
|
pipelineGatePreview: "pipeline_gate_preview",
|
|
5260
5685
|
ruleMembers: "rule_members",
|
|
5686
|
+
ruleMembersVisible: "rule_members_visible",
|
|
5261
5687
|
ruleMembership: "rule_membership",
|
|
5262
5688
|
ruleRun: "rule_run",
|
|
5263
5689
|
ruleEval: "rule_eval",
|
|
@@ -5272,6 +5698,12 @@ var DOORS = {
|
|
|
5272
5698
|
// are server-lane doors and a browser never calls them.
|
|
5273
5699
|
forms: "forms",
|
|
5274
5700
|
formDeclare: "form_declare",
|
|
5701
|
+
/**
|
|
5702
|
+
* WHICH QUESTIONS THE OWNER'S PREVIEW ASKS NEXT (lane FORMS-FIX-1). The same store answer a
|
|
5703
|
+
* stranger's form gets (`custom._form_questions_asked` → `custom.rule_eval`), behind the
|
|
5704
|
+
* organization wall and the caller's own rung on the Table — never `rule_eval` from a browser.
|
|
5705
|
+
*/
|
|
5706
|
+
formPreviewAsks: "form_preview_asks",
|
|
5275
5707
|
// PRODUCTS row 14 — "let clients book a 30-minute consult". A booking page IS an
|
|
5276
5708
|
// `anon_form`, so it is opened and closed by `anonPublish` like any other; this is
|
|
5277
5709
|
// the owner's list of them with the numbers that make it worth opening. The
|
|
@@ -5326,11 +5758,22 @@ var DOORS = {
|
|
|
5326
5758
|
// ask, under the same wall and visibility gate as `portals` itself.
|
|
5327
5759
|
portalTables: "portal_tables",
|
|
5328
5760
|
tableFacts: "table_facts",
|
|
5761
|
+
// WHERE A TABLE LIVES, AND MOVING IT (SC-1' / SC-1-TAILS). `table_home` answers only when the
|
|
5762
|
+
// caller may open the Table (custom.where_id_opens) and names its organization FROM THE TABLE;
|
|
5763
|
+
// `table_move` moves it into another organization the caller belongs to, with every Table
|
|
5764
|
+
// inside it, or refuses with one sentence.
|
|
5765
|
+
tableHome: "table_home",
|
|
5766
|
+
tableMove: "table_move",
|
|
5329
5767
|
portalCard: "portal_card",
|
|
5330
5768
|
portalDeclare: "portal_declare",
|
|
5331
5769
|
portalInvite: "portal_invite",
|
|
5332
5770
|
portalRevoke: "portal_revoke",
|
|
5333
5771
|
portalPreview: "portal_preview",
|
|
5772
|
+
// S6 — the CLIENT's two form doors. They are store doors (schema `custom`), called by a
|
|
5773
|
+
// signed-in portal principal, and each refuses anybody who is not a live principal of that
|
|
5774
|
+
// live portal — so, unlike the sign-in pair above, they belong in this table.
|
|
5775
|
+
portalForm: "portal_form",
|
|
5776
|
+
portalFormSubmit: "portal_form_submit",
|
|
5334
5777
|
// ORG-CLEANUP, 2026-09-22 — A PORTAL IS ARCHIVED, NEVER DELETED.
|
|
5335
5778
|
// `portals` keeps its signature and HIDES archived portals (the law's default
|
|
5336
5779
|
// is to hide); `list_portals(org, 'active'|'archived'|'all')` is the
|
|
@@ -5526,6 +5969,8 @@ var DOORS = {
|
|
|
5526
5969
|
workApprovalRequest: "work_approval_request",
|
|
5527
5970
|
workApprovalMayDecide: "work_approval_may_decide",
|
|
5528
5971
|
workApprovalDecide: "work_approval_decide",
|
|
5972
|
+
/** Lane S4: many inbox decisions, one call, a verdict per item. */
|
|
5973
|
+
workDecideMany: "work_decide_many",
|
|
5529
5974
|
workApprovalRead: "work_approval_read",
|
|
5530
5975
|
// PRODUCTS row 13 — checklists and SOP runs.
|
|
5531
5976
|
checklistRefusal: "checklist_refusal",
|
|
@@ -5551,6 +5996,14 @@ var DOORS = {
|
|
|
5551
5996
|
tableFromExample: "table_from_example",
|
|
5552
5997
|
autonumberBackfill: "autonumber_backfill",
|
|
5553
5998
|
ioExport: "io_export",
|
|
5999
|
+
tableListEverywhere: "table_list_everywhere",
|
|
6000
|
+
tableSync: "table_sync",
|
|
6001
|
+
scopeTableProvision: "scope_table_provision",
|
|
6002
|
+
tableRowCounts: "table_row_counts",
|
|
6003
|
+
recordHeaders: "record_headers",
|
|
6004
|
+
viewRecordOrderSet: "view_record_order_set",
|
|
6005
|
+
readRecordsInViewOrder: "read_records_in_view_order",
|
|
6006
|
+
tableShareOutsideGrant: "table_share_outside_grant",
|
|
5554
6007
|
// Declared here BECAUSE they do not exist yet: naming them is what makes the
|
|
5555
6008
|
// absence visible to `pnpm check:store-registry` and to the suite.
|
|
5556
6009
|
metadataSearch: "metadata_search",
|
|
@@ -5568,148 +6021,6 @@ function doorCensus() {
|
|
|
5568
6021
|
return { present: present.sort(), absent: absent.sort() };
|
|
5569
6022
|
}
|
|
5570
6023
|
|
|
5571
|
-
// src/core/pgError.ts
|
|
5572
|
-
var BY_SQLSTATE = {
|
|
5573
|
-
PT409: "stale_write",
|
|
5574
|
-
"42501": "door",
|
|
5575
|
-
"23514": "refused_by_rule",
|
|
5576
|
-
"22004": "invalid_argument",
|
|
5577
|
-
"22023": "invalid_argument",
|
|
5578
|
-
// 22P02 — a value does not fit the type the store holds for it. The class is
|
|
5579
|
-
// real and was hit live: an organization whose copy of a package table grew
|
|
5580
|
-
// `status` as a uuid-valued Field was sent the text "open", and the store
|
|
5581
|
-
// answered `invalid input syntax for type uuid: "open"`. It is the CALL that
|
|
5582
|
-
// is wrong for the shape the store holds, which is what `invalid_argument`
|
|
5583
|
-
// already means — the store's own sentence names the value and the type.
|
|
5584
|
-
"22P02": "invalid_argument",
|
|
5585
|
-
// 42703 — a column that is not there. The store raises it from
|
|
5586
|
-
// `custom.entity_table`, which is asked for a column by NAME, so the class is
|
|
5587
|
-
// exactly "the call named something the shape does not have": the same thing
|
|
5588
|
-
// `invalid_argument` already means, and the store's own sentence names the
|
|
5589
|
-
// column. Added 2026-09-20 (lane FORMS) because the package's live-door suite
|
|
5590
|
-
// caught it raised by a door this package did not know about — which is the
|
|
5591
|
-
// suite working, and is why the map is a census rather than a guess.
|
|
5592
|
-
"42703": "invalid_argument",
|
|
5593
|
-
// 22007 — a date or a time the store could not read as one. It is raised by
|
|
5594
|
-
// `custom.dashboard_window_sql`, which is handed a window a caller wrote
|
|
5595
|
-
// ("last 30 days", a literal date), so the class is again "the call named
|
|
5596
|
-
// something the shape does not accept" and the store's own sentence names the
|
|
5597
|
-
// text it could not read. Added 2026-09-20 (lane SCREEN-BREAK) after the
|
|
5598
|
-
// live-door census caught it — the same way 42703 arrived, which is the
|
|
5599
|
-
// census working rather than a guess.
|
|
5600
|
-
"22007": "invalid_argument",
|
|
5601
|
-
// 22003 — a number the store could not hold at the size it was handed. It is
|
|
5602
|
-
// raised by `custom.context_resolve`, which is given a window or a depth a
|
|
5603
|
-
// caller wrote, so the class is the same one 22P02, 42703 and 22007 already
|
|
5604
|
-
// are: the CALL named a value the shape does not accept, and the store's own
|
|
5605
|
-
// sentence names the number and the range. Added 2026-09-20 (lane GRID-TWO)
|
|
5606
|
-
// when the live-door census caught the release gate red on it — which is the
|
|
5607
|
-
// census working, and the fourth time it has been the thing that found a new
|
|
5608
|
-
// refusal class rather than a person guessing one.
|
|
5609
|
-
"22003": "invalid_argument",
|
|
5610
|
-
"02000": "not_found",
|
|
5611
|
-
"23503": "missing_reference",
|
|
5612
|
-
"23505": "already_exists",
|
|
5613
|
-
"0A000": "not_supported",
|
|
5614
|
-
"53400": "store_limit",
|
|
5615
|
-
"22012": "store_limit",
|
|
5616
|
-
// 54001 — `custom.record_delete` stops a cascade that nests past sixty-four
|
|
5617
|
-
// levels of containment, because that is a loop somebody built rather than a
|
|
5618
|
-
// hierarchy, and saying so beats recursing until the server runs out of
|
|
5619
|
-
// stack. It is a LIMIT of the store, and the store's own sentence names what
|
|
5620
|
-
// to do about it, so it reads as one.
|
|
5621
|
-
"54001": "store_limit",
|
|
5622
|
-
// 54000 — the grid primitives' organization ceilings (GRID-PRIMITIVES, 2026-09-23):
|
|
5623
|
-
// `custom.action_run` on a selection past its knob, `custom.table_decorate` past the
|
|
5624
|
-
// rule count, `custom.table_from_example` past `custom/example_rows_max`,
|
|
5625
|
-
// `custom.table_webhook_declare` past `custom/table_webhooks_max`, and
|
|
5626
|
-
// `custom.hub_changed_by`. Each names its knob in the hint and writes nothing, so it
|
|
5627
|
-
// reads exactly like 54001: a limit of the store, in the store's own sentence.
|
|
5628
|
-
"54000": "store_limit",
|
|
5629
|
-
// VIS: `custom.has_visibility_at` refuses to answer from a snapshot older
|
|
5630
|
-
// than a write this reader has already seen, rather than returning something
|
|
5631
|
-
// they just lost access to. It is retryable on a fresher connection, which is
|
|
5632
|
-
// a different instruction from every other refusal here.
|
|
5633
|
-
"40001": "stale_read",
|
|
5634
|
-
// 57014 — the store cancelled the statement at its own timeout. Like
|
|
5635
|
-
// `stale_read` and unlike every other refusal here, the call was never
|
|
5636
|
-
// refused on its merits: the same call on a smaller page, or once the
|
|
5637
|
-
// organization's kernel is not being rewritten, can succeed. It gets its own
|
|
5638
|
-
// code so a screen can offer "try again" honestly instead of showing a
|
|
5639
|
-
// failure sentence the caller cannot act on.
|
|
5640
|
-
"57014": "timed_out",
|
|
5641
|
-
// PostgREST's own two: the store is not being SERVED, which is a different
|
|
5642
|
-
// thing from the store refusing. `custom` absent from `pgrst.db_schemas`, or
|
|
5643
|
-
// the grants not copied, land here.
|
|
5644
|
-
PGRST202: "store_not_served",
|
|
5645
|
-
PGRST205: "store_not_served",
|
|
5646
|
-
PGRST301: "door",
|
|
5647
|
-
// PT428 — NOT A FAILURE, and the release gate's live census is what found it
|
|
5648
|
-
// missing: the store raises it and this package answered `internal`, which is
|
|
5649
|
-
// the one classification that tells a screen nothing. A stage gate whose
|
|
5650
|
-
// `on_fail` is `require_approval` files the request and leaves the record
|
|
5651
|
-
// where it was; the transaction is rolled back to the pipeline's own block, so
|
|
5652
|
-
// "nothing was written" is a fact rather than a promise. Landed by lane
|
|
5653
|
-
// STAGE-RULES; mapped here by STAGE-RULES-2 the day after, when the census
|
|
5654
|
-
// caught it — the sixth time that census has been the thing that found a
|
|
5655
|
-
// refusal class rather than a person guessing one.
|
|
5656
|
-
PT428: "needs_approval"
|
|
5657
|
-
};
|
|
5658
|
-
var MAPPED_SQLSTATES = Object.keys(BY_SQLSTATE);
|
|
5659
|
-
function parseDetail(details) {
|
|
5660
|
-
if (!details) return void 0;
|
|
5661
|
-
const trimmed = details.trim();
|
|
5662
|
-
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return details;
|
|
5663
|
-
try {
|
|
5664
|
-
return JSON.parse(trimmed);
|
|
5665
|
-
} catch {
|
|
5666
|
-
return details;
|
|
5667
|
-
}
|
|
5668
|
-
}
|
|
5669
|
-
function isNotASqlstate(code) {
|
|
5670
|
-
if (!code) return true;
|
|
5671
|
-
return /^[0-9]{3}$/.test(code);
|
|
5672
|
-
}
|
|
5673
|
-
function mapPgError(error, where) {
|
|
5674
|
-
const raw = error.code ?? void 0;
|
|
5675
|
-
const sqlstate = raw && raw !== "" ? raw : void 0;
|
|
5676
|
-
const mapped = {
|
|
5677
|
-
code: "internal",
|
|
5678
|
-
message: error.message,
|
|
5679
|
-
...error.hint ? { hint: error.hint } : {},
|
|
5680
|
-
...sqlstate ? { sqlstate } : {}
|
|
5681
|
-
};
|
|
5682
|
-
if (isNotASqlstate(sqlstate)) {
|
|
5683
|
-
mapped.code = "unreachable";
|
|
5684
|
-
} else {
|
|
5685
|
-
mapped.code = BY_SQLSTATE[sqlstate] ?? "internal";
|
|
5686
|
-
}
|
|
5687
|
-
const detail = parseDetail(error.details);
|
|
5688
|
-
if (detail !== void 0) mapped.detail = detail;
|
|
5689
|
-
if (mapped.code === "internal") {
|
|
5690
|
-
mapped.unmapped = true;
|
|
5691
|
-
mapped.diagnostic = `@ai-matrx/records did not recognise SQLSTATE ${sqlstate ?? "(none)"} from ${where}; the store's own message is the thing to act on.`;
|
|
5692
|
-
} else if (mapped.code === "unreachable") {
|
|
5693
|
-
mapped.unmapped = true;
|
|
5694
|
-
mapped.diagnostic = `@ai-matrx/records never reached the store from ${where}: ${raw ? `the transport answered ${raw}` : "no response"} \u2014 ${error.message}`;
|
|
5695
|
-
mapped.message = "We could not reach your data.";
|
|
5696
|
-
delete mapped.hint;
|
|
5697
|
-
}
|
|
5698
|
-
return mapped;
|
|
5699
|
-
}
|
|
5700
|
-
function staleWriteDetail(error) {
|
|
5701
|
-
if (error.code !== "stale_write") return null;
|
|
5702
|
-
const detail = error.detail;
|
|
5703
|
-
if (!detail || typeof detail.expected_version !== "number" || typeof detail.current_version !== "number") {
|
|
5704
|
-
return null;
|
|
5705
|
-
}
|
|
5706
|
-
return {
|
|
5707
|
-
expected_version: detail.expected_version,
|
|
5708
|
-
current_version: detail.current_version,
|
|
5709
|
-
contested_fields: detail.contested_fields ?? {}
|
|
5710
|
-
};
|
|
5711
|
-
}
|
|
5712
|
-
|
|
5713
6024
|
// src/core/recordChanges.ts
|
|
5714
6025
|
var listeners = /* @__PURE__ */ new Set();
|
|
5715
6026
|
function announceRecordChanged(recordId) {
|
|
@@ -5975,6 +6286,40 @@ function createRecordsClient(config) {
|
|
|
5975
6286
|
if (!doorExists(DOORS.tableFacts)) return ok(null);
|
|
5976
6287
|
return callDoor(DOORS.tableFacts, { p_organization_id: org }, "tableFacts");
|
|
5977
6288
|
},
|
|
6289
|
+
async tableHome({ table_id }) {
|
|
6290
|
+
const answered = await callDoor(DOORS.tableHome, { p_table_id: table_id }, "tableHome");
|
|
6291
|
+
if (!answered.ok) return answered;
|
|
6292
|
+
if (answered.data === null || answered.data === void 0) return ok(null);
|
|
6293
|
+
const home = parseTableHome(answered.data);
|
|
6294
|
+
if (!home) {
|
|
6295
|
+
const refusal = {
|
|
6296
|
+
code: "not_supported",
|
|
6297
|
+
message: "The record store answered where this table lives in a shape this client cannot read, so nothing is claimed about it.",
|
|
6298
|
+
hint: "Regenerate @ai-matrx/records against the store (pnpm records:generate) \u2014 the door and this client disagree."
|
|
6299
|
+
};
|
|
6300
|
+
scream({ code: refusal.code, message: refusal.message, ...refusal.hint ? { hint: refusal.hint } : {}, where: "tableHome" });
|
|
6301
|
+
return err(refusal);
|
|
6302
|
+
}
|
|
6303
|
+
return ok(home);
|
|
6304
|
+
},
|
|
6305
|
+
async tableMove({ table_id, to_organization_id, expected_version }) {
|
|
6306
|
+
const answered = await callDoor(
|
|
6307
|
+
DOORS.tableMove,
|
|
6308
|
+
{ p_table_id: table_id, p_to_organization_id: to_organization_id, p_expected_version: expected_version ?? null },
|
|
6309
|
+
"tableMove"
|
|
6310
|
+
);
|
|
6311
|
+
if (!answered.ok) return answered;
|
|
6312
|
+
const moved = parseTableMoved(answered.data);
|
|
6313
|
+
if (!moved) {
|
|
6314
|
+
const refusal = {
|
|
6315
|
+
code: "not_supported",
|
|
6316
|
+
message: "The record store answered the move in a shape this client cannot read. Look where the table lives before trying again."
|
|
6317
|
+
};
|
|
6318
|
+
scream({ code: refusal.code, message: refusal.message, where: "tableMove" });
|
|
6319
|
+
return err(refusal);
|
|
6320
|
+
}
|
|
6321
|
+
return ok(moved);
|
|
6322
|
+
},
|
|
5978
6323
|
async tableArchived({ table_id }) {
|
|
5979
6324
|
const kernel = await callDoor(DOORS.tableKernelId, {}, "tableArchived.kernel");
|
|
5980
6325
|
if (!kernel.ok) return err(kernel.error);
|
|
@@ -6351,10 +6696,11 @@ function createRecordsClient(config) {
|
|
|
6351
6696
|
"pipelineRead"
|
|
6352
6697
|
);
|
|
6353
6698
|
},
|
|
6354
|
-
async pipelineBoard({ table_id, measure }) {
|
|
6699
|
+
async pipelineBoard({ table_id, measure, filter }) {
|
|
6700
|
+
const asked = filter && stableFilterKey(filter) !== "{}";
|
|
6355
6701
|
return callDoor(
|
|
6356
6702
|
DOORS.pipelineBoard,
|
|
6357
|
-
{ p_organization_id: org, p_table_id: table_id, p_measure: measure ?? null },
|
|
6703
|
+
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 },
|
|
6358
6704
|
"pipelineBoard"
|
|
6359
6705
|
);
|
|
6360
6706
|
},
|
|
@@ -6378,6 +6724,13 @@ function createRecordsClient(config) {
|
|
|
6378
6724
|
"pipelineMove"
|
|
6379
6725
|
);
|
|
6380
6726
|
},
|
|
6727
|
+
async pipelineMoveMany({ moves }) {
|
|
6728
|
+
return callDoor(
|
|
6729
|
+
DOORS.pipelineMoveMany,
|
|
6730
|
+
{ p_organization_id: org, p_moves: moves },
|
|
6731
|
+
"pipelineMoveMany"
|
|
6732
|
+
);
|
|
6733
|
+
},
|
|
6381
6734
|
async pipelineDeclare({ table_id, spec }) {
|
|
6382
6735
|
return callDoor(
|
|
6383
6736
|
DOORS.pipelineDeclare,
|
|
@@ -6427,6 +6780,18 @@ function createRecordsClient(config) {
|
|
|
6427
6780
|
"pipelineGatePreview"
|
|
6428
6781
|
);
|
|
6429
6782
|
},
|
|
6783
|
+
async ruleMembersVisible({ rule_id, limit = 200, offset = 0 }) {
|
|
6784
|
+
const door = await callDoor(
|
|
6785
|
+
DOORS.ruleMembersVisible,
|
|
6786
|
+
{ p_organization_id: org, p_rule_id: rule_id, p_limit: limit, p_offset: offset },
|
|
6787
|
+
"ruleMembersVisible"
|
|
6788
|
+
);
|
|
6789
|
+
if (!door.ok) return err(door.error);
|
|
6790
|
+
return ok((door.data ?? []).map((row) => {
|
|
6791
|
+
const { document, hidden } = unfold(row.document);
|
|
6792
|
+
return { id: row.id, document, level: row.level, hidden };
|
|
6793
|
+
}));
|
|
6794
|
+
},
|
|
6430
6795
|
async ruleMembers({ rule_id }) {
|
|
6431
6796
|
return callDoor(
|
|
6432
6797
|
DOORS.ruleMembers,
|
|
@@ -6479,6 +6844,13 @@ function createRecordsClient(config) {
|
|
|
6479
6844
|
"bookingDeclare"
|
|
6480
6845
|
);
|
|
6481
6846
|
},
|
|
6847
|
+
async formPreviewAsks({ table_id, questions, values }) {
|
|
6848
|
+
return callDoor(
|
|
6849
|
+
DOORS.formPreviewAsks,
|
|
6850
|
+
{ p_organization_id: org, p_table_id: table_id, p_questions: questions, p_values: values },
|
|
6851
|
+
"formPreviewAsks"
|
|
6852
|
+
);
|
|
6853
|
+
},
|
|
6482
6854
|
async formDeclare(args) {
|
|
6483
6855
|
return callDoor(
|
|
6484
6856
|
DOORS.formDeclare,
|
|
@@ -6654,11 +7026,33 @@ function createRecordsClient(config) {
|
|
|
6654
7026
|
p_tables: args.tables,
|
|
6655
7027
|
p_portal_id: args.portal_id ?? null,
|
|
6656
7028
|
p_slug: args.slug ?? null,
|
|
6657
|
-
p_sign_in_method: args.sign_in_method ?? "magic_link"
|
|
7029
|
+
p_sign_in_method: args.sign_in_method ?? "magic_link",
|
|
7030
|
+
// S6: sent only when given, so a caller that says nothing about the look keeps it.
|
|
7031
|
+
...args.config !== void 0 ? { p_config: args.config } : {}
|
|
6658
7032
|
},
|
|
6659
7033
|
"portalDeclare"
|
|
6660
7034
|
);
|
|
6661
7035
|
},
|
|
7036
|
+
async portalForm({ portal_id, form_id }) {
|
|
7037
|
+
return callDoor(
|
|
7038
|
+
DOORS.portalForm,
|
|
7039
|
+
{ p_organization_id: org, p_portal_id: portal_id, p_form_id: form_id },
|
|
7040
|
+
"portalForm"
|
|
7041
|
+
);
|
|
7042
|
+
},
|
|
7043
|
+
async portalFormSubmit({ portal_id, form_id, answers, client_key }) {
|
|
7044
|
+
return callDoor(
|
|
7045
|
+
DOORS.portalFormSubmit,
|
|
7046
|
+
{
|
|
7047
|
+
p_organization_id: org,
|
|
7048
|
+
p_portal_id: portal_id,
|
|
7049
|
+
p_form_id: form_id,
|
|
7050
|
+
p_payload: answers,
|
|
7051
|
+
p_client_key: client_key ?? null
|
|
7052
|
+
},
|
|
7053
|
+
"portalFormSubmit"
|
|
7054
|
+
);
|
|
7055
|
+
},
|
|
6662
7056
|
async portalInvite(args) {
|
|
6663
7057
|
return callDoor(
|
|
6664
7058
|
DOORS.portalInvite,
|
|
@@ -6857,10 +7251,17 @@ function createRecordsClient(config) {
|
|
|
6857
7251
|
"dashboardDeclare"
|
|
6858
7252
|
);
|
|
6859
7253
|
},
|
|
6860
|
-
async dashboardRun({ dashboard_id, filter }) {
|
|
7254
|
+
async dashboardRun({ dashboard_id, filter, compare, grain }) {
|
|
6861
7255
|
return callDoor(
|
|
6862
7256
|
DOORS.dashboardRun,
|
|
6863
|
-
{
|
|
7257
|
+
{
|
|
7258
|
+
p_organization_id: org,
|
|
7259
|
+
p_dashboard_id: dashboard_id,
|
|
7260
|
+
p_filter: filter ?? {},
|
|
7261
|
+
// Sent only when asked, so a caller that never compares calls exactly what it did.
|
|
7262
|
+
...compare ? { p_compare: compare } : {},
|
|
7263
|
+
...grain ? { p_grain: grain } : {}
|
|
7264
|
+
},
|
|
6864
7265
|
"dashboardRun"
|
|
6865
7266
|
);
|
|
6866
7267
|
},
|
|
@@ -6885,7 +7286,7 @@ function createRecordsClient(config) {
|
|
|
6885
7286
|
"dashboardDelete"
|
|
6886
7287
|
);
|
|
6887
7288
|
},
|
|
6888
|
-
async recordAggregate({ table_id, groupBy, measures, bucket, filter, limit }) {
|
|
7289
|
+
async recordAggregate({ table_id, groupBy, measures, bucket, filter, limit, compare }) {
|
|
6889
7290
|
return callDoor(
|
|
6890
7291
|
DOORS.recordAggregate,
|
|
6891
7292
|
{
|
|
@@ -6895,7 +7296,8 @@ function createRecordsClient(config) {
|
|
|
6895
7296
|
p_measures: measures ?? [],
|
|
6896
7297
|
p_bucket: bucket ?? null,
|
|
6897
7298
|
p_filter: filter ?? {},
|
|
6898
|
-
p_limit: limit ?? 200
|
|
7299
|
+
p_limit: limit ?? 200,
|
|
7300
|
+
...compare ? { p_compare: compare } : {}
|
|
6899
7301
|
},
|
|
6900
7302
|
"recordAggregate"
|
|
6901
7303
|
);
|
|
@@ -7623,6 +8025,13 @@ function createRecordsClient(config) {
|
|
|
7623
8025
|
"workApprovalDecide"
|
|
7624
8026
|
);
|
|
7625
8027
|
},
|
|
8028
|
+
async workDecideMany({ decisions }) {
|
|
8029
|
+
return callDoor(
|
|
8030
|
+
DOORS.workDecideMany,
|
|
8031
|
+
{ p_organization_id: org, p_decisions: decisions },
|
|
8032
|
+
"workDecideMany"
|
|
8033
|
+
);
|
|
8034
|
+
},
|
|
7626
8035
|
async workApprovalRead({ approval_id }) {
|
|
7627
8036
|
return callDoor(
|
|
7628
8037
|
DOORS.workApprovalRead,
|
|
@@ -7826,6 +8235,69 @@ function createRecordsClient(config) {
|
|
|
7826
8235
|
"tableFromExample"
|
|
7827
8236
|
);
|
|
7828
8237
|
},
|
|
8238
|
+
async tableListEverywhere() {
|
|
8239
|
+
return callDoor(
|
|
8240
|
+
DOORS.tableListEverywhere,
|
|
8241
|
+
{ p_organization_id: org },
|
|
8242
|
+
"tableListEverywhere"
|
|
8243
|
+
);
|
|
8244
|
+
},
|
|
8245
|
+
async tableSync({ home_id, spec }) {
|
|
8246
|
+
return callDoor(
|
|
8247
|
+
DOORS.tableSync,
|
|
8248
|
+
{ p_organization_id: org, p_home_id: home_id, p_spec: spec },
|
|
8249
|
+
"tableSync"
|
|
8250
|
+
);
|
|
8251
|
+
},
|
|
8252
|
+
async scopeTableProvision({ home_id, item_id, scope_id }) {
|
|
8253
|
+
return callDoor(
|
|
8254
|
+
DOORS.scopeTableProvision,
|
|
8255
|
+
{ p_organization_id: org, p_item_id: item_id, p_scope_id: scope_id, p_home_id: home_id ?? null },
|
|
8256
|
+
"scopeTableProvision"
|
|
8257
|
+
);
|
|
8258
|
+
},
|
|
8259
|
+
async tableRowCounts({ table_ids }) {
|
|
8260
|
+
return callDoor(
|
|
8261
|
+
DOORS.tableRowCounts,
|
|
8262
|
+
{ p_organization_id: org, p_table_ids: table_ids },
|
|
8263
|
+
"tableRowCounts"
|
|
8264
|
+
);
|
|
8265
|
+
},
|
|
8266
|
+
async recordHeaders({ ids }) {
|
|
8267
|
+
return callDoor(
|
|
8268
|
+
DOORS.recordHeaders,
|
|
8269
|
+
{ p_organization_id: org, p_ids: ids },
|
|
8270
|
+
"recordHeaders"
|
|
8271
|
+
);
|
|
8272
|
+
},
|
|
8273
|
+
async tableShareOutsideGrant({ table_id, person, level }) {
|
|
8274
|
+
return callDoor(
|
|
8275
|
+
DOORS.tableShareOutsideGrant,
|
|
8276
|
+
{ p_organization_id: org, p_table_id: table_id, p_person: person, p_level: level ?? "viewer" },
|
|
8277
|
+
"tableShareOutsideGrant"
|
|
8278
|
+
);
|
|
8279
|
+
},
|
|
8280
|
+
async viewRecordOrderSet({ view_id, record_ids }) {
|
|
8281
|
+
return callDoor(
|
|
8282
|
+
DOORS.viewRecordOrderSet,
|
|
8283
|
+
{ p_organization_id: org, p_view_id: view_id, p_record_ids: record_ids },
|
|
8284
|
+
"viewRecordOrderSet"
|
|
8285
|
+
);
|
|
8286
|
+
},
|
|
8287
|
+
async readInViewOrder({ view_id, limit, offset }) {
|
|
8288
|
+
const door = await callDoor(
|
|
8289
|
+
DOORS.readRecordsInViewOrder,
|
|
8290
|
+
{ p_organization_id: org, p_view_id: view_id, p_by_id: false, p_limit: limit ?? 200, p_offset: offset ?? 0 },
|
|
8291
|
+
"readInViewOrder"
|
|
8292
|
+
);
|
|
8293
|
+
if (!door.ok) return err(door.error);
|
|
8294
|
+
return ok(
|
|
8295
|
+
(door.data ?? []).map((row) => {
|
|
8296
|
+
const { document, hidden } = unfold(row.document);
|
|
8297
|
+
return { id: row.id, document, level: row.level, hidden, position: row.position ?? null };
|
|
8298
|
+
})
|
|
8299
|
+
);
|
|
8300
|
+
},
|
|
7829
8301
|
async autonumberBackfill({ field_id }) {
|
|
7830
8302
|
return callDoor(
|
|
7831
8303
|
DOORS.autonumberBackfill,
|
|
@@ -7881,6 +8353,113 @@ function asWriteConflict(error, recordId) {
|
|
|
7881
8353
|
};
|
|
7882
8354
|
}
|
|
7883
8355
|
|
|
8356
|
+
// src/core/declareTable.ts
|
|
8357
|
+
function tokenFor(name) {
|
|
8358
|
+
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 48) || "table";
|
|
8359
|
+
}
|
|
8360
|
+
var DEFAULT_FIELDS = [
|
|
8361
|
+
{ key: "title", label: "Title", type: "text", sort: 10, required: false }
|
|
8362
|
+
];
|
|
8363
|
+
function fieldDeclarationFor(field) {
|
|
8364
|
+
return {
|
|
8365
|
+
key: field.key,
|
|
8366
|
+
label: field.label,
|
|
8367
|
+
sort: field.sort ?? 100,
|
|
8368
|
+
required: field.required ?? false,
|
|
8369
|
+
multi: field.multi ?? false,
|
|
8370
|
+
...declaredType(field),
|
|
8371
|
+
...field.config ? { config: field.config } : {},
|
|
8372
|
+
...field.options ? { options: field.options } : {},
|
|
8373
|
+
...field.relationTarget ? { relation_target: field.relationTarget } : {},
|
|
8374
|
+
...field.unit ? { unit: field.unit } : {},
|
|
8375
|
+
...field.kind ? { kind: field.kind } : {}
|
|
8376
|
+
};
|
|
8377
|
+
}
|
|
8378
|
+
async function declareTable(client, spec) {
|
|
8379
|
+
const fields = spec.fields && spec.fields.length > 0 ? spec.fields : DEFAULT_FIELDS;
|
|
8380
|
+
const slug = spec.slug ?? tokenFor(spec.name);
|
|
8381
|
+
const titleField = spec.titleField ?? fields[0].key;
|
|
8382
|
+
const personKernel = await client.personKernelId();
|
|
8383
|
+
if (!personKernel.ok) return personKernel;
|
|
8384
|
+
const home = await client.recordWrite({
|
|
8385
|
+
table_id: personKernel.data,
|
|
8386
|
+
data: { name: `${spec.name} Home` }
|
|
8387
|
+
});
|
|
8388
|
+
if (!home.ok) return home;
|
|
8389
|
+
const table = await client.tableDeclare({
|
|
8390
|
+
homeId: home.data,
|
|
8391
|
+
spec: {
|
|
8392
|
+
name: spec.name,
|
|
8393
|
+
slug,
|
|
8394
|
+
type: "entity",
|
|
8395
|
+
label_singular: spec.labelSingular ?? spec.name,
|
|
8396
|
+
label_plural: spec.labelPlural ?? spec.name,
|
|
8397
|
+
display: "list",
|
|
8398
|
+
weight: "light",
|
|
8399
|
+
ordered: false,
|
|
8400
|
+
row_order: "manual",
|
|
8401
|
+
title_field: titleField,
|
|
8402
|
+
retention_days: 365,
|
|
8403
|
+
// AGT-1: a table a person made is agent-writable by declaration; the
|
|
8404
|
+
// organization turns it off, never this package.
|
|
8405
|
+
agent_writable: true,
|
|
8406
|
+
default_sort: [{ field: titleField, direction: "asc" }],
|
|
8407
|
+
fields: fields.map((f) => ({ name: f.key }))
|
|
8408
|
+
}
|
|
8409
|
+
});
|
|
8410
|
+
if (!table.ok) {
|
|
8411
|
+
await client.recordDelete({ record_id: home.data });
|
|
8412
|
+
return table;
|
|
8413
|
+
}
|
|
8414
|
+
const written = await addFields(client, table.data, fields);
|
|
8415
|
+
if (!written.ok) return undo(client, table.data, home.data, spec.name, written.error);
|
|
8416
|
+
return { ok: true, data: table.data, homeId: home.data };
|
|
8417
|
+
}
|
|
8418
|
+
async function undo(client, tableId, homeId, name, refusal) {
|
|
8419
|
+
const removed = await client.recordDelete({ record_id: tableId });
|
|
8420
|
+
if (removed.ok) {
|
|
8421
|
+
await client.recordDelete({ record_id: homeId });
|
|
8422
|
+
return { ok: false, error: refusal };
|
|
8423
|
+
}
|
|
8424
|
+
return {
|
|
8425
|
+
ok: false,
|
|
8426
|
+
error: {
|
|
8427
|
+
...refusal,
|
|
8428
|
+
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.`
|
|
8429
|
+
}
|
|
8430
|
+
};
|
|
8431
|
+
}
|
|
8432
|
+
async function addFields(client, tableId, fields) {
|
|
8433
|
+
for (const field of fields) {
|
|
8434
|
+
const result = await client.fieldDeclare({
|
|
8435
|
+
table_id: tableId,
|
|
8436
|
+
spec: fieldDeclarationFor(field)
|
|
8437
|
+
});
|
|
8438
|
+
if (!result.ok) return result;
|
|
8439
|
+
}
|
|
8440
|
+
return { ok: true, data: tableId };
|
|
8441
|
+
}
|
|
8442
|
+
var PLAIN_WORDS = {
|
|
8443
|
+
text: "text",
|
|
8444
|
+
string: "text",
|
|
8445
|
+
long_text: "long_text",
|
|
8446
|
+
longtext: "long_text",
|
|
8447
|
+
paragraph: "long_text",
|
|
8448
|
+
number: "number",
|
|
8449
|
+
range: "number",
|
|
8450
|
+
integer: "number",
|
|
8451
|
+
decimal: "number",
|
|
8452
|
+
float: "number"
|
|
8453
|
+
};
|
|
8454
|
+
function declaredType(field) {
|
|
8455
|
+
const word = (field.type ?? "text").toLowerCase();
|
|
8456
|
+
const plain = PLAIN_WORDS[word];
|
|
8457
|
+
if (plain) {
|
|
8458
|
+
return { plain: plain === "text" && field.config?.["multiline"] === true ? "long_text" : plain };
|
|
8459
|
+
}
|
|
8460
|
+
return { parity_type: word };
|
|
8461
|
+
}
|
|
8462
|
+
|
|
7884
8463
|
// src/core/supabase.ts
|
|
7885
8464
|
function refuse(code, message, hint) {
|
|
7886
8465
|
return { data: null, error: { code, message, hint, details: "" } };
|