@ai-matrx/records 0.99.32 → 0.99.34
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 +16 -1
- package/dist/{client-CrwedBYU.d.ts → client-CSfkP7Xf.d.ts} +5 -74
- package/dist/{client-D1EwyWfh.d.cts → client-jKf1Z-kp.d.cts} +5 -74
- package/dist/core/index.cjs +149 -105
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +9 -331
- package/dist/core/index.d.ts +9 -331
- package/dist/core/index.js +149 -105
- package/dist/core/index.js.map +1 -1
- package/dist/data/index.cjs +280 -236
- package/dist/data/index.cjs.map +1 -1
- package/dist/data/index.d.cts +3 -2
- package/dist/data/index.d.ts +3 -2
- package/dist/data/index.js +280 -236
- package/dist/data/index.js.map +1 -1
- package/dist/{declareTable-CC5rTTIA.d.ts → declareTable-0emxEewn.d.ts} +1 -1
- package/dist/{declareTable-BIIt0Wqt.d.cts → declareTable-800bwUn3.d.cts} +1 -1
- package/dist/door-call/index.cjs +1287 -0
- package/dist/door-call/index.cjs.map +1 -0
- package/dist/door-call/index.d.cts +20 -0
- package/dist/door-call/index.d.ts +20 -0
- package/dist/door-call/index.js +1264 -0
- package/dist/door-call/index.js.map +1 -0
- package/dist/{doors-D1yAs0a0.d.cts → doors-BgjRUdxw.d.cts} +13 -1
- package/dist/{doors-D2z3kH2i.d.ts → doors-D7_jEKtg.d.ts} +13 -1
- package/dist/index.d.cts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/{objectOrganization-DazBoDAq.d.cts → objectOrganization-B5YFGVUB.d.cts} +1 -1
- package/dist/{objectOrganization-D1ox3gUx.d.ts → objectOrganization-DozoVIxS.d.ts} +1 -1
- package/dist/pickListDoors-C-_eAEGL.d.cts +135 -0
- package/dist/pickListDoors-DgWVnNRI.d.ts +135 -0
- package/dist/questions/index.d.cts +3 -2
- package/dist/questions/index.d.ts +3 -2
- package/dist/react/index.cjs +363 -319
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +37 -22
- package/dist/react/index.d.ts +37 -22
- package/dist/react/index.js +363 -319
- package/dist/react/index.js.map +1 -1
- package/dist/scopes/index.cjs +7 -0
- package/dist/scopes/index.cjs.map +1 -1
- package/dist/scopes/index.d.cts +2 -2
- package/dist/scopes/index.d.ts +2 -2
- package/dist/scopes/index.js +7 -0
- package/dist/scopes/index.js.map +1 -1
- package/dist/setup/index.cjs +289 -245
- package/dist/setup/index.cjs.map +1 -1
- package/dist/setup/index.d.cts +4 -3
- package/dist/setup/index.d.ts +4 -3
- package/dist/setup/index.js +289 -245
- package/dist/setup/index.js.map +1 -1
- package/dist/table-logic-BPquabNN.d.ts +328 -0
- package/dist/table-logic-CTh6lwaq.d.cts +328 -0
- package/dist/typed-table/index.cjs +280 -236
- package/dist/typed-table/index.cjs.map +1 -1
- package/dist/typed-table/index.d.cts +4 -3
- package/dist/typed-table/index.d.ts +4 -3
- package/dist/typed-table/index.js +280 -236
- package/dist/typed-table/index.js.map +1 -1
- package/dist/views/index.d.cts +3 -2
- package/dist/views/index.d.ts +3 -2
- package/package.json +13 -3
|
@@ -0,0 +1,1264 @@
|
|
|
1
|
+
// src/core/supabase.ts
|
|
2
|
+
function refuse(code, message, hint) {
|
|
3
|
+
return { data: null, error: { code, message, hint, details: "" } };
|
|
4
|
+
}
|
|
5
|
+
function supabaseDataSource(client) {
|
|
6
|
+
if (isBound(client)) return client;
|
|
7
|
+
const supabase = client;
|
|
8
|
+
const hasSchema = typeof supabase.schema === "function";
|
|
9
|
+
const bound = {
|
|
10
|
+
// PAGE-BUNDLE: the real adapter opens a table page with one `custom.table_page_bundle` call.
|
|
11
|
+
opensInOneCall: true,
|
|
12
|
+
rpc(fn, args, options) {
|
|
13
|
+
if (!hasSchema) {
|
|
14
|
+
return Promise.resolve(
|
|
15
|
+
refuse(
|
|
16
|
+
"data_source_unbound",
|
|
17
|
+
`The records client was given something that is not a supabase client: it has no schema() method, so the door ${fn} cannot be called in the schema it lives in.`,
|
|
18
|
+
"Pass a supabase-js v2 client (createClient(...)) to supabaseDataSource()."
|
|
19
|
+
)
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
const name = options?.schema;
|
|
23
|
+
if (!name) {
|
|
24
|
+
return Promise.resolve(
|
|
25
|
+
refuse(
|
|
26
|
+
"schema_unsaid",
|
|
27
|
+
`The door ${fn} was called without saying which schema it lives in. Every door of this store is custom.${fn} and every trust door is iam.${fn}; an unqualified call reaches PostgREST's default profile (public) and is refused there.`,
|
|
28
|
+
"This is a bug in the caller, not in your data: /core says the schema on every call."
|
|
29
|
+
)
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return supabase.schema(name).rpc(fn, args);
|
|
33
|
+
},
|
|
34
|
+
schema(name) {
|
|
35
|
+
return supabase.schema(name);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(bound, BOUND, { value: true, enumerable: false });
|
|
39
|
+
return bound;
|
|
40
|
+
}
|
|
41
|
+
var BOUND = /* @__PURE__ */ Symbol.for("@ai-matrx/records/supabaseDataSource");
|
|
42
|
+
function isBound(source) {
|
|
43
|
+
return source[BOUND] === true;
|
|
44
|
+
}
|
|
45
|
+
function asRecordsDataSource(source) {
|
|
46
|
+
if (source === null || typeof source !== "object") return supabaseDataSource({});
|
|
47
|
+
if (isBound(source)) return source;
|
|
48
|
+
const candidate = source;
|
|
49
|
+
const looksLikeSupabaseJs = typeof candidate.schema === "function" && typeof candidate.from === "function" && candidate.auth !== null && typeof candidate.auth === "object";
|
|
50
|
+
if (looksLikeSupabaseJs || typeof candidate.rpc !== "function") return supabaseDataSource(source);
|
|
51
|
+
return source;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/results.ts
|
|
55
|
+
var ok = (data) => ({ ok: true, data });
|
|
56
|
+
var err = (error) => ({ ok: false, error });
|
|
57
|
+
|
|
58
|
+
// src/core/pgError.ts
|
|
59
|
+
var BY_SQLSTATE = {
|
|
60
|
+
PT409: "stale_write",
|
|
61
|
+
"42501": "door",
|
|
62
|
+
"23514": "refused_by_rule",
|
|
63
|
+
"22004": "invalid_argument",
|
|
64
|
+
"22023": "invalid_argument",
|
|
65
|
+
// MX005 — a value the store cannot take as asked: since 2026-10-06 (AP-3 U3) a word that names more than one
|
|
66
|
+
// choice or linked record raises it (it used to borrow PT409 and read as somebody else's write winning).
|
|
67
|
+
MX005: "invalid_argument",
|
|
68
|
+
// MXD01 — the held-change door tried the proposed values before filing an
|
|
69
|
+
// approval request and the store refused them. Nothing is waiting for a
|
|
70
|
+
// person to decide; correct the value before asking again, which is the same
|
|
71
|
+
// caller-side condition as the other invalid-argument refusals.
|
|
72
|
+
MXD01: "invalid_argument",
|
|
73
|
+
// 22P02 — a value does not fit the type the store holds for it. The class is
|
|
74
|
+
// real and was hit live: an organization whose copy of a package table grew
|
|
75
|
+
// `status` as a uuid-valued Field was sent the text "open", and the store
|
|
76
|
+
// answered `invalid input syntax for type uuid: "open"`. It is the CALL that
|
|
77
|
+
// is wrong for the shape the store holds, which is what `invalid_argument`
|
|
78
|
+
// already means — the store's own sentence names the value and the type.
|
|
79
|
+
"22P02": "invalid_argument",
|
|
80
|
+
// 42703 — a column that is not there. The store raises it from
|
|
81
|
+
// `custom.entity_table`, which is asked for a column by NAME, so the class is
|
|
82
|
+
// exactly "the call named something the shape does not have": the same thing
|
|
83
|
+
// `invalid_argument` already means, and the store's own sentence names the
|
|
84
|
+
// column. Added 2026-09-20 (lane FORMS) because the package's live-door suite
|
|
85
|
+
// caught it raised by a door this package did not know about — which is the
|
|
86
|
+
// suite working, and is why the map is a census rather than a guess.
|
|
87
|
+
"42703": "invalid_argument",
|
|
88
|
+
// 22007 — a date or a time the store could not read as one. It is raised by
|
|
89
|
+
// `custom.dashboard_window_sql`, which is handed a window a caller wrote
|
|
90
|
+
// ("last 30 days", a literal date), so the class is again "the call named
|
|
91
|
+
// something the shape does not accept" and the store's own sentence names the
|
|
92
|
+
// text it could not read. Added 2026-09-20 (lane SCREEN-BREAK) after the
|
|
93
|
+
// live-door census caught it — the same way 42703 arrived, which is the
|
|
94
|
+
// census working rather than a guess.
|
|
95
|
+
"22007": "invalid_argument",
|
|
96
|
+
// 22003 — a number the store could not hold at the size it was handed. It is
|
|
97
|
+
// raised by `custom.context_resolve`, which is given a window or a depth a
|
|
98
|
+
// caller wrote, so the class is the same one 22P02, 42703 and 22007 already
|
|
99
|
+
// are: the CALL named a value the shape does not accept, and the store's own
|
|
100
|
+
// sentence names the number and the range. Added 2026-09-20 (lane GRID-TWO)
|
|
101
|
+
// when the live-door census caught the release gate red on it — which is the
|
|
102
|
+
// census working, and the fourth time it has been the thing that found a new
|
|
103
|
+
// refusal class rather than a person guessing one.
|
|
104
|
+
"22003": "invalid_argument",
|
|
105
|
+
// 42883 — `custom._template_call` (the template family, 2026-10-03) was handed a step that
|
|
106
|
+
// names a door the store does not have, or an argument that door does not take. The TEMPLATE
|
|
107
|
+
// named something the shape does not have — the same class 42703 is — and the store's own
|
|
108
|
+
// sentence names the door or the argument. Caught by the live-door census on records 0.68.1's
|
|
109
|
+
// tag, the eighth time the census found a class before a person did. (A bare 42883 on a door
|
|
110
|
+
// this package itself asks for is still `door_absent`, mapped before this funnel in
|
|
111
|
+
// `owedDoor`.)
|
|
112
|
+
"42883": "invalid_argument",
|
|
113
|
+
"02000": "not_found",
|
|
114
|
+
// P0002 — `custom.inbox_snooze` / `custom.inbox_clear` (lane S5-PRIME-2, 2026-09-24): "There is
|
|
115
|
+
// nothing in your inbox with that id". An invented id, another person's item and another
|
|
116
|
+
// organization's all answer it alike, so it reads exactly as 02000 does: not found.
|
|
117
|
+
"P0002": "not_found",
|
|
118
|
+
"23503": "missing_reference",
|
|
119
|
+
"23505": "already_exists",
|
|
120
|
+
"0A000": "not_supported",
|
|
121
|
+
"53400": "store_limit",
|
|
122
|
+
"22012": "store_limit",
|
|
123
|
+
// 54001 — `custom.record_delete` stops a cascade that nests past sixty-four
|
|
124
|
+
// levels of containment, because that is a loop somebody built rather than a
|
|
125
|
+
// hierarchy, and saying so beats recursing until the server runs out of
|
|
126
|
+
// stack. It is a LIMIT of the store, and the store's own sentence names what
|
|
127
|
+
// to do about it, so it reads as one.
|
|
128
|
+
"54001": "store_limit",
|
|
129
|
+
// 54000 — the grid primitives' organization ceilings (GRID-PRIMITIVES, 2026-09-23):
|
|
130
|
+
// `custom.action_run` on a selection past its knob, `custom.table_decorate` past the
|
|
131
|
+
// rule count, `custom.table_from_example` past `custom/example_rows_max`,
|
|
132
|
+
// `custom.table_webhook_declare` past `custom/table_webhooks_max`, and
|
|
133
|
+
// `custom.hub_changed_by`. Each names its knob in the hint and writes nothing, so it
|
|
134
|
+
// reads exactly like 54001: a limit of the store, in the store's own sentence.
|
|
135
|
+
"54000": "store_limit",
|
|
136
|
+
// VIS: `custom.has_visibility_at` refuses to answer from a snapshot older
|
|
137
|
+
// than a write this reader has already seen, rather than returning something
|
|
138
|
+
// they just lost access to. It is retryable on a fresher connection, which is
|
|
139
|
+
// a different instruction from every other refusal here.
|
|
140
|
+
// 55000 — `custom.table_move` (SC-1', 2026-09-24) refuses a move the table's present state
|
|
141
|
+
// does not allow: a destination that cannot take it, a table something else holds, contents
|
|
142
|
+
// that point at themselves in a circle. The store's own sentence names the reason and nothing
|
|
143
|
+
// moves, so it reads as a refusal on the merits. Caught by the live-door census on records
|
|
144
|
+
// 0.58.3's tag, the seventh time the census found a class before a person did.
|
|
145
|
+
"55000": "refused_by_rule",
|
|
146
|
+
// 42P17 — a worked-out column that would read itself round a circle (STORE-TAILS-3,
|
|
147
|
+
// 2026-09-25): `custom._field_reads_what_it_reads` refuses the declaration or edit that would
|
|
148
|
+
// close the circle, naming every column in it, and `custom.far_value` refuses a circle stored
|
|
149
|
+
// before that rule when a lookup or rollup is worked out. Refused on its merits, in the store's
|
|
150
|
+
// own sentence, nothing written — so it reads as 55000 does.
|
|
151
|
+
"42P17": "refused_by_rule",
|
|
152
|
+
// P0001 — `custom.context_scope_write` (2026-10-03) refuses a scope whose parent breaks the
|
|
153
|
+
// parent rule in the old trigger's own class and words (manager ruling H6). Refused on its
|
|
154
|
+
// merits, nothing written. Caught by the live-door census on records 0.65.0's tag.
|
|
155
|
+
P0001: "refused_by_rule",
|
|
156
|
+
"40001": "stale_read",
|
|
157
|
+
// 57014 — the store cancelled the statement at its own timeout. Like
|
|
158
|
+
// `stale_read` and unlike every other refusal here, the call was never
|
|
159
|
+
// refused on its merits: the same call on a smaller page, or once the
|
|
160
|
+
// organization's kernel is not being rewritten, can succeed. It gets its own
|
|
161
|
+
// code so a screen can offer "try again" honestly instead of showing a
|
|
162
|
+
// failure sentence the caller cannot act on.
|
|
163
|
+
"57014": "timed_out",
|
|
164
|
+
// PostgREST's own two: the store is not being SERVED, which is a different
|
|
165
|
+
// thing from the store refusing. `custom` absent from `pgrst.db_schemas`, or
|
|
166
|
+
// the grants not copied, land here.
|
|
167
|
+
PGRST202: "store_not_served",
|
|
168
|
+
PGRST205: "store_not_served",
|
|
169
|
+
PGRST301: "door",
|
|
170
|
+
// PT428 — NOT A FAILURE, and the release gate's live census is what found it
|
|
171
|
+
// missing: the store raises it and this package answered `internal`, which is
|
|
172
|
+
// the one classification that tells a screen nothing. A stage gate whose
|
|
173
|
+
// `on_fail` is `require_approval` files the request and leaves the record
|
|
174
|
+
// where it was; the transaction is rolled back to the pipeline's own block, so
|
|
175
|
+
// "nothing was written" is a fact rather than a promise. Landed by lane
|
|
176
|
+
// STAGE-RULES; mapped here by STAGE-RULES-2 the day after, when the census
|
|
177
|
+
// caught it — the sixth time that census has been the thing that found a
|
|
178
|
+
// refusal class rather than a person guessing one.
|
|
179
|
+
PT428: "needs_approval"
|
|
180
|
+
};
|
|
181
|
+
var MAPPED_SQLSTATES = Object.keys(BY_SQLSTATE);
|
|
182
|
+
function parseDetail(details) {
|
|
183
|
+
if (!details) return void 0;
|
|
184
|
+
const trimmed = details.trim();
|
|
185
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return details;
|
|
186
|
+
try {
|
|
187
|
+
return JSON.parse(trimmed);
|
|
188
|
+
} catch {
|
|
189
|
+
return details;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function isNotASqlstate(code) {
|
|
193
|
+
if (!code) return true;
|
|
194
|
+
return /^[0-9]{3}$/.test(code);
|
|
195
|
+
}
|
|
196
|
+
function mapPgError(error, where) {
|
|
197
|
+
const raw = error.code ?? void 0;
|
|
198
|
+
const sqlstate = raw && raw !== "" ? raw : void 0;
|
|
199
|
+
const mapped = {
|
|
200
|
+
code: "internal",
|
|
201
|
+
message: error.message,
|
|
202
|
+
...error.hint ? { hint: error.hint } : {},
|
|
203
|
+
...sqlstate ? { sqlstate } : {}
|
|
204
|
+
};
|
|
205
|
+
if (isNotASqlstate(sqlstate)) {
|
|
206
|
+
mapped.code = "unreachable";
|
|
207
|
+
} else {
|
|
208
|
+
mapped.code = BY_SQLSTATE[sqlstate] ?? "internal";
|
|
209
|
+
}
|
|
210
|
+
const detail = parseDetail(error.details);
|
|
211
|
+
if (detail !== void 0) mapped.detail = detail;
|
|
212
|
+
if (mapped.code === "internal") {
|
|
213
|
+
mapped.unmapped = true;
|
|
214
|
+
mapped.diagnostic = `@ai-matrx/records did not recognise SQLSTATE ${sqlstate ?? "(none)"} from ${where}; the store's own message is the thing to act on.`;
|
|
215
|
+
} else if (mapped.code === "unreachable") {
|
|
216
|
+
mapped.unmapped = true;
|
|
217
|
+
mapped.diagnostic = `@ai-matrx/records never reached the store from ${where}: ${raw ? `the transport answered ${raw}` : "no response"} \u2014 ${error.message}`;
|
|
218
|
+
mapped.message = "We could not reach your data.";
|
|
219
|
+
delete mapped.hint;
|
|
220
|
+
}
|
|
221
|
+
return mapped;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/core/primedBundle.ts
|
|
225
|
+
var REGISTRY = /* @__PURE__ */ Symbol.for("@ai-matrx/records/primed-table-page-bundles");
|
|
226
|
+
function registry() {
|
|
227
|
+
const g = globalThis;
|
|
228
|
+
return g[REGISTRY] ??= /* @__PURE__ */ new Map();
|
|
229
|
+
}
|
|
230
|
+
function forgetPrimedTablePageBundlesOf(tableId) {
|
|
231
|
+
const primed = registry();
|
|
232
|
+
for (const key of [...primed.keys()]) if (tableId === null || key.split("|")[1] === tableId) primed.delete(key);
|
|
233
|
+
}
|
|
234
|
+
var DOOR_REGISTRY = /* @__PURE__ */ Symbol.for("@ai-matrx/records/primed-door-answers");
|
|
235
|
+
function doorRegistry() {
|
|
236
|
+
const g = globalThis;
|
|
237
|
+
return g[DOOR_REGISTRY] ??= /* @__PURE__ */ new Map();
|
|
238
|
+
}
|
|
239
|
+
var PRIMED_DOOR_TTL_MS = 1e4;
|
|
240
|
+
function takePrimedDoorAnswer(key) {
|
|
241
|
+
const registry2 = doorRegistry();
|
|
242
|
+
const held = registry2.get(key);
|
|
243
|
+
if (!held) return null;
|
|
244
|
+
if (Date.now() - held.at < PRIMED_DOOR_TTL_MS) return { data: held.data };
|
|
245
|
+
registry2.delete(key);
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
function dropPrimedDoorAnswers(doors) {
|
|
249
|
+
const registry2 = doorRegistry();
|
|
250
|
+
if (!doors) {
|
|
251
|
+
registry2.clear();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
for (const key of [...registry2.keys()]) if (doors.some((door) => key.startsWith(`${door}|`))) registry2.delete(key);
|
|
255
|
+
}
|
|
256
|
+
var LOOK_WRITES = {
|
|
257
|
+
view_declare: ["views", "view_look_read", "grid_layout", "table_page_bundle"],
|
|
258
|
+
view_designate: ["views", "view_look_read", "grid_layout", "table_page_bundle"],
|
|
259
|
+
view_look_set: ["views", "view_look_read", "grid_layout", "table_page_bundle"],
|
|
260
|
+
view_record_order_set: ["views", "view_look_read", "grid_layout", "table_page_bundle"]
|
|
261
|
+
};
|
|
262
|
+
function isWritingDoor(door) {
|
|
263
|
+
return /(_declare|_set(_|$)|_update|_write|_delete|_restore|_archive|_upsert|_reparent|_change|_duplicate|_move|_decide|_run$|_submit|_publish|_import_|_start$|_complete|_sign$|_add$|_retire|_propose|_promote|_land$|_pin$|_mute|_snooze|_unsnooze|_clear|_unclear|_revoke|_issue|_bind|_unbind|_apply|_backfill|_sync$|_decorate|_designate|_ensure|^migrate_|^set_table_|^home_add|^capture_|^claim_|^record_stage|^anon_|^io_import_|^io_comment_resolve|^enrich_(declare|land|pin)|^field_(declare|duplicate|propose|restore|retire|update)|^table_(declare|ensure|from_example|move|sync)|^value_restore|^relation_own)/.test(door);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/core/doorCall.ts
|
|
267
|
+
var DEFAULT_DOOR_SCHEMA = "custom";
|
|
268
|
+
function canonicalArgs(value) {
|
|
269
|
+
return JSON.stringify(
|
|
270
|
+
value,
|
|
271
|
+
(_key, held) => held && typeof held === "object" && !Array.isArray(held) ? Object.fromEntries(Object.keys(held).sort().map((k) => [k, held[k]])) : held
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
var A_PERSONS_REFUSAL = /* @__PURE__ */ new Set([
|
|
275
|
+
"refused_by_rule",
|
|
276
|
+
"stale_write",
|
|
277
|
+
"invalid_argument",
|
|
278
|
+
"already_exists",
|
|
279
|
+
"missing_reference"
|
|
280
|
+
]);
|
|
281
|
+
function isAPersonsRefusal(code) {
|
|
282
|
+
return A_PERSONS_REFUSAL.has(code);
|
|
283
|
+
}
|
|
284
|
+
function recordsErrorSink(onError) {
|
|
285
|
+
return onError ?? ((e) => {
|
|
286
|
+
const line = `[@ai-matrx/records] ${e.where}: ${e.code} \u2014 ${e.message}${e.hint ? ` (${e.hint})` : ""}`;
|
|
287
|
+
if (isAPersonsRefusal(e.code)) console.info(line);
|
|
288
|
+
else console.error(line);
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
function doorAbsentRefusal(name, owedBy) {
|
|
292
|
+
return {
|
|
293
|
+
code: "door_absent",
|
|
294
|
+
message: `The record store has no door \`custom.${name}\` yet, so this call was not made. Nothing was read and nothing was written.`,
|
|
295
|
+
hint: owedBy ? `That door is ${owedBy}. Until it lands, this call refuses rather than guessing: an empty answer here would read like "there is nothing", which is a different sentence.` : `No lane in the build book claims this door. Either the name is wrong, or the door was removed from the store and this package has not been regenerated \u2014 run \`pnpm records:generate\`.`
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function primedDoorAnswer(door, args) {
|
|
299
|
+
const look = LOOK_WRITES[door];
|
|
300
|
+
if (look) {
|
|
301
|
+
dropPrimedDoorAnswers(look);
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
if (isWritingDoor(door)) {
|
|
305
|
+
dropPrimedDoorAnswers();
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
return takePrimedDoorAnswer(`${door}|${canonicalArgs(args)}`);
|
|
309
|
+
}
|
|
310
|
+
function createDoorCall(options) {
|
|
311
|
+
const { dataSource, doorReady, held, wrote } = options;
|
|
312
|
+
const schema = options.schema ?? DEFAULT_DOOR_SCHEMA;
|
|
313
|
+
const scream = recordsErrorSink(options.onError);
|
|
314
|
+
const absent = options.absent ?? ((door) => doorAbsentRefusal(door));
|
|
315
|
+
const readsInFlight = /* @__PURE__ */ new Map();
|
|
316
|
+
const say = (refusal, where) => scream({ code: refusal.code, message: refusal.message, ...refusal.hint ? { hint: refusal.hint } : {}, where });
|
|
317
|
+
return async function callDoor(door, args, where, opts) {
|
|
318
|
+
const primed = primedDoorAnswer(door, args);
|
|
319
|
+
if (primed) return ok(primed.data);
|
|
320
|
+
if (held) {
|
|
321
|
+
const bundled = await held(door, args);
|
|
322
|
+
if (bundled) return ok(bundled.data);
|
|
323
|
+
}
|
|
324
|
+
if (doorReady && !doorReady(door)) {
|
|
325
|
+
const refusal = absent(door);
|
|
326
|
+
say(refusal, where);
|
|
327
|
+
return err(refusal);
|
|
328
|
+
}
|
|
329
|
+
const flightKey = isWritingDoor(door) ? null : `${door}|${canonicalArgs(args)}`;
|
|
330
|
+
const flying = flightKey ? readsInFlight.get(flightKey) : void 0;
|
|
331
|
+
const response = await (flying ?? (() => {
|
|
332
|
+
const call = Promise.resolve(dataSource.rpc(`${door}`, args, { schema }));
|
|
333
|
+
if (flightKey) {
|
|
334
|
+
readsInFlight.set(flightKey, call);
|
|
335
|
+
const forget = () => {
|
|
336
|
+
if (readsInFlight.get(flightKey) === call) readsInFlight.delete(flightKey);
|
|
337
|
+
};
|
|
338
|
+
call.then(forget, forget);
|
|
339
|
+
}
|
|
340
|
+
return call;
|
|
341
|
+
})());
|
|
342
|
+
if (response.error) {
|
|
343
|
+
const code = response.error.code;
|
|
344
|
+
const refusal = !doorReady && (code === "PGRST202" || code === "42883") ? absent(door) : mapPgError(response.error, where);
|
|
345
|
+
if (opts?.quiet) console.info(`[@ai-matrx/records] ${where}: ${refusal.code} - ${refusal.message}`);
|
|
346
|
+
else say(refusal, where);
|
|
347
|
+
return err(refusal);
|
|
348
|
+
}
|
|
349
|
+
if (isWritingDoor(door) || LOOK_WRITES[door]) {
|
|
350
|
+
const tableId = args["p_table_id"];
|
|
351
|
+
wrote?.(door, args);
|
|
352
|
+
forgetPrimedTablePageBundlesOf(typeof tableId === "string" ? tableId : null);
|
|
353
|
+
dropPrimedDoorAnswers(LOOK_WRITES[door]);
|
|
354
|
+
}
|
|
355
|
+
return ok(response.data);
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// src/core/pickListDoors.ts
|
|
360
|
+
var PICK_LIST_DOORS = {
|
|
361
|
+
pickListGet: "pick_list_get",
|
|
362
|
+
pickListForSelection: "pick_list_for_selection",
|
|
363
|
+
pickListUpdate: "pick_list_update"
|
|
364
|
+
};
|
|
365
|
+
function createPickListDoors(callDoor) {
|
|
366
|
+
return {
|
|
367
|
+
async pickListGet({ list_id }) {
|
|
368
|
+
return callDoor(PICK_LIST_DOORS.pickListGet, { p_list_id: list_id }, "pickListGet");
|
|
369
|
+
},
|
|
370
|
+
async pickListForSelection({ list_id }) {
|
|
371
|
+
return callDoor(
|
|
372
|
+
PICK_LIST_DOORS.pickListForSelection,
|
|
373
|
+
{ p_list_id: list_id },
|
|
374
|
+
"pickListForSelection"
|
|
375
|
+
);
|
|
376
|
+
},
|
|
377
|
+
async pickListUpdate({ list_id, list_name, description, items }) {
|
|
378
|
+
return callDoor(
|
|
379
|
+
PICK_LIST_DOORS.pickListUpdate,
|
|
380
|
+
{
|
|
381
|
+
p_list_id: list_id,
|
|
382
|
+
p_list_name: list_name ?? null,
|
|
383
|
+
p_description: description ?? null,
|
|
384
|
+
p_items: items ? [...items] : null
|
|
385
|
+
},
|
|
386
|
+
"pickListUpdate"
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/scopes/types.ts
|
|
393
|
+
var SYSTEM_CONTEXT_TYPE_ID = "__system__";
|
|
394
|
+
|
|
395
|
+
// src/scopes/codec.ts
|
|
396
|
+
import { isUuidShape } from "@ai-matrx/kit/uuid";
|
|
397
|
+
var str = (v, fallback = "") => typeof v === "string" ? v : fallback;
|
|
398
|
+
var strOrNull = (v) => typeof v === "string" ? v : null;
|
|
399
|
+
var num = (v, fallback = 0) => {
|
|
400
|
+
const n = typeof v === "number" ? v : typeof v === "string" ? Number(v) : NaN;
|
|
401
|
+
return Number.isFinite(n) ? n : fallback;
|
|
402
|
+
};
|
|
403
|
+
function scopeTypeFromStore(row) {
|
|
404
|
+
const singular = row.label_singular ?? row.name ?? "";
|
|
405
|
+
return {
|
|
406
|
+
id: row.id,
|
|
407
|
+
organization_id: row.organization_id,
|
|
408
|
+
label_singular: singular,
|
|
409
|
+
label_plural: row.label_plural ?? singular,
|
|
410
|
+
icon: row.icon ?? "folder",
|
|
411
|
+
color: row.color ?? "",
|
|
412
|
+
slug: row.slug ?? null,
|
|
413
|
+
description: row.description ?? "",
|
|
414
|
+
sort_order: num(row.sort_order),
|
|
415
|
+
max_assignments_per_entity: row.max_assignments_per_entity ?? null,
|
|
416
|
+
default_variable_keys: Array.isArray(row.default_variable_keys) ? row.default_variable_keys : [],
|
|
417
|
+
created_by: row.created_by ?? null,
|
|
418
|
+
created_at: row.created_at ?? "",
|
|
419
|
+
updated_at: row.updated_at ?? ""
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
function scopeTypeLabelFromStore(t) {
|
|
423
|
+
const singular = t.label_singular ?? t.name ?? "";
|
|
424
|
+
return { id: t.id, slug: t.slug ?? null, label_singular: singular, label_plural: t.label_plural ?? singular, icon: t.icon ?? null, color: t.color ?? null };
|
|
425
|
+
}
|
|
426
|
+
function settingFromStore(value) {
|
|
427
|
+
if (!Array.isArray(value)) return value;
|
|
428
|
+
return value.map((el) => {
|
|
429
|
+
if (typeof el !== "string") return el;
|
|
430
|
+
const t = el.trim();
|
|
431
|
+
if (!(t.startsWith("{") && t.endsWith("}")) && !(t.startsWith("[") && t.endsWith("]"))) return el;
|
|
432
|
+
try {
|
|
433
|
+
return JSON.parse(t);
|
|
434
|
+
} catch {
|
|
435
|
+
return el;
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
function scopeFromStore(row) {
|
|
440
|
+
const settings = {};
|
|
441
|
+
for (const [k, v] of Object.entries(row.settings ?? {})) settings[k] = settingFromStore(v);
|
|
442
|
+
return {
|
|
443
|
+
id: row.id,
|
|
444
|
+
scope_type_id: row.scope_type_id,
|
|
445
|
+
organization_id: row.organization_id,
|
|
446
|
+
name: row.name ?? "",
|
|
447
|
+
description: row.description ?? "",
|
|
448
|
+
slug: row.slug ?? null,
|
|
449
|
+
sort_order: num(row.sort_order),
|
|
450
|
+
parent_scope_id: row.parent_scope_id ?? null,
|
|
451
|
+
settings,
|
|
452
|
+
created_by: row.created_by ?? null,
|
|
453
|
+
created_at: row.created_at ?? "",
|
|
454
|
+
updated_at: row.updated_at ?? ""
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
function scopeWithTypeFromStore(row) {
|
|
458
|
+
return { ...scopeFromStore(row), scope_type: row.scope_type ? scopeTypeLabelFromStore(row.scope_type) : null };
|
|
459
|
+
}
|
|
460
|
+
function archivedScopeTypeFromStore(row) {
|
|
461
|
+
const singular = row.label_singular ?? "";
|
|
462
|
+
return {
|
|
463
|
+
id: row.id,
|
|
464
|
+
organization_id: row.organization_id,
|
|
465
|
+
label_singular: singular,
|
|
466
|
+
label_plural: row.label_plural ?? singular,
|
|
467
|
+
icon: row.icon ?? "folder",
|
|
468
|
+
color: row.color ?? "",
|
|
469
|
+
archived_at: row.deleted_at ?? "",
|
|
470
|
+
created_by: row.created_by ?? null,
|
|
471
|
+
archived_scope_count: row.archived_scope_count ?? 0
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
var FILE_KERNEL_ID = "11111111-0000-4000-8000-000000000006";
|
|
475
|
+
var KINDS = /* @__PURE__ */ new Set([
|
|
476
|
+
"string",
|
|
477
|
+
"markdown",
|
|
478
|
+
"email",
|
|
479
|
+
"url",
|
|
480
|
+
"phone",
|
|
481
|
+
"color",
|
|
482
|
+
"number",
|
|
483
|
+
"percent",
|
|
484
|
+
"currency",
|
|
485
|
+
"boolean",
|
|
486
|
+
"date",
|
|
487
|
+
"datetime",
|
|
488
|
+
"time",
|
|
489
|
+
"object",
|
|
490
|
+
"array",
|
|
491
|
+
"reference",
|
|
492
|
+
"document"
|
|
493
|
+
]);
|
|
494
|
+
function contextFieldKind(doc, carried) {
|
|
495
|
+
if (carried?.as_text && carried.value_type && KINDS.has(carried.value_type)) return carried.value_type;
|
|
496
|
+
const behavior = String(doc.type ?? "text");
|
|
497
|
+
const fmt = doc.format ? String(doc.format) : "";
|
|
498
|
+
const shown = doc.display_format && typeof doc.display_format === "object" ? String(doc.display_format.id ?? "") : "";
|
|
499
|
+
const kind = String(doc.config?.kind ?? "");
|
|
500
|
+
const multi = Boolean(doc.multi);
|
|
501
|
+
if (behavior === "boolean") return "boolean";
|
|
502
|
+
if (behavior === "relation") return doc.relation_target === FILE_KERNEL_ID && !multi ? "document" : "reference";
|
|
503
|
+
if (behavior === "range") {
|
|
504
|
+
if (kind === "date") return "date";
|
|
505
|
+
if (kind === "datetime") return "datetime";
|
|
506
|
+
if (fmt === "percent" || fmt === "currency") return fmt;
|
|
507
|
+
return "number";
|
|
508
|
+
}
|
|
509
|
+
if (behavior === "text") {
|
|
510
|
+
if (multi) return "array";
|
|
511
|
+
if (fmt === "json") return "object";
|
|
512
|
+
for (const word of [fmt, shown]) {
|
|
513
|
+
if (word === "time" || word === "email" || word === "url" || word === "phone" || word === "color" || word === "markdown") return word;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return "string";
|
|
517
|
+
}
|
|
518
|
+
var POLICIES = /* @__PURE__ */ new Set(["include", "on_request", "exclude"]);
|
|
519
|
+
var SENSITIVITIES = /* @__PURE__ */ new Set(["public", "internal", "confidential", "restricted"]);
|
|
520
|
+
function contextFieldFromStore(row) {
|
|
521
|
+
const d = row.data ?? {};
|
|
522
|
+
const key = str(d.key);
|
|
523
|
+
const policy = str(d.context_policy, "include");
|
|
524
|
+
const sensitivity = str(d.sensitivity, "internal");
|
|
525
|
+
return {
|
|
526
|
+
id: row.id,
|
|
527
|
+
scope_type_id: row.scope_type_id,
|
|
528
|
+
organization_id: row.organization_id,
|
|
529
|
+
key,
|
|
530
|
+
label: str(d.label, key),
|
|
531
|
+
description: str(d.description),
|
|
532
|
+
kind: contextFieldKind(d, row.carried),
|
|
533
|
+
type: str(d.type, "text"),
|
|
534
|
+
multi: Boolean(d.multi),
|
|
535
|
+
format: strOrNull(d.format),
|
|
536
|
+
config: d.config ?? {},
|
|
537
|
+
relation_target: strOrNull(d.relation_target),
|
|
538
|
+
// The store's sort counts the scope's own two columns first (custom._ctx_store_item: + 2).
|
|
539
|
+
sort: Math.max(0, num(d.sort) - 2),
|
|
540
|
+
context_policy: POLICIES.has(policy) ? policy : "include",
|
|
541
|
+
sensitivity: SENSITIVITIES.has(sensitivity) ? sensitivity : "internal",
|
|
542
|
+
source: str(d.source, "manual"),
|
|
543
|
+
status: str(d.status, "active"),
|
|
544
|
+
status_note: strOrNull(d.status_note),
|
|
545
|
+
category: strOrNull(d.category),
|
|
546
|
+
tags: Array.isArray(d.tags) ? d.tags : [],
|
|
547
|
+
max_items: typeof d.max_items === "number" ? d.max_items : 1,
|
|
548
|
+
custom_component: d.custom_component ?? null,
|
|
549
|
+
reference_source: d.reference_source ?? null,
|
|
550
|
+
allowed_scope_type_ids: Array.isArray(d.allowed_scope_type_ids) ? d.allowed_scope_type_ids : null,
|
|
551
|
+
allowed_reference_types: Array.isArray(d.allowed_reference_types) ? d.allowed_reference_types : null,
|
|
552
|
+
review_interval_days: typeof d.review_interval_days === "number" ? d.review_interval_days : null,
|
|
553
|
+
depends_on: Array.isArray(d.depends_on) ? d.depends_on : [],
|
|
554
|
+
version: num(row.version, 1),
|
|
555
|
+
created_by: row.created_by ?? null,
|
|
556
|
+
created_at: row.created_at ?? "",
|
|
557
|
+
updated_at: row.updated_at ?? ""
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
var POLICY_TO_DOOR = { include: "always", on_request: "on_demand", exclude: "never" };
|
|
561
|
+
var SENSITIVITY_TO_DOOR = {
|
|
562
|
+
public: "public",
|
|
563
|
+
internal: "internal",
|
|
564
|
+
confidential: "restricted",
|
|
565
|
+
restricted: "privileged"
|
|
566
|
+
};
|
|
567
|
+
var SENSITIVITY_FROM_DOOR = {
|
|
568
|
+
public: "public",
|
|
569
|
+
internal: "internal",
|
|
570
|
+
restricted: "confidential",
|
|
571
|
+
privileged: "restricted"
|
|
572
|
+
};
|
|
573
|
+
function systemContextFieldFromStore(row, index) {
|
|
574
|
+
const key = str(row.key);
|
|
575
|
+
const kind = str(row.value_type, "string");
|
|
576
|
+
return {
|
|
577
|
+
id: str(row.id),
|
|
578
|
+
scope_type_id: SYSTEM_CONTEXT_TYPE_ID,
|
|
579
|
+
organization_id: "",
|
|
580
|
+
key,
|
|
581
|
+
label: str(row.display_name, key),
|
|
582
|
+
description: str(row.description),
|
|
583
|
+
kind: KINDS.has(kind) ? kind : "string",
|
|
584
|
+
type: "system",
|
|
585
|
+
multi: kind === "array",
|
|
586
|
+
format: null,
|
|
587
|
+
config: {},
|
|
588
|
+
relation_target: null,
|
|
589
|
+
sort: typeof row.sort_order === "number" ? row.sort_order : index,
|
|
590
|
+
context_policy: "include",
|
|
591
|
+
// The system table keeps the old sensitivity words (`restricted`/`privileged`), like the write doors.
|
|
592
|
+
sensitivity: SENSITIVITY_FROM_DOOR[str(row.sensitivity)] ?? "public",
|
|
593
|
+
source: "system",
|
|
594
|
+
status: "active",
|
|
595
|
+
status_note: null,
|
|
596
|
+
category: null,
|
|
597
|
+
tags: [],
|
|
598
|
+
max_items: 0,
|
|
599
|
+
custom_component: null,
|
|
600
|
+
reference_source: null,
|
|
601
|
+
allowed_scope_type_ids: null,
|
|
602
|
+
allowed_reference_types: null,
|
|
603
|
+
review_interval_days: null,
|
|
604
|
+
depends_on: [],
|
|
605
|
+
version: 0,
|
|
606
|
+
created_by: null,
|
|
607
|
+
created_at: "",
|
|
608
|
+
updated_at: "",
|
|
609
|
+
item_class: strOrNull(row.item_class)
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
function referencesOf(row) {
|
|
613
|
+
const v = row.value;
|
|
614
|
+
const list = Array.isArray(v) ? v : v == null ? [] : [v];
|
|
615
|
+
const labels = row.labels ?? {};
|
|
616
|
+
const files = row.files ?? {};
|
|
617
|
+
const toFile = row.field?.relation_target === FILE_KERNEL_ID;
|
|
618
|
+
const out = [];
|
|
619
|
+
for (const el of list) {
|
|
620
|
+
let ref = null;
|
|
621
|
+
if (typeof el === "string" && isUuidShape(el)) ref = { id: el, type: toFile ? "file" : "scope" };
|
|
622
|
+
else if (el && typeof el === "object" && typeof el.id === "string") {
|
|
623
|
+
const token = String(el.token ?? "");
|
|
624
|
+
const own = el.label;
|
|
625
|
+
ref = { id: el.id, type: token === "dataset" ? "table" : token || (toFile ? "file" : "scope") };
|
|
626
|
+
if (typeof own === "string" && own) ref.label = own;
|
|
627
|
+
}
|
|
628
|
+
if (!ref) continue;
|
|
629
|
+
const label = labels[ref.id];
|
|
630
|
+
if (label) ref.label = label;
|
|
631
|
+
const fileId = files[ref.id];
|
|
632
|
+
if (fileId) ref.file_id = fileId;
|
|
633
|
+
out.push(ref);
|
|
634
|
+
}
|
|
635
|
+
return out;
|
|
636
|
+
}
|
|
637
|
+
function contextValueFromStore(row) {
|
|
638
|
+
const kind = contextFieldKind(row.field ?? {}, row.field?.carried);
|
|
639
|
+
const carriedText = Boolean(row.field?.carried?.as_text);
|
|
640
|
+
const isRef = !carriedText && (kind === "reference" || kind === "document");
|
|
641
|
+
return {
|
|
642
|
+
scope_id: row.scope_id,
|
|
643
|
+
field_id: row.context_item_id,
|
|
644
|
+
key: row.key,
|
|
645
|
+
kind,
|
|
646
|
+
value: row.value ?? null,
|
|
647
|
+
references: isRef ? referencesOf(row) : [],
|
|
648
|
+
version: num(row.version, 1),
|
|
649
|
+
set_at: row.set_at ?? null,
|
|
650
|
+
source_type: row.source_type ?? null,
|
|
651
|
+
authored_by: row.authored_by ?? null,
|
|
652
|
+
whole_value: row.whole_value ?? null,
|
|
653
|
+
incomplete: null
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
async function sha256Hex(text) {
|
|
657
|
+
const subtle = globalThis.crypto?.subtle;
|
|
658
|
+
if (!subtle) return null;
|
|
659
|
+
const digest = await subtle.digest("SHA-256", new TextEncoder().encode(text));
|
|
660
|
+
return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
661
|
+
}
|
|
662
|
+
async function completeWholeValues(values, read) {
|
|
663
|
+
const texts = /* @__PURE__ */ new Map();
|
|
664
|
+
await Promise.all(
|
|
665
|
+
values.map(async (cell) => {
|
|
666
|
+
const w = cell.whole_value;
|
|
667
|
+
if (!w || w.in_value || typeof cell.value !== "string") return;
|
|
668
|
+
const head = cell.value;
|
|
669
|
+
const chars = typeof w.chars === "number" ? w.chars : null;
|
|
670
|
+
const incomplete = (fileId, why) => {
|
|
671
|
+
cell.incomplete = { head, chars, file_id: fileId, why };
|
|
672
|
+
};
|
|
673
|
+
if (!w.file_id) return incomplete(null, "The rest of this text is still being saved.");
|
|
674
|
+
if (!read) return incomplete(w.file_id, "This place cannot open the file that holds the whole text.");
|
|
675
|
+
if (!texts.has(w.file_id)) texts.set(w.file_id, read(w.file_id));
|
|
676
|
+
try {
|
|
677
|
+
const text = await texts.get(w.file_id);
|
|
678
|
+
if (chars !== null && [...text].length !== chars) {
|
|
679
|
+
return incomplete(w.file_id, `The file holds ${[...text].length} characters, not ${chars}.`);
|
|
680
|
+
}
|
|
681
|
+
if (w.sha256) {
|
|
682
|
+
const sha = await sha256Hex(text);
|
|
683
|
+
if (sha === null) return incomplete(w.file_id, "This browser cannot check the file.");
|
|
684
|
+
if (sha !== w.sha256) return incomplete(w.file_id, "The file's content is not the text the cell points at.");
|
|
685
|
+
}
|
|
686
|
+
cell.value = text;
|
|
687
|
+
} catch (thrown) {
|
|
688
|
+
incomplete(w.file_id, thrown instanceof Error ? thrown.message : String(thrown));
|
|
689
|
+
}
|
|
690
|
+
})
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
function definedOnly(fields) {
|
|
694
|
+
const out = {};
|
|
695
|
+
for (const [k, v] of Object.entries(fields)) if (v !== void 0) out[k] = v;
|
|
696
|
+
return out;
|
|
697
|
+
}
|
|
698
|
+
function fieldSpecForDoor(spec) {
|
|
699
|
+
return definedOnly({
|
|
700
|
+
key: spec.key,
|
|
701
|
+
display_name: spec.label,
|
|
702
|
+
value_type: spec.kind,
|
|
703
|
+
description: spec.description,
|
|
704
|
+
category: spec.category,
|
|
705
|
+
fetch_hint: spec.context_policy ? POLICY_TO_DOOR[spec.context_policy] : void 0,
|
|
706
|
+
sensitivity: spec.sensitivity ? SENSITIVITY_TO_DOOR[spec.sensitivity] : void 0,
|
|
707
|
+
tags: spec.tags,
|
|
708
|
+
sort_order: spec.sort,
|
|
709
|
+
status: spec.status,
|
|
710
|
+
status_note: spec.status_note,
|
|
711
|
+
custom_component: spec.custom_component,
|
|
712
|
+
review_interval_days: spec.review_interval_days,
|
|
713
|
+
allowed_reference_types: spec.allowed_reference_types,
|
|
714
|
+
max_items: spec.max_items,
|
|
715
|
+
allowed_scope_type_ids: spec.allowed_scope_type_ids,
|
|
716
|
+
reference_source: spec.reference_source
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
function referenceFence(references) {
|
|
720
|
+
const kinds = [...new Set(references.map((r) => r.type))];
|
|
721
|
+
const type = kinds.length === 1 ? kinds[0] : "mixed";
|
|
722
|
+
const items = references.map((r) => {
|
|
723
|
+
if (r.type === "file" && r.file_id) return r.label ? { file_id: r.file_id, label: r.label } : { file_id: r.file_id };
|
|
724
|
+
const item = { id: r.id };
|
|
725
|
+
if (r.label) item.label = r.label;
|
|
726
|
+
if (kinds.length > 1) item.type = r.type;
|
|
727
|
+
return item;
|
|
728
|
+
});
|
|
729
|
+
return "```matrx\n" + JSON.stringify({ matrx_version: 1, kind: "reference", type, items }, null, 2) + "\n```";
|
|
730
|
+
}
|
|
731
|
+
function valuePayloadForDoor(write) {
|
|
732
|
+
const base = definedOnly({
|
|
733
|
+
scope_id: write.scope_id,
|
|
734
|
+
context_item_id: write.field_id,
|
|
735
|
+
source_type: write.source_type,
|
|
736
|
+
change_summary: write.change_summary
|
|
737
|
+
});
|
|
738
|
+
const v = write.value;
|
|
739
|
+
if (write.kind === "reference" || write.kind === "document") {
|
|
740
|
+
if (write.references) return { ...base, value_text: write.references.length ? referenceFence(write.references) : null };
|
|
741
|
+
if (write.kind === "document" && typeof v === "string") return { ...base, value_document_url: v || null };
|
|
742
|
+
return { ...base, value_text: typeof v === "string" ? v || null : null };
|
|
743
|
+
}
|
|
744
|
+
if (v === null || v === void 0 || v === "") {
|
|
745
|
+
return { ...base, value_text: null };
|
|
746
|
+
}
|
|
747
|
+
switch (write.kind) {
|
|
748
|
+
case "number":
|
|
749
|
+
case "percent":
|
|
750
|
+
return typeof v === "number" ? { ...base, value_number: v } : { ...base, value_text: String(v) };
|
|
751
|
+
case "currency":
|
|
752
|
+
return typeof v === "object" ? { ...base, value_json: v } : typeof v === "number" ? { ...base, value_number: v } : { ...base, value_text: String(v) };
|
|
753
|
+
case "boolean":
|
|
754
|
+
return typeof v === "boolean" ? { ...base, value_boolean: v } : { ...base, value_text: String(v) };
|
|
755
|
+
case "date":
|
|
756
|
+
return { ...base, value_date: String(v) };
|
|
757
|
+
case "datetime":
|
|
758
|
+
return { ...base, value_timestamp: String(v) };
|
|
759
|
+
case "time":
|
|
760
|
+
return { ...base, value_time: String(v) };
|
|
761
|
+
case "object":
|
|
762
|
+
case "array":
|
|
763
|
+
return typeof v === "object" ? { ...base, value_json: v } : { ...base, value_text: String(v) };
|
|
764
|
+
default:
|
|
765
|
+
return typeof v === "object" ? { ...base, value_json: v } : { ...base, value_text: String(v) };
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// src/scopes/rules.ts
|
|
770
|
+
function toSlug(name) {
|
|
771
|
+
return name.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
772
|
+
}
|
|
773
|
+
function isValidSlug(s) {
|
|
774
|
+
return /^[a-z0-9]+(?:[-_][a-z0-9]+)*$/.test(s);
|
|
775
|
+
}
|
|
776
|
+
var RESERVED_SCOPE_SLUGS = /* @__PURE__ */ new Set(["context-items", "scopes", "edit", "manage", "new", "settings"]);
|
|
777
|
+
function isReservedSlug(s) {
|
|
778
|
+
return RESERVED_SCOPE_SLUGS.has(s.toLowerCase().replace(/_/g, "-"));
|
|
779
|
+
}
|
|
780
|
+
function resolveSlug(supplied, fallbackName) {
|
|
781
|
+
const slug = supplied?.trim() || toSlug(fallbackName);
|
|
782
|
+
if (!slug || !isValidSlug(slug)) {
|
|
783
|
+
return {
|
|
784
|
+
ok: false,
|
|
785
|
+
error: { code: "invalid_argument", message: "Use letters, numbers and single hyphens for the short name." }
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
if (isReservedSlug(slug)) {
|
|
789
|
+
return { ok: false, error: { code: "invalid_argument", message: `"${slug}" is a page name here. Pick another short name.` } };
|
|
790
|
+
}
|
|
791
|
+
return { ok: true, data: slug };
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// src/scopes/doors.ts
|
|
795
|
+
var FIELD_KERNEL_ID = "11111111-0000-4000-8000-000000000002";
|
|
796
|
+
var SCOPES_PER_CALL = 500;
|
|
797
|
+
var SCOPE_TYPES_PER_FIELDS_CALL = 100;
|
|
798
|
+
var SCOPES_PER_VALUES_CALL = 200;
|
|
799
|
+
var TYPE_SCOPES_PAGE = 200;
|
|
800
|
+
var ARCHIVED_SCOPES_PAGE = 200;
|
|
801
|
+
var uniq = (ids) => [...new Set(ids.filter(Boolean))];
|
|
802
|
+
async function inBatches(ids, size, call) {
|
|
803
|
+
const unique = uniq(ids);
|
|
804
|
+
const batches = [];
|
|
805
|
+
for (let i = 0; i < unique.length; i += size) batches.push(unique.slice(i, i + size));
|
|
806
|
+
const answers = await Promise.all(batches.map(call));
|
|
807
|
+
const out = [];
|
|
808
|
+
for (const a of answers) {
|
|
809
|
+
if (!a.ok) return a;
|
|
810
|
+
out.push(...a.data ?? []);
|
|
811
|
+
}
|
|
812
|
+
return { ok: true, data: out };
|
|
813
|
+
}
|
|
814
|
+
function noRow(door) {
|
|
815
|
+
return { ok: false, error: { code: "internal", message: `${door} answered without the row it wrote.`, diagnostic: `@ai-matrx/records/scopes: ${door}` } };
|
|
816
|
+
}
|
|
817
|
+
var VALUE_DOOR_CODES = {
|
|
818
|
+
unauthorized: "door",
|
|
819
|
+
forbidden: "door",
|
|
820
|
+
forbidden_org: "door",
|
|
821
|
+
not_found: "not_found",
|
|
822
|
+
invalid_argument: "invalid_argument",
|
|
823
|
+
conflict: "stale_write",
|
|
824
|
+
unavailable: "not_supported"
|
|
825
|
+
};
|
|
826
|
+
function createScopeDoors(call) {
|
|
827
|
+
async function write(door, args) {
|
|
828
|
+
const res = await call(door, args, `scopes.${door}`);
|
|
829
|
+
if (!res.ok) return res;
|
|
830
|
+
const row = res.data?.row;
|
|
831
|
+
if (!row || typeof row !== "object") return noRow(door);
|
|
832
|
+
return { ok: true, data: row };
|
|
833
|
+
}
|
|
834
|
+
async function archiveOrRestore(door, args, id) {
|
|
835
|
+
const res = await call(door, args, `scopes.${door}`);
|
|
836
|
+
return res.ok ? { ok: true, data: { id } } : res;
|
|
837
|
+
}
|
|
838
|
+
async function fieldById(scopeTypeId, fieldId, door) {
|
|
839
|
+
const fields = await doors.fields([scopeTypeId]);
|
|
840
|
+
if (!fields.ok) return fields;
|
|
841
|
+
const field = fields.data.find((f) => f.id === fieldId);
|
|
842
|
+
return field ? { ok: true, data: field } : noRow(door);
|
|
843
|
+
}
|
|
844
|
+
const doors = {
|
|
845
|
+
async tree(organizationIds) {
|
|
846
|
+
const ids = uniq(organizationIds);
|
|
847
|
+
if (ids.length === 0) return { ok: true, data: [] };
|
|
848
|
+
const res = await call("context_tree", { p_organization_ids: ids }, "scopes.tree");
|
|
849
|
+
if (!res.ok) return res;
|
|
850
|
+
const byType = /* @__PURE__ */ new Map();
|
|
851
|
+
for (const s of res.data?.scopes ?? []) {
|
|
852
|
+
const scope = scopeFromStore(s);
|
|
853
|
+
const list = byType.get(scope.scope_type_id) ?? [];
|
|
854
|
+
list.push(scope);
|
|
855
|
+
byType.set(scope.scope_type_id, list);
|
|
856
|
+
}
|
|
857
|
+
return { ok: true, data: (res.data?.types ?? []).map((t) => ({ ...scopeTypeFromStore(t), scopes: byType.get(t.id) ?? [] })) };
|
|
858
|
+
},
|
|
859
|
+
async types(organizationIds, options) {
|
|
860
|
+
const withCounts = options?.withCounts ?? true;
|
|
861
|
+
const ids = uniq(organizationIds);
|
|
862
|
+
if (ids.length === 0) return { ok: true, data: { types: [], counts: withCounts ? {} : null } };
|
|
863
|
+
const res = await call("context_tree_types", { p_organization_ids: ids, p_with_counts: withCounts }, "scopes.types");
|
|
864
|
+
if (!res.ok) return res;
|
|
865
|
+
const rows = res.data?.types ?? [];
|
|
866
|
+
const counts = withCounts ? {} : null;
|
|
867
|
+
if (counts) for (const r of rows) counts[r.id] = Number(r.scope_count ?? 0);
|
|
868
|
+
return { ok: true, data: { types: rows.map(scopeTypeFromStore), counts } };
|
|
869
|
+
},
|
|
870
|
+
async typeScopesPage(scopeTypeId, offset = 0, limit = TYPE_SCOPES_PAGE) {
|
|
871
|
+
const res = await call(
|
|
872
|
+
"context_tree_type_scopes",
|
|
873
|
+
{ p_scope_type_id: scopeTypeId, p_offset: offset, p_limit: limit },
|
|
874
|
+
"scopes.typeScopesPage"
|
|
875
|
+
);
|
|
876
|
+
if (!res.ok) return res;
|
|
877
|
+
return {
|
|
878
|
+
ok: true,
|
|
879
|
+
data: { scopes: (res.data?.scopes ?? []).map(scopeFromStore), total: Number(res.data?.total ?? 0), next_offset: res.data?.next_offset ?? null }
|
|
880
|
+
};
|
|
881
|
+
},
|
|
882
|
+
async search(organizationIds, query, limit = 100) {
|
|
883
|
+
const ids = uniq(organizationIds);
|
|
884
|
+
const q = query.trim();
|
|
885
|
+
if (ids.length === 0 || q === "") return { ok: true, data: { scopes: [], total: 0 } };
|
|
886
|
+
const res = await call(
|
|
887
|
+
"context_tree_search",
|
|
888
|
+
{ p_organization_ids: ids, p_query: q, p_limit: limit },
|
|
889
|
+
"scopes.search"
|
|
890
|
+
);
|
|
891
|
+
if (!res.ok) return res;
|
|
892
|
+
return { ok: true, data: { scopes: (res.data?.scopes ?? []).map(scopeFromStore), total: Number(res.data?.total ?? 0) } };
|
|
893
|
+
},
|
|
894
|
+
async scopes(ids) {
|
|
895
|
+
const res = await inBatches(ids, SCOPES_PER_CALL, (batch) => call("context_scopes", { p_scope_ids: batch }, "scopes.scopes"));
|
|
896
|
+
return res.ok ? { ok: true, data: res.data.map(scopeWithTypeFromStore) } : res;
|
|
897
|
+
},
|
|
898
|
+
async fields(scopeTypeIds) {
|
|
899
|
+
const res = await inBatches(
|
|
900
|
+
scopeTypeIds,
|
|
901
|
+
SCOPE_TYPES_PER_FIELDS_CALL,
|
|
902
|
+
(batch) => call("context_items", { p_scope_type_ids: batch }, "scopes.fields")
|
|
903
|
+
);
|
|
904
|
+
return res.ok ? { ok: true, data: res.data.map(contextFieldFromStore) } : res;
|
|
905
|
+
},
|
|
906
|
+
async field(fieldId, organizationIds) {
|
|
907
|
+
const orgs = uniq(organizationIds);
|
|
908
|
+
const answers = await Promise.all(
|
|
909
|
+
orgs.map(
|
|
910
|
+
(org) => call(
|
|
911
|
+
"read_records_by_ids",
|
|
912
|
+
{ p_organization_id: org, p_table_id: FIELD_KERNEL_ID, p_record_ids: [fieldId], p_by_id: false },
|
|
913
|
+
"scopes.field"
|
|
914
|
+
)
|
|
915
|
+
)
|
|
916
|
+
);
|
|
917
|
+
let refusal = null;
|
|
918
|
+
for (const answer of answers) {
|
|
919
|
+
if (!answer.ok) {
|
|
920
|
+
refusal ??= answer;
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
const typeId = answer.data?.find((r) => r.id === fieldId)?.document?.entity_definition_id;
|
|
924
|
+
if (typeof typeId !== "string") continue;
|
|
925
|
+
const fields = await doors.fields([typeId]);
|
|
926
|
+
if (!fields.ok) return fields;
|
|
927
|
+
const field = fields.data.find((f) => f.id === fieldId);
|
|
928
|
+
if (field) return { ok: true, data: field };
|
|
929
|
+
}
|
|
930
|
+
return refusal ?? {
|
|
931
|
+
ok: false,
|
|
932
|
+
error: { code: "not_found", message: "This context field could not be found.", diagnostic: `@ai-matrx/records/scopes: field ${fieldId}` }
|
|
933
|
+
};
|
|
934
|
+
},
|
|
935
|
+
async values(scopeIds, options) {
|
|
936
|
+
const res = await inBatches(
|
|
937
|
+
scopeIds,
|
|
938
|
+
SCOPES_PER_VALUES_CALL,
|
|
939
|
+
(batch) => call("context_values", { p_scope_ids: batch }, "scopes.values")
|
|
940
|
+
);
|
|
941
|
+
if (!res.ok) return res;
|
|
942
|
+
const cells = res.data.map(contextValueFromStore);
|
|
943
|
+
await completeWholeValues(cells, options?.readFileText ?? null);
|
|
944
|
+
return { ok: true, data: cells };
|
|
945
|
+
},
|
|
946
|
+
async archivedTypes(organizationId) {
|
|
947
|
+
const res = await call("context_archived_types", { p_organization_id: organizationId }, "scopes.archivedTypes");
|
|
948
|
+
return res.ok ? { ok: true, data: (res.data ?? []).map(archivedScopeTypeFromStore) } : res;
|
|
949
|
+
},
|
|
950
|
+
async archivedScopesOfType(organizationId, scopeTypeId, wanted) {
|
|
951
|
+
const out = [];
|
|
952
|
+
for (let offset = 0; out.length < wanted.size; offset += ARCHIVED_SCOPES_PAGE) {
|
|
953
|
+
const res = await call(
|
|
954
|
+
"read_records_archived",
|
|
955
|
+
{ p_organization_id: organizationId, p_table_id: scopeTypeId, p_lane: "org", p_by_id: false, p_limit: ARCHIVED_SCOPES_PAGE, p_offset: offset },
|
|
956
|
+
"scopes.archivedScopesOfType"
|
|
957
|
+
);
|
|
958
|
+
if (!res.ok) return res;
|
|
959
|
+
const rows = res.data ?? [];
|
|
960
|
+
for (const r of rows) {
|
|
961
|
+
if (!wanted.has(r.id)) continue;
|
|
962
|
+
out.push({
|
|
963
|
+
id: r.id,
|
|
964
|
+
name: typeof r.document?.name === "string" ? r.document.name : null,
|
|
965
|
+
slug: typeof r.document?.slug === "string" ? r.document.slug : null,
|
|
966
|
+
archived_at: r.archived_at ?? null
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
if (rows.length < ARCHIVED_SCOPES_PAGE) break;
|
|
970
|
+
}
|
|
971
|
+
return { ok: true, data: out };
|
|
972
|
+
},
|
|
973
|
+
async systemItems() {
|
|
974
|
+
const res = await call("context_system_items", {}, "scopes.systemItems");
|
|
975
|
+
return res.ok ? { ok: true, data: (res.data ?? []).map(systemContextFieldFromStore) } : res;
|
|
976
|
+
},
|
|
977
|
+
async tableTemplates(organizationId) {
|
|
978
|
+
const res = await call("table_templates", { p_org_id: organizationId }, "scopes.tableTemplates");
|
|
979
|
+
return res.ok ? { ok: true, data: Array.isArray(res.data) ? res.data : [] } : res;
|
|
980
|
+
},
|
|
981
|
+
async templates() {
|
|
982
|
+
const res = await call("context_templates", {}, "scopes.templates");
|
|
983
|
+
return res.ok ? { ok: true, data: res.data ?? [] } : res;
|
|
984
|
+
},
|
|
985
|
+
async createType(organizationId, spec) {
|
|
986
|
+
const slug = resolveSlug(spec.slug, spec.label_plural || spec.label_singular || "");
|
|
987
|
+
if (!slug.ok) return slug;
|
|
988
|
+
const res = await write("context_type_write", {
|
|
989
|
+
p_organization_id: organizationId,
|
|
990
|
+
p_type_id: null,
|
|
991
|
+
p_spec: definedOnly({
|
|
992
|
+
label_singular: spec.label_singular,
|
|
993
|
+
label_plural: spec.label_plural,
|
|
994
|
+
icon: spec.icon ?? "folder",
|
|
995
|
+
description: spec.description ?? "",
|
|
996
|
+
sort_order: spec.sort_order ?? 0,
|
|
997
|
+
max_assignments: spec.max_assignments_per_entity ?? void 0,
|
|
998
|
+
default_variable_keys: spec.default_variable_keys ?? [],
|
|
999
|
+
color: spec.color,
|
|
1000
|
+
slug: slug.data
|
|
1001
|
+
})
|
|
1002
|
+
});
|
|
1003
|
+
return res.ok ? { ok: true, data: scopeTypeFromStore(res.data) } : res;
|
|
1004
|
+
},
|
|
1005
|
+
async updateType(typeId, spec) {
|
|
1006
|
+
let slug;
|
|
1007
|
+
if (spec.slug !== void 0) {
|
|
1008
|
+
const resolved = resolveSlug(spec.slug, spec.label_plural ?? spec.label_singular ?? "");
|
|
1009
|
+
if (!resolved.ok) return resolved;
|
|
1010
|
+
slug = resolved.data;
|
|
1011
|
+
}
|
|
1012
|
+
const res = await write("context_type_write", {
|
|
1013
|
+
p_organization_id: null,
|
|
1014
|
+
p_type_id: typeId,
|
|
1015
|
+
p_spec: definedOnly({
|
|
1016
|
+
label_singular: spec.label_singular,
|
|
1017
|
+
label_plural: spec.label_plural,
|
|
1018
|
+
icon: spec.icon,
|
|
1019
|
+
description: spec.description,
|
|
1020
|
+
sort_order: spec.sort_order,
|
|
1021
|
+
max_assignments: spec.max_assignments_per_entity,
|
|
1022
|
+
color: spec.color,
|
|
1023
|
+
slug
|
|
1024
|
+
})
|
|
1025
|
+
});
|
|
1026
|
+
return res.ok ? { ok: true, data: scopeTypeFromStore(res.data) } : res;
|
|
1027
|
+
},
|
|
1028
|
+
archiveType: (typeId) => archiveOrRestore("context_type_archive", { p_type_id: typeId }, typeId),
|
|
1029
|
+
restoreType: (typeId) => archiveOrRestore("context_type_restore", { p_type_id: typeId }, typeId),
|
|
1030
|
+
async createScope(organizationId, typeId, spec) {
|
|
1031
|
+
const slug = resolveSlug(spec.slug, spec.name ?? "");
|
|
1032
|
+
if (!slug.ok) return slug;
|
|
1033
|
+
const res = await write("context_scope_write", {
|
|
1034
|
+
p_organization_id: organizationId,
|
|
1035
|
+
p_scope_id: null,
|
|
1036
|
+
p_type_id: typeId,
|
|
1037
|
+
p_spec: definedOnly({
|
|
1038
|
+
name: spec.name,
|
|
1039
|
+
parent_scope_id: spec.parent_scope_id,
|
|
1040
|
+
description: spec.description ?? "",
|
|
1041
|
+
settings: spec.settings ?? {},
|
|
1042
|
+
slug: slug.data,
|
|
1043
|
+
sort_order: spec.sort_order
|
|
1044
|
+
})
|
|
1045
|
+
});
|
|
1046
|
+
return res.ok ? { ok: true, data: scopeFromStore(res.data) } : res;
|
|
1047
|
+
},
|
|
1048
|
+
async updateScope(scopeId, spec) {
|
|
1049
|
+
let slug;
|
|
1050
|
+
if (spec.slug !== void 0) {
|
|
1051
|
+
const resolved = resolveSlug(spec.slug, spec.name ?? "");
|
|
1052
|
+
if (!resolved.ok) return resolved;
|
|
1053
|
+
slug = resolved.data;
|
|
1054
|
+
}
|
|
1055
|
+
const res = await write("context_scope_write", {
|
|
1056
|
+
p_organization_id: null,
|
|
1057
|
+
p_scope_id: scopeId,
|
|
1058
|
+
p_type_id: null,
|
|
1059
|
+
p_spec: definedOnly({ name: spec.name, description: spec.description, settings: spec.settings, slug, sort_order: spec.sort_order })
|
|
1060
|
+
});
|
|
1061
|
+
return res.ok ? { ok: true, data: scopeFromStore(res.data) } : res;
|
|
1062
|
+
},
|
|
1063
|
+
archiveScope: (scopeId) => archiveOrRestore("context_scope_archive", { p_scope_id: scopeId }, scopeId),
|
|
1064
|
+
restoreScope: (scopeId) => archiveOrRestore("context_scope_restore", { p_scope_id: scopeId }, scopeId),
|
|
1065
|
+
async createField(scopeTypeId, spec) {
|
|
1066
|
+
const res = await write("context_item_write", {
|
|
1067
|
+
p_item_id: null,
|
|
1068
|
+
p_scope_type_id: scopeTypeId,
|
|
1069
|
+
p_spec: fieldSpecForDoor({ kind: "string", description: "", context_policy: "on_request", sensitivity: "internal", tags: [], ...spec })
|
|
1070
|
+
});
|
|
1071
|
+
if (!res.ok) return res;
|
|
1072
|
+
return fieldById(scopeTypeId, String(res.data.id), "context_item_write");
|
|
1073
|
+
},
|
|
1074
|
+
async updateField(fieldId, scopeTypeId, spec) {
|
|
1075
|
+
const res = await write("context_item_write", { p_item_id: fieldId, p_scope_type_id: null, p_spec: fieldSpecForDoor(spec) });
|
|
1076
|
+
if (!res.ok) return res;
|
|
1077
|
+
return fieldById(scopeTypeId, fieldId, "context_item_write");
|
|
1078
|
+
},
|
|
1079
|
+
archiveField: (fieldId) => archiveOrRestore("context_item_archive", { p_item_id: fieldId }, fieldId),
|
|
1080
|
+
restoreField: (fieldId) => archiveOrRestore("context_item_restore", { p_item_id: fieldId }, fieldId),
|
|
1081
|
+
async writeValue(cell) {
|
|
1082
|
+
const res = await call(
|
|
1083
|
+
"context_value_write",
|
|
1084
|
+
{ p_payload: { source_type: "ai_enriched", ...valuePayloadForDoor(cell) } },
|
|
1085
|
+
"scopes.writeValue"
|
|
1086
|
+
);
|
|
1087
|
+
if (!res.ok) return res;
|
|
1088
|
+
const envelope = res.data ?? {};
|
|
1089
|
+
if (!envelope.ok) {
|
|
1090
|
+
const code = VALUE_DOOR_CODES[envelope.error?.code ?? ""] ?? "internal";
|
|
1091
|
+
return { ok: false, error: { code, message: envelope.error?.message ?? "The value was not saved." } };
|
|
1092
|
+
}
|
|
1093
|
+
return {
|
|
1094
|
+
ok: true,
|
|
1095
|
+
data: {
|
|
1096
|
+
scope_id: cell.scope_id,
|
|
1097
|
+
field_id: cell.field_id,
|
|
1098
|
+
version: Number(envelope.data?.version ?? 1),
|
|
1099
|
+
source_type: String(envelope.data?.source_type ?? cell.source_type ?? "ai_enriched")
|
|
1100
|
+
}
|
|
1101
|
+
};
|
|
1102
|
+
},
|
|
1103
|
+
tableProvision({ organization_id, item_id, scope_id, home_id }) {
|
|
1104
|
+
return call(
|
|
1105
|
+
"scope_table_provision",
|
|
1106
|
+
{ p_organization_id: organization_id, p_item_id: item_id, p_scope_id: scope_id, p_home_id: home_id ?? null },
|
|
1107
|
+
"scopeTableProvision"
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
1111
|
+
return doors;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// src/core/storeDoors.ts
|
|
1115
|
+
var STORE_SCHEMA = "custom";
|
|
1116
|
+
function storeDoors(client) {
|
|
1117
|
+
const source = supabaseDataSource(client ?? {});
|
|
1118
|
+
const call = (fn, args = {}) => source.rpc(fn, args, { schema: STORE_SCHEMA });
|
|
1119
|
+
const typed = (fn, args) => call(fn, args);
|
|
1120
|
+
return {
|
|
1121
|
+
rpc: call,
|
|
1122
|
+
async whereIdOpens(id) {
|
|
1123
|
+
const raw = await call("where_id_opens", { p_id: id });
|
|
1124
|
+
const e = raw.error;
|
|
1125
|
+
return {
|
|
1126
|
+
data: raw.data ?? null,
|
|
1127
|
+
error: e ? { code: typeof e.code === "string" ? e.code : null, message: typeof e.message === "string" ? e.message : "The record store refused." } : null
|
|
1128
|
+
};
|
|
1129
|
+
},
|
|
1130
|
+
tableKernelId: () => typed("table_kernel_id", {}),
|
|
1131
|
+
readRecordsByIds: (organizationId, tableId, ids) => typed("read_records_by_ids", { p_organization_id: organizationId, p_table_id: tableId, p_record_ids: ids }),
|
|
1132
|
+
readRecordsPage: (args) => typed("read_records_page", args),
|
|
1133
|
+
recordTable: (organizationId, recordId) => typed("record_table", { p_organization_id: organizationId, p_record_id: recordId }),
|
|
1134
|
+
applicableFields: (organizationId, tableId) => typed("applicable_fields", { p_organization_id: organizationId, p_table_id: tableId }),
|
|
1135
|
+
fieldDeclare: (organizationId, tableId, spec) => typed("field_declare", { p_organization_id: organizationId, p_table_id: tableId, p_spec: spec }),
|
|
1136
|
+
recordUpdate: (organizationId, recordId, patch) => typed("record_update", { p_organization_id: organizationId, p_record_id: recordId, p_patch: patch }),
|
|
1137
|
+
entityReferenceWords: (organizationId, refs) => typed("entity_reference_words", { p_organization_id: organizationId, p_refs: refs.map((r) => ({ token: r.token, id: r.id })) }),
|
|
1138
|
+
templates: (filter) => typed("templates", { p_filter: filter }),
|
|
1139
|
+
templateFromTables: ({ organizationId, tableIds, includeRows, rowsPerTable }) => typed("template_from_tables", {
|
|
1140
|
+
p_organization_id: organizationId,
|
|
1141
|
+
p_table_ids: tableIds,
|
|
1142
|
+
p_include_rows: includeRows,
|
|
1143
|
+
p_rows_per_table: rowsPerTable
|
|
1144
|
+
}),
|
|
1145
|
+
templateDeclare: (scope, spec) => typed("template_declare", { p_scope: scope, p_spec: spec }),
|
|
1146
|
+
templateKeep: (templateId) => typed("template_keep", { p_template_id: templateId }),
|
|
1147
|
+
templateArchive: (templateId, restore) => typed("template_archive", restore === void 0 ? { p_template_id: templateId } : { p_template_id: templateId, p_restore: restore }),
|
|
1148
|
+
templateInstallClaim: ({ organizationId, installId, kind, label, sourceId, leaseSeconds }) => typed("template_install_claim", {
|
|
1149
|
+
p_organization_id: organizationId,
|
|
1150
|
+
p_install_id: installId,
|
|
1151
|
+
p_kind: kind,
|
|
1152
|
+
p_label: label,
|
|
1153
|
+
...sourceId ? { p_source_id: sourceId } : {},
|
|
1154
|
+
p_lease_seconds: leaseSeconds
|
|
1155
|
+
}),
|
|
1156
|
+
templateInstallNote: ({ organizationId, installId, kind, id, label }) => typed("template_install_note", {
|
|
1157
|
+
p_organization_id: organizationId,
|
|
1158
|
+
p_install_id: installId,
|
|
1159
|
+
p_kind: kind,
|
|
1160
|
+
p_id: id,
|
|
1161
|
+
p_label: label
|
|
1162
|
+
})
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// src/core/table-logic.ts
|
|
1167
|
+
function isBulkOpError(r) {
|
|
1168
|
+
return typeof r === "object" && r !== null && "error" in r;
|
|
1169
|
+
}
|
|
1170
|
+
function describeBulkFailures(failed) {
|
|
1171
|
+
const inTrash = failed.filter((f) => f.error === "row_in_trash").length;
|
|
1172
|
+
const missing = failed.length - inTrash;
|
|
1173
|
+
const parts = [];
|
|
1174
|
+
if (inTrash > 0) {
|
|
1175
|
+
parts.push(
|
|
1176
|
+
`${inTrash} row${inTrash === 1 ? " is" : "s are"} in Trash \u2014 restore ${inTrash === 1 ? "it" : "them"} from Trash to edit`
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
if (missing > 0) {
|
|
1180
|
+
parts.push(
|
|
1181
|
+
`${missing} row${missing === 1 ? "" : "s"} could not be found \u2014 ${missing === 1 ? "it" : "they"} may have been removed by someone else`
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
return `${parts.join("; ")}.`;
|
|
1185
|
+
}
|
|
1186
|
+
function isServiceFailure(r) {
|
|
1187
|
+
return r.success === false;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// src/objectOrganization.ts
|
|
1191
|
+
var WHERE_ID_OPENS_DOOR = "where_id_opens";
|
|
1192
|
+
function doorIsAbsent(error) {
|
|
1193
|
+
if (error.code === "PGRST202" || error.code === "42883") return true;
|
|
1194
|
+
return /could not find the function/i.test(error.message ?? "");
|
|
1195
|
+
}
|
|
1196
|
+
var DOOR_ABSENT_WHY = "custom.where_id_opens is missing from this database, so this page cannot tell which organization the record lives in and holds instead of guessing. Remedy: the chair applies migrations/campaign/openbyid_one_address_opens_any_id.sql and openbyid_the_resolver_can_be_reached.sql.";
|
|
1197
|
+
async function resolveObjectOrganization(dataSource, id) {
|
|
1198
|
+
let answered;
|
|
1199
|
+
try {
|
|
1200
|
+
answered = await dataSource.rpc(WHERE_ID_OPENS_DOOR, { p_id: id }, { schema: "custom" });
|
|
1201
|
+
} catch (thrown) {
|
|
1202
|
+
return { state: "unavailable", why: thrown instanceof Error ? thrown.message : "The record store did not answer." };
|
|
1203
|
+
}
|
|
1204
|
+
return readObjectOrganizationAnswer(answered, id);
|
|
1205
|
+
}
|
|
1206
|
+
function readObjectOrganizationAnswer(answered, id) {
|
|
1207
|
+
if (answered.error) {
|
|
1208
|
+
if (doorIsAbsent(answered.error)) {
|
|
1209
|
+
console.warn(`[@ai-matrx/records] ${DOOR_ABSENT_WHY}`);
|
|
1210
|
+
return { state: "unavailable", why: DOOR_ABSENT_WHY };
|
|
1211
|
+
}
|
|
1212
|
+
return { state: "unavailable", why: answered.error.message ?? "The record store refused to say where this is." };
|
|
1213
|
+
}
|
|
1214
|
+
const row = answered.data;
|
|
1215
|
+
if (row === null || row === void 0) return { state: "not-given" };
|
|
1216
|
+
if (typeof row !== "object" || typeof row.organization_id !== "string" || typeof row.kind !== "string") {
|
|
1217
|
+
return { state: "unavailable", why: "The record store answered something this page cannot read." };
|
|
1218
|
+
}
|
|
1219
|
+
return {
|
|
1220
|
+
state: "found",
|
|
1221
|
+
organizationId: row.organization_id,
|
|
1222
|
+
kind: row.kind,
|
|
1223
|
+
path: typeof row.path === "string" ? row.path : null,
|
|
1224
|
+
live: row.live !== false,
|
|
1225
|
+
resolvedId: typeof row.resolved_id === "string" ? row.resolved_id : id
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// src/door-call/index.ts
|
|
1230
|
+
function lightDoorCall(client, options = {}) {
|
|
1231
|
+
return createDoorCall({ ...options, dataSource: asRecordsDataSource(client) });
|
|
1232
|
+
}
|
|
1233
|
+
function lightScopeDoors(client, options = {}) {
|
|
1234
|
+
return createScopeDoors(lightDoorCall(client, options));
|
|
1235
|
+
}
|
|
1236
|
+
function lightPickListDoors(client, options = {}) {
|
|
1237
|
+
return createPickListDoors(lightDoorCall(client, options));
|
|
1238
|
+
}
|
|
1239
|
+
export {
|
|
1240
|
+
A_PERSONS_REFUSAL,
|
|
1241
|
+
DEFAULT_DOOR_SCHEMA,
|
|
1242
|
+
PICK_LIST_DOORS,
|
|
1243
|
+
STORE_SCHEMA,
|
|
1244
|
+
asRecordsDataSource,
|
|
1245
|
+
canonicalArgs,
|
|
1246
|
+
createDoorCall,
|
|
1247
|
+
createPickListDoors,
|
|
1248
|
+
createScopeDoors,
|
|
1249
|
+
describeBulkFailures,
|
|
1250
|
+
doorAbsentRefusal,
|
|
1251
|
+
isAPersonsRefusal,
|
|
1252
|
+
isBulkOpError,
|
|
1253
|
+
isServiceFailure,
|
|
1254
|
+
lightDoorCall,
|
|
1255
|
+
lightPickListDoors,
|
|
1256
|
+
lightScopeDoors,
|
|
1257
|
+
primedDoorAnswer,
|
|
1258
|
+
readObjectOrganizationAnswer,
|
|
1259
|
+
recordsErrorSink,
|
|
1260
|
+
resolveObjectOrganization,
|
|
1261
|
+
storeDoors,
|
|
1262
|
+
supabaseDataSource
|
|
1263
|
+
};
|
|
1264
|
+
//# sourceMappingURL=index.js.map
|