@ekairos/domain 1.22.39-beta.development.0 → 1.22.39
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/README.md +104 -23
- package/SKILL.md +56 -0
- package/dist/cli/bin.d.ts +1 -1
- package/dist/cli/bin.d.ts.map +1 -1
- package/dist/cli/bin.js +141 -20
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/create-app.d.ts +34 -1
- package/dist/cli/create-app.d.ts.map +1 -1
- package/dist/cli/create-app.js +2139 -508
- package/dist/cli/create-app.js.map +1 -1
- package/dist/cli/http.d.ts.map +1 -1
- package/dist/cli/http.js +2 -6
- package/dist/cli/http.js.map +1 -1
- package/dist/cli/server.d.ts.map +1 -1
- package/dist/cli/server.js +5 -4
- package/dist/cli/server.js.map +1 -1
- package/dist/cli/types.d.ts +1 -0
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/ui.d.ts.map +1 -1
- package/dist/cli/ui.js +2 -0
- package/dist/cli/ui.js.map +1 -1
- package/dist/context.test-runner.js +3 -1
- package/dist/context.test-runner.js.map +1 -1
- package/dist/domain-doc.d.ts +2 -0
- package/dist/domain-doc.d.ts.map +1 -1
- package/dist/domain-doc.js +14 -0
- package/dist/domain-doc.js.map +1 -1
- package/dist/index.d.ts +190 -54
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +247 -78
- package/dist/index.js.map +1 -1
- package/dist/next.d.ts +1 -4
- package/dist/next.d.ts.map +1 -1
- package/dist/next.js +197 -89
- package/dist/next.js.map +1 -1
- package/dist/platform-sync.d.ts +30 -0
- package/dist/platform-sync.d.ts.map +1 -0
- package/dist/platform-sync.js +121 -0
- package/dist/platform-sync.js.map +1 -0
- package/dist/runtime-handle.d.ts +14 -3
- package/dist/runtime-handle.d.ts.map +1 -1
- package/dist/runtime-handle.js +2 -0
- package/dist/runtime-handle.js.map +1 -1
- package/dist/runtime.d.ts +8 -7
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +10 -8
- package/dist/runtime.js.map +1 -1
- package/package.json +15 -11
package/dist/index.js
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import { i } from "@instantdb/core";
|
|
2
|
-
import {
|
|
3
|
-
export { parseDomainDoc, renderDomainDoc, filterDomainDoc, } from "./domain-doc.js";
|
|
2
|
+
import { z } from "zod";
|
|
4
3
|
export { EkairosRuntime, } from "./runtime-handle.js";
|
|
5
4
|
let domainDocLoader = null;
|
|
5
|
+
let domainDocNormalizer = null;
|
|
6
6
|
export function configureDomainDocLoader(loader) {
|
|
7
7
|
domainDocLoader = loader ?? null;
|
|
8
8
|
}
|
|
9
|
+
export function configureDomainDocNormalizer(normalizer) {
|
|
10
|
+
domainDocNormalizer = normalizer ?? null;
|
|
11
|
+
}
|
|
9
12
|
const EKAIROS_META = Symbol.for("@ekairos/domain/meta");
|
|
10
13
|
const EKAIROS_ACTIONS = Symbol.for("@ekairos/domain/actions");
|
|
11
14
|
const EKAIROS_ACTION_MAP = Symbol.for("@ekairos/domain/action-map");
|
|
12
15
|
const EKAIROS_ACTION_BINDING = Symbol.for("@ekairos/domain/action-binding");
|
|
13
|
-
const EKAIROS_ACTION_STACK = Symbol.for("@ekairos/domain/action-stack");
|
|
14
16
|
function getMeta(source) {
|
|
15
|
-
if (!source
|
|
17
|
+
if (!isObjectLike(source))
|
|
16
18
|
return null;
|
|
17
19
|
return source[EKAIROS_META] ?? null;
|
|
18
20
|
}
|
|
19
21
|
function getActionBinding(source) {
|
|
20
|
-
if (!source
|
|
22
|
+
if (!isObjectLike(source))
|
|
21
23
|
return null;
|
|
22
24
|
const binding = source[EKAIROS_ACTION_BINDING];
|
|
23
25
|
if (!binding || typeof binding !== "object")
|
|
@@ -50,7 +52,7 @@ function bindAction(action, params) {
|
|
|
50
52
|
return registration;
|
|
51
53
|
}
|
|
52
54
|
function getStoredActions(source) {
|
|
53
|
-
if (!source
|
|
55
|
+
if (!isObjectLike(source))
|
|
54
56
|
return [];
|
|
55
57
|
const raw = source[EKAIROS_ACTIONS];
|
|
56
58
|
if (!Array.isArray(raw))
|
|
@@ -61,7 +63,7 @@ function getStoredActions(source) {
|
|
|
61
63
|
typeof entry.execute === "function");
|
|
62
64
|
}
|
|
63
65
|
function getStoredActionMap(source) {
|
|
64
|
-
if (!source
|
|
66
|
+
if (!isObjectLike(source))
|
|
65
67
|
return {};
|
|
66
68
|
const raw = source[EKAIROS_ACTION_MAP];
|
|
67
69
|
if (!raw || typeof raw !== "object")
|
|
@@ -69,7 +71,7 @@ function getStoredActionMap(source) {
|
|
|
69
71
|
return raw;
|
|
70
72
|
}
|
|
71
73
|
function setStoredActions(source, actions) {
|
|
72
|
-
if (!source
|
|
74
|
+
if (!isObjectLike(source))
|
|
73
75
|
return;
|
|
74
76
|
const frozenActions = Object.freeze([...actions]);
|
|
75
77
|
Object.defineProperty(source, EKAIROS_ACTIONS, {
|
|
@@ -80,7 +82,7 @@ function setStoredActions(source, actions) {
|
|
|
80
82
|
});
|
|
81
83
|
}
|
|
82
84
|
function setStoredActionMap(source, actionMap) {
|
|
83
|
-
if (!source
|
|
85
|
+
if (!isObjectLike(source))
|
|
84
86
|
return;
|
|
85
87
|
Object.defineProperty(source, EKAIROS_ACTION_MAP, {
|
|
86
88
|
value: Object.freeze({ ...actionMap }),
|
|
@@ -89,29 +91,13 @@ function setStoredActionMap(source, actionMap) {
|
|
|
89
91
|
writable: true,
|
|
90
92
|
});
|
|
91
93
|
}
|
|
92
|
-
function readRuntimeActionStack(runtime) {
|
|
93
|
-
if (!runtime || typeof runtime !== "object")
|
|
94
|
-
return [];
|
|
95
|
-
const stack = runtime[EKAIROS_ACTION_STACK];
|
|
96
|
-
return Array.isArray(stack) ? [...stack] : [];
|
|
97
|
-
}
|
|
98
|
-
function cloneRuntimeWithActionStack(runtime, stack) {
|
|
99
|
-
if (!runtime || typeof runtime !== "object")
|
|
100
|
-
return runtime;
|
|
101
|
-
const scoped = Object.assign(Object.create(Object.getPrototypeOf(runtime)), runtime);
|
|
102
|
-
Object.defineProperty(scoped, EKAIROS_ACTION_STACK, {
|
|
103
|
-
value: [...stack],
|
|
104
|
-
enumerable: false,
|
|
105
|
-
configurable: true,
|
|
106
|
-
writable: true,
|
|
107
|
-
});
|
|
108
|
-
return scoped;
|
|
109
|
-
}
|
|
110
94
|
function normalizeActionLike(value, params) {
|
|
111
|
-
const action =
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
95
|
+
const action = value;
|
|
96
|
+
if (!action ||
|
|
97
|
+
typeof action !== "object" ||
|
|
98
|
+
typeof action.execute !== "function" ||
|
|
99
|
+
!action.input ||
|
|
100
|
+
!action.output) {
|
|
115
101
|
throw new Error(`Invalid domain action definition: ${params.fallbackName}`);
|
|
116
102
|
}
|
|
117
103
|
const explicitName = typeof action.name === "string" ? action.name.trim() : "";
|
|
@@ -241,6 +227,17 @@ function listKeys(value) {
|
|
|
241
227
|
return [];
|
|
242
228
|
return Object.keys(value).filter((key) => !key.startsWith("$"));
|
|
243
229
|
}
|
|
230
|
+
function isObjectLike(value) {
|
|
231
|
+
return !!value && (typeof value === "object" || typeof value === "function");
|
|
232
|
+
}
|
|
233
|
+
function isMaterializedDomainSource(value) {
|
|
234
|
+
if (!isObjectLike(value))
|
|
235
|
+
return false;
|
|
236
|
+
const source = value;
|
|
237
|
+
return (typeof source.instantSchema === "function" ||
|
|
238
|
+
typeof source.toInstantSchema === "function" ||
|
|
239
|
+
("entities" in source && "links" in source && "rooms" in source));
|
|
240
|
+
}
|
|
244
241
|
function resolveSchema(source) {
|
|
245
242
|
if (!source)
|
|
246
243
|
return null;
|
|
@@ -282,6 +279,48 @@ function assertSchemaIncludes(fullSchema, requiredSchema) {
|
|
|
282
279
|
throw new Error(`ConcreteDomain: schema is missing required keys (${parts.join(" | ")})`);
|
|
283
280
|
}
|
|
284
281
|
}
|
|
282
|
+
function collectTransitiveDomainNames(source, seen = new Set()) {
|
|
283
|
+
const names = new Set();
|
|
284
|
+
if (!isObjectLike(source))
|
|
285
|
+
return names;
|
|
286
|
+
if (seen.has(source))
|
|
287
|
+
return names;
|
|
288
|
+
seen.add(source);
|
|
289
|
+
const meta = getMeta(source);
|
|
290
|
+
if (!meta)
|
|
291
|
+
return names;
|
|
292
|
+
if (meta.name)
|
|
293
|
+
names.add(meta.name);
|
|
294
|
+
for (const getter of meta.includes ?? []) {
|
|
295
|
+
if (!getter)
|
|
296
|
+
continue;
|
|
297
|
+
let child = null;
|
|
298
|
+
try {
|
|
299
|
+
child = getter();
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
child = null;
|
|
303
|
+
}
|
|
304
|
+
for (const name of collectTransitiveDomainNames(child, seen)) {
|
|
305
|
+
names.add(name);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return names;
|
|
309
|
+
}
|
|
310
|
+
function assertDomainNamesInclude(rootDomain, requiredDomain) {
|
|
311
|
+
const rootMeta = getMeta(rootDomain);
|
|
312
|
+
const requiredMeta = getMeta(requiredDomain);
|
|
313
|
+
if (!rootMeta || !requiredMeta)
|
|
314
|
+
return;
|
|
315
|
+
const rootNames = collectTransitiveDomainNames(rootDomain);
|
|
316
|
+
const requiredNames = collectTransitiveDomainNames(requiredDomain);
|
|
317
|
+
if (rootNames.size === 0 || requiredNames.size === 0)
|
|
318
|
+
return;
|
|
319
|
+
const missing = Array.from(requiredNames).filter((name) => !rootNames.has(name));
|
|
320
|
+
if (missing.length > 0) {
|
|
321
|
+
throw new Error(`ConcreteDomain: domain is missing required names (${missing.join(", ")})`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
285
324
|
function createConcreteDomain(domainInstance, db, fullSchema, bindings) {
|
|
286
325
|
const baseSchema = fullSchema ?? resolveSchema(domainInstance);
|
|
287
326
|
const actionMap = getStoredActionMap(domainInstance);
|
|
@@ -292,8 +331,16 @@ function createConcreteDomain(domainInstance, db, fullSchema, bindings) {
|
|
|
292
331
|
context: (options) => domainInstance.context(options),
|
|
293
332
|
contextString: (options) => domainInstance.contextString(options),
|
|
294
333
|
};
|
|
295
|
-
if (bindings?.
|
|
296
|
-
const inheritedStack =
|
|
334
|
+
if (bindings?.runtime !== undefined) {
|
|
335
|
+
const inheritedStack = [];
|
|
336
|
+
const createActionRuntime = (stack) => {
|
|
337
|
+
const runtime = {
|
|
338
|
+
...concrete,
|
|
339
|
+
...(bindings.env !== undefined ? { env: bindings.env } : {}),
|
|
340
|
+
};
|
|
341
|
+
runtime.actions = buildActions(stack);
|
|
342
|
+
return runtime;
|
|
343
|
+
};
|
|
297
344
|
const buildActions = (stack) => Object.fromEntries(Object.entries(actionMap).map(([key, action]) => [
|
|
298
345
|
key,
|
|
299
346
|
async (input) => {
|
|
@@ -305,25 +352,51 @@ function createConcreteDomain(domainInstance, db, fullSchema, bindings) {
|
|
|
305
352
|
throw new Error(`domain_action_cycle:${key}`);
|
|
306
353
|
}
|
|
307
354
|
const nextStack = [...stack, key];
|
|
308
|
-
const scopedRuntime =
|
|
355
|
+
const scopedRuntime = createActionRuntime(nextStack);
|
|
356
|
+
const parsedInput = action.input.parse(input);
|
|
309
357
|
const params = {
|
|
310
|
-
|
|
311
|
-
input,
|
|
358
|
+
input: parsedInput,
|
|
312
359
|
runtime: scopedRuntime,
|
|
313
360
|
};
|
|
314
|
-
|
|
361
|
+
const output = await execute(params);
|
|
362
|
+
return action.output.parse(output);
|
|
315
363
|
},
|
|
316
364
|
]));
|
|
317
|
-
|
|
318
|
-
|
|
365
|
+
if (bindings.env !== undefined) {
|
|
366
|
+
;
|
|
367
|
+
concrete.env = bindings.env;
|
|
368
|
+
}
|
|
319
369
|
;
|
|
320
370
|
concrete.actions = buildActions(inheritedStack);
|
|
321
371
|
}
|
|
322
372
|
return concrete;
|
|
323
373
|
}
|
|
374
|
+
function promoteRuntimeDomainScope(scoped) {
|
|
375
|
+
const promoted = { ...scoped };
|
|
376
|
+
const db = scoped?.db;
|
|
377
|
+
if (db && typeof db.query === "function") {
|
|
378
|
+
promoted.query = db.query.bind(db);
|
|
379
|
+
}
|
|
380
|
+
const actions = scoped?.actions;
|
|
381
|
+
if (actions && typeof actions === "object") {
|
|
382
|
+
for (const [key, action] of Object.entries(actions)) {
|
|
383
|
+
if (key in promoted)
|
|
384
|
+
continue;
|
|
385
|
+
promoted[key] = action;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return promoted;
|
|
389
|
+
}
|
|
390
|
+
async function callDomainRuntimeScope(domainInstance, runtime, options) {
|
|
391
|
+
if (!runtime || typeof runtime.use !== "function") {
|
|
392
|
+
throw new Error("domain(runtime) requires an Ekairos runtime with use(domain).");
|
|
393
|
+
}
|
|
394
|
+
return promoteRuntimeDomainScope(await runtime.use(domainInstance, options));
|
|
395
|
+
}
|
|
324
396
|
export function materializeDomain(params) {
|
|
325
397
|
const baseSchema = resolveSchema(params.rootDomain);
|
|
326
398
|
const requiredSchema = resolveSchema(params.subdomain);
|
|
399
|
+
assertDomainNamesInclude(params.rootDomain, params.subdomain);
|
|
327
400
|
assertSchemaIncludes(baseSchema, requiredSchema);
|
|
328
401
|
return createConcreteDomain(params.subdomain, params.db, baseSchema, params.bindings);
|
|
329
402
|
}
|
|
@@ -340,18 +413,18 @@ function loadDomainDoc(scope, meta) {
|
|
|
340
413
|
function normalizeDoc(docInfo, options) {
|
|
341
414
|
if (!docInfo?.doc)
|
|
342
415
|
return { doc: null, docPath: docInfo?.docPath };
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
return { doc:
|
|
416
|
+
if (domainDocNormalizer) {
|
|
417
|
+
try {
|
|
418
|
+
const normalized = domainDocNormalizer({ docInfo, options });
|
|
419
|
+
if (normalized)
|
|
420
|
+
return normalized;
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
// Fall through to raw docs. Domain context must remain usable without the
|
|
424
|
+
// optional markdown/YAML parser in workflow bundles.
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return { doc: docInfo.doc, docPath: docInfo.docPath };
|
|
355
428
|
}
|
|
356
429
|
function buildRegistryEntries(meta, options) {
|
|
357
430
|
if (!meta)
|
|
@@ -370,7 +443,7 @@ function buildRegistryEntries(meta, options) {
|
|
|
370
443
|
catch {
|
|
371
444
|
child = null;
|
|
372
445
|
}
|
|
373
|
-
if (!child
|
|
446
|
+
if (!isObjectLike(child))
|
|
374
447
|
continue;
|
|
375
448
|
if (seen.has(child))
|
|
376
449
|
continue;
|
|
@@ -492,7 +565,7 @@ function resolveIncludeNames(meta) {
|
|
|
492
565
|
catch {
|
|
493
566
|
child = null;
|
|
494
567
|
}
|
|
495
|
-
if (!child
|
|
568
|
+
if (!isObjectLike(child))
|
|
496
569
|
continue;
|
|
497
570
|
const childMeta = getMeta(child);
|
|
498
571
|
if (childMeta?.name)
|
|
@@ -500,6 +573,64 @@ function resolveIncludeNames(meta) {
|
|
|
500
573
|
}
|
|
501
574
|
return Array.from(names);
|
|
502
575
|
}
|
|
576
|
+
function isRuntimeEntityDef(value) {
|
|
577
|
+
return Boolean(value &&
|
|
578
|
+
typeof value === "object" &&
|
|
579
|
+
"attrs" in value &&
|
|
580
|
+
value.attrs &&
|
|
581
|
+
typeof value.attrs === "object");
|
|
582
|
+
}
|
|
583
|
+
function stripRuntimeEntityLinks(entity) {
|
|
584
|
+
if (!isRuntimeEntityDef(entity))
|
|
585
|
+
return entity;
|
|
586
|
+
return i.entity({ ...entity.attrs });
|
|
587
|
+
}
|
|
588
|
+
function normalizeRuntimeAttrDef(value) {
|
|
589
|
+
if (!value || typeof value !== "object") {
|
|
590
|
+
return value;
|
|
591
|
+
}
|
|
592
|
+
const record = value;
|
|
593
|
+
const sorted = {};
|
|
594
|
+
for (const key of Object.keys(record).sort()) {
|
|
595
|
+
sorted[key] = normalizeRuntimeAttrDef(record[key]);
|
|
596
|
+
}
|
|
597
|
+
return sorted;
|
|
598
|
+
}
|
|
599
|
+
function stableRuntimeAttrDef(value) {
|
|
600
|
+
return JSON.stringify(normalizeRuntimeAttrDef(value));
|
|
601
|
+
}
|
|
602
|
+
function areRuntimeAttrDefsEquivalent(baseAttr, nextAttr) {
|
|
603
|
+
if (baseAttr === nextAttr)
|
|
604
|
+
return true;
|
|
605
|
+
return stableRuntimeAttrDef(baseAttr) === stableRuntimeAttrDef(nextAttr);
|
|
606
|
+
}
|
|
607
|
+
function mergeRuntimeEntityDefs(entityName, baseEntity, nextEntity) {
|
|
608
|
+
if (!isRuntimeEntityDef(baseEntity) || !isRuntimeEntityDef(nextEntity)) {
|
|
609
|
+
return stripRuntimeEntityLinks(nextEntity);
|
|
610
|
+
}
|
|
611
|
+
const conflictingAttrs = Object.keys(nextEntity.attrs).filter((attr) => Object.prototype.hasOwnProperty.call(baseEntity.attrs, attr) &&
|
|
612
|
+
!areRuntimeAttrDefsEquivalent(baseEntity.attrs[attr], nextEntity.attrs[attr]));
|
|
613
|
+
if (conflictingAttrs.length > 0) {
|
|
614
|
+
throw new Error(`domain_duplicate_entity_attr:${entityName}.${conflictingAttrs.join(",")}`);
|
|
615
|
+
}
|
|
616
|
+
return i.entity({
|
|
617
|
+
...baseEntity.attrs,
|
|
618
|
+
...nextEntity.attrs,
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
function mergeRuntimeEntities(baseEntities, nextEntities) {
|
|
622
|
+
const merged = {};
|
|
623
|
+
for (const [entityName, entity] of Object.entries(baseEntities)) {
|
|
624
|
+
merged[entityName] = stripRuntimeEntityLinks(entity);
|
|
625
|
+
}
|
|
626
|
+
for (const [entityName, entity] of Object.entries(nextEntities)) {
|
|
627
|
+
merged[entityName] =
|
|
628
|
+
entityName in merged
|
|
629
|
+
? mergeRuntimeEntityDefs(entityName, merged[entityName], entity)
|
|
630
|
+
: stripRuntimeEntityLinks(entity);
|
|
631
|
+
}
|
|
632
|
+
return merged;
|
|
633
|
+
}
|
|
503
634
|
function makeInstance(def, metaIncludes = []) {
|
|
504
635
|
const meta = {
|
|
505
636
|
name: def.name,
|
|
@@ -519,7 +650,7 @@ function makeInstance(def, metaIncludes = []) {
|
|
|
519
650
|
const otherDef = "schema" in other
|
|
520
651
|
? { entities: other.entities, links: other.links, rooms: other.rooms }
|
|
521
652
|
: other;
|
|
522
|
-
const mergedEntities =
|
|
653
|
+
const mergedEntities = mergeRuntimeEntities(def.entities, otherDef.entities);
|
|
523
654
|
const mergedLinks = { ...def.links, ...otherDef.links };
|
|
524
655
|
const mergedRooms = { ...def.rooms, ...otherDef.rooms };
|
|
525
656
|
const composed = makeInstance({
|
|
@@ -576,10 +707,10 @@ export function domain(arg) {
|
|
|
576
707
|
// Support lazy includes for circular dependencies by storing references and resolving at schema()/toInstantSchema() time
|
|
577
708
|
// AL preserves literal link keys from included domains
|
|
578
709
|
function createBuilder(deps, linkDeps, lazyIncludes = [], meta) {
|
|
579
|
-
|
|
710
|
+
const builder = {
|
|
580
711
|
includes(other) {
|
|
581
712
|
// Support lazy includes via function for circular dependencies
|
|
582
|
-
if (typeof other === 'function') {
|
|
713
|
+
if (typeof other === 'function' && !isMaterializedDomainSource(other)) {
|
|
583
714
|
const lazyGetter = () => {
|
|
584
715
|
try {
|
|
585
716
|
return other();
|
|
@@ -613,7 +744,7 @@ export function domain(arg) {
|
|
|
613
744
|
return createBuilder(deps, linkDeps, [...lazyIncludes, lazyGetter], nextMeta);
|
|
614
745
|
}
|
|
615
746
|
const links = other.links;
|
|
616
|
-
const mergedEntities =
|
|
747
|
+
const mergedEntities = mergeRuntimeEntities(deps, entities);
|
|
617
748
|
// Preserve literal link keys by merging directly (not casting to LinksDef)
|
|
618
749
|
const mergedLinks = (links ? { ...linkDeps, ...links } : { ...linkDeps });
|
|
619
750
|
const includeRef = () => other;
|
|
@@ -628,10 +759,11 @@ export function domain(arg) {
|
|
|
628
759
|
return createBuilder(deps, linkDeps, [...lazyIncludes, lazyGetter], nextMeta);
|
|
629
760
|
}
|
|
630
761
|
},
|
|
631
|
-
|
|
762
|
+
withSchema(def) {
|
|
632
763
|
// Resolve lazy includes at schema() time (when all domains should be initialized)
|
|
633
764
|
// This handles circular dependencies by deferring entity resolution
|
|
634
765
|
let resolvedDeps = { ...deps };
|
|
766
|
+
const pendingLazyIncludes = [];
|
|
635
767
|
// Preserve literal link keys from accumulated links
|
|
636
768
|
let resolvedLinks = { ...linkDeps };
|
|
637
769
|
for (const lazyGetter of lazyIncludes) {
|
|
@@ -640,7 +772,7 @@ export function domain(arg) {
|
|
|
640
772
|
if (other) {
|
|
641
773
|
const entities = other.entities;
|
|
642
774
|
if (entities) {
|
|
643
|
-
resolvedDeps =
|
|
775
|
+
resolvedDeps = mergeRuntimeEntities(resolvedDeps, entities);
|
|
644
776
|
}
|
|
645
777
|
const links = other.links;
|
|
646
778
|
if (links) {
|
|
@@ -648,14 +780,18 @@ export function domain(arg) {
|
|
|
648
780
|
resolvedLinks = { ...resolvedLinks, ...links };
|
|
649
781
|
}
|
|
650
782
|
}
|
|
783
|
+
else {
|
|
784
|
+
pendingLazyIncludes.push(lazyGetter);
|
|
785
|
+
}
|
|
651
786
|
}
|
|
652
787
|
catch (e) {
|
|
653
788
|
// If lazy resolution fails, continue - entities might be available via string references
|
|
654
789
|
// This is expected for circular dependencies that will be resolved when all domains are composed
|
|
790
|
+
pendingLazyIncludes.push(lazyGetter);
|
|
655
791
|
}
|
|
656
792
|
}
|
|
657
793
|
// Runtime merge for output; compile-time validation handled by types above
|
|
658
|
-
const allEntities =
|
|
794
|
+
const allEntities = mergeRuntimeEntities(resolvedDeps, def.entities);
|
|
659
795
|
// allLinks contains merged links from included domains + current domain
|
|
660
796
|
// Preserve literal link keys (owner, related, parent, etc.) by using MergeLinks
|
|
661
797
|
const allLinks = { ...resolvedLinks, ...def.links };
|
|
@@ -672,13 +808,13 @@ export function domain(arg) {
|
|
|
672
808
|
let finalLinks = cloneLinksDef(capturedLinks);
|
|
673
809
|
let hasUnresolvedIncludes = false;
|
|
674
810
|
// Try to resolve lazy includes one more time (domains should be initialized by now)
|
|
675
|
-
for (const lazyGetter of
|
|
811
|
+
for (const lazyGetter of pendingLazyIncludes) {
|
|
676
812
|
try {
|
|
677
813
|
const other = lazyGetter();
|
|
678
814
|
if (other) {
|
|
679
815
|
const entities = other.entities;
|
|
680
816
|
if (entities) {
|
|
681
|
-
finalEntities =
|
|
817
|
+
finalEntities = mergeRuntimeEntities(finalEntities, entities);
|
|
682
818
|
}
|
|
683
819
|
const links = other.links;
|
|
684
820
|
if (links) {
|
|
@@ -730,7 +866,9 @@ export function domain(arg) {
|
|
|
730
866
|
}
|
|
731
867
|
return frozenSchema;
|
|
732
868
|
};
|
|
733
|
-
|
|
869
|
+
let result;
|
|
870
|
+
const callableResult = (runtime, options) => callDomainRuntimeScope(result, runtime, options);
|
|
871
|
+
result = Object.assign(callableResult, {
|
|
734
872
|
entities: Object.freeze({ ...allEntities }),
|
|
735
873
|
// Strip base phantom from public type so it's assignable to i.schema()
|
|
736
874
|
links: Object.freeze(cloneLinksDef(allLinks)),
|
|
@@ -739,22 +877,35 @@ export function domain(arg) {
|
|
|
739
877
|
originalEntities: Object.freeze({ ...allEntities }),
|
|
740
878
|
instantSchema,
|
|
741
879
|
toInstantSchema: instantSchema,
|
|
742
|
-
};
|
|
880
|
+
});
|
|
743
881
|
attachMeta(result, freezeMeta(meta));
|
|
744
882
|
result.context = (options) => buildContext(result, options);
|
|
745
883
|
result.contextString = (options) => contextToString(buildContext(result, options));
|
|
746
884
|
result.fromDB = (db, bindings) => createConcreteDomain(result, db, resolveSchema(result), bindings);
|
|
747
|
-
const
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
885
|
+
const reboundByAction = new Map();
|
|
886
|
+
const reboundActionMap = Object.fromEntries(Object.entries(seedActionMap).map(([key, action]) => {
|
|
887
|
+
const rebound = bindAction(action, {
|
|
888
|
+
name: action.name,
|
|
889
|
+
domain: result,
|
|
890
|
+
key,
|
|
891
|
+
});
|
|
892
|
+
reboundByAction.set(action, rebound);
|
|
893
|
+
return [key, rebound];
|
|
751
894
|
}));
|
|
895
|
+
const reboundActions = seedActions.map((action) => {
|
|
896
|
+
const rebound = reboundByAction.get(action);
|
|
897
|
+
if (rebound)
|
|
898
|
+
return rebound;
|
|
899
|
+
return bindAction(action, {
|
|
900
|
+
name: action.name,
|
|
901
|
+
domain: result,
|
|
902
|
+
key: getActionBinding(action)?.key,
|
|
903
|
+
});
|
|
904
|
+
});
|
|
752
905
|
setStoredActions(result, [...reboundActions]);
|
|
753
|
-
setStoredActionMap(result,
|
|
754
|
-
result.actions = (
|
|
755
|
-
|
|
756
|
-
return [...getStoredActions(result)];
|
|
757
|
-
}
|
|
906
|
+
setStoredActionMap(result, reboundActionMap);
|
|
907
|
+
result.actions = getStoredActionMap(result);
|
|
908
|
+
result.withActions = (actionsInput) => {
|
|
758
909
|
const current = getStoredActions(result);
|
|
759
910
|
const currentMap = getStoredActionMap(result);
|
|
760
911
|
const additions = normalizeActionCollection(result, actionsInput);
|
|
@@ -762,11 +913,16 @@ export function domain(arg) {
|
|
|
762
913
|
};
|
|
763
914
|
result.getActions = () => [...getStoredActions(result)];
|
|
764
915
|
result.getActionMap = () => ({ ...getStoredActionMap(result) });
|
|
916
|
+
result.definition = () => result;
|
|
765
917
|
return Object.freeze(result);
|
|
766
918
|
};
|
|
767
919
|
return createDomainResult([], {});
|
|
768
920
|
},
|
|
921
|
+
schema(def) {
|
|
922
|
+
return this.withSchema(def);
|
|
923
|
+
},
|
|
769
924
|
};
|
|
925
|
+
return builder;
|
|
770
926
|
}
|
|
771
927
|
if (typeof arg === "string" && !arg.trim()) {
|
|
772
928
|
throw new Error("domain() requires a name");
|
|
@@ -779,21 +935,34 @@ export function composeDomain(name, includes = []) {
|
|
|
779
935
|
for (const include of includes) {
|
|
780
936
|
builder = builder.includes(include);
|
|
781
937
|
}
|
|
782
|
-
return builder.
|
|
938
|
+
return builder.withSchema({ entities: {}, links: {}, rooms: {} });
|
|
783
939
|
}
|
|
784
940
|
/**
|
|
785
941
|
* Define a domain action without changing the public action contract.
|
|
786
942
|
*
|
|
787
943
|
* Convention for new actions:
|
|
788
944
|
*
|
|
789
|
-
* `async execute({ runtime, input }) {
|
|
945
|
+
* `async execute({ runtime, input }) { await runtime.db.transact([...]); }`
|
|
790
946
|
*
|
|
791
|
-
* Actions
|
|
792
|
-
* composition
|
|
947
|
+
* Actions receive a runtime already scoped to the declaring domain. Nested
|
|
948
|
+
* action composition is available through `runtime.actions.*`.
|
|
793
949
|
*/
|
|
950
|
+
function toJsonSchema(schema) {
|
|
951
|
+
try {
|
|
952
|
+
return z.toJSONSchema(schema, { target: "draft-7" });
|
|
953
|
+
}
|
|
954
|
+
catch {
|
|
955
|
+
return undefined;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
794
958
|
export function defineDomainAction(action) {
|
|
795
|
-
return
|
|
959
|
+
return Object.freeze({
|
|
960
|
+
...action,
|
|
961
|
+
inputSchema: toJsonSchema(action.input),
|
|
962
|
+
outputSchema: toJsonSchema(action.output),
|
|
963
|
+
});
|
|
796
964
|
}
|
|
965
|
+
export const defineAction = defineDomainAction;
|
|
797
966
|
export function getDomainActions(source) {
|
|
798
967
|
return getStoredActions(source);
|
|
799
968
|
}
|