@coffer-org/server 3.4.0 → 5.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/frontend-agent.d.ts +1 -1
- package/dist/frontend-agent.js +2 -3
- 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-http.js +4 -2
- package/dist/mcp-local.d.ts +1 -1
- package/dist/mcp-local.js +1 -1
- package/dist/mcp-tools.d.ts +13 -1
- package/dist/mcp-tools.js +46 -16
- 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 +2 -0
- package/dist/orchestrator/environment.js +14 -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 +4 -0
- package/dist/orchestrator/system-assembly.js +3 -0
- package/dist/orchestrator/system-capabilities.d.ts +2 -0
- package/dist/orchestrator/system-capabilities.js +63 -0
- package/dist/orchestrator/types.d.ts +141 -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,66 @@
|
|
|
1
|
+
import { type FieldMeta, type FieldView, type ContainerNode, type LayoutEl } from '@coffer-org/sdk/fields';
|
|
2
|
+
import { PartContractError } from './part-errors.ts';
|
|
3
|
+
import { type RefState } from './part-ref.ts';
|
|
4
|
+
import type { PartCtx, PartMemo } from './part-injection.ts';
|
|
5
|
+
export type Row = Record<string, unknown>;
|
|
6
|
+
export interface PartComputeCtx {
|
|
7
|
+
record: Row;
|
|
8
|
+
parent: Row;
|
|
9
|
+
settings: Record<string, unknown>;
|
|
10
|
+
global: Record<string, unknown>;
|
|
11
|
+
meta: {
|
|
12
|
+
library: string;
|
|
13
|
+
shelf: string;
|
|
14
|
+
};
|
|
15
|
+
self: Row;
|
|
16
|
+
ref(key: string): Promise<Row | null>;
|
|
17
|
+
now: string;
|
|
18
|
+
fields(path: string | (string | number)[]): FieldView | null;
|
|
19
|
+
path: (string | number)[];
|
|
20
|
+
}
|
|
21
|
+
export interface Level {
|
|
22
|
+
fields: LayoutEl[];
|
|
23
|
+
scope: Scope;
|
|
24
|
+
proxy: Row;
|
|
25
|
+
label: string;
|
|
26
|
+
}
|
|
27
|
+
export interface Scope {
|
|
28
|
+
st: WalkState;
|
|
29
|
+
uid: number;
|
|
30
|
+
obj: Row;
|
|
31
|
+
units: Map<string, FieldMeta | ContainerNode>;
|
|
32
|
+
embedded: Map<string, LayoutEl[]>;
|
|
33
|
+
status: Map<string, 'done'>;
|
|
34
|
+
proxy: Row;
|
|
35
|
+
groups: Map<string, Scope>;
|
|
36
|
+
path: readonly (string | number)[];
|
|
37
|
+
level: Level;
|
|
38
|
+
}
|
|
39
|
+
export interface WalkState {
|
|
40
|
+
ctx: PartCtx;
|
|
41
|
+
mode: 'read' | 'write';
|
|
42
|
+
memo: PartMemo;
|
|
43
|
+
refs: RefState | null;
|
|
44
|
+
selfKey: string | null;
|
|
45
|
+
recordLevel: Level;
|
|
46
|
+
now: string;
|
|
47
|
+
}
|
|
48
|
+
export declare function partValue(p: {
|
|
49
|
+
value?: unknown;
|
|
50
|
+
}, ctx: PartComputeCtx): Promise<unknown>;
|
|
51
|
+
export declare function rethrowLoud(e: unknown): void;
|
|
52
|
+
export declare function isComputeUnit(fm: FieldMeta): boolean;
|
|
53
|
+
export declare function scopeUnits(fields: LayoutEl[]): Map<string, FieldMeta | ContainerNode>;
|
|
54
|
+
export declare function scopeEmbedded(fields: LayoutEl[]): Map<string, LayoutEl[]>;
|
|
55
|
+
export declare function levelRelations(fields: LayoutEl[]): Map<string, FieldMeta>;
|
|
56
|
+
export declare function childScope(scope: Scope, key: string, create?: boolean): Scope | null;
|
|
57
|
+
export declare function newScope(st: WalkState, obj: Row, fields: LayoutEl[], path: readonly (string | number)[], level: Level | null, label: string): Scope;
|
|
58
|
+
export declare function refFor(scope: Scope, key: string, field: string, role: string): Promise<Row | null>;
|
|
59
|
+
export declare function storedRefError(scope: Scope, key: string, field: string, role: string): PartContractError;
|
|
60
|
+
export declare function computeCtxFor(scope: Scope, self: Row, cache: number | undefined, field: string, role: string): PartComputeCtx;
|
|
61
|
+
export declare function completeValue(scope: Scope, key: string, fm: FieldMeta, value: unknown): Promise<unknown>;
|
|
62
|
+
export declare function materializable(fm: FieldMeta): boolean;
|
|
63
|
+
export declare function storedFilled(fm: FieldMeta, value: unknown): boolean;
|
|
64
|
+
export declare function isPlainObject(v: unknown): boolean;
|
|
65
|
+
export declare function isGroupUnit(fm: FieldMeta | ContainerNode): fm is ContainerNode;
|
|
66
|
+
export declare function runFieldUnit(scope: Scope, key: string, fm: FieldMeta | ContainerNode): Promise<void>;
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { hasChildren, isNamedField, isCollectionGroup, isEmbeddedGroup, isStorageGroup, } from '@coffer-org/sdk/fields';
|
|
2
|
+
import { mandatedClientParts } from '@coffer-org/sdk/parts';
|
|
3
|
+
import { PartContractError } from "./part-errors.js";
|
|
4
|
+
import { createRefLoader } from "./part-ref.js";
|
|
5
|
+
import { wholeClockColumn, partClockColumn } from "./cache-clock.js";
|
|
6
|
+
let scopeUid = 0;
|
|
7
|
+
export async function partValue(p, ctx) {
|
|
8
|
+
return typeof p.value === 'function' ? await p.value(ctx) : p.value;
|
|
9
|
+
}
|
|
10
|
+
export function rethrowLoud(e) {
|
|
11
|
+
if (e instanceof PartContractError)
|
|
12
|
+
throw e;
|
|
13
|
+
}
|
|
14
|
+
export function isComputeUnit(fm) {
|
|
15
|
+
return Boolean(fm.parts?.length) || fm.compute !== undefined;
|
|
16
|
+
}
|
|
17
|
+
const unitsCache = new WeakMap();
|
|
18
|
+
export function scopeUnits(fields) {
|
|
19
|
+
const cached = unitsCache.get(fields);
|
|
20
|
+
if (cached)
|
|
21
|
+
return cached;
|
|
22
|
+
const out = new Map();
|
|
23
|
+
const scan = (list) => {
|
|
24
|
+
for (const it of list) {
|
|
25
|
+
if (isNamedField(it)) {
|
|
26
|
+
if (isComputeUnit(it.type))
|
|
27
|
+
out.set(it.key, it.type);
|
|
28
|
+
}
|
|
29
|
+
else if (hasChildren(it)) {
|
|
30
|
+
if (isStorageGroup(it) && it.compute !== undefined)
|
|
31
|
+
out.set(it.key, it);
|
|
32
|
+
else if (!isStorageGroup(it))
|
|
33
|
+
scan(it.fields);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
scan(fields);
|
|
38
|
+
unitsCache.set(fields, out);
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
const embeddedCache = new WeakMap();
|
|
42
|
+
export function scopeEmbedded(fields) {
|
|
43
|
+
const cached = embeddedCache.get(fields);
|
|
44
|
+
if (cached)
|
|
45
|
+
return cached;
|
|
46
|
+
const out = new Map();
|
|
47
|
+
const scan = (list) => {
|
|
48
|
+
for (const it of list) {
|
|
49
|
+
if (!hasChildren(it))
|
|
50
|
+
continue;
|
|
51
|
+
if (!isStorageGroup(it))
|
|
52
|
+
scan(it.fields);
|
|
53
|
+
else if (isEmbeddedGroup(it))
|
|
54
|
+
out.set(it.key, it.fields);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
scan(fields);
|
|
58
|
+
embeddedCache.set(fields, out);
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
const relationsCache = new WeakMap();
|
|
62
|
+
export function levelRelations(fields) {
|
|
63
|
+
const cached = relationsCache.get(fields);
|
|
64
|
+
if (cached)
|
|
65
|
+
return cached;
|
|
66
|
+
const out = new Map();
|
|
67
|
+
const scan = (list) => {
|
|
68
|
+
for (const it of list) {
|
|
69
|
+
if (isNamedField(it)) {
|
|
70
|
+
if (it.type.relation)
|
|
71
|
+
out.set(it.key, it.type);
|
|
72
|
+
}
|
|
73
|
+
else if (hasChildren(it) && it.scope === 'hoist')
|
|
74
|
+
scan(it.fields);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
scan(fields);
|
|
78
|
+
relationsCache.set(fields, out);
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
export function childScope(scope, key, create = false) {
|
|
82
|
+
const existing = scope.groups.get(key);
|
|
83
|
+
if (existing)
|
|
84
|
+
return existing;
|
|
85
|
+
const sub = scope.embedded.get(key);
|
|
86
|
+
if (!sub)
|
|
87
|
+
return null;
|
|
88
|
+
const cur = scope.obj[key];
|
|
89
|
+
if (!isPlainObject(cur) && !create)
|
|
90
|
+
return null;
|
|
91
|
+
const copy = isPlainObject(cur) ? { ...cur } : {};
|
|
92
|
+
scope.obj[key] = copy;
|
|
93
|
+
const child = newScope(scope.st, copy, sub, [...scope.path, key], scope.level, scope.level.label);
|
|
94
|
+
scope.groups.set(key, child);
|
|
95
|
+
return child;
|
|
96
|
+
}
|
|
97
|
+
export function newScope(st, obj, fields, path, level, label) {
|
|
98
|
+
const scope = {
|
|
99
|
+
st,
|
|
100
|
+
uid: ++scopeUid,
|
|
101
|
+
obj,
|
|
102
|
+
units: scopeUnits(fields),
|
|
103
|
+
embedded: scopeEmbedded(fields),
|
|
104
|
+
status: new Map(),
|
|
105
|
+
path,
|
|
106
|
+
level: level,
|
|
107
|
+
proxy: obj,
|
|
108
|
+
groups: new Map(),
|
|
109
|
+
};
|
|
110
|
+
if (!level)
|
|
111
|
+
scope.level = { fields, scope, proxy: obj, label };
|
|
112
|
+
return scope;
|
|
113
|
+
}
|
|
114
|
+
function refsOf(st) {
|
|
115
|
+
if (!st.refs) {
|
|
116
|
+
st.refs = {
|
|
117
|
+
chain: st.selfKey ? [st.selfKey] : [],
|
|
118
|
+
loader: createRefLoader((_m, record) => Promise.resolve(record), st.ctx.em),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return st.refs;
|
|
122
|
+
}
|
|
123
|
+
function siteOf(field, role) {
|
|
124
|
+
return role ? `'${field}.${role}'` : `'${field}'`;
|
|
125
|
+
}
|
|
126
|
+
export function refFor(scope, key, field, role) {
|
|
127
|
+
const level = scope.level;
|
|
128
|
+
const fm = levelRelations(level.fields).get(key);
|
|
129
|
+
if (!fm?.relation) {
|
|
130
|
+
const known = [...levelRelations(level.fields).keys()];
|
|
131
|
+
throw new PartContractError(`ctx.ref('${key}') in ${scope.st.ctx.meta.library}/${scope.st.ctx.meta.shelf} ${siteOf(field, role)}: ` +
|
|
132
|
+
`'${key}' is not a relation field of ${level.label} ` +
|
|
133
|
+
`(relation fields there: ${known.length ? known.map((k) => `'${k}'`).join(', ') : 'none'}). ` +
|
|
134
|
+
`ctx.ref takes the KEY of a relation declared next to this field, not a path.`);
|
|
135
|
+
}
|
|
136
|
+
if (fm.hints?.['multi'] === true || fm.hints?.['multiple'] === true) {
|
|
137
|
+
throw new PartContractError(`ctx.ref('${key}') in ${scope.st.ctx.meta.library}/${scope.st.ctx.meta.shelf} ${siteOf(field, role)}: ` +
|
|
138
|
+
`'${key}' is a MULTIPLE relation — ctx.ref resolves one record, not a list.`);
|
|
139
|
+
}
|
|
140
|
+
const raw = level.scope.obj[key];
|
|
141
|
+
if (raw == null || raw === '')
|
|
142
|
+
return Promise.resolve(null);
|
|
143
|
+
const id = typeof raw === 'number' ? raw : Number(raw);
|
|
144
|
+
if (!Number.isInteger(id))
|
|
145
|
+
return Promise.resolve(null);
|
|
146
|
+
const refs = refsOf(scope.st);
|
|
147
|
+
return refs.loader.load(fm.relation.library, fm.relation.shelf, id, refs.chain);
|
|
148
|
+
}
|
|
149
|
+
export function storedRefError(scope, key, field, role) {
|
|
150
|
+
const { library, shelf } = scope.st.ctx.meta;
|
|
151
|
+
const noun = role ? 'part' : 'field';
|
|
152
|
+
return new PartContractError(`ctx.ref('${key}') in ${library}/${shelf} ${siteOf(field, role)}: a computed-AND-STORED ${noun} must not read ` +
|
|
153
|
+
`another record unless it declares \`cache\`. Its column is written once, at write time, and nothing ` +
|
|
154
|
+
`recomputes it when the record it read changes — the value would be stale and look authoritative. Declare ` +
|
|
155
|
+
`\`cache\` on the ${noun} to allow a refreshed cross-record read.`);
|
|
156
|
+
}
|
|
157
|
+
function resolveField(levelObj, fields, path, i) {
|
|
158
|
+
if (i >= path.length)
|
|
159
|
+
return null;
|
|
160
|
+
const seg = path[i];
|
|
161
|
+
for (const el of fields) {
|
|
162
|
+
if (isNamedField(el)) {
|
|
163
|
+
if (typeof seg !== 'string' || el.key !== seg)
|
|
164
|
+
continue;
|
|
165
|
+
const fm = el.type;
|
|
166
|
+
if (i === path.length - 1) {
|
|
167
|
+
const cacheKey = fm.cache !== undefined ? wholeClockColumn(el.key) : null;
|
|
168
|
+
return { value: levelObj[el.key], kind: fm.kind, cacheKey, owner: levelObj };
|
|
169
|
+
}
|
|
170
|
+
const partSeg = path[i + 1];
|
|
171
|
+
if (i + 2 !== path.length || typeof partSeg !== 'string')
|
|
172
|
+
return null;
|
|
173
|
+
const part = fm.parts?.find((p) => p.key === partSeg);
|
|
174
|
+
if (!part)
|
|
175
|
+
return null;
|
|
176
|
+
const composite = levelObj[el.key];
|
|
177
|
+
const value = isPlainObject(composite) ? composite[partSeg] : undefined;
|
|
178
|
+
const cacheKey = part.meta.cache !== undefined ? partClockColumn(el.key, partSeg) : null;
|
|
179
|
+
return { value, kind: part.meta.kind, cacheKey, owner: levelObj };
|
|
180
|
+
}
|
|
181
|
+
if (hasChildren(el)) {
|
|
182
|
+
const sub = el.fields;
|
|
183
|
+
if (!isStorageGroup(el)) {
|
|
184
|
+
const found = resolveField(levelObj, sub, path, i);
|
|
185
|
+
if (found)
|
|
186
|
+
return found;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (typeof seg !== 'string' || el.key !== seg)
|
|
190
|
+
continue;
|
|
191
|
+
if (isCollectionGroup(el)) {
|
|
192
|
+
const idx = path[i + 1];
|
|
193
|
+
if (typeof idx !== 'number')
|
|
194
|
+
return null;
|
|
195
|
+
const rows = levelObj[el.key];
|
|
196
|
+
const row = Array.isArray(rows) ? rows[idx] : undefined;
|
|
197
|
+
if (!isPlainObject(row))
|
|
198
|
+
return null;
|
|
199
|
+
if (i + 2 === path.length)
|
|
200
|
+
return { value: row, kind: el.kind ?? 'group', cacheKey: null, owner: levelObj };
|
|
201
|
+
return resolveField(row, sub, path, i + 2);
|
|
202
|
+
}
|
|
203
|
+
if (i === path.length - 1) {
|
|
204
|
+
return { value: levelObj[el.key], kind: el.kind ?? 'group', cacheKey: null, owner: levelObj };
|
|
205
|
+
}
|
|
206
|
+
const child = levelObj[el.key];
|
|
207
|
+
if (!isPlainObject(child))
|
|
208
|
+
return null;
|
|
209
|
+
return resolveField(child, sub, path, i + 1);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
function fieldsFor(st, path) {
|
|
215
|
+
const segs = typeof path === 'string' ? path.split('.') : path;
|
|
216
|
+
const found = resolveField(st.recordLevel.proxy, st.recordLevel.fields, segs, 0);
|
|
217
|
+
if (!found)
|
|
218
|
+
return null;
|
|
219
|
+
const rawUpdatedAt = st.recordLevel.proxy['updated_at'];
|
|
220
|
+
const updatedAt = rawUpdatedAt instanceof Date ? rawUpdatedAt.toISOString() : typeof rawUpdatedAt === 'string' ? rawUpdatedAt : null;
|
|
221
|
+
const rawComputedAt = found.cacheKey != null ? found.owner[found.cacheKey] : undefined;
|
|
222
|
+
const computedAt = typeof rawComputedAt === 'number' ? rawComputedAt : null;
|
|
223
|
+
return { value: found.value, kind: found.kind, updatedAt, computedAt };
|
|
224
|
+
}
|
|
225
|
+
export function computeCtxFor(scope, self, cache, field, role) {
|
|
226
|
+
const st = scope.st;
|
|
227
|
+
return {
|
|
228
|
+
record: st.recordLevel.proxy,
|
|
229
|
+
parent: scope.level.proxy,
|
|
230
|
+
settings: st.ctx.settings,
|
|
231
|
+
global: st.ctx.global,
|
|
232
|
+
meta: st.ctx.meta,
|
|
233
|
+
self,
|
|
234
|
+
ref: cache === undefined
|
|
235
|
+
? (key) => {
|
|
236
|
+
throw storedRefError(scope, key, field, role);
|
|
237
|
+
}
|
|
238
|
+
: (key) => refFor(scope, key, field, role),
|
|
239
|
+
now: st.now,
|
|
240
|
+
fields: (path) => fieldsFor(st, path),
|
|
241
|
+
path: role ? [...scope.path, field, role] : [...scope.path, field],
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
export async function completeValue(scope, key, fm, value) {
|
|
245
|
+
if (value == null || typeof value !== 'object' || Array.isArray(value))
|
|
246
|
+
return value;
|
|
247
|
+
const st = scope.st;
|
|
248
|
+
if (st.mode !== 'write')
|
|
249
|
+
return value;
|
|
250
|
+
const self = { ...value };
|
|
251
|
+
for (const p of fm.parts) {
|
|
252
|
+
if (p.mode !== 'computedStored')
|
|
253
|
+
continue;
|
|
254
|
+
try {
|
|
255
|
+
self[p.key] = await partValue(p, computeCtxFor(scope, self, p.meta?.cache, key, p.role));
|
|
256
|
+
if (p.meta?.cache !== undefined)
|
|
257
|
+
scope.obj[partClockColumn(key, p.key)] = Date.now();
|
|
258
|
+
}
|
|
259
|
+
catch (e) {
|
|
260
|
+
rethrowLoud(e);
|
|
261
|
+
console.warn(`[part-compute] '${key}.${p.role}' threw`, e);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return self;
|
|
265
|
+
}
|
|
266
|
+
export function materializable(fm) {
|
|
267
|
+
const parts = fm.parts ?? [];
|
|
268
|
+
if (!parts.some((p) => p.mode === 'computedStored'))
|
|
269
|
+
return false;
|
|
270
|
+
if (mandatedClientParts(fm.required, parts).length > 0)
|
|
271
|
+
return false;
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
export function storedFilled(fm, value) {
|
|
275
|
+
if (value == null || typeof value !== 'object')
|
|
276
|
+
return false;
|
|
277
|
+
const v = value;
|
|
278
|
+
return (fm.parts ?? []).some((p) => v[p.key] != null && v[p.key] !== '');
|
|
279
|
+
}
|
|
280
|
+
export function isPlainObject(v) {
|
|
281
|
+
return v != null && typeof v === 'object' && !Array.isArray(v);
|
|
282
|
+
}
|
|
283
|
+
export function isGroupUnit(fm) {
|
|
284
|
+
return hasChildren(fm);
|
|
285
|
+
}
|
|
286
|
+
export async function runFieldUnit(scope, key, fm) {
|
|
287
|
+
if (isGroupUnit(fm)) {
|
|
288
|
+
if (scope.st.mode !== 'write')
|
|
289
|
+
return;
|
|
290
|
+
let value;
|
|
291
|
+
try {
|
|
292
|
+
value = await partValue({ value: fm.compute }, computeCtxFor(scope, {}, undefined, key, ''));
|
|
293
|
+
}
|
|
294
|
+
catch (e) {
|
|
295
|
+
rethrowLoud(e);
|
|
296
|
+
console.warn(`[part-compute] '${key}' threw`, e);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (isCollectionGroup(fm)) {
|
|
300
|
+
scope.obj[key] = Array.isArray(value) ? value : [];
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const child = childScope(scope, key, true);
|
|
304
|
+
if (value == null) {
|
|
305
|
+
for (const c of fm.fields)
|
|
306
|
+
if (isNamedField(c))
|
|
307
|
+
child.obj[c.key] = null;
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (isPlainObject(value)) {
|
|
311
|
+
for (const [k, v] of Object.entries(value))
|
|
312
|
+
child.obj[k] = v;
|
|
313
|
+
}
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const wholeFn = fm.compute;
|
|
317
|
+
if (wholeFn !== undefined && !fm.parts?.length) {
|
|
318
|
+
if (scope.st.mode !== 'write')
|
|
319
|
+
return;
|
|
320
|
+
try {
|
|
321
|
+
scope.obj[key] = await partValue({ value: wholeFn }, computeCtxFor(scope, {}, fm.cache, key, ''));
|
|
322
|
+
if (fm.cache !== undefined)
|
|
323
|
+
scope.obj[wholeClockColumn(key)] = Date.now();
|
|
324
|
+
}
|
|
325
|
+
catch (e) {
|
|
326
|
+
rethrowLoud(e);
|
|
327
|
+
console.warn(`[part-compute] '${key}' threw`, e);
|
|
328
|
+
}
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (!fm.parts?.length)
|
|
332
|
+
return;
|
|
333
|
+
if (wholeFn !== undefined) {
|
|
334
|
+
if (scope.st.mode !== 'write')
|
|
335
|
+
return;
|
|
336
|
+
let value;
|
|
337
|
+
try {
|
|
338
|
+
value = await partValue({ value: wholeFn }, computeCtxFor(scope, {}, fm.cache, key, ''));
|
|
339
|
+
if (fm.cache !== undefined)
|
|
340
|
+
scope.obj[wholeClockColumn(key)] = Date.now();
|
|
341
|
+
}
|
|
342
|
+
catch (e) {
|
|
343
|
+
rethrowLoud(e);
|
|
344
|
+
console.warn(`[part-compute] '${key}' threw`, e);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
scope.obj[key] = value == null ? value : await completeValue(scope, key, fm, value);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const value = scope.obj[key];
|
|
351
|
+
if (value == null) {
|
|
352
|
+
if (scope.st.mode !== 'write' || fm.hints?.['multiple'] === true || !materializable(fm))
|
|
353
|
+
return;
|
|
354
|
+
const made = await completeValue(scope, key, fm, {});
|
|
355
|
+
if (storedFilled(fm, made))
|
|
356
|
+
scope.obj[key] = made;
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (fm.hints?.['multiple'] === true) {
|
|
360
|
+
if (!Array.isArray(value))
|
|
361
|
+
return;
|
|
362
|
+
const out = [];
|
|
363
|
+
for (const v of value)
|
|
364
|
+
out.push(await completeValue(scope, key, fm, v));
|
|
365
|
+
scope.obj[key] = out;
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
scope.obj[key] = await completeValue(scope, key, fm, value);
|
|
369
|
+
}
|
package/dist/condition-where.js
CHANGED
|
@@ -8,7 +8,7 @@ export class ConditionCompileError extends Error {
|
|
|
8
8
|
this.name = 'ConditionCompileError';
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
-
const coerce = (f, v) => typeof v === 'boolean' ? v : coerceFilter(String(v), f.column);
|
|
11
|
+
const coerce = (f, v) => (typeof v === 'boolean' ? v : coerceFilter(String(v), f.column));
|
|
12
12
|
function memberOperator(pred, key, where) {
|
|
13
13
|
if (typeof pred !== 'object' || pred === null)
|
|
14
14
|
return undefined;
|
|
@@ -68,8 +68,6 @@ export function conditionToWhere(m, cond) {
|
|
|
68
68
|
const f = fm[key];
|
|
69
69
|
if (!f)
|
|
70
70
|
throw new ConditionCompileError(`${where}: unknown field '${key}'`);
|
|
71
|
-
if (f.virtual)
|
|
72
|
-
throw new ConditionCompileError(`${where}: '${key}' is virtual and has no column`);
|
|
73
71
|
const memberOp = memberOperator(spec, key, where);
|
|
74
72
|
if (memberOp && !f.hints['multiple']) {
|
|
75
73
|
throw new ConditionCompileError(`${where}: '${key}' is not a multiple field — '${memberOp}' does not apply`);
|
package/dist/data-dir.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
export function dataDir() {
|
|
4
|
+
return resolve(process.env.DATA_DIR ?? join(process.cwd(), 'data'));
|
|
5
|
+
}
|
|
6
|
+
export function instanceDir(...segments) {
|
|
7
|
+
const dir = join(dataDir(), ...segments);
|
|
8
|
+
mkdirSync(dir, { recursive: true });
|
|
9
|
+
return dir;
|
|
10
|
+
}
|
|
11
|
+
export function carryInto(dst, ...legacyDirs) {
|
|
12
|
+
for (const src of legacyDirs)
|
|
13
|
+
copyMissing(src, dst);
|
|
14
|
+
}
|
|
15
|
+
function copyMissing(src, dst) {
|
|
16
|
+
let entries;
|
|
17
|
+
try {
|
|
18
|
+
entries = readdirSync(src, { withFileTypes: true });
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
const from = join(src, entry.name);
|
|
25
|
+
const to = join(dst, entry.name);
|
|
26
|
+
if (entry.isDirectory()) {
|
|
27
|
+
copyMissing(from, to);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (existsSync(to))
|
|
31
|
+
continue;
|
|
32
|
+
try {
|
|
33
|
+
mkdirSync(dst, { recursive: true });
|
|
34
|
+
copyFileSync(from, to);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
package/dist/entity-schema.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { EntitySchema } from '@mikro-orm/core';
|
|
2
2
|
import { fieldEntries, collectionGroups, storageColumns } from '@coffer-org/sdk/shelf';
|
|
3
|
+
import { cacheClockColumns } from "./cache-clock.js";
|
|
4
|
+
import { SYSTEM_SETTINGS_ID, SYSTEM_SETTINGS } from "./system-settings.js";
|
|
3
5
|
function mikroType(col) {
|
|
4
6
|
if (col === 'integer')
|
|
5
7
|
return 'integer';
|
|
@@ -23,8 +25,6 @@ function columnProp(field, nullable) {
|
|
|
23
25
|
}
|
|
24
26
|
function addFieldProps(properties, fields, plainNullable) {
|
|
25
27
|
for (const [key, field] of fieldEntries(fields)) {
|
|
26
|
-
if (field.virtual)
|
|
27
|
-
continue;
|
|
28
28
|
if (field.columns) {
|
|
29
29
|
for (const [col, type] of storageColumns(key, field))
|
|
30
30
|
properties[col] = { type: mikroType(type), nullable: true };
|
|
@@ -33,6 +33,8 @@ function addFieldProps(properties, fields, plainNullable) {
|
|
|
33
33
|
properties[key] = columnProp(field, plainNullable(field));
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
+
for (const col of cacheClockColumns(fields))
|
|
37
|
+
properties[col] = { type: 'integer', nullable: true };
|
|
36
38
|
}
|
|
37
39
|
export function shelfTableName(library, shelf) {
|
|
38
40
|
return `${library}__${shelf}`;
|
|
@@ -329,4 +331,5 @@ export const systemEntities = [
|
|
|
329
331
|
OAuthTokenSchema,
|
|
330
332
|
ThreadMessageSchema,
|
|
331
333
|
ThreadStateSchema,
|
|
334
|
+
buildSettingsEntitySchema(SYSTEM_SETTINGS_ID, SYSTEM_SETTINGS),
|
|
332
335
|
];
|
package/dist/extend-table.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { ExtendDef } from '@coffer-org/sdk/extend';
|
|
|
3
3
|
import { type PartMemo } from './part-injection.ts';
|
|
4
4
|
type Row = Record<string, unknown>;
|
|
5
5
|
export declare function extendEntityName(e: ExtendDef): string;
|
|
6
|
-
export declare function getExtendRecord(e: ExtendDef, baseId: number, em?: EntityManager,
|
|
6
|
+
export declare function getExtendRecord(e: ExtendDef, baseId: number, em?: EntityManager, _memo?: PartMemo): Promise<Row | undefined>;
|
|
7
7
|
export declare function getExtendRecords(e: ExtendDef, baseIds: number[], em?: EntityManager): Promise<Map<number, Row>>;
|
|
8
8
|
export declare function upsertExtendRecord(em: EntityManager, e: ExtendDef, baseId: number, data: Row, memo?: PartMemo): Promise<void>;
|
|
9
9
|
export declare function deleteExtendRecord(em: EntityManager, e: ExtendDef, baseId: number): Promise<void>;
|
package/dist/extend-table.js
CHANGED
|
@@ -4,7 +4,9 @@ import { encodeJsonAt } from "./mutate.js";
|
|
|
4
4
|
import { encodeTemporalAt, decodeTemporalAt } from "./temporal.js";
|
|
5
5
|
import { chunk } from "./batch.js";
|
|
6
6
|
import { selectRows } from "./read-rows.js";
|
|
7
|
-
import {
|
|
7
|
+
import { hasStoredPartWork } from "./part-injection.js";
|
|
8
|
+
import { changedFields, runTriggers } from "./trigger-queue.js";
|
|
9
|
+
import { getActiveRegistry } from "./registry-context.js";
|
|
8
10
|
export function extendEntityName(e) {
|
|
9
11
|
return `extend__${e.id}`;
|
|
10
12
|
}
|
|
@@ -17,7 +19,7 @@ function extendPartCtx(e, em, memo) {
|
|
|
17
19
|
...(memo ? { memo } : {}),
|
|
18
20
|
};
|
|
19
21
|
}
|
|
20
|
-
export async function getExtendRecord(e, baseId, em,
|
|
22
|
+
export async function getExtendRecord(e, baseId, em, _memo) {
|
|
21
23
|
const fork = em ?? getEm().fork();
|
|
22
24
|
const name = extendEntityName(e);
|
|
23
25
|
const [flat] = (await selectRows(fork, name, { base_id: baseId }, { limit: 1 }));
|
|
@@ -25,8 +27,7 @@ export async function getExtendRecord(e, baseId, em, memo) {
|
|
|
25
27
|
return undefined;
|
|
26
28
|
const decoded = nestEmbeddedAt(e.fields, decodeTemporalAt(e.fields, flat));
|
|
27
29
|
const collections = await readAt(fork, name, e.fields, baseId);
|
|
28
|
-
|
|
29
|
-
return injectParts(e.fields, record, extendPartCtx(e, em, memo));
|
|
30
|
+
return { ...decoded, ...collections };
|
|
30
31
|
}
|
|
31
32
|
export async function getExtendRecords(e, baseIds, em) {
|
|
32
33
|
const out = new Map();
|
|
@@ -45,8 +46,7 @@ export async function getExtendRecords(e, baseIds, em) {
|
|
|
45
46
|
for (const flat of flats) {
|
|
46
47
|
const id = Number(flat.base_id);
|
|
47
48
|
const decoded = nestEmbeddedAt(e.fields, decodeTemporalAt(e.fields, flat));
|
|
48
|
-
|
|
49
|
-
out.set(id, await injectParts(e.fields, record, extendPartCtx(e, em)));
|
|
49
|
+
out.set(id, { ...decoded, ...(collectionsById.get(id) ?? {}) });
|
|
50
50
|
}
|
|
51
51
|
return out;
|
|
52
52
|
}
|
|
@@ -61,8 +61,22 @@ async function storedExtendState(em, e, baseId) {
|
|
|
61
61
|
export async function upsertExtendRecord(em, e, baseId, data, memo) {
|
|
62
62
|
const name = extendEntityName(e);
|
|
63
63
|
const needParts = hasStoredPartWork(e.fields);
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
let target;
|
|
65
|
+
let withParts;
|
|
66
|
+
if (needParts) {
|
|
67
|
+
const stored = await storedExtendState(em, e, baseId);
|
|
68
|
+
target = { ...stored, ...data };
|
|
69
|
+
const changed = changedFields(stored, target, e.fields);
|
|
70
|
+
const graph = getActiveRegistry().mutateGraph;
|
|
71
|
+
const seedAll = Object.keys(stored).length === 0;
|
|
72
|
+
withParts = await runTriggers(e.fields, target, changed, graph, `extend:${e.id}`, extendPartCtx(e, em, memo), {
|
|
73
|
+
seedAll,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
target = data;
|
|
78
|
+
withParts = data;
|
|
79
|
+
}
|
|
66
80
|
const write = {};
|
|
67
81
|
for (const [k, v] of Object.entries(withParts))
|
|
68
82
|
if (k in data || v !== target[k])
|