@coffer-org/server 3.3.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 +42 -28
- 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/db.js +2 -0
- package/dist/entity-schema.d.ts +1 -0
- package/dist/entity-schema.js +25 -2
- package/dist/extend-io.d.ts +3 -2
- package/dist/extend-io.js +4 -4
- package/dist/extend-table.d.ts +3 -2
- package/dist/extend-table.js +48 -11
- package/dist/file-fields.js +50 -23
- package/dist/global-search.js +1 -1
- package/dist/http-errors.d.ts +7 -0
- package/dist/http-errors.js +24 -0
- package/dist/index.js +31 -35
- 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 +4 -2
- package/dist/mcp-tools.d.ts +12 -1
- package/dist/mcp-tools.js +41 -8
- package/dist/migrations.d.ts +30 -2
- package/dist/migrations.js +368 -19
- package/dist/mutate.js +86 -57
- 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-errors.d.ts +3 -0
- package/dist/part-errors.js +6 -0
- package/dist/part-injection.d.ts +11 -3
- package/dist/part-injection.js +72 -92
- package/dist/part-ref.d.ts +13 -0
- package/dist/part-ref.js +109 -0
- 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 +21 -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 +123 -26
- package/dist/records-api.d.ts +5 -1
- package/dist/records-api.js +47 -15
- package/dist/registry-context.d.ts +1 -1
- package/dist/registry-context.js +3 -0
- package/dist/rename-guard.d.ts +1 -0
- package/dist/rename-guard.js +9 -0
- package/dist/schema-sync.js +38 -1
- 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/db.js
CHANGED
|
@@ -2,6 +2,7 @@ import { mkdirSync } from 'node:fs';
|
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
3
|
import { MikroORM, EntityCaseNamingStrategy } from '@mikro-orm/core';
|
|
4
4
|
import { SqliteDriver } from '@mikro-orm/sqlite';
|
|
5
|
+
import { disableColumnRenameDetection } from "./rename-guard.js";
|
|
5
6
|
const SKIP_TABLES = [/^_search(_|$)/];
|
|
6
7
|
let _orm;
|
|
7
8
|
export function getOrm() {
|
|
@@ -21,6 +22,7 @@ export async function closeDb() {
|
|
|
21
22
|
_orm = undefined;
|
|
22
23
|
}
|
|
23
24
|
export async function initDb(entities) {
|
|
25
|
+
disableColumnRenameDetection();
|
|
24
26
|
const driverName = process.env['DB_DRIVER'] ?? 'sqlite';
|
|
25
27
|
const common = {
|
|
26
28
|
entities,
|
package/dist/entity-schema.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export declare function buildExtendCollectionEntities(e: ExtendDef): EntitySchem
|
|
|
13
13
|
export declare const EventSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
|
|
14
14
|
export declare const PluginRowSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
|
|
15
15
|
export declare const MigrationRowSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
|
|
16
|
+
export declare const MigrationOpRowSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
|
|
16
17
|
export declare const SeedRowSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
|
|
17
18
|
export declare const EmbeddingSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
|
|
18
19
|
export declare const PluginStateSchema: EntitySchema<any, never, import("@mikro-orm/core").EntityCtor<any>>;
|
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}`;
|
|
@@ -122,6 +124,25 @@ export const MigrationRowSchema = new EntitySchema({
|
|
|
122
124
|
version: { type: 'integer', nullable: false },
|
|
123
125
|
},
|
|
124
126
|
});
|
|
127
|
+
export const MigrationOpRowSchema = new EntitySchema({
|
|
128
|
+
name: '_MigrationOp',
|
|
129
|
+
tableName: '_migration_ops',
|
|
130
|
+
properties: {
|
|
131
|
+
plugin_id: { type: 'text', primary: true },
|
|
132
|
+
version: { type: 'integer', primary: true },
|
|
133
|
+
seq: { type: 'integer', primary: true },
|
|
134
|
+
op: { type: 'text', nullable: false },
|
|
135
|
+
table_name: { type: 'text', nullable: false },
|
|
136
|
+
performed: { type: 'integer', nullable: false },
|
|
137
|
+
rows: { type: 'integer', nullable: true },
|
|
138
|
+
scanned: { type: 'integer', nullable: true },
|
|
139
|
+
code: { type: 'text', nullable: true },
|
|
140
|
+
reason: { type: 'text', nullable: true },
|
|
141
|
+
empty_targets: { type: 'text', nullable: true },
|
|
142
|
+
stranded_sources: { type: 'text', nullable: true },
|
|
143
|
+
applied_at: { type: 'text', nullable: true },
|
|
144
|
+
},
|
|
145
|
+
});
|
|
125
146
|
export const SeedRowSchema = new EntitySchema({
|
|
126
147
|
name: '_Seed',
|
|
127
148
|
tableName: '_seeds',
|
|
@@ -296,6 +317,7 @@ export const systemEntities = [
|
|
|
296
317
|
EventSchema,
|
|
297
318
|
PluginRowSchema,
|
|
298
319
|
MigrationRowSchema,
|
|
320
|
+
MigrationOpRowSchema,
|
|
299
321
|
SeedRowSchema,
|
|
300
322
|
EmbeddingSchema,
|
|
301
323
|
PluginStateSchema,
|
|
@@ -309,4 +331,5 @@ export const systemEntities = [
|
|
|
309
331
|
OAuthTokenSchema,
|
|
310
332
|
ThreadMessageSchema,
|
|
311
333
|
ThreadStateSchema,
|
|
334
|
+
buildSettingsEntitySchema(SYSTEM_SETTINGS_ID, SYSTEM_SETTINGS),
|
|
312
335
|
];
|
package/dist/extend-io.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { EntityManager } from '@mikro-orm/core';
|
|
2
|
+
import type { PartMemo } from './part-injection.ts';
|
|
2
3
|
export declare function splitBody(body: unknown): {
|
|
3
4
|
base: Record<string, unknown>;
|
|
4
5
|
extData: Record<string, Record<string, unknown>>;
|
|
5
6
|
};
|
|
6
7
|
export declare function deleteExtends(em: EntityManager, library: string, shelf: string, id: number): Promise<void>;
|
|
7
|
-
export declare function readExtends(em: EntityManager, library: string, shelf: string, id: number): Promise<Record<string, unknown>>;
|
|
8
|
+
export declare function readExtends(em: EntityManager, library: string, shelf: string, id: number, memo?: PartMemo): Promise<Record<string, unknown>>;
|
|
8
9
|
export declare function validateExtends(library: string, shelf: string, extData: Record<string, Record<string, unknown>>): void;
|
|
9
|
-
export declare function saveExtends(em: EntityManager, library: string, shelf: string, baseId: number, extData: Record<string, Record<string, unknown
|
|
10
|
+
export declare function saveExtends(em: EntityManager, library: string, shelf: string, baseId: number, extData: Record<string, Record<string, unknown>>, memo?: PartMemo): Promise<void>;
|
package/dist/extend-io.js
CHANGED
|
@@ -20,10 +20,10 @@ export async function deleteExtends(em, library, shelf, id) {
|
|
|
20
20
|
await deleteExtendRecord(em, e, id);
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
-
export async function readExtends(em, library, shelf, id) {
|
|
23
|
+
export async function readExtends(em, library, shelf, id, memo) {
|
|
24
24
|
const out = {};
|
|
25
25
|
for (const e of getExtendsFor(library, shelf)) {
|
|
26
|
-
out[e.id] = (await getExtendRecord(e, id, em)) ?? null;
|
|
26
|
+
out[e.id] = (await getExtendRecord(e, id, em, memo)) ?? null;
|
|
27
27
|
}
|
|
28
28
|
return out;
|
|
29
29
|
}
|
|
@@ -41,7 +41,7 @@ export function validateExtends(library, shelf, extData) {
|
|
|
41
41
|
if (issues.length)
|
|
42
42
|
throw new ValidationError(issues);
|
|
43
43
|
}
|
|
44
|
-
export async function saveExtends(em, library, shelf, baseId, extData) {
|
|
44
|
+
export async function saveExtends(em, library, shelf, baseId, extData, memo) {
|
|
45
45
|
validateExtends(library, shelf, extData);
|
|
46
46
|
for (const e of getExtendsFor(library, shelf)) {
|
|
47
47
|
const data = extData[e.id];
|
|
@@ -53,7 +53,7 @@ export async function saveExtends(em, library, shelf, baseId, extData) {
|
|
|
53
53
|
const fileIssues = normalizeFileFieldsAt(e.fields, row);
|
|
54
54
|
if (fileIssues.length)
|
|
55
55
|
throw new ValidationError(fileIssues);
|
|
56
|
-
await upsertExtendRecord(em, e, baseId, row);
|
|
56
|
+
await upsertExtendRecord(em, e, baseId, row, memo);
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
}
|
package/dist/extend-table.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { EntityManager } from '@mikro-orm/core';
|
|
2
2
|
import type { ExtendDef } from '@coffer-org/sdk/extend';
|
|
3
|
+
import { type PartMemo } from './part-injection.ts';
|
|
3
4
|
type Row = Record<string, unknown>;
|
|
4
5
|
export declare function extendEntityName(e: ExtendDef): string;
|
|
5
|
-
export declare function getExtendRecord(e: ExtendDef, baseId: number, em?: EntityManager): Promise<Row | undefined>;
|
|
6
|
+
export declare function getExtendRecord(e: ExtendDef, baseId: number, em?: EntityManager, _memo?: PartMemo): Promise<Row | undefined>;
|
|
6
7
|
export declare function getExtendRecords(e: ExtendDef, baseIds: number[], em?: EntityManager): Promise<Map<number, Row>>;
|
|
7
|
-
export declare function upsertExtendRecord(em: EntityManager, e: ExtendDef, baseId: number, data: Row): Promise<void>;
|
|
8
|
+
export declare function upsertExtendRecord(em: EntityManager, e: ExtendDef, baseId: number, data: Row, memo?: PartMemo): Promise<void>;
|
|
8
9
|
export declare function deleteExtendRecord(em: EntityManager, e: ExtendDef, baseId: number): Promise<void>;
|
|
9
10
|
export {};
|