@ekairos/domain 1.22.37-beta.development.0 → 1.22.37

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.
Files changed (47) hide show
  1. package/README.md +102 -23
  2. package/SKILL.md +56 -0
  3. package/dist/cli/bin.d.ts +1 -1
  4. package/dist/cli/bin.d.ts.map +1 -1
  5. package/dist/cli/bin.js +141 -20
  6. package/dist/cli/bin.js.map +1 -1
  7. package/dist/cli/create-app.d.ts +34 -1
  8. package/dist/cli/create-app.d.ts.map +1 -1
  9. package/dist/cli/create-app.js +2139 -508
  10. package/dist/cli/create-app.js.map +1 -1
  11. package/dist/cli/http.d.ts.map +1 -1
  12. package/dist/cli/http.js +2 -6
  13. package/dist/cli/http.js.map +1 -1
  14. package/dist/cli/server.d.ts.map +1 -1
  15. package/dist/cli/server.js +3 -2
  16. package/dist/cli/server.js.map +1 -1
  17. package/dist/cli/types.d.ts +1 -0
  18. package/dist/cli/types.d.ts.map +1 -1
  19. package/dist/cli/ui.d.ts.map +1 -1
  20. package/dist/cli/ui.js +2 -0
  21. package/dist/cli/ui.js.map +1 -1
  22. package/dist/context.test-runner.js +3 -1
  23. package/dist/context.test-runner.js.map +1 -1
  24. package/dist/domain-doc.d.ts +2 -0
  25. package/dist/domain-doc.d.ts.map +1 -1
  26. package/dist/domain-doc.js +14 -0
  27. package/dist/domain-doc.js.map +1 -1
  28. package/dist/index.d.ts +190 -54
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +247 -78
  31. package/dist/index.js.map +1 -1
  32. package/dist/next.d.ts.map +1 -1
  33. package/dist/next.js +3 -2
  34. package/dist/next.js.map +1 -1
  35. package/dist/platform-sync.d.ts +15 -0
  36. package/dist/platform-sync.d.ts.map +1 -0
  37. package/dist/platform-sync.js +105 -0
  38. package/dist/platform-sync.js.map +1 -0
  39. package/dist/runtime-handle.d.ts +14 -3
  40. package/dist/runtime-handle.d.ts.map +1 -1
  41. package/dist/runtime-handle.js +2 -0
  42. package/dist/runtime-handle.js.map +1 -1
  43. package/dist/runtime.d.ts +8 -7
  44. package/dist/runtime.d.ts.map +1 -1
  45. package/dist/runtime.js +10 -8
  46. package/dist/runtime.js.map +1 -1
  47. package/package.json +15 -11
package/dist/index.js CHANGED
@@ -1,23 +1,25 @@
1
1
  import { i } from "@instantdb/core";
2
- import { filterDomainDoc, parseDomainDoc, renderDomainDoc, } from "./domain-doc.js";
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 || typeof source !== "object")
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 || typeof source !== "object")
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 || typeof source !== "object")
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 || typeof source !== "object")
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 || typeof source !== "object")
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 || typeof source !== "object")
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 = typeof value === "function"
112
- ? { execute: value }
113
- : value;
114
- if (!action || typeof action !== "object" || typeof action.execute !== "function") {
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?.env !== undefined && bindings?.runtime !== undefined) {
296
- const inheritedStack = readRuntimeActionStack(bindings.runtime);
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 = cloneRuntimeWithActionStack(bindings.runtime, nextStack);
355
+ const scopedRuntime = createActionRuntime(nextStack);
356
+ const parsedInput = action.input.parse(input);
309
357
  const params = {
310
- env: bindings.env,
311
- input,
358
+ input: parsedInput,
312
359
  runtime: scopedRuntime,
313
360
  };
314
- return await execute(params);
361
+ const output = await execute(params);
362
+ return action.output.parse(output);
315
363
  },
316
364
  ]));
317
- ;
318
- concrete.env = bindings.env;
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
- const parsed = parseDomainDoc(docInfo.doc);
344
- if (!parsed)
345
- return { doc: docInfo.doc, docPath: docInfo.docPath };
346
- const filtered = filterDomainDoc(parsed.data, {
347
- subdomains: options.subdomains,
348
- entities: options.entities,
349
- });
350
- const rendered = renderDomainDoc(filtered, {
351
- titlePrefix: options.titlePrefix,
352
- includeSubdomains: options.includeSubdomains,
353
- });
354
- return { doc: rendered, docPath: docInfo.docPath };
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 || typeof child !== "object")
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 || typeof child !== "object")
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 = { ...def.entities, ...otherDef.entities };
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
- return {
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 = { ...deps, ...entities };
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
- schema(def) {
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 = { ...resolvedDeps, ...entities };
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 = { ...resolvedDeps, ...def.entities };
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 lazyIncludes) {
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 = { ...finalEntities, ...entities };
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
- const result = {
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 reboundActions = seedActions.map((action) => bindAction(action, {
748
- name: action.name,
749
- domain: result,
750
- key: getActionBinding(action)?.key,
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, { ...seedActionMap });
754
- result.actions = (actionsInput) => {
755
- if (actionsInput === undefined) {
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.schema({ entities: {}, links: {}, rooms: {} });
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 }) { "use step"; const domain = await runtime.use(myDomain); ... }`
945
+ * `async execute({ runtime, input }) { await runtime.db.transact([...]); }`
790
946
  *
791
- * Actions remain callable directly, from nested `runtime.use(domain).actions.*`
792
- * composition, and from higher-level workflows that orchestrate them.
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 action;
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
  }