@coffer-org/server 3.4.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth-api.js +4 -5
- package/dist/cache-clock.d.ts +8 -0
- package/dist/cache-clock.js +103 -0
- package/dist/cache-refresh.d.ts +7 -0
- package/dist/cache-refresh.js +322 -0
- package/dist/collection-io.d.ts +13 -0
- package/dist/collection-io.js +40 -29
- package/dist/compute-unit.d.ts +66 -0
- package/dist/compute-unit.js +369 -0
- package/dist/condition-where.js +1 -3
- package/dist/data-dir.d.ts +3 -0
- package/dist/data-dir.js +39 -0
- package/dist/entity-schema.js +5 -2
- package/dist/extend-table.d.ts +1 -1
- package/dist/extend-table.js +22 -8
- package/dist/file-fields.js +50 -23
- package/dist/global-search.js +1 -1
- package/dist/index.js +20 -26
- package/dist/mcp-contract/client.d.ts +24 -0
- package/dist/mcp-contract/client.js +12 -0
- package/dist/mcp-contract/schema.d.ts +46 -0
- package/dist/mcp-contract/schema.js +90 -0
- package/dist/mcp-contract/tools.d.ts +19 -0
- package/dist/mcp-contract/tools.js +160 -0
- package/dist/mcp-local.d.ts +1 -1
- package/dist/mcp-local.js +1 -1
- package/dist/mcp-tools.d.ts +12 -1
- package/dist/mcp-tools.js +41 -8
- package/dist/migrations.d.ts +5 -0
- package/dist/migrations.js +122 -0
- package/dist/mutate.js +77 -36
- package/dist/orchestrator/agent-capabilities.d.ts +2 -0
- package/dist/orchestrator/agent-capabilities.js +201 -0
- package/dist/orchestrator/allow.d.ts +16 -0
- package/dist/orchestrator/allow.js +65 -0
- package/dist/orchestrator/attachments.d.ts +2 -0
- package/dist/orchestrator/attachments.js +11 -0
- package/dist/orchestrator/config.d.ts +4 -0
- package/dist/orchestrator/config.js +19 -0
- package/dist/orchestrator/db.d.ts +17 -0
- package/dist/orchestrator/db.js +12 -0
- package/dist/orchestrator/diagnostics.d.ts +9 -0
- package/dist/orchestrator/diagnostics.js +13 -0
- package/dist/orchestrator/environment.d.ts +3 -0
- package/dist/orchestrator/environment.js +30 -0
- package/dist/orchestrator/file-inspection.d.ts +3 -0
- package/dist/orchestrator/file-inspection.js +61 -0
- package/dist/orchestrator/format.d.ts +1 -0
- package/dist/orchestrator/format.js +6 -0
- package/dist/orchestrator/index.d.ts +30 -0
- package/dist/orchestrator/index.js +56 -0
- package/dist/orchestrator/live-message.d.ts +14 -0
- package/dist/orchestrator/live-message.js +100 -0
- package/dist/orchestrator/pipeline.d.ts +11 -0
- package/dist/orchestrator/pipeline.js +198 -0
- package/dist/orchestrator/registry.d.ts +13 -0
- package/dist/orchestrator/registry.js +74 -0
- package/dist/orchestrator/starters.d.ts +19 -0
- package/dist/orchestrator/starters.js +81 -0
- package/dist/orchestrator/suggestion-capabilities.d.ts +5 -0
- package/dist/orchestrator/suggestion-capabilities.js +33 -0
- package/dist/orchestrator/system-assembly.d.ts +7 -0
- package/dist/orchestrator/system-assembly.js +13 -0
- package/dist/orchestrator/system-capabilities.d.ts +2 -0
- package/dist/orchestrator/system-capabilities.js +63 -0
- package/dist/orchestrator/types.d.ts +144 -0
- package/dist/orchestrator/types.js +1 -0
- package/dist/part-injection.d.ts +3 -4
- package/dist/part-injection.js +25 -368
- package/dist/plugin-hooks.d.ts +1 -0
- package/dist/plugin-i18n.js +2 -1
- package/dist/plugin-runtime.d.ts +2 -2
- package/dist/plugin-runtime.js +20 -11
- package/dist/plugin-updates.d.ts +2 -2
- package/dist/plugin-updates.js +6 -12
- package/dist/public-url.js +2 -1
- package/dist/recompute-derived.d.ts +2 -1
- package/dist/recompute-derived.js +111 -34
- package/dist/records-api.js +14 -16
- package/dist/registry-context.d.ts +1 -1
- package/dist/registry-context.js +3 -0
- package/dist/search-index.js +1 -1
- package/dist/search-indexer.d.ts +1 -1
- package/dist/search-indexer.js +3 -3
- package/dist/settings-groups.d.ts +14 -0
- package/dist/settings-groups.js +25 -0
- package/dist/settings-write.d.ts +4 -4
- package/dist/settings-write.js +23 -26
- package/dist/system-settings.d.ts +3 -0
- package/dist/system-settings.js +27 -0
- package/dist/thread-store.d.ts +3 -2
- package/dist/thread-store.js +6 -5
- package/dist/trigger-queue.d.ts +33 -0
- package/dist/trigger-queue.js +432 -0
- package/dist/uploads.js +6 -3
- package/dist/value-compare.d.ts +1 -0
- package/dist/value-compare.js +7 -0
- package/package.json +17 -3
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import { hasChildren, isNamedField, isCollectionGroup, isJsonStored, } from '@coffer-org/sdk/fields';
|
|
2
|
+
import { fieldEntries } from '@coffer-org/sdk/shelf';
|
|
3
|
+
import { newScope, childScope, runFieldUnit, isPlainObject, isComputeUnit, isGroupUnit, } from "./compute-unit.js";
|
|
4
|
+
import { newPartMemo } from "./part-injection.js";
|
|
5
|
+
import { sameValue } from "./value-compare.js";
|
|
6
|
+
import { getShelf } from "./registry-context.js";
|
|
7
|
+
import { shelfTableName } from "./entity-schema.js";
|
|
8
|
+
import { selectRows } from "./read-rows.js";
|
|
9
|
+
import { decodeTemporal, encodeTemporal } from "./temporal.js";
|
|
10
|
+
import { nestEmbedded, flattenEmbedded, readCollections, writeCollections, splitCollections } from "./collection-io.js";
|
|
11
|
+
const MAX_UNIT_COMPUTATIONS = 10_000;
|
|
12
|
+
export class TriggerQueueOverflowError extends Error {
|
|
13
|
+
}
|
|
14
|
+
const MAX_TOUCHED_RECORDS = 1000;
|
|
15
|
+
export class TriggerRecordCapError extends Error {
|
|
16
|
+
}
|
|
17
|
+
function readHop(row, fields, hop, i) {
|
|
18
|
+
const seg = hop[i];
|
|
19
|
+
for (const el of fields) {
|
|
20
|
+
if (isNamedField(el)) {
|
|
21
|
+
if (el.key !== seg)
|
|
22
|
+
continue;
|
|
23
|
+
return el.type.relation ? [{ value: row[seg], relation: el.type.relation }] : [];
|
|
24
|
+
}
|
|
25
|
+
if (hasChildren(el)) {
|
|
26
|
+
const sub = el.fields;
|
|
27
|
+
if (!el.key) {
|
|
28
|
+
const found = readHop(row, sub, hop, i);
|
|
29
|
+
if (found.length)
|
|
30
|
+
return found;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (el.key !== seg)
|
|
34
|
+
continue;
|
|
35
|
+
if (isCollectionGroup(el)) {
|
|
36
|
+
const rows = Array.isArray(row[seg]) ? row[seg] : [];
|
|
37
|
+
return rows.flatMap((r) => (isPlainObject(r) ? readHop(r, sub, hop, i + 1) : []));
|
|
38
|
+
}
|
|
39
|
+
const child = row[seg];
|
|
40
|
+
return isPlainObject(child) ? readHop(child, sub, hop, i + 1) : [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
function idsOf(value) {
|
|
46
|
+
if (Array.isArray(value))
|
|
47
|
+
return value.filter((v) => Number.isInteger(v) && v > 0);
|
|
48
|
+
return Number.isInteger(value) && value > 0 ? [value] : [];
|
|
49
|
+
}
|
|
50
|
+
async function loadRecords(em, m, ids) {
|
|
51
|
+
const table = shelfTableName(m.library, m.shelf);
|
|
52
|
+
const flats = (await selectRows(em, table, { id: { $in: ids }, deleted_at: null }));
|
|
53
|
+
const out = new Map();
|
|
54
|
+
for (const flat of flats) {
|
|
55
|
+
const id = Number(flat['id']);
|
|
56
|
+
const nested = nestEmbedded(m, decodeTemporal(m, flat));
|
|
57
|
+
const collections = await readCollections(em, m, id);
|
|
58
|
+
out.set(id, { ...nested, ...collections });
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
async function resolveHopChain(em, startRow, startFields, hops) {
|
|
63
|
+
let current = [{ row: startRow, fields: startFields }];
|
|
64
|
+
let loaded = [];
|
|
65
|
+
for (const hop of hops) {
|
|
66
|
+
const byShelf = new Map();
|
|
67
|
+
for (const cur of current) {
|
|
68
|
+
for (const match of readHop(cur.row, cur.fields, hop, 0)) {
|
|
69
|
+
const m = getShelf(match.relation.library, match.relation.shelf);
|
|
70
|
+
if (!m)
|
|
71
|
+
continue;
|
|
72
|
+
const key = `${match.relation.library}/${match.relation.shelf}`;
|
|
73
|
+
let entry = byShelf.get(key);
|
|
74
|
+
if (!entry) {
|
|
75
|
+
entry = { m, ids: new Set() };
|
|
76
|
+
byShelf.set(key, entry);
|
|
77
|
+
}
|
|
78
|
+
for (const id of idsOf(match.value))
|
|
79
|
+
entry.ids.add(id);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
loaded = [];
|
|
83
|
+
for (const { m, ids } of byShelf.values()) {
|
|
84
|
+
if (!ids.size)
|
|
85
|
+
continue;
|
|
86
|
+
const rows = await loadRecords(em, m, [...ids]);
|
|
87
|
+
for (const [id, row] of rows)
|
|
88
|
+
loaded.push({ m, row, id });
|
|
89
|
+
}
|
|
90
|
+
current = loaded.map((l) => ({ row: l.row, fields: l.m.fields }));
|
|
91
|
+
}
|
|
92
|
+
return loaded;
|
|
93
|
+
}
|
|
94
|
+
function encodeJsonForWrite(fields, data) {
|
|
95
|
+
const out = { ...data };
|
|
96
|
+
for (const [k, fm] of fieldEntries(fields)) {
|
|
97
|
+
if (isJsonStored(fm) && out[k] !== null && out[k] !== undefined && typeof out[k] === 'object') {
|
|
98
|
+
out[k] = JSON.stringify(out[k]);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
async function runCrossRecordTarget(target, rootRow, rootFields, graph, ctx, cross) {
|
|
104
|
+
const em = ctx.em;
|
|
105
|
+
if (!em)
|
|
106
|
+
return;
|
|
107
|
+
const endpoints = await resolveHopChain(em, rootRow, rootFields, target.hops);
|
|
108
|
+
for (const { m, row, id } of endpoints) {
|
|
109
|
+
const key = `${m.library}/${m.shelf}#${id}`;
|
|
110
|
+
if (cross.visited.has(key))
|
|
111
|
+
continue;
|
|
112
|
+
cross.visited.add(key);
|
|
113
|
+
if (cross.visited.size > cross.cap) {
|
|
114
|
+
throw new TriggerRecordCapError(`[trigger-queue] a cross-record write touched more than ${cross.cap} distinct records in one ` +
|
|
115
|
+
`transaction (reaching ${m.library}/${m.shelf}#${id}) — a chain of relation hops is not ` +
|
|
116
|
+
`converging (this guards the runtime cascade; a cycle in the DECLARED graph is rejected at ` +
|
|
117
|
+
`compose time by buildMutateGraph, so this is not that).`);
|
|
118
|
+
}
|
|
119
|
+
const before = row;
|
|
120
|
+
const after = { ...row };
|
|
121
|
+
const shelfKeyB = `${m.library}/${m.shelf}`;
|
|
122
|
+
const triggered = await runTriggers(m.fields, after, [], graph, shelfKeyB, { ...ctx, meta: { library: m.library, shelf: m.shelf }, memo: undefined }, { seedTargets: [{ hops: [], path: target.path, relative: false }], crossRecordState: cross });
|
|
123
|
+
const changedB = changedFields(before, triggered, m.fields);
|
|
124
|
+
if (!changedB.length)
|
|
125
|
+
continue;
|
|
126
|
+
cross.touched?.push({
|
|
127
|
+
library: m.library,
|
|
128
|
+
shelf: m.shelf,
|
|
129
|
+
id,
|
|
130
|
+
changed: changedB.map((c) => c.path.join('.')),
|
|
131
|
+
});
|
|
132
|
+
const { base, collections } = splitCollections(m, triggered);
|
|
133
|
+
const dbRow = encodeJsonForWrite(m.fields, encodeTemporal(m, flattenEmbedded(m, base)));
|
|
134
|
+
await em.upsert(shelfTableName(m.library, m.shelf), { ...dbRow, id });
|
|
135
|
+
const changedTop = new Set(changedB.map((c) => c.path[0]));
|
|
136
|
+
const toWrite = {};
|
|
137
|
+
for (const [k, rows] of Object.entries(collections))
|
|
138
|
+
if (changedTop.has(k))
|
|
139
|
+
toWrite[k] = rows;
|
|
140
|
+
if (Object.keys(toWrite).length)
|
|
141
|
+
await writeCollections(em, m, id, toWrite);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function valuesDiffer(a, b) {
|
|
145
|
+
if (a instanceof Date || b instanceof Date)
|
|
146
|
+
return !sameValue(a, b);
|
|
147
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
148
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
|
|
149
|
+
return true;
|
|
150
|
+
return a.some((v, i) => valuesDiffer(v, b[i]));
|
|
151
|
+
}
|
|
152
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
153
|
+
const av = a;
|
|
154
|
+
const bv = b;
|
|
155
|
+
const keys = new Set([...Object.keys(av), ...Object.keys(bv)]);
|
|
156
|
+
for (const k of keys)
|
|
157
|
+
if (valuesDiffer(av[k], bv[k]))
|
|
158
|
+
return true;
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
return !sameValue(a, b);
|
|
162
|
+
}
|
|
163
|
+
export function changedFields(before, after, fields) {
|
|
164
|
+
const out = [];
|
|
165
|
+
collectChanges(before, after, fields, out);
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
function asRow(v) {
|
|
169
|
+
return isPlainObject(v) ? v : {};
|
|
170
|
+
}
|
|
171
|
+
function collectChanges(before, after, fields, out) {
|
|
172
|
+
for (const el of fields) {
|
|
173
|
+
if (isNamedField(el)) {
|
|
174
|
+
const key = el.key;
|
|
175
|
+
if (el.type.parts?.length) {
|
|
176
|
+
const bObj = asRow(before[key]);
|
|
177
|
+
const aObj = asRow(after[key]);
|
|
178
|
+
for (const p of el.type.parts) {
|
|
179
|
+
if (valuesDiffer(bObj[p.key], aObj[p.key]))
|
|
180
|
+
out.push({ path: [key, p.key] });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else if (valuesDiffer(before[key], after[key])) {
|
|
184
|
+
out.push({ path: [key] });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
else if (hasChildren(el)) {
|
|
188
|
+
const sub = el.fields;
|
|
189
|
+
if (!el.key) {
|
|
190
|
+
collectChanges(before, after, sub, out);
|
|
191
|
+
}
|
|
192
|
+
else if (isCollectionGroup(el)) {
|
|
193
|
+
const bRows = Array.isArray(before[el.key]) ? before[el.key] : [];
|
|
194
|
+
const aRows = Array.isArray(after[el.key]) ? after[el.key] : [];
|
|
195
|
+
if (bRows.length !== aRows.length)
|
|
196
|
+
out.push({ path: [el.key] });
|
|
197
|
+
const rowKeys = new Set();
|
|
198
|
+
const n = Math.min(bRows.length, aRows.length);
|
|
199
|
+
for (let i = 0; i < n; i++) {
|
|
200
|
+
const rowOut = [];
|
|
201
|
+
collectChanges(asRow(bRows[i]), asRow(aRows[i]), sub, rowOut);
|
|
202
|
+
for (const c of rowOut)
|
|
203
|
+
rowKeys.add(c.path.join(''));
|
|
204
|
+
}
|
|
205
|
+
for (const s of rowKeys)
|
|
206
|
+
out.push({ path: [el.key, ...s.split('')] });
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
const subOut = [];
|
|
210
|
+
collectChanges(asRow(before[el.key]), asRow(after[el.key]), sub, subOut);
|
|
211
|
+
for (const c of subOut)
|
|
212
|
+
out.push({ path: [el.key, ...c.path] });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function markDone(scope) {
|
|
218
|
+
for (const key of scope.units.keys())
|
|
219
|
+
scope.status.set(key, 'done');
|
|
220
|
+
return scope;
|
|
221
|
+
}
|
|
222
|
+
const rowScopesCache = new WeakMap();
|
|
223
|
+
function rowScopesOf(scope, key, sub) {
|
|
224
|
+
let byKey = rowScopesCache.get(scope);
|
|
225
|
+
if (!byKey) {
|
|
226
|
+
byKey = new Map();
|
|
227
|
+
rowScopesCache.set(scope, byKey);
|
|
228
|
+
}
|
|
229
|
+
const cached = byKey.get(key);
|
|
230
|
+
if (cached)
|
|
231
|
+
return cached;
|
|
232
|
+
const rows = scope.obj[key];
|
|
233
|
+
const arr = Array.isArray(rows) ? rows : [];
|
|
234
|
+
const copies = arr.map((r) => (isPlainObject(r) ? { ...r } : r));
|
|
235
|
+
scope.obj[key] = copies;
|
|
236
|
+
const scopes = [];
|
|
237
|
+
copies.forEach((r, i) => {
|
|
238
|
+
if (!isPlainObject(r))
|
|
239
|
+
return;
|
|
240
|
+
scopes.push(markDone(newScope(scope.st, r, sub, [...scope.path, key, i], null, `collection row '${key}'`)));
|
|
241
|
+
});
|
|
242
|
+
byKey.set(key, scopes);
|
|
243
|
+
return scopes;
|
|
244
|
+
}
|
|
245
|
+
function locateUnits(scope, fields, path, i, materialized) {
|
|
246
|
+
const seg = path[i];
|
|
247
|
+
const last = i === path.length - 1;
|
|
248
|
+
for (const el of fields) {
|
|
249
|
+
if (isNamedField(el)) {
|
|
250
|
+
if (el.key !== seg)
|
|
251
|
+
continue;
|
|
252
|
+
return [{ scope, key: el.key, fm: el.type }];
|
|
253
|
+
}
|
|
254
|
+
if (hasChildren(el)) {
|
|
255
|
+
const sub = el.fields;
|
|
256
|
+
if (!el.key) {
|
|
257
|
+
const found = locateUnits(scope, sub, path, i, materialized);
|
|
258
|
+
if (found.length)
|
|
259
|
+
return found;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (el.key !== seg)
|
|
263
|
+
continue;
|
|
264
|
+
if (last)
|
|
265
|
+
return el.compute !== undefined ? [{ scope, key: el.key, fm: el }] : [];
|
|
266
|
+
if (isCollectionGroup(el)) {
|
|
267
|
+
const rows = rowScopesOf(scope, el.key, sub);
|
|
268
|
+
return rows.flatMap((row) => locateUnits(row, sub, path, i + 1, materialized));
|
|
269
|
+
}
|
|
270
|
+
let child = childScope(scope, el.key);
|
|
271
|
+
if (!child) {
|
|
272
|
+
child = newScope(scope.st, {}, sub, [...scope.path, el.key], scope.level, scope.level.label);
|
|
273
|
+
scope.obj[el.key] = child.obj;
|
|
274
|
+
scope.groups.set(el.key, child);
|
|
275
|
+
materialized.push({ parent: scope, key: el.key });
|
|
276
|
+
}
|
|
277
|
+
markDone(child);
|
|
278
|
+
return locateUnits(child, sub, path, i + 1, materialized);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return [];
|
|
282
|
+
}
|
|
283
|
+
function isPathPrefix(prefix, path) {
|
|
284
|
+
return prefix.length <= path.length && prefix.every((seg, i) => path[i] === seg);
|
|
285
|
+
}
|
|
286
|
+
function edgesFrom(graph, shelfKey, path) {
|
|
287
|
+
return (graph.get(shelfKey) ?? []).filter((e) => isPathPrefix(path, e.from));
|
|
288
|
+
}
|
|
289
|
+
function movedNodes(item, before, after) {
|
|
290
|
+
if (isGroupUnit(item.fm)) {
|
|
291
|
+
const base = item.path;
|
|
292
|
+
const out = [];
|
|
293
|
+
if (valuesDiffer(before, after))
|
|
294
|
+
out.push(base);
|
|
295
|
+
if (!isCollectionGroup(item.fm)) {
|
|
296
|
+
const b = isPlainObject(before) ? before : {};
|
|
297
|
+
const a = isPlainObject(after) ? after : {};
|
|
298
|
+
for (const child of item.fm.fields) {
|
|
299
|
+
if (isNamedField(child) && valuesDiffer(b[child.key], a[child.key]))
|
|
300
|
+
out.push([...base, child.key]);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return out;
|
|
304
|
+
}
|
|
305
|
+
const parts = item.fm.parts;
|
|
306
|
+
const lastSeg = item.path[item.path.length - 1];
|
|
307
|
+
const base = parts?.some((p) => p.key === lastSeg) ? item.path.slice(0, -1) : item.path;
|
|
308
|
+
const out = [];
|
|
309
|
+
if (valuesDiffer(before, after))
|
|
310
|
+
out.push(base);
|
|
311
|
+
if (parts?.length) {
|
|
312
|
+
const b = isPlainObject(before) ? before : {};
|
|
313
|
+
const a = isPlainObject(after) ? after : {};
|
|
314
|
+
for (const p of parts)
|
|
315
|
+
if (valuesDiffer(b[p.key], a[p.key]))
|
|
316
|
+
out.push([...base, p.key]);
|
|
317
|
+
}
|
|
318
|
+
return out;
|
|
319
|
+
}
|
|
320
|
+
export function allUnitPaths(fields, scope = []) {
|
|
321
|
+
const out = [];
|
|
322
|
+
for (const el of fields) {
|
|
323
|
+
if (isNamedField(el)) {
|
|
324
|
+
if (isComputeUnit(el.type))
|
|
325
|
+
out.push([...scope, el.key]);
|
|
326
|
+
}
|
|
327
|
+
else if (hasChildren(el)) {
|
|
328
|
+
const p = el.key ? [...scope, el.key] : scope;
|
|
329
|
+
if (el.key && el.compute !== undefined)
|
|
330
|
+
out.push(p);
|
|
331
|
+
out.push(...allUnitPaths(el.fields, p));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return out;
|
|
335
|
+
}
|
|
336
|
+
function declaresCache(fm) {
|
|
337
|
+
return fm.cache !== undefined || (fm.parts?.some((p) => p.meta.cache !== undefined) ?? false);
|
|
338
|
+
}
|
|
339
|
+
function cacheUnitPaths(fields, scope = []) {
|
|
340
|
+
const out = [];
|
|
341
|
+
for (const el of fields) {
|
|
342
|
+
if (isNamedField(el)) {
|
|
343
|
+
if (isComputeUnit(el.type) && declaresCache(el.type))
|
|
344
|
+
out.push([...scope, el.key]);
|
|
345
|
+
}
|
|
346
|
+
else if (hasChildren(el)) {
|
|
347
|
+
out.push(...cacheUnitPaths(el.fields, el.key ? [...scope, el.key] : scope));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return out;
|
|
351
|
+
}
|
|
352
|
+
export async function runTriggers(fields, row, changed, graph, shelfKey, ctx, opts = {}) {
|
|
353
|
+
const id = row['id'] ?? row['base_id'];
|
|
354
|
+
const st = {
|
|
355
|
+
ctx,
|
|
356
|
+
mode: 'write',
|
|
357
|
+
memo: ctx.memo ?? newPartMemo(),
|
|
358
|
+
refs: ctx.refs ?? null,
|
|
359
|
+
selfKey: typeof id === 'number' ? `${ctx.meta.library}/${ctx.meta.shelf}#${id}` : null,
|
|
360
|
+
recordLevel: null,
|
|
361
|
+
now: ctx.now ?? new Date().toISOString(),
|
|
362
|
+
};
|
|
363
|
+
const root = markDone(newScope(st, { ...row }, fields, [], null, shelfKey));
|
|
364
|
+
st.recordLevel = root.level;
|
|
365
|
+
const queue = [];
|
|
366
|
+
const pending = new Set();
|
|
367
|
+
const materialized = [];
|
|
368
|
+
const cross = opts.crossRecordState ?? {
|
|
369
|
+
visited: new Set(),
|
|
370
|
+
cap: opts.recordCap ?? MAX_TOUCHED_RECORDS,
|
|
371
|
+
...(opts.touched ? { touched: opts.touched } : {}),
|
|
372
|
+
};
|
|
373
|
+
const enqueue = async (target) => {
|
|
374
|
+
if (target.hops.length > 0) {
|
|
375
|
+
await runCrossRecordTarget(target, root.obj, fields, graph, ctx, cross);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
for (const unit of locateUnits(root, fields, target.path, 0, materialized)) {
|
|
379
|
+
const dedupeKey = `${unit.scope.uid}:${unit.key}`;
|
|
380
|
+
if (pending.has(dedupeKey))
|
|
381
|
+
continue;
|
|
382
|
+
pending.add(dedupeKey);
|
|
383
|
+
queue.push({ scope: unit.scope, key: unit.key, fm: unit.fm, path: target.path, dedupeKey });
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
if (opts.seedAll) {
|
|
387
|
+
for (const path of allUnitPaths(fields))
|
|
388
|
+
await enqueue({ hops: [], path, relative: false });
|
|
389
|
+
}
|
|
390
|
+
else if (opts.seedTargets) {
|
|
391
|
+
for (const target of opts.seedTargets)
|
|
392
|
+
await enqueue(target);
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
for (const path of cacheUnitPaths(fields))
|
|
396
|
+
await enqueue({ hops: [], path, relative: false });
|
|
397
|
+
}
|
|
398
|
+
for (const c of changed) {
|
|
399
|
+
await enqueue({ hops: [], path: c.path, relative: false });
|
|
400
|
+
for (const edge of edgesFrom(graph, shelfKey, c.path))
|
|
401
|
+
await enqueue(edge.to);
|
|
402
|
+
}
|
|
403
|
+
let computations = 0;
|
|
404
|
+
while (queue.length > 0) {
|
|
405
|
+
const wave = queue.splice(0, queue.length);
|
|
406
|
+
for (const item of wave)
|
|
407
|
+
pending.delete(item.dedupeKey);
|
|
408
|
+
computations += wave.length;
|
|
409
|
+
if (computations > MAX_UNIT_COMPUTATIONS) {
|
|
410
|
+
throw new TriggerQueueOverflowError(`[trigger-queue] ${shelfKey}: more than ${MAX_UNIT_COMPUTATIONS} unit computations in one drain — ` +
|
|
411
|
+
`a compute function is not converging (this guards the runtime queue; a cycle in the DECLARED ` +
|
|
412
|
+
`graph is rejected at compose time by buildMutateGraph, so this is not that).`);
|
|
413
|
+
}
|
|
414
|
+
const movedPerItem = await Promise.all(wave.map(async (item) => {
|
|
415
|
+
const before = item.scope.obj[item.key];
|
|
416
|
+
await runFieldUnit(item.scope, item.key, item.fm);
|
|
417
|
+
return movedNodes(item, before, item.scope.obj[item.key]);
|
|
418
|
+
}));
|
|
419
|
+
for (const nodes of movedPerItem) {
|
|
420
|
+
for (const node of nodes) {
|
|
421
|
+
for (const edge of edgesFrom(graph, shelfKey, node))
|
|
422
|
+
await enqueue(edge.to);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
for (const g of materialized) {
|
|
427
|
+
const obj = g.parent.obj[g.key];
|
|
428
|
+
if (isPlainObject(obj) && Object.keys(obj).length === 0)
|
|
429
|
+
delete g.parent.obj[g.key];
|
|
430
|
+
}
|
|
431
|
+
return root.obj;
|
|
432
|
+
}
|
package/dist/uploads.js
CHANGED
|
@@ -3,6 +3,7 @@ import { writeFile } from 'node:fs/promises';
|
|
|
3
3
|
import { basename, extname, join } from 'node:path';
|
|
4
4
|
import { randomUUID } from 'node:crypto';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { instanceDir } from "./data-dir.js";
|
|
6
7
|
const MIME_EXT = {
|
|
7
8
|
'image/jpeg': '.jpg',
|
|
8
9
|
'image/png': '.png',
|
|
@@ -14,9 +15,11 @@ const MIME_EXT = {
|
|
|
14
15
|
'application/json': '.json',
|
|
15
16
|
};
|
|
16
17
|
export function uploadsDir() {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
const override = process.env.UPLOADS;
|
|
19
|
+
if (!override)
|
|
20
|
+
return instanceDir('uploads');
|
|
21
|
+
mkdirSync(override, { recursive: true });
|
|
22
|
+
return override;
|
|
20
23
|
}
|
|
21
24
|
export function seedAsset(absPath) {
|
|
22
25
|
const name = `seed-${basename(absPath)}`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function sameValue(a: unknown, b: unknown): boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coffer-org/server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24"
|
|
@@ -13,6 +13,18 @@
|
|
|
13
13
|
"types": "./dist/index.d.ts",
|
|
14
14
|
"default": "./dist/index.js"
|
|
15
15
|
},
|
|
16
|
+
"./orchestrator": {
|
|
17
|
+
"types": "./dist/orchestrator/index.d.ts",
|
|
18
|
+
"default": "./dist/orchestrator/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./mcp": {
|
|
21
|
+
"types": "./dist/mcp-contract/tools.d.ts",
|
|
22
|
+
"default": "./dist/mcp-contract/tools.js"
|
|
23
|
+
},
|
|
24
|
+
"./mcp/*": {
|
|
25
|
+
"types": "./dist/mcp-contract/*.d.ts",
|
|
26
|
+
"default": "./dist/mcp-contract/*.js"
|
|
27
|
+
},
|
|
16
28
|
"./*": {
|
|
17
29
|
"types": "./dist/*.d.ts",
|
|
18
30
|
"default": "./dist/*.js"
|
|
@@ -24,8 +36,7 @@
|
|
|
24
36
|
"postpack": "node ../../scripts/swap-exports.mjs src"
|
|
25
37
|
},
|
|
26
38
|
"dependencies": {
|
|
27
|
-
"@coffer-org/
|
|
28
|
-
"@coffer-org/sdk": "^3.4.0",
|
|
39
|
+
"@coffer-org/sdk": "^4.0.0",
|
|
29
40
|
"@extractus/oembed-extractor": "^4.1.0",
|
|
30
41
|
"@fastify/cors": "^11.2.0",
|
|
31
42
|
"@fastify/multipart": "^10.0.0",
|
|
@@ -41,5 +52,8 @@
|
|
|
41
52
|
},
|
|
42
53
|
"optionalDependencies": {
|
|
43
54
|
"@mikro-orm/postgresql": "^7.1.6"
|
|
55
|
+
},
|
|
56
|
+
"coffer": {
|
|
57
|
+
"runtime": "node"
|
|
44
58
|
}
|
|
45
59
|
}
|