@almadar/runtime 1.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.
@@ -0,0 +1,3185 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { OrbitalSchemaSchema, isEntityReference, parseEntityRef, parseImportedTraitRef, isPageReference, isPageReferenceString, isPageReferenceObject, parsePageRef } from '@almadar/core';
4
+ import { Router } from 'express';
5
+ import { evaluateGuard, createMinimalContext, resolveBinding, evaluate } from '@almadar/evaluator';
6
+ import { faker } from '@faker-js/faker';
7
+
8
+ var __defProp = Object.defineProperty;
9
+ var __getOwnPropNames = Object.getOwnPropertyNames;
10
+ var __esm = (fn, res) => function __init() {
11
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
12
+ };
13
+ var __export = (target, all) => {
14
+ for (var name in all)
15
+ __defProp(target, name, { get: all[name], enumerable: true });
16
+ };
17
+
18
+ // src/loader/external-loader.ts
19
+ var external_loader_exports = {};
20
+ __export(external_loader_exports, {
21
+ ExternalOrbitalLoader: () => ExternalOrbitalLoader,
22
+ ImportChain: () => ImportChain,
23
+ LoaderCache: () => LoaderCache,
24
+ createLoader: () => createLoader,
25
+ parseImportPath: () => parseImportPath
26
+ });
27
+ function createLoader(basePath, options) {
28
+ return new ExternalOrbitalLoader({
29
+ basePath,
30
+ ...options
31
+ });
32
+ }
33
+ function parseImportPath(importPath) {
34
+ const hashIndex = importPath.indexOf("#");
35
+ if (hashIndex === -1) {
36
+ return { path: importPath };
37
+ }
38
+ return {
39
+ path: importPath.slice(0, hashIndex),
40
+ fragment: importPath.slice(hashIndex + 1)
41
+ };
42
+ }
43
+ var ImportChain, LoaderCache, ExternalOrbitalLoader;
44
+ var init_external_loader = __esm({
45
+ "src/loader/external-loader.ts"() {
46
+ ImportChain = class _ImportChain {
47
+ chain = [];
48
+ /**
49
+ * Try to add a path to the chain.
50
+ * @returns Error message if circular, null if OK
51
+ */
52
+ push(absolutePath) {
53
+ if (this.chain.includes(absolutePath)) {
54
+ const cycle = [...this.chain.slice(this.chain.indexOf(absolutePath)), absolutePath];
55
+ return `Circular import detected: ${cycle.join(" -> ")}`;
56
+ }
57
+ this.chain.push(absolutePath);
58
+ return null;
59
+ }
60
+ /**
61
+ * Remove the last path from the chain.
62
+ */
63
+ pop() {
64
+ this.chain.pop();
65
+ }
66
+ /**
67
+ * Clone the chain for nested loading.
68
+ */
69
+ clone() {
70
+ const newChain = new _ImportChain();
71
+ newChain.chain = [...this.chain];
72
+ return newChain;
73
+ }
74
+ };
75
+ LoaderCache = class {
76
+ cache = /* @__PURE__ */ new Map();
77
+ get(absolutePath) {
78
+ return this.cache.get(absolutePath);
79
+ }
80
+ set(absolutePath, schema) {
81
+ this.cache.set(absolutePath, schema);
82
+ }
83
+ has(absolutePath) {
84
+ return this.cache.has(absolutePath);
85
+ }
86
+ clear() {
87
+ this.cache.clear();
88
+ }
89
+ get size() {
90
+ return this.cache.size;
91
+ }
92
+ };
93
+ ExternalOrbitalLoader = class {
94
+ options;
95
+ cache;
96
+ constructor(options) {
97
+ this.options = {
98
+ stdLibPath: options.stdLibPath ?? "",
99
+ scopedPaths: options.scopedPaths ?? {},
100
+ allowOutsideBasePath: options.allowOutsideBasePath ?? false,
101
+ ...options
102
+ };
103
+ this.cache = new LoaderCache();
104
+ }
105
+ /**
106
+ * Load a schema from an import path.
107
+ *
108
+ * @param importPath - The import path (e.g., "./health.orb", "std/behaviors/game-core")
109
+ * @param fromPath - The path of the file doing the import (for relative resolution)
110
+ * @param chain - Import chain for circular detection
111
+ */
112
+ async load(importPath, fromPath, chain) {
113
+ const importChain = chain ?? new ImportChain();
114
+ const resolveResult = this.resolvePath(importPath, fromPath);
115
+ if (!resolveResult.success) {
116
+ return resolveResult;
117
+ }
118
+ const absolutePath = resolveResult.data;
119
+ const circularError = importChain.push(absolutePath);
120
+ if (circularError) {
121
+ return { success: false, error: circularError };
122
+ }
123
+ try {
124
+ const cached = this.cache.get(absolutePath);
125
+ if (cached) {
126
+ return { success: true, data: cached };
127
+ }
128
+ const loadResult = await this.loadFile(absolutePath);
129
+ if (!loadResult.success) {
130
+ return loadResult;
131
+ }
132
+ const loaded = {
133
+ schema: loadResult.data,
134
+ sourcePath: absolutePath,
135
+ importPath
136
+ };
137
+ this.cache.set(absolutePath, loaded);
138
+ return { success: true, data: loaded };
139
+ } finally {
140
+ importChain.pop();
141
+ }
142
+ }
143
+ /**
144
+ * Load a specific orbital from a schema by name.
145
+ *
146
+ * @param importPath - The import path
147
+ * @param orbitalName - The orbital name (optional, defaults to first orbital)
148
+ * @param fromPath - The path of the file doing the import
149
+ * @param chain - Import chain for circular detection
150
+ */
151
+ async loadOrbital(importPath, orbitalName, fromPath, chain) {
152
+ const schemaResult = await this.load(importPath, fromPath, chain);
153
+ if (!schemaResult.success) {
154
+ return schemaResult;
155
+ }
156
+ const schema = schemaResult.data.schema;
157
+ let orbital;
158
+ if (orbitalName) {
159
+ const found = schema.orbitals.find((o) => o.name === orbitalName);
160
+ if (!found) {
161
+ return {
162
+ success: false,
163
+ error: `Orbital "${orbitalName}" not found in ${importPath}. Available: ${schema.orbitals.map((o) => o.name).join(", ")}`
164
+ };
165
+ }
166
+ orbital = found;
167
+ } else {
168
+ if (schema.orbitals.length === 0) {
169
+ return {
170
+ success: false,
171
+ error: `No orbitals found in ${importPath}`
172
+ };
173
+ }
174
+ orbital = schema.orbitals[0];
175
+ }
176
+ return {
177
+ success: true,
178
+ data: {
179
+ orbital,
180
+ sourcePath: schemaResult.data.sourcePath,
181
+ importPath
182
+ }
183
+ };
184
+ }
185
+ /**
186
+ * Resolve an import path to an absolute filesystem path.
187
+ */
188
+ resolvePath(importPath, fromPath) {
189
+ if (importPath.startsWith("std/")) {
190
+ return this.resolveStdPath(importPath);
191
+ }
192
+ if (importPath.startsWith("@")) {
193
+ return this.resolveScopedPath(importPath);
194
+ }
195
+ if (importPath.startsWith("./") || importPath.startsWith("../")) {
196
+ return this.resolveRelativePath(importPath, fromPath);
197
+ }
198
+ if (path.isAbsolute(importPath)) {
199
+ if (!this.options.allowOutsideBasePath) {
200
+ return {
201
+ success: false,
202
+ error: `Absolute paths not allowed: ${importPath}`
203
+ };
204
+ }
205
+ return { success: true, data: importPath };
206
+ }
207
+ return this.resolveRelativePath(`./${importPath}`, fromPath);
208
+ }
209
+ /**
210
+ * Resolve a standard library path.
211
+ */
212
+ resolveStdPath(importPath) {
213
+ if (!this.options.stdLibPath) {
214
+ return {
215
+ success: false,
216
+ error: `Standard library path not configured. Cannot load: ${importPath}`
217
+ };
218
+ }
219
+ const relativePath = importPath.slice(4);
220
+ let absolutePath = path.join(this.options.stdLibPath, relativePath);
221
+ if (!absolutePath.endsWith(".orb")) {
222
+ absolutePath += ".orb";
223
+ }
224
+ const normalizedPath = path.normalize(absolutePath);
225
+ const normalizedStdLib = path.normalize(this.options.stdLibPath);
226
+ if (!normalizedPath.startsWith(normalizedStdLib)) {
227
+ return {
228
+ success: false,
229
+ error: `Path traversal outside std library: ${importPath}`
230
+ };
231
+ }
232
+ return { success: true, data: absolutePath };
233
+ }
234
+ /**
235
+ * Resolve a scoped package path.
236
+ */
237
+ resolveScopedPath(importPath) {
238
+ const match = importPath.match(/^(@[^/]+)/);
239
+ if (!match) {
240
+ return {
241
+ success: false,
242
+ error: `Invalid scoped package path: ${importPath}`
243
+ };
244
+ }
245
+ const scope = match[1];
246
+ const scopeRoot = this.options.scopedPaths[scope];
247
+ if (!scopeRoot) {
248
+ return {
249
+ success: false,
250
+ error: `Scoped package "${scope}" not configured. Available: ${Object.keys(this.options.scopedPaths).join(", ") || "none"}`
251
+ };
252
+ }
253
+ const relativePath = importPath.slice(scope.length + 1);
254
+ let absolutePath = path.join(scopeRoot, relativePath);
255
+ if (!absolutePath.endsWith(".orb")) {
256
+ absolutePath += ".orb";
257
+ }
258
+ const normalizedPath = path.normalize(absolutePath);
259
+ const normalizedRoot = path.normalize(scopeRoot);
260
+ if (!normalizedPath.startsWith(normalizedRoot)) {
261
+ return {
262
+ success: false,
263
+ error: `Path traversal outside scoped package: ${importPath}`
264
+ };
265
+ }
266
+ return { success: true, data: absolutePath };
267
+ }
268
+ /**
269
+ * Resolve a relative path.
270
+ */
271
+ resolveRelativePath(importPath, fromPath) {
272
+ const baseDir = fromPath ? path.dirname(fromPath) : this.options.basePath;
273
+ let absolutePath = path.resolve(baseDir, importPath);
274
+ if (!absolutePath.endsWith(".orb")) {
275
+ absolutePath += ".orb";
276
+ }
277
+ if (!this.options.allowOutsideBasePath) {
278
+ const normalizedPath = path.normalize(absolutePath);
279
+ const normalizedBase = path.normalize(this.options.basePath);
280
+ if (!normalizedPath.startsWith(normalizedBase)) {
281
+ return {
282
+ success: false,
283
+ error: `Path traversal outside base path: ${importPath}. Base: ${this.options.basePath}`
284
+ };
285
+ }
286
+ }
287
+ return { success: true, data: absolutePath };
288
+ }
289
+ /**
290
+ * Load and parse an .orb file.
291
+ */
292
+ async loadFile(absolutePath) {
293
+ try {
294
+ if (!fs.existsSync(absolutePath)) {
295
+ return {
296
+ success: false,
297
+ error: `File not found: ${absolutePath}`
298
+ };
299
+ }
300
+ const content = await fs.promises.readFile(absolutePath, "utf-8");
301
+ let data;
302
+ try {
303
+ data = JSON.parse(content);
304
+ } catch (e) {
305
+ return {
306
+ success: false,
307
+ error: `Invalid JSON in ${absolutePath}: ${e instanceof Error ? e.message : String(e)}`
308
+ };
309
+ }
310
+ const parseResult = OrbitalSchemaSchema.safeParse(data);
311
+ if (!parseResult.success) {
312
+ const errors = parseResult.error.errors.map((e) => ` - ${e.path.join(".")}: ${e.message}`).join("\n");
313
+ return {
314
+ success: false,
315
+ error: `Invalid schema in ${absolutePath}:
316
+ ${errors}`
317
+ };
318
+ }
319
+ return { success: true, data: parseResult.data };
320
+ } catch (e) {
321
+ return {
322
+ success: false,
323
+ error: `Failed to load ${absolutePath}: ${e instanceof Error ? e.message : String(e)}`
324
+ };
325
+ }
326
+ }
327
+ /**
328
+ * Clear the cache.
329
+ */
330
+ clearCache() {
331
+ this.cache.clear();
332
+ }
333
+ /**
334
+ * Get cache statistics.
335
+ */
336
+ getCacheStats() {
337
+ return { size: this.cache.size };
338
+ }
339
+ };
340
+ }
341
+ });
342
+
343
+ // src/EventBus.ts
344
+ var EventBus = class {
345
+ listeners = /* @__PURE__ */ new Map();
346
+ debug;
347
+ constructor(options = {}) {
348
+ this.debug = options.debug ?? false;
349
+ }
350
+ /**
351
+ * Emit an event to all registered listeners
352
+ */
353
+ emit(type, payload, source) {
354
+ const event = {
355
+ type,
356
+ payload,
357
+ timestamp: Date.now(),
358
+ source
359
+ };
360
+ const listeners = this.listeners.get(type);
361
+ const listenerCount = listeners?.size ?? 0;
362
+ if (this.debug) {
363
+ if (listenerCount > 0) {
364
+ console.log(`[EventBus] Emit: ${type} \u2192 ${listenerCount} listener(s)`, payload);
365
+ } else {
366
+ console.warn(`[EventBus] Emit: ${type} (NO LISTENERS)`, payload);
367
+ }
368
+ }
369
+ if (listeners) {
370
+ const listenersCopy = Array.from(listeners);
371
+ for (const listener of listenersCopy) {
372
+ try {
373
+ listener(event);
374
+ } catch (error) {
375
+ console.error(`[EventBus] Error in listener for '${type}':`, error);
376
+ }
377
+ }
378
+ }
379
+ if (type !== "*") {
380
+ const wildcardListeners = this.listeners.get("*");
381
+ if (wildcardListeners) {
382
+ for (const listener of Array.from(wildcardListeners)) {
383
+ try {
384
+ listener(event);
385
+ } catch (error) {
386
+ console.error(`[EventBus] Error in wildcard listener:`, error);
387
+ }
388
+ }
389
+ }
390
+ }
391
+ }
392
+ /**
393
+ * Subscribe to an event type
394
+ */
395
+ on(type, listener) {
396
+ if (!this.listeners.has(type)) {
397
+ this.listeners.set(type, /* @__PURE__ */ new Set());
398
+ }
399
+ const listeners = this.listeners.get(type);
400
+ listeners.add(listener);
401
+ if (this.debug) {
402
+ console.log(`[EventBus] Subscribed to '${type}', total: ${listeners.size}`);
403
+ }
404
+ return () => {
405
+ listeners.delete(listener);
406
+ if (this.debug) {
407
+ console.log(`[EventBus] Unsubscribed from '${type}', remaining: ${listeners.size}`);
408
+ }
409
+ if (listeners.size === 0) {
410
+ this.listeners.delete(type);
411
+ }
412
+ };
413
+ }
414
+ /**
415
+ * Subscribe to ALL events (wildcard listener)
416
+ * Useful for event tracking, logging, debugging
417
+ */
418
+ onAny(listener) {
419
+ return this.on("*", listener);
420
+ }
421
+ /**
422
+ * Check if there are listeners for an event type
423
+ */
424
+ hasListeners(type) {
425
+ const listeners = this.listeners.get(type);
426
+ return listeners !== void 0 && listeners.size > 0;
427
+ }
428
+ /**
429
+ * Get all registered event types
430
+ */
431
+ getRegisteredEvents() {
432
+ return Array.from(this.listeners.keys());
433
+ }
434
+ /**
435
+ * Clear all listeners
436
+ */
437
+ clear() {
438
+ if (this.debug) {
439
+ console.log(`[EventBus] Clearing all listeners (${this.listeners.size} event types)`);
440
+ }
441
+ this.listeners.clear();
442
+ }
443
+ /**
444
+ * Get listener count for an event type (for testing)
445
+ */
446
+ getListenerCount(type) {
447
+ return this.listeners.get(type)?.size ?? 0;
448
+ }
449
+ };
450
+ var OPERATORS = /* @__PURE__ */ new Set([
451
+ // Comparison
452
+ "=",
453
+ "!=",
454
+ "<",
455
+ ">",
456
+ "<=",
457
+ ">=",
458
+ // Logic
459
+ "and",
460
+ "or",
461
+ "not",
462
+ "if",
463
+ // Math
464
+ "+",
465
+ "-",
466
+ "*",
467
+ "/",
468
+ "%",
469
+ // Array
470
+ "count",
471
+ "first",
472
+ "last",
473
+ "map",
474
+ "filter",
475
+ "find",
476
+ "some",
477
+ "every",
478
+ "reduce",
479
+ // String
480
+ "concat",
481
+ "upper",
482
+ "lower",
483
+ "trim",
484
+ "substring",
485
+ "split",
486
+ "join",
487
+ "str",
488
+ // Effects
489
+ "set",
490
+ "emit",
491
+ "navigate",
492
+ "persist",
493
+ "notify",
494
+ "render-ui",
495
+ "render",
496
+ "spawn",
497
+ "despawn",
498
+ "call-service",
499
+ "do",
500
+ "when",
501
+ "increment",
502
+ "decrement",
503
+ "log"
504
+ ]);
505
+ function interpolateProps(props, ctx) {
506
+ const result = {};
507
+ for (const [key, value] of Object.entries(props)) {
508
+ result[key] = interpolateValue(value, ctx);
509
+ }
510
+ return result;
511
+ }
512
+ function interpolateValue(value, ctx) {
513
+ if (value === null || value === void 0) {
514
+ return value;
515
+ }
516
+ if (typeof value === "string") {
517
+ return interpolateString(value, ctx);
518
+ }
519
+ if (Array.isArray(value)) {
520
+ return interpolateArray(value, ctx);
521
+ }
522
+ if (typeof value === "object") {
523
+ return interpolateProps(value, ctx);
524
+ }
525
+ return value;
526
+ }
527
+ function interpolateString(value, ctx) {
528
+ if (value.startsWith("@") && isPureBinding(value)) {
529
+ return resolveBinding(value, ctx);
530
+ }
531
+ if (value.includes("@")) {
532
+ return interpolateEmbeddedBindings(value, ctx);
533
+ }
534
+ return value;
535
+ }
536
+ function isPureBinding(value) {
537
+ return /^@[\w]+(?:\.[\w]+)*$/.test(value);
538
+ }
539
+ function interpolateEmbeddedBindings(value, ctx) {
540
+ return value.replace(/@[\w]+(?:\.[\w]+)*/g, (match) => {
541
+ const resolved = resolveBinding(match, ctx);
542
+ return resolved !== void 0 ? String(resolved) : match;
543
+ });
544
+ }
545
+ function interpolateArray(value, ctx) {
546
+ if (value.length === 0) {
547
+ return [];
548
+ }
549
+ if (isSExpression(value)) {
550
+ return evaluate(value, ctx);
551
+ }
552
+ return value.map((item) => interpolateValue(item, ctx));
553
+ }
554
+ function isSExpression(value) {
555
+ if (value.length === 0) return false;
556
+ const first = value[0];
557
+ if (typeof first !== "string") return false;
558
+ if (OPERATORS.has(first)) return true;
559
+ if (first.includes("/")) return true;
560
+ if (first === "lambda" || first === "let") return true;
561
+ return false;
562
+ }
563
+ function createContextFromBindings(bindings) {
564
+ return createMinimalContext(
565
+ bindings.entity || {},
566
+ bindings.payload || {},
567
+ bindings.state || "idle"
568
+ );
569
+ }
570
+ function findInitialState(trait) {
571
+ if (!trait.states || trait.states.length === 0) {
572
+ console.warn(`[StateMachine] Trait "${trait.name}" has no states defined, using "unknown"`);
573
+ return "unknown";
574
+ }
575
+ const markedInitial = trait.states.find((s) => s.isInitial)?.name;
576
+ const firstState = trait.states[0]?.name;
577
+ return markedInitial || firstState || "unknown";
578
+ }
579
+ function createInitialTraitState(trait) {
580
+ return {
581
+ traitName: trait.name,
582
+ currentState: findInitialState(trait),
583
+ previousState: null,
584
+ lastEvent: null,
585
+ context: {}
586
+ };
587
+ }
588
+ function findTransition(trait, currentState, eventKey) {
589
+ if (!trait.transitions || trait.transitions.length === 0) {
590
+ return void 0;
591
+ }
592
+ return trait.transitions.find((t) => {
593
+ if (Array.isArray(t.from)) {
594
+ return t.from.includes(currentState) && t.event === eventKey;
595
+ }
596
+ return t.from === currentState && t.event === eventKey;
597
+ });
598
+ }
599
+ function normalizeEventKey(eventKey) {
600
+ return eventKey.startsWith("UI:") ? eventKey.slice(3) : eventKey;
601
+ }
602
+ function processEvent(options) {
603
+ const { traitState, trait, eventKey, payload, entityData } = options;
604
+ const normalizedEvent = normalizeEventKey(eventKey);
605
+ const transition = findTransition(trait, traitState.currentState, normalizedEvent);
606
+ if (!transition) {
607
+ return {
608
+ executed: false,
609
+ newState: traitState.currentState,
610
+ previousState: traitState.currentState,
611
+ effects: []
612
+ };
613
+ }
614
+ if (transition.guard) {
615
+ const ctx = createContextFromBindings({
616
+ entity: entityData,
617
+ payload,
618
+ state: traitState.currentState
619
+ });
620
+ try {
621
+ const guardPasses = evaluateGuard(
622
+ transition.guard,
623
+ ctx
624
+ );
625
+ if (!guardPasses) {
626
+ return {
627
+ executed: false,
628
+ newState: traitState.currentState,
629
+ previousState: traitState.currentState,
630
+ effects: [],
631
+ transition: {
632
+ from: traitState.currentState,
633
+ to: transition.to,
634
+ event: normalizedEvent
635
+ },
636
+ guardResult: false
637
+ };
638
+ }
639
+ } catch (error) {
640
+ console.error("[StateMachineCore] Guard evaluation error:", error);
641
+ }
642
+ }
643
+ return {
644
+ executed: true,
645
+ newState: transition.to,
646
+ previousState: traitState.currentState,
647
+ effects: transition.effects || [],
648
+ transition: {
649
+ from: traitState.currentState,
650
+ to: transition.to,
651
+ event: normalizedEvent
652
+ },
653
+ guardResult: transition.guard ? true : void 0
654
+ };
655
+ }
656
+ var StateMachineManager = class {
657
+ traits = /* @__PURE__ */ new Map();
658
+ states = /* @__PURE__ */ new Map();
659
+ constructor(traits = []) {
660
+ for (const trait of traits) {
661
+ this.addTrait(trait);
662
+ }
663
+ }
664
+ /**
665
+ * Add a trait to the manager.
666
+ */
667
+ addTrait(trait) {
668
+ this.traits.set(trait.name, trait);
669
+ this.states.set(trait.name, createInitialTraitState(trait));
670
+ }
671
+ /**
672
+ * Remove a trait from the manager.
673
+ */
674
+ removeTrait(traitName) {
675
+ this.traits.delete(traitName);
676
+ this.states.delete(traitName);
677
+ }
678
+ /**
679
+ * Get current state for a trait.
680
+ */
681
+ getState(traitName) {
682
+ return this.states.get(traitName);
683
+ }
684
+ /**
685
+ * Get all current states.
686
+ */
687
+ getAllStates() {
688
+ return new Map(this.states);
689
+ }
690
+ /**
691
+ * Check if a trait can handle an event from its current state.
692
+ */
693
+ canHandleEvent(traitName, eventKey) {
694
+ const trait = this.traits.get(traitName);
695
+ const state = this.states.get(traitName);
696
+ if (!trait || !state) return false;
697
+ return !!findTransition(trait, state.currentState, normalizeEventKey(eventKey));
698
+ }
699
+ /**
700
+ * Send an event to all traits.
701
+ *
702
+ * @returns Array of transition results (one per trait that had a matching transition)
703
+ */
704
+ sendEvent(eventKey, payload, entityData) {
705
+ const results = [];
706
+ for (const [traitName, trait] of this.traits) {
707
+ const traitState = this.states.get(traitName);
708
+ if (!traitState) continue;
709
+ const result = processEvent({
710
+ traitState,
711
+ trait,
712
+ eventKey,
713
+ payload,
714
+ entityData
715
+ });
716
+ if (result.executed) {
717
+ this.states.set(traitName, {
718
+ ...traitState,
719
+ currentState: result.newState,
720
+ previousState: result.previousState,
721
+ lastEvent: normalizeEventKey(eventKey),
722
+ context: { ...traitState.context, ...payload }
723
+ });
724
+ results.push({ traitName, result });
725
+ }
726
+ }
727
+ return results;
728
+ }
729
+ /**
730
+ * Reset a trait to its initial state.
731
+ */
732
+ resetTrait(traitName) {
733
+ const trait = this.traits.get(traitName);
734
+ if (trait) {
735
+ this.states.set(traitName, createInitialTraitState(trait));
736
+ }
737
+ }
738
+ /**
739
+ * Reset all traits to initial states.
740
+ */
741
+ resetAll() {
742
+ for (const [traitName, trait] of this.traits) {
743
+ this.states.set(traitName, createInitialTraitState(trait));
744
+ }
745
+ }
746
+ };
747
+
748
+ // src/EffectExecutor.ts
749
+ function parseEffect(effect) {
750
+ if (!Array.isArray(effect) || effect.length === 0) {
751
+ return null;
752
+ }
753
+ const [operator, ...args] = effect;
754
+ if (typeof operator !== "string") {
755
+ return null;
756
+ }
757
+ return { operator, args };
758
+ }
759
+ function resolveArgs(args, bindings) {
760
+ const ctx = createContextFromBindings(bindings);
761
+ return args.map((arg) => interpolateValue(arg, ctx));
762
+ }
763
+ var EffectExecutor = class {
764
+ handlers;
765
+ bindings;
766
+ context;
767
+ debug;
768
+ constructor(options) {
769
+ this.handlers = options.handlers;
770
+ this.bindings = options.bindings;
771
+ this.context = options.context;
772
+ this.debug = options.debug ?? false;
773
+ }
774
+ /**
775
+ * Execute a single effect.
776
+ */
777
+ async execute(effect) {
778
+ const parsed = parseEffect(effect);
779
+ if (!parsed) {
780
+ if (this.debug) {
781
+ console.warn("[EffectExecutor] Invalid effect format:", effect);
782
+ }
783
+ return;
784
+ }
785
+ const { operator, args } = parsed;
786
+ const resolvedArgs = resolveArgs(args, this.bindings);
787
+ if (this.debug) {
788
+ console.log("[EffectExecutor] Executing:", operator, resolvedArgs);
789
+ }
790
+ try {
791
+ await this.dispatch(operator, resolvedArgs);
792
+ } catch (error) {
793
+ console.error("[EffectExecutor] Error executing effect:", operator, error);
794
+ throw error;
795
+ }
796
+ }
797
+ /**
798
+ * Execute multiple effects in sequence.
799
+ */
800
+ async executeAll(effects) {
801
+ for (const effect of effects) {
802
+ await this.execute(effect);
803
+ }
804
+ }
805
+ /**
806
+ * Execute multiple effects in parallel.
807
+ */
808
+ async executeParallel(effects) {
809
+ await Promise.all(effects.map((effect) => this.execute(effect)));
810
+ }
811
+ // ==========================================================================
812
+ // Effect Dispatch
813
+ // ==========================================================================
814
+ async dispatch(operator, args) {
815
+ switch (operator) {
816
+ // === Universal Effects ===
817
+ case "emit": {
818
+ const event = args[0];
819
+ const payload = args[1];
820
+ this.handlers.emit(event, payload);
821
+ break;
822
+ }
823
+ case "set": {
824
+ const [entityId, field, value] = args;
825
+ this.handlers.set(entityId, field, value);
826
+ break;
827
+ }
828
+ case "persist": {
829
+ const action = args[0];
830
+ const entityType = args[1];
831
+ const data = args[2];
832
+ await this.handlers.persist(action, entityType, data);
833
+ break;
834
+ }
835
+ case "call-service": {
836
+ const service = args[0];
837
+ const action = args[1];
838
+ const params = args[2];
839
+ await this.handlers.callService(service, action, params);
840
+ break;
841
+ }
842
+ case "fetch": {
843
+ if (this.handlers.fetch) {
844
+ const entityType = args[0];
845
+ const options = args[1];
846
+ await this.handlers.fetch(entityType, options);
847
+ } else {
848
+ this.logUnsupported("fetch");
849
+ }
850
+ break;
851
+ }
852
+ case "spawn": {
853
+ if (this.handlers.spawn) {
854
+ const entityType = args[0];
855
+ const props = args[1];
856
+ this.handlers.spawn(entityType, props);
857
+ } else {
858
+ this.logUnsupported("spawn");
859
+ }
860
+ break;
861
+ }
862
+ case "despawn": {
863
+ if (this.handlers.despawn) {
864
+ const entityId = args[0];
865
+ this.handlers.despawn(entityId);
866
+ } else {
867
+ this.logUnsupported("despawn");
868
+ }
869
+ break;
870
+ }
871
+ case "log": {
872
+ if (this.handlers.log) {
873
+ const message = args[0];
874
+ const level = args[1];
875
+ const data = args[2];
876
+ this.handlers.log(message, level, data);
877
+ } else {
878
+ console.log(args[0], args.slice(1));
879
+ }
880
+ break;
881
+ }
882
+ // === Client-Only Effects ===
883
+ case "render-ui":
884
+ case "render": {
885
+ if (this.handlers.renderUI) {
886
+ const slot = args[0];
887
+ const pattern = args[1];
888
+ const props = args[2];
889
+ const priority = args[3];
890
+ this.handlers.renderUI(slot, pattern, props, priority);
891
+ } else {
892
+ this.logUnsupported("render-ui");
893
+ }
894
+ break;
895
+ }
896
+ case "navigate": {
897
+ if (this.handlers.navigate) {
898
+ const path2 = args[0];
899
+ const params = args[1];
900
+ this.handlers.navigate(path2, params);
901
+ } else {
902
+ this.logUnsupported("navigate");
903
+ }
904
+ break;
905
+ }
906
+ case "notify": {
907
+ if (this.handlers.notify) {
908
+ const message = args[0];
909
+ const type = args[1] || "info";
910
+ this.handlers.notify(message, type);
911
+ } else {
912
+ console.log(`[Notify:${args[1] || "info"}] ${args[0]}`);
913
+ }
914
+ break;
915
+ }
916
+ // === Compound Effects ===
917
+ case "do": {
918
+ const nestedEffects = args;
919
+ for (const nested of nestedEffects) {
920
+ await this.execute(nested);
921
+ }
922
+ break;
923
+ }
924
+ case "when": {
925
+ const condition = args[0];
926
+ const thenEffect = args[1];
927
+ const elseEffect = args[2];
928
+ if (condition) {
929
+ await this.execute(thenEffect);
930
+ } else if (elseEffect) {
931
+ await this.execute(elseEffect);
932
+ }
933
+ break;
934
+ }
935
+ default: {
936
+ if (this.debug) {
937
+ console.warn("[EffectExecutor] Unknown operator:", operator);
938
+ }
939
+ }
940
+ }
941
+ }
942
+ logUnsupported(operator) {
943
+ if (this.debug) {
944
+ console.warn(
945
+ `[EffectExecutor] Effect "${operator}" not supported on this platform`
946
+ );
947
+ }
948
+ }
949
+ };
950
+ var MockPersistenceAdapter = class {
951
+ stores = /* @__PURE__ */ new Map();
952
+ schemas = /* @__PURE__ */ new Map();
953
+ idCounters = /* @__PURE__ */ new Map();
954
+ config;
955
+ constructor(config = {}) {
956
+ this.config = {
957
+ defaultSeedCount: 6,
958
+ debug: false,
959
+ ...config
960
+ };
961
+ if (config.seed !== void 0) {
962
+ faker.seed(config.seed);
963
+ if (this.config.debug) {
964
+ console.log(`[MockPersistence] Using seed: ${config.seed}`);
965
+ }
966
+ }
967
+ }
968
+ // ============================================================================
969
+ // Store Management
970
+ // ============================================================================
971
+ getStore(entityName) {
972
+ const normalized = entityName.toLowerCase();
973
+ if (!this.stores.has(normalized)) {
974
+ this.stores.set(normalized, /* @__PURE__ */ new Map());
975
+ this.idCounters.set(normalized, 0);
976
+ }
977
+ return this.stores.get(normalized);
978
+ }
979
+ nextId(entityName) {
980
+ const normalized = entityName.toLowerCase();
981
+ const counter = (this.idCounters.get(normalized) ?? 0) + 1;
982
+ this.idCounters.set(normalized, counter);
983
+ return `${this.capitalizeFirst(entityName)} Id ${counter}`;
984
+ }
985
+ // ============================================================================
986
+ // Schema & Seeding
987
+ // ============================================================================
988
+ /**
989
+ * Register an entity schema and seed mock data.
990
+ */
991
+ registerEntity(schema, seedCount) {
992
+ const normalized = schema.name.toLowerCase();
993
+ this.schemas.set(normalized, schema);
994
+ const count = seedCount ?? this.config.defaultSeedCount ?? 6;
995
+ this.seed(schema.name, schema.fields, count);
996
+ }
997
+ /**
998
+ * Seed an entity with mock data.
999
+ */
1000
+ seed(entityName, fields, count) {
1001
+ const store = this.getStore(entityName);
1002
+ const normalized = entityName.toLowerCase();
1003
+ if (this.config.debug) {
1004
+ console.log(`[MockPersistence] Seeding ${count} ${entityName}...`);
1005
+ }
1006
+ for (let i = 0; i < count; i++) {
1007
+ const item = this.generateMockItem(normalized, entityName, fields, i + 1);
1008
+ store.set(item.id, item);
1009
+ }
1010
+ }
1011
+ /**
1012
+ * Generate a single mock item based on field schemas.
1013
+ */
1014
+ generateMockItem(normalizedName, entityName, fields, index) {
1015
+ const id = this.nextId(entityName);
1016
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1017
+ const item = {
1018
+ id,
1019
+ createdAt: faker.date.past({ years: 1 }).toISOString(),
1020
+ updatedAt: now
1021
+ };
1022
+ for (const field of fields) {
1023
+ if (field.name === "id" || field.name === "createdAt" || field.name === "updatedAt") {
1024
+ continue;
1025
+ }
1026
+ item[field.name] = this.generateFieldValue(entityName, field, index);
1027
+ }
1028
+ return item;
1029
+ }
1030
+ /**
1031
+ * Generate a mock value for a field based on its schema.
1032
+ */
1033
+ generateFieldValue(entityName, field, index) {
1034
+ if (field.default !== void 0) {
1035
+ if (field.default === "@now") {
1036
+ return (/* @__PURE__ */ new Date()).toISOString();
1037
+ }
1038
+ return field.default;
1039
+ }
1040
+ if (!field.required && Math.random() > 0.8) {
1041
+ return null;
1042
+ }
1043
+ const fieldType = field.type.toLowerCase();
1044
+ switch (fieldType) {
1045
+ case "string":
1046
+ return this.generateStringValue(entityName, field, index);
1047
+ case "number":
1048
+ return faker.number.int({ min: 0, max: 100 });
1049
+ case "boolean":
1050
+ return faker.datatype.boolean();
1051
+ case "date":
1052
+ case "timestamp":
1053
+ case "datetime":
1054
+ return this.generateDateValue(field);
1055
+ case "enum":
1056
+ if (field.values && field.values.length > 0) {
1057
+ return faker.helpers.arrayElement(field.values);
1058
+ }
1059
+ return null;
1060
+ case "relation":
1061
+ return null;
1062
+ // Relations need special handling
1063
+ case "array":
1064
+ case "object":
1065
+ return field.type === "array" ? [] : {};
1066
+ default:
1067
+ return this.generateStringValue(entityName, field, index);
1068
+ }
1069
+ }
1070
+ /**
1071
+ * Generate a string value based on field name heuristics.
1072
+ */
1073
+ generateStringValue(entityName, field, index) {
1074
+ const name = field.name.toLowerCase();
1075
+ if (field.values && field.values.length > 0) {
1076
+ return faker.helpers.arrayElement(field.values);
1077
+ }
1078
+ if (name.includes("email")) return faker.internet.email();
1079
+ if (name.includes("phone")) return faker.phone.number();
1080
+ if (name.includes("address")) return faker.location.streetAddress();
1081
+ if (name.includes("city")) return faker.location.city();
1082
+ if (name.includes("country")) return faker.location.country();
1083
+ if (name.includes("url") || name.includes("website")) return faker.internet.url();
1084
+ if (name.includes("avatar") || name.includes("image")) return faker.image.avatar();
1085
+ if (name.includes("color")) return faker.color.human();
1086
+ if (name.includes("uuid")) return faker.string.uuid();
1087
+ if (name.includes("description") || name.includes("bio")) return faker.lorem.paragraph();
1088
+ const entityLabel = this.capitalizeFirst(entityName);
1089
+ const fieldLabel = this.capitalizeFirst(field.name);
1090
+ return `${entityLabel} ${fieldLabel} ${index}`;
1091
+ }
1092
+ /**
1093
+ * Generate a date value based on field name heuristics.
1094
+ */
1095
+ generateDateValue(field) {
1096
+ const name = field.name.toLowerCase();
1097
+ let date;
1098
+ if (name.includes("created") || name.includes("start") || name.includes("birth")) {
1099
+ date = faker.date.past({ years: 2 });
1100
+ } else if (name.includes("updated") || name.includes("modified")) {
1101
+ date = faker.date.recent({ days: 30 });
1102
+ } else if (name.includes("deadline") || name.includes("due") || name.includes("end") || name.includes("expires")) {
1103
+ date = faker.date.future({ years: 1 });
1104
+ } else {
1105
+ date = faker.date.anytime();
1106
+ }
1107
+ return date.toISOString();
1108
+ }
1109
+ capitalizeFirst(str) {
1110
+ return str.charAt(0).toUpperCase() + str.slice(1);
1111
+ }
1112
+ // ============================================================================
1113
+ // PersistenceAdapter Implementation
1114
+ // ============================================================================
1115
+ async create(entityType, data) {
1116
+ const store = this.getStore(entityType);
1117
+ const id = this.nextId(entityType);
1118
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1119
+ const item = {
1120
+ ...data,
1121
+ id,
1122
+ createdAt: now,
1123
+ updatedAt: now
1124
+ };
1125
+ store.set(id, item);
1126
+ return { id };
1127
+ }
1128
+ async update(entityType, id, data) {
1129
+ const store = this.getStore(entityType);
1130
+ const existing = store.get(id);
1131
+ if (!existing) {
1132
+ throw new Error(`Entity ${entityType} with id ${id} not found`);
1133
+ }
1134
+ const updated = {
1135
+ ...existing,
1136
+ ...data,
1137
+ id,
1138
+ // Preserve original ID
1139
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1140
+ };
1141
+ store.set(id, updated);
1142
+ }
1143
+ async delete(entityType, id) {
1144
+ const store = this.getStore(entityType);
1145
+ if (!store.has(id)) {
1146
+ throw new Error(`Entity ${entityType} with id ${id} not found`);
1147
+ }
1148
+ store.delete(id);
1149
+ }
1150
+ async getById(entityType, id) {
1151
+ const store = this.getStore(entityType);
1152
+ return store.get(id) ?? null;
1153
+ }
1154
+ async list(entityType) {
1155
+ const store = this.getStore(entityType);
1156
+ return Array.from(store.values());
1157
+ }
1158
+ // ============================================================================
1159
+ // Utilities
1160
+ // ============================================================================
1161
+ /**
1162
+ * Clear all data for an entity.
1163
+ */
1164
+ clear(entityName) {
1165
+ const normalized = entityName.toLowerCase();
1166
+ this.stores.delete(normalized);
1167
+ this.idCounters.delete(normalized);
1168
+ }
1169
+ /**
1170
+ * Clear all data.
1171
+ */
1172
+ clearAll() {
1173
+ this.stores.clear();
1174
+ this.idCounters.clear();
1175
+ }
1176
+ /**
1177
+ * Get count of items for an entity.
1178
+ */
1179
+ count(entityName) {
1180
+ const store = this.getStore(entityName);
1181
+ return store.size;
1182
+ }
1183
+ };
1184
+
1185
+ // src/resolver/reference-resolver.ts
1186
+ init_external_loader();
1187
+ var ReferenceResolver = class {
1188
+ loader;
1189
+ options;
1190
+ localTraits;
1191
+ constructor(options) {
1192
+ this.options = options;
1193
+ this.loader = options.loader ?? new ExternalOrbitalLoader(options);
1194
+ this.localTraits = options.localTraits ?? /* @__PURE__ */ new Map();
1195
+ }
1196
+ /**
1197
+ * Resolve all references in an orbital.
1198
+ */
1199
+ async resolve(orbital, sourcePath, chain) {
1200
+ const errors = [];
1201
+ const warnings = [];
1202
+ const importChain = chain ?? new ImportChain();
1203
+ const importsResult = await this.resolveImports(
1204
+ orbital.uses ?? [],
1205
+ sourcePath,
1206
+ importChain
1207
+ );
1208
+ if (!importsResult.success) {
1209
+ return { success: false, errors: importsResult.errors };
1210
+ }
1211
+ const imports = importsResult.data;
1212
+ const entityResult = this.resolveEntity(orbital.entity, imports);
1213
+ if (!entityResult.success) {
1214
+ errors.push(...entityResult.errors);
1215
+ }
1216
+ const traitsResult = this.resolveTraits(orbital.traits, imports);
1217
+ if (!traitsResult.success) {
1218
+ errors.push(...traitsResult.errors);
1219
+ }
1220
+ const pagesResult = this.resolvePages(orbital.pages, imports);
1221
+ if (!pagesResult.success) {
1222
+ errors.push(...pagesResult.errors);
1223
+ }
1224
+ if (errors.length > 0) {
1225
+ return { success: false, errors };
1226
+ }
1227
+ if (!entityResult.success || !traitsResult.success || !pagesResult.success) {
1228
+ return { success: false, errors: ["Internal error: unexpected failure state"] };
1229
+ }
1230
+ return {
1231
+ success: true,
1232
+ data: {
1233
+ name: orbital.name,
1234
+ entity: entityResult.data.entity,
1235
+ entitySource: entityResult.data.source,
1236
+ traits: traitsResult.data,
1237
+ pages: pagesResult.data,
1238
+ imports,
1239
+ original: orbital
1240
+ },
1241
+ warnings
1242
+ };
1243
+ }
1244
+ /**
1245
+ * Resolve `uses` declarations to loaded orbitals.
1246
+ */
1247
+ async resolveImports(uses, sourcePath, chain) {
1248
+ const errors = [];
1249
+ const orbitals = /* @__PURE__ */ new Map();
1250
+ if (this.options.skipExternalLoading) {
1251
+ return {
1252
+ success: true,
1253
+ data: { orbitals },
1254
+ warnings: ["External loading skipped"]
1255
+ };
1256
+ }
1257
+ for (const use of uses) {
1258
+ if (orbitals.has(use.as)) {
1259
+ errors.push(`Duplicate import alias: ${use.as}`);
1260
+ continue;
1261
+ }
1262
+ const loadResult = await this.loader.loadOrbital(
1263
+ use.from,
1264
+ void 0,
1265
+ sourcePath,
1266
+ chain
1267
+ );
1268
+ if (!loadResult.success) {
1269
+ errors.push(`Failed to load "${use.from}" as "${use.as}": ${loadResult.error}`);
1270
+ continue;
1271
+ }
1272
+ orbitals.set(use.as, {
1273
+ alias: use.as,
1274
+ from: use.from,
1275
+ orbital: loadResult.data.orbital,
1276
+ sourcePath: loadResult.data.sourcePath
1277
+ });
1278
+ }
1279
+ if (errors.length > 0) {
1280
+ return { success: false, errors };
1281
+ }
1282
+ return { success: true, data: { orbitals }, warnings: [] };
1283
+ }
1284
+ /**
1285
+ * Resolve entity reference.
1286
+ */
1287
+ resolveEntity(entityRef, imports) {
1288
+ if (!isEntityReference(entityRef)) {
1289
+ return {
1290
+ success: true,
1291
+ data: { entity: entityRef },
1292
+ warnings: []
1293
+ };
1294
+ }
1295
+ const parsed = parseEntityRef(entityRef);
1296
+ if (!parsed) {
1297
+ return {
1298
+ success: false,
1299
+ errors: [`Invalid entity reference format: ${entityRef}. Expected "Alias.entity"`]
1300
+ };
1301
+ }
1302
+ const imported = imports.orbitals.get(parsed.alias);
1303
+ if (!imported) {
1304
+ return {
1305
+ success: false,
1306
+ errors: [
1307
+ `Unknown import alias in entity reference: ${parsed.alias}. Available aliases: ${Array.from(imports.orbitals.keys()).join(", ") || "none"}`
1308
+ ]
1309
+ };
1310
+ }
1311
+ const importedEntity = this.getEntityFromOrbital(imported.orbital);
1312
+ if (!importedEntity) {
1313
+ return {
1314
+ success: false,
1315
+ errors: [
1316
+ `Imported orbital "${parsed.alias}" does not have an inline entity. Entity references cannot be chained.`
1317
+ ]
1318
+ };
1319
+ }
1320
+ const persistence = importedEntity.persistence ?? "persistent";
1321
+ return {
1322
+ success: true,
1323
+ data: {
1324
+ entity: importedEntity,
1325
+ source: {
1326
+ alias: parsed.alias,
1327
+ persistence
1328
+ }
1329
+ },
1330
+ warnings: []
1331
+ };
1332
+ }
1333
+ /**
1334
+ * Get the entity from an orbital (handling EntityRef).
1335
+ */
1336
+ getEntityFromOrbital(orbital) {
1337
+ const entityRef = orbital.entity;
1338
+ if (typeof entityRef === "string") {
1339
+ return null;
1340
+ }
1341
+ return entityRef;
1342
+ }
1343
+ /**
1344
+ * Resolve trait references.
1345
+ */
1346
+ resolveTraits(traitRefs, imports) {
1347
+ const errors = [];
1348
+ const resolved = [];
1349
+ for (const traitRef of traitRefs) {
1350
+ const result = this.resolveTraitRef(traitRef, imports);
1351
+ if (!result.success) {
1352
+ errors.push(...result.errors);
1353
+ } else {
1354
+ resolved.push(result.data);
1355
+ }
1356
+ }
1357
+ if (errors.length > 0) {
1358
+ return { success: false, errors };
1359
+ }
1360
+ return { success: true, data: resolved, warnings: [] };
1361
+ }
1362
+ /**
1363
+ * Resolve a single trait reference.
1364
+ */
1365
+ resolveTraitRef(traitRef, imports) {
1366
+ if (typeof traitRef !== "string" && "stateMachine" in traitRef) {
1367
+ return {
1368
+ success: true,
1369
+ data: {
1370
+ trait: traitRef,
1371
+ source: { type: "inline" }
1372
+ },
1373
+ warnings: []
1374
+ };
1375
+ }
1376
+ if (typeof traitRef !== "string" && "ref" in traitRef) {
1377
+ const refObj = traitRef;
1378
+ return this.resolveTraitRefString(refObj.ref, imports, refObj.config, refObj.linkedEntity);
1379
+ }
1380
+ if (typeof traitRef === "string") {
1381
+ return this.resolveTraitRefString(traitRef, imports);
1382
+ }
1383
+ return {
1384
+ success: false,
1385
+ errors: [`Unknown trait reference format: ${JSON.stringify(traitRef)}`]
1386
+ };
1387
+ }
1388
+ /**
1389
+ * Resolve a trait reference string.
1390
+ */
1391
+ resolveTraitRefString(ref, imports, config, linkedEntity) {
1392
+ const parsed = parseImportedTraitRef(ref);
1393
+ if (parsed) {
1394
+ const imported = imports.orbitals.get(parsed.alias);
1395
+ if (!imported) {
1396
+ return {
1397
+ success: false,
1398
+ errors: [
1399
+ `Unknown import alias in trait reference: ${parsed.alias}. Available aliases: ${Array.from(imports.orbitals.keys()).join(", ") || "none"}`
1400
+ ]
1401
+ };
1402
+ }
1403
+ const trait = this.findTraitInOrbital(imported.orbital, parsed.traitName);
1404
+ if (!trait) {
1405
+ return {
1406
+ success: false,
1407
+ errors: [
1408
+ `Trait "${parsed.traitName}" not found in imported orbital "${parsed.alias}". Available traits: ${this.listTraitsInOrbital(imported.orbital).join(", ") || "none"}`
1409
+ ]
1410
+ };
1411
+ }
1412
+ return {
1413
+ success: true,
1414
+ data: {
1415
+ trait,
1416
+ source: { type: "imported", alias: parsed.alias, traitName: parsed.traitName },
1417
+ config,
1418
+ linkedEntity
1419
+ },
1420
+ warnings: []
1421
+ };
1422
+ }
1423
+ const localTrait = this.localTraits.get(ref);
1424
+ if (localTrait) {
1425
+ return {
1426
+ success: true,
1427
+ data: {
1428
+ trait: localTrait,
1429
+ source: { type: "local", name: ref },
1430
+ config,
1431
+ linkedEntity
1432
+ },
1433
+ warnings: []
1434
+ };
1435
+ }
1436
+ return {
1437
+ success: false,
1438
+ errors: [
1439
+ `Trait "${ref}" not found. For imported traits, use format "Alias.traits.TraitName". Local traits available: ${Array.from(this.localTraits.keys()).join(", ") || "none"}`
1440
+ ]
1441
+ };
1442
+ }
1443
+ /**
1444
+ * Find a trait in an orbital by name.
1445
+ */
1446
+ findTraitInOrbital(orbital, traitName) {
1447
+ for (const traitRef of orbital.traits) {
1448
+ if (typeof traitRef !== "string" && "stateMachine" in traitRef) {
1449
+ if (traitRef.name === traitName) {
1450
+ return traitRef;
1451
+ }
1452
+ }
1453
+ if (typeof traitRef !== "string" && "ref" in traitRef) {
1454
+ const refObj = traitRef;
1455
+ if (refObj.ref === traitName || refObj.name === traitName) ;
1456
+ }
1457
+ }
1458
+ return null;
1459
+ }
1460
+ /**
1461
+ * List trait names in an orbital.
1462
+ */
1463
+ listTraitsInOrbital(orbital) {
1464
+ const names = [];
1465
+ for (const traitRef of orbital.traits) {
1466
+ if (typeof traitRef !== "string" && "stateMachine" in traitRef) {
1467
+ names.push(traitRef.name);
1468
+ }
1469
+ }
1470
+ return names;
1471
+ }
1472
+ /**
1473
+ * Resolve page references.
1474
+ */
1475
+ resolvePages(pageRefs, imports) {
1476
+ const errors = [];
1477
+ const resolved = [];
1478
+ for (const pageRef of pageRefs) {
1479
+ const result = this.resolvePageRef(pageRef, imports);
1480
+ if (!result.success) {
1481
+ errors.push(...result.errors);
1482
+ } else {
1483
+ resolved.push(result.data);
1484
+ }
1485
+ }
1486
+ if (errors.length > 0) {
1487
+ return { success: false, errors };
1488
+ }
1489
+ return { success: true, data: resolved, warnings: [] };
1490
+ }
1491
+ /**
1492
+ * Resolve a single page reference.
1493
+ */
1494
+ resolvePageRef(pageRef, imports) {
1495
+ if (!isPageReference(pageRef)) {
1496
+ return {
1497
+ success: true,
1498
+ data: {
1499
+ page: pageRef,
1500
+ source: { type: "inline" },
1501
+ pathOverridden: false
1502
+ },
1503
+ warnings: []
1504
+ };
1505
+ }
1506
+ if (isPageReferenceString(pageRef)) {
1507
+ return this.resolvePageRefString(pageRef, imports);
1508
+ }
1509
+ if (isPageReferenceObject(pageRef)) {
1510
+ return this.resolvePageRefObject(pageRef, imports);
1511
+ }
1512
+ return {
1513
+ success: false,
1514
+ errors: [`Unknown page reference format: ${JSON.stringify(pageRef)}`]
1515
+ };
1516
+ }
1517
+ /**
1518
+ * Resolve a page reference string.
1519
+ */
1520
+ resolvePageRefString(ref, imports) {
1521
+ const parsed = parsePageRef(ref);
1522
+ if (!parsed) {
1523
+ return {
1524
+ success: false,
1525
+ errors: [`Invalid page reference format: ${ref}. Expected "Alias.pages.PageName"`]
1526
+ };
1527
+ }
1528
+ const imported = imports.orbitals.get(parsed.alias);
1529
+ if (!imported) {
1530
+ return {
1531
+ success: false,
1532
+ errors: [
1533
+ `Unknown import alias in page reference: ${parsed.alias}. Available aliases: ${Array.from(imports.orbitals.keys()).join(", ") || "none"}`
1534
+ ]
1535
+ };
1536
+ }
1537
+ const page = this.findPageInOrbital(imported.orbital, parsed.pageName);
1538
+ if (!page) {
1539
+ return {
1540
+ success: false,
1541
+ errors: [
1542
+ `Page "${parsed.pageName}" not found in imported orbital "${parsed.alias}". Available pages: ${this.listPagesInOrbital(imported.orbital).join(", ") || "none"}`
1543
+ ]
1544
+ };
1545
+ }
1546
+ return {
1547
+ success: true,
1548
+ data: {
1549
+ page,
1550
+ source: { type: "imported", alias: parsed.alias, pageName: parsed.pageName },
1551
+ pathOverridden: false
1552
+ },
1553
+ warnings: []
1554
+ };
1555
+ }
1556
+ /**
1557
+ * Resolve a page reference object with optional path override.
1558
+ */
1559
+ resolvePageRefObject(refObj, imports) {
1560
+ const baseResult = this.resolvePageRefString(refObj.ref, imports);
1561
+ if (!baseResult.success) {
1562
+ return baseResult;
1563
+ }
1564
+ const resolved = baseResult.data;
1565
+ if (refObj.path) {
1566
+ const originalPath = resolved.page.path;
1567
+ resolved.page = {
1568
+ ...resolved.page,
1569
+ path: refObj.path
1570
+ };
1571
+ resolved.pathOverridden = true;
1572
+ resolved.originalPath = originalPath;
1573
+ }
1574
+ return {
1575
+ success: true,
1576
+ data: resolved,
1577
+ warnings: baseResult.warnings
1578
+ };
1579
+ }
1580
+ /**
1581
+ * Find a page in an orbital by name.
1582
+ */
1583
+ findPageInOrbital(orbital, pageName) {
1584
+ const pages = orbital.pages;
1585
+ if (!pages) return null;
1586
+ for (const pageRef of pages) {
1587
+ if (typeof pageRef !== "string" && !("ref" in pageRef)) {
1588
+ const page = pageRef;
1589
+ if (page.name === pageName) {
1590
+ return { ...page };
1591
+ }
1592
+ }
1593
+ }
1594
+ return null;
1595
+ }
1596
+ /**
1597
+ * List page names in an orbital.
1598
+ */
1599
+ listPagesInOrbital(orbital) {
1600
+ const pages = orbital.pages;
1601
+ if (!pages) return [];
1602
+ const names = [];
1603
+ for (const pageRef of pages) {
1604
+ if (typeof pageRef !== "string" && !("ref" in pageRef)) {
1605
+ names.push(pageRef.name);
1606
+ }
1607
+ }
1608
+ return names;
1609
+ }
1610
+ /**
1611
+ * Add local traits for resolution.
1612
+ */
1613
+ addLocalTraits(traits) {
1614
+ for (const trait of traits) {
1615
+ this.localTraits.set(trait.name, trait);
1616
+ }
1617
+ }
1618
+ /**
1619
+ * Clear loader cache.
1620
+ */
1621
+ clearCache() {
1622
+ this.loader.clearCache();
1623
+ }
1624
+ };
1625
+ async function resolveSchema(schema, options) {
1626
+ const resolver = new ReferenceResolver(options);
1627
+ const errors = [];
1628
+ const warnings = [];
1629
+ const resolved = [];
1630
+ for (const orbital of schema.orbitals) {
1631
+ const inlineTraits = orbital.traits.filter(
1632
+ (t) => typeof t !== "string" && "stateMachine" in t
1633
+ );
1634
+ resolver.addLocalTraits(inlineTraits);
1635
+ }
1636
+ for (const orbital of schema.orbitals) {
1637
+ const result = await resolver.resolve(orbital);
1638
+ if (!result.success) {
1639
+ errors.push(`Orbital "${orbital.name}": ${result.errors.join(", ")}`);
1640
+ } else {
1641
+ resolved.push(result.data);
1642
+ warnings.push(...result.warnings.map((w) => `Orbital "${orbital.name}": ${w}`));
1643
+ }
1644
+ }
1645
+ if (errors.length > 0) {
1646
+ return { success: false, errors };
1647
+ }
1648
+ return { success: true, data: resolved, warnings };
1649
+ }
1650
+
1651
+ // src/loader/schema-loader.ts
1652
+ function isElectron() {
1653
+ return typeof process !== "undefined" && !!process.versions?.electron;
1654
+ }
1655
+ function isBrowser() {
1656
+ return typeof window !== "undefined" && !isElectron();
1657
+ }
1658
+ function isNode() {
1659
+ return typeof process !== "undefined" && !isBrowser();
1660
+ }
1661
+
1662
+ // src/loader/index.ts
1663
+ init_external_loader();
1664
+ var HttpImportChain = class _HttpImportChain {
1665
+ chain = [];
1666
+ /**
1667
+ * Try to add a path to the chain.
1668
+ * @returns Error message if circular, null if OK
1669
+ */
1670
+ push(absolutePath) {
1671
+ if (this.chain.includes(absolutePath)) {
1672
+ const cycle = [
1673
+ ...this.chain.slice(this.chain.indexOf(absolutePath)),
1674
+ absolutePath
1675
+ ];
1676
+ return `Circular import detected: ${cycle.join(" -> ")}`;
1677
+ }
1678
+ this.chain.push(absolutePath);
1679
+ return null;
1680
+ }
1681
+ /**
1682
+ * Remove the last path from the chain.
1683
+ */
1684
+ pop() {
1685
+ this.chain.pop();
1686
+ }
1687
+ /**
1688
+ * Clone the chain for nested loading.
1689
+ */
1690
+ clone() {
1691
+ const newChain = new _HttpImportChain();
1692
+ newChain.chain = [...this.chain];
1693
+ return newChain;
1694
+ }
1695
+ };
1696
+ var HttpLoaderCache = class {
1697
+ cache = /* @__PURE__ */ new Map();
1698
+ get(url) {
1699
+ return this.cache.get(url);
1700
+ }
1701
+ set(url, schema) {
1702
+ this.cache.set(url, schema);
1703
+ }
1704
+ has(url) {
1705
+ return this.cache.has(url);
1706
+ }
1707
+ clear() {
1708
+ this.cache.clear();
1709
+ }
1710
+ get size() {
1711
+ return this.cache.size;
1712
+ }
1713
+ };
1714
+ var HttpLoader = class {
1715
+ options;
1716
+ cache;
1717
+ constructor(options) {
1718
+ this.options = {
1719
+ basePath: options.basePath,
1720
+ stdLibPath: options.stdLibPath ?? "",
1721
+ scopedPaths: options.scopedPaths ?? {},
1722
+ fetchOptions: options.fetchOptions,
1723
+ timeout: options.timeout ?? 3e4,
1724
+ credentials: options.credentials ?? "same-origin"
1725
+ };
1726
+ this.cache = new HttpLoaderCache();
1727
+ }
1728
+ /**
1729
+ * Load a schema from an import path.
1730
+ */
1731
+ async load(importPath, fromPath, chain) {
1732
+ const importChain = chain ?? new HttpImportChain();
1733
+ const resolveResult = this.resolvePath(importPath, fromPath);
1734
+ if (!resolveResult.success) {
1735
+ return resolveResult;
1736
+ }
1737
+ const absoluteUrl = resolveResult.data;
1738
+ const circularError = importChain.push(absoluteUrl);
1739
+ if (circularError) {
1740
+ return { success: false, error: circularError };
1741
+ }
1742
+ try {
1743
+ const cached = this.cache.get(absoluteUrl);
1744
+ if (cached) {
1745
+ return { success: true, data: cached };
1746
+ }
1747
+ const loadResult = await this.fetchSchema(absoluteUrl);
1748
+ if (!loadResult.success) {
1749
+ return loadResult;
1750
+ }
1751
+ const loaded = {
1752
+ schema: loadResult.data,
1753
+ sourcePath: absoluteUrl,
1754
+ importPath
1755
+ };
1756
+ this.cache.set(absoluteUrl, loaded);
1757
+ return { success: true, data: loaded };
1758
+ } finally {
1759
+ importChain.pop();
1760
+ }
1761
+ }
1762
+ /**
1763
+ * Load a specific orbital from a schema by name.
1764
+ */
1765
+ async loadOrbital(importPath, orbitalName, fromPath, chain) {
1766
+ const schemaResult = await this.load(importPath, fromPath, chain);
1767
+ if (!schemaResult.success) {
1768
+ return schemaResult;
1769
+ }
1770
+ const schema = schemaResult.data.schema;
1771
+ let orbital;
1772
+ if (orbitalName) {
1773
+ const found = schema.orbitals.find(
1774
+ (o) => o.name === orbitalName
1775
+ );
1776
+ if (!found) {
1777
+ return {
1778
+ success: false,
1779
+ error: `Orbital "${orbitalName}" not found in ${importPath}. Available: ${schema.orbitals.map((o) => o.name).join(", ")}`
1780
+ };
1781
+ }
1782
+ orbital = found;
1783
+ } else {
1784
+ if (schema.orbitals.length === 0) {
1785
+ return {
1786
+ success: false,
1787
+ error: `No orbitals found in ${importPath}`
1788
+ };
1789
+ }
1790
+ orbital = schema.orbitals[0];
1791
+ }
1792
+ return {
1793
+ success: true,
1794
+ data: {
1795
+ orbital,
1796
+ sourcePath: schemaResult.data.sourcePath,
1797
+ importPath
1798
+ }
1799
+ };
1800
+ }
1801
+ /**
1802
+ * Resolve an import path to an absolute URL.
1803
+ */
1804
+ resolvePath(importPath, fromPath) {
1805
+ if (importPath.startsWith("http://") || importPath.startsWith("https://")) {
1806
+ return { success: true, data: importPath };
1807
+ }
1808
+ if (importPath.startsWith("std/")) {
1809
+ return this.resolveStdPath(importPath);
1810
+ }
1811
+ if (importPath.startsWith("@")) {
1812
+ return this.resolveScopedPath(importPath);
1813
+ }
1814
+ if (importPath.startsWith("./") || importPath.startsWith("../")) {
1815
+ return this.resolveRelativePath(importPath, fromPath);
1816
+ }
1817
+ return this.resolveRelativePath(`./${importPath}`, fromPath);
1818
+ }
1819
+ /**
1820
+ * Resolve a standard library path.
1821
+ */
1822
+ resolveStdPath(importPath) {
1823
+ if (!this.options.stdLibPath) {
1824
+ return {
1825
+ success: false,
1826
+ error: `Standard library URL not configured. Cannot load: ${importPath}`
1827
+ };
1828
+ }
1829
+ const relativePath = importPath.slice(4);
1830
+ let absoluteUrl = this.joinUrl(this.options.stdLibPath, relativePath);
1831
+ if (!absoluteUrl.endsWith(".orb")) {
1832
+ absoluteUrl += ".orb";
1833
+ }
1834
+ return { success: true, data: absoluteUrl };
1835
+ }
1836
+ /**
1837
+ * Resolve a scoped package path.
1838
+ */
1839
+ resolveScopedPath(importPath) {
1840
+ const match = importPath.match(/^(@[^/]+)/);
1841
+ if (!match) {
1842
+ return {
1843
+ success: false,
1844
+ error: `Invalid scoped package path: ${importPath}`
1845
+ };
1846
+ }
1847
+ const scope = match[1];
1848
+ const scopeRoot = this.options.scopedPaths[scope];
1849
+ if (!scopeRoot) {
1850
+ return {
1851
+ success: false,
1852
+ error: `Scoped package "${scope}" not configured. Available: ${Object.keys(this.options.scopedPaths).join(", ") || "none"}`
1853
+ };
1854
+ }
1855
+ const relativePath = importPath.slice(scope.length + 1);
1856
+ let absoluteUrl = this.joinUrl(scopeRoot, relativePath);
1857
+ if (!absoluteUrl.endsWith(".orb")) {
1858
+ absoluteUrl += ".orb";
1859
+ }
1860
+ return { success: true, data: absoluteUrl };
1861
+ }
1862
+ /**
1863
+ * Resolve a relative path.
1864
+ */
1865
+ resolveRelativePath(importPath, fromPath) {
1866
+ const baseUrl = fromPath ? this.getParentUrl(fromPath) : this.options.basePath;
1867
+ let absoluteUrl = this.joinUrl(baseUrl, importPath);
1868
+ if (!absoluteUrl.endsWith(".orb")) {
1869
+ absoluteUrl += ".orb";
1870
+ }
1871
+ return { success: true, data: absoluteUrl };
1872
+ }
1873
+ /**
1874
+ * Fetch and parse an OrbitalSchema from a URL.
1875
+ */
1876
+ async fetchSchema(url) {
1877
+ try {
1878
+ const controller = new AbortController();
1879
+ const timeoutId = setTimeout(
1880
+ () => controller.abort(),
1881
+ this.options.timeout
1882
+ );
1883
+ try {
1884
+ const response = await fetch(url, {
1885
+ ...this.options.fetchOptions,
1886
+ credentials: this.options.credentials,
1887
+ signal: controller.signal,
1888
+ headers: {
1889
+ Accept: "application/json",
1890
+ ...this.options.fetchOptions?.headers
1891
+ }
1892
+ });
1893
+ if (!response.ok) {
1894
+ return {
1895
+ success: false,
1896
+ error: `HTTP ${response.status}: ${response.statusText} for ${url}`
1897
+ };
1898
+ }
1899
+ const text = await response.text();
1900
+ let data;
1901
+ try {
1902
+ data = JSON.parse(text);
1903
+ } catch (e) {
1904
+ return {
1905
+ success: false,
1906
+ error: `Invalid JSON in ${url}: ${e instanceof Error ? e.message : String(e)}`
1907
+ };
1908
+ }
1909
+ const parseResult = OrbitalSchemaSchema.safeParse(data);
1910
+ if (!parseResult.success) {
1911
+ const errors = parseResult.error.errors.map((e) => ` - ${e.path.join(".")}: ${e.message}`).join("\n");
1912
+ return {
1913
+ success: false,
1914
+ error: `Invalid schema in ${url}:
1915
+ ${errors}`
1916
+ };
1917
+ }
1918
+ return { success: true, data: parseResult.data };
1919
+ } finally {
1920
+ clearTimeout(timeoutId);
1921
+ }
1922
+ } catch (e) {
1923
+ if (e instanceof Error && e.name === "AbortError") {
1924
+ return {
1925
+ success: false,
1926
+ error: `Request timeout for ${url} (${this.options.timeout}ms)`
1927
+ };
1928
+ }
1929
+ return {
1930
+ success: false,
1931
+ error: `Failed to fetch ${url}: ${e instanceof Error ? e.message : String(e)}`
1932
+ };
1933
+ }
1934
+ }
1935
+ /**
1936
+ * Join URL parts, handling relative paths and trailing slashes.
1937
+ */
1938
+ joinUrl(base, path2) {
1939
+ if (path2.startsWith("http://") || path2.startsWith("https://")) {
1940
+ return path2;
1941
+ }
1942
+ if (typeof URL !== "undefined") {
1943
+ try {
1944
+ const baseUrl = base.endsWith("/") ? base : base + "/";
1945
+ return new URL(path2, baseUrl).href;
1946
+ } catch {
1947
+ }
1948
+ }
1949
+ const normalizedBase = base.endsWith("/") ? base.slice(0, -1) : base;
1950
+ const normalizedPath = path2.startsWith("/") ? path2 : "/" + path2;
1951
+ if (normalizedPath.startsWith("/./")) {
1952
+ return normalizedBase + normalizedPath.slice(2);
1953
+ }
1954
+ if (normalizedPath.startsWith("/../")) {
1955
+ const baseParts = normalizedBase.split("/");
1956
+ baseParts.pop();
1957
+ return baseParts.join("/") + normalizedPath.slice(3);
1958
+ }
1959
+ return normalizedBase + normalizedPath;
1960
+ }
1961
+ /**
1962
+ * Get parent URL (directory) from a URL.
1963
+ */
1964
+ getParentUrl(url) {
1965
+ if (typeof URL !== "undefined") {
1966
+ try {
1967
+ const urlObj = new URL(url);
1968
+ const pathParts = urlObj.pathname.split("/");
1969
+ pathParts.pop();
1970
+ urlObj.pathname = pathParts.join("/");
1971
+ return urlObj.href;
1972
+ } catch {
1973
+ }
1974
+ }
1975
+ const lastSlash = url.lastIndexOf("/");
1976
+ if (lastSlash !== -1) {
1977
+ return url.slice(0, lastSlash);
1978
+ }
1979
+ return url;
1980
+ }
1981
+ /**
1982
+ * Clear the cache.
1983
+ */
1984
+ clearCache() {
1985
+ this.cache.clear();
1986
+ }
1987
+ /**
1988
+ * Get cache statistics.
1989
+ */
1990
+ getCacheStats() {
1991
+ return { size: this.cache.size };
1992
+ }
1993
+ };
1994
+
1995
+ // src/loader/unified-loader.ts
1996
+ var externalLoaderModule = null;
1997
+ async function getExternalLoaderModule() {
1998
+ if (externalLoaderModule) {
1999
+ return externalLoaderModule;
2000
+ }
2001
+ if (isBrowser()) {
2002
+ return null;
2003
+ }
2004
+ try {
2005
+ externalLoaderModule = await Promise.resolve().then(() => (init_external_loader(), external_loader_exports));
2006
+ return externalLoaderModule;
2007
+ } catch {
2008
+ return null;
2009
+ }
2010
+ }
2011
+ var UnifiedImportChain = class _UnifiedImportChain {
2012
+ chain = [];
2013
+ push(path2) {
2014
+ const normalized = this.normalizePath(path2);
2015
+ if (this.chain.includes(normalized)) {
2016
+ const cycle = [
2017
+ ...this.chain.slice(this.chain.indexOf(normalized)),
2018
+ normalized
2019
+ ];
2020
+ return `Circular import detected: ${cycle.join(" -> ")}`;
2021
+ }
2022
+ this.chain.push(normalized);
2023
+ return null;
2024
+ }
2025
+ pop() {
2026
+ this.chain.pop();
2027
+ }
2028
+ clone() {
2029
+ const newChain = new _UnifiedImportChain();
2030
+ newChain.chain = [...this.chain];
2031
+ return newChain;
2032
+ }
2033
+ normalizePath(path2) {
2034
+ if (path2.startsWith("http://") || path2.startsWith("https://")) {
2035
+ return path2;
2036
+ }
2037
+ return path2.replace(/\\/g, "/");
2038
+ }
2039
+ };
2040
+ var UnifiedLoader = class {
2041
+ options;
2042
+ httpLoader = null;
2043
+ fsLoader = null;
2044
+ fsLoaderInitialized = false;
2045
+ cache = /* @__PURE__ */ new Map();
2046
+ constructor(options) {
2047
+ this.options = options;
2048
+ this.httpLoader = new HttpLoader({
2049
+ basePath: options.basePath,
2050
+ stdLibPath: options.stdLibPath,
2051
+ scopedPaths: options.scopedPaths,
2052
+ ...options.http
2053
+ });
2054
+ }
2055
+ /**
2056
+ * Initialize the filesystem loader if available.
2057
+ */
2058
+ async initFsLoader() {
2059
+ if (this.fsLoaderInitialized) {
2060
+ return;
2061
+ }
2062
+ this.fsLoaderInitialized = true;
2063
+ if (this.options.forceLoader === "http") {
2064
+ return;
2065
+ }
2066
+ const module = await getExternalLoaderModule();
2067
+ if (module) {
2068
+ this.fsLoader = new module.ExternalOrbitalLoader({
2069
+ basePath: this.options.basePath,
2070
+ stdLibPath: this.options.stdLibPath,
2071
+ scopedPaths: this.options.scopedPaths,
2072
+ ...this.options.fileSystem
2073
+ });
2074
+ }
2075
+ }
2076
+ /**
2077
+ * Determine which loader to use for an import path.
2078
+ */
2079
+ getLoaderForPath(importPath) {
2080
+ if (this.options.forceLoader) {
2081
+ return this.options.forceLoader;
2082
+ }
2083
+ if (importPath.startsWith("http://") || importPath.startsWith("https://")) {
2084
+ return "http";
2085
+ }
2086
+ if (importPath.startsWith("std/") && this.options.stdLibPath) {
2087
+ if (this.options.stdLibPath.startsWith("http://") || this.options.stdLibPath.startsWith("https://")) {
2088
+ return "http";
2089
+ }
2090
+ }
2091
+ if (importPath.startsWith("@") && this.options.scopedPaths) {
2092
+ const match = importPath.match(/^(@[^/]+)/);
2093
+ if (match) {
2094
+ const scopePath = this.options.scopedPaths[match[1]];
2095
+ if (scopePath && (scopePath.startsWith("http://") || scopePath.startsWith("https://"))) {
2096
+ return "http";
2097
+ }
2098
+ }
2099
+ }
2100
+ if (isBrowser()) {
2101
+ return "http";
2102
+ }
2103
+ return "filesystem";
2104
+ }
2105
+ /**
2106
+ * Load a schema from an import path.
2107
+ *
2108
+ * Note: We delegate chain management to the inner loader (HttpLoader or FsLoader).
2109
+ * The inner loader handles circular import detection, so we don't push/pop here.
2110
+ * We only use the unified cache to avoid duplicate loads across loaders.
2111
+ */
2112
+ async load(importPath, fromPath, chain) {
2113
+ await this.initFsLoader();
2114
+ const importChain = chain ?? new UnifiedImportChain();
2115
+ const loaderType = this.getLoaderForPath(importPath);
2116
+ const resolveResult = this.resolvePath(importPath, fromPath);
2117
+ if (!resolveResult.success) {
2118
+ return resolveResult;
2119
+ }
2120
+ const absolutePath = resolveResult.data;
2121
+ const cached = this.cache.get(absolutePath);
2122
+ if (cached) {
2123
+ return { success: true, data: cached };
2124
+ }
2125
+ let result;
2126
+ if (loaderType === "http") {
2127
+ if (!this.httpLoader) {
2128
+ return {
2129
+ success: false,
2130
+ error: "HTTP loader not available"
2131
+ };
2132
+ }
2133
+ result = await this.httpLoader.load(importPath, fromPath, importChain);
2134
+ } else {
2135
+ if (!this.fsLoader) {
2136
+ if (this.httpLoader) {
2137
+ result = await this.httpLoader.load(
2138
+ importPath,
2139
+ fromPath,
2140
+ importChain
2141
+ );
2142
+ } else {
2143
+ return {
2144
+ success: false,
2145
+ error: `Filesystem loader not available and import "${importPath}" cannot be loaded via HTTP. This typically happens when loading local files in a browser environment.`
2146
+ };
2147
+ }
2148
+ } else {
2149
+ result = await this.fsLoader.load(importPath, fromPath, importChain);
2150
+ }
2151
+ }
2152
+ if (result.success) {
2153
+ this.cache.set(absolutePath, result.data);
2154
+ }
2155
+ return result;
2156
+ }
2157
+ /**
2158
+ * Load a specific orbital from a schema by name.
2159
+ */
2160
+ async loadOrbital(importPath, orbitalName, fromPath, chain) {
2161
+ const schemaResult = await this.load(importPath, fromPath, chain);
2162
+ if (!schemaResult.success) {
2163
+ return schemaResult;
2164
+ }
2165
+ const schema = schemaResult.data.schema;
2166
+ if (orbitalName) {
2167
+ const found = schema.orbitals.find((o) => o.name === orbitalName);
2168
+ if (!found) {
2169
+ return {
2170
+ success: false,
2171
+ error: `Orbital "${orbitalName}" not found in ${importPath}. Available: ${schema.orbitals.map((o) => o.name).join(", ")}`
2172
+ };
2173
+ }
2174
+ return {
2175
+ success: true,
2176
+ data: {
2177
+ orbital: found,
2178
+ sourcePath: schemaResult.data.sourcePath,
2179
+ importPath
2180
+ }
2181
+ };
2182
+ }
2183
+ if (schema.orbitals.length === 0) {
2184
+ return {
2185
+ success: false,
2186
+ error: `No orbitals found in ${importPath}`
2187
+ };
2188
+ }
2189
+ return {
2190
+ success: true,
2191
+ data: {
2192
+ orbital: schema.orbitals[0],
2193
+ sourcePath: schemaResult.data.sourcePath,
2194
+ importPath
2195
+ }
2196
+ };
2197
+ }
2198
+ /**
2199
+ * Resolve an import path to an absolute path/URL.
2200
+ */
2201
+ resolvePath(importPath, fromPath) {
2202
+ const loaderType = this.getLoaderForPath(importPath);
2203
+ if (loaderType === "http" && this.httpLoader) {
2204
+ return this.httpLoader.resolvePath(importPath, fromPath);
2205
+ }
2206
+ if (this.fsLoader) {
2207
+ return this.fsLoader.resolvePath(importPath, fromPath);
2208
+ }
2209
+ if (this.httpLoader) {
2210
+ return this.httpLoader.resolvePath(importPath, fromPath);
2211
+ }
2212
+ return {
2213
+ success: false,
2214
+ error: "No loader available for path resolution"
2215
+ };
2216
+ }
2217
+ /**
2218
+ * Clear all caches.
2219
+ */
2220
+ clearCache() {
2221
+ this.cache.clear();
2222
+ this.httpLoader?.clearCache();
2223
+ this.fsLoader?.clearCache();
2224
+ }
2225
+ /**
2226
+ * Get combined cache statistics.
2227
+ */
2228
+ getCacheStats() {
2229
+ let size = this.cache.size;
2230
+ if (this.httpLoader) {
2231
+ size += this.httpLoader.getCacheStats().size;
2232
+ }
2233
+ if (this.fsLoader) {
2234
+ size += this.fsLoader.getCacheStats().size;
2235
+ }
2236
+ return { size };
2237
+ }
2238
+ /**
2239
+ * Check if filesystem loading is available.
2240
+ */
2241
+ async hasFilesystemAccess() {
2242
+ await this.initFsLoader();
2243
+ return this.fsLoader !== null;
2244
+ }
2245
+ /**
2246
+ * Get current environment info.
2247
+ */
2248
+ getEnvironmentInfo() {
2249
+ return {
2250
+ isElectron: isElectron(),
2251
+ isBrowser: isBrowser(),
2252
+ isNode: isNode(),
2253
+ hasFilesystem: this.fsLoader !== null
2254
+ };
2255
+ }
2256
+ };
2257
+ function createUnifiedLoader(options) {
2258
+ return new UnifiedLoader(options);
2259
+ }
2260
+
2261
+ // src/UsesIntegration.ts
2262
+ async function preprocessSchema(schema, options) {
2263
+ const namespaceEvents = options.namespaceEvents ?? true;
2264
+ const resolveResult = await resolveSchema(schema, options);
2265
+ if (!resolveResult.success) {
2266
+ return { success: false, errors: resolveResult.errors };
2267
+ }
2268
+ const resolved = resolveResult.data;
2269
+ const warnings = resolveResult.warnings;
2270
+ const preprocessedOrbitals = [];
2271
+ const entitySharing = {};
2272
+ const eventNamespaces = {};
2273
+ for (const resolvedOrbital of resolved) {
2274
+ const orbitalName = resolvedOrbital.name;
2275
+ const persistence = resolvedOrbital.entitySource?.persistence ?? resolvedOrbital.entity.persistence ?? "persistent";
2276
+ entitySharing[orbitalName] = {
2277
+ entityName: resolvedOrbital.entity.name,
2278
+ persistence,
2279
+ isShared: persistence !== "runtime",
2280
+ sourceAlias: resolvedOrbital.entitySource?.alias,
2281
+ collectionName: resolvedOrbital.entity.collection
2282
+ };
2283
+ eventNamespaces[orbitalName] = {};
2284
+ for (const resolvedTrait of resolvedOrbital.traits) {
2285
+ const traitName = resolvedTrait.trait.name;
2286
+ const namespace = {
2287
+ emits: {},
2288
+ listens: {}
2289
+ };
2290
+ if (namespaceEvents && resolvedTrait.source.type === "imported") {
2291
+ const emits = resolvedTrait.trait.emits ?? [];
2292
+ for (const emit of emits) {
2293
+ const eventName = typeof emit === "string" ? emit : emit.event;
2294
+ namespace.emits[eventName] = `${orbitalName}.${traitName}.${eventName}`;
2295
+ }
2296
+ const listens = resolvedTrait.trait.listens ?? [];
2297
+ for (const listen of listens) {
2298
+ namespace.listens[listen.event] = listen.event;
2299
+ }
2300
+ }
2301
+ eventNamespaces[orbitalName][traitName] = namespace;
2302
+ }
2303
+ const preprocessedOrbital = {
2304
+ name: orbitalName,
2305
+ description: resolvedOrbital.original.description,
2306
+ visual_prompt: resolvedOrbital.original.visual_prompt,
2307
+ // Resolved entity (always inline now)
2308
+ entity: resolvedOrbital.entity,
2309
+ // Resolved traits (inline definitions)
2310
+ traits: resolvedOrbital.traits.map((rt) => {
2311
+ if (rt.config || rt.linkedEntity) {
2312
+ return {
2313
+ ref: rt.trait.name,
2314
+ config: rt.config,
2315
+ linkedEntity: rt.linkedEntity,
2316
+ // Include the resolved trait definition for runtime
2317
+ _resolved: rt.trait
2318
+ };
2319
+ }
2320
+ return rt.trait;
2321
+ }),
2322
+ // Resolved pages (inline definitions with path overrides applied)
2323
+ pages: resolvedOrbital.pages.map((rp) => rp.page),
2324
+ // Preserve other fields
2325
+ exposes: resolvedOrbital.original.exposes,
2326
+ domainContext: resolvedOrbital.original.domainContext,
2327
+ design: resolvedOrbital.original.design
2328
+ };
2329
+ preprocessedOrbitals.push(preprocessedOrbital);
2330
+ }
2331
+ const preprocessedSchema = {
2332
+ ...schema,
2333
+ orbitals: preprocessedOrbitals
2334
+ };
2335
+ return {
2336
+ success: true,
2337
+ data: {
2338
+ schema: preprocessedSchema,
2339
+ entitySharing,
2340
+ eventNamespaces,
2341
+ warnings
2342
+ }
2343
+ };
2344
+ }
2345
+
2346
+ // src/OrbitalServerRuntime.ts
2347
+ var InMemoryPersistence = class {
2348
+ data = /* @__PURE__ */ new Map();
2349
+ idCounter = 0;
2350
+ async create(entityType, data) {
2351
+ const id = `${entityType}-${++this.idCounter}`;
2352
+ if (!this.data.has(entityType)) {
2353
+ this.data.set(entityType, /* @__PURE__ */ new Map());
2354
+ }
2355
+ this.data.get(entityType).set(id, { ...data, id });
2356
+ return { id };
2357
+ }
2358
+ async update(entityType, id, data) {
2359
+ const collection = this.data.get(entityType);
2360
+ if (collection?.has(id)) {
2361
+ const existing = collection.get(id);
2362
+ collection.set(id, { ...existing, ...data });
2363
+ }
2364
+ }
2365
+ async delete(entityType, id) {
2366
+ this.data.get(entityType)?.delete(id);
2367
+ }
2368
+ async getById(entityType, id) {
2369
+ return this.data.get(entityType)?.get(id) || null;
2370
+ }
2371
+ async list(entityType) {
2372
+ const collection = this.data.get(entityType);
2373
+ return collection ? Array.from(collection.values()) : [];
2374
+ }
2375
+ };
2376
+ var OrbitalServerRuntime = class {
2377
+ orbitals = /* @__PURE__ */ new Map();
2378
+ eventBus;
2379
+ config;
2380
+ persistence;
2381
+ listenerCleanups = [];
2382
+ tickBindings = [];
2383
+ loader = null;
2384
+ preprocessedCache = /* @__PURE__ */ new Map();
2385
+ entitySharingMap = {};
2386
+ eventNamespaceMap = {};
2387
+ constructor(config = {}) {
2388
+ this.config = {
2389
+ mode: "mock",
2390
+ // Default to mock mode for preview
2391
+ autoPreprocess: false,
2392
+ namespaceEvents: true,
2393
+ ...config
2394
+ };
2395
+ this.eventBus = new EventBus();
2396
+ if (config.loaderConfig) {
2397
+ this.loader = config.loaderConfig.loader ?? createUnifiedLoader({
2398
+ basePath: config.loaderConfig.basePath,
2399
+ stdLibPath: config.loaderConfig.stdLibPath,
2400
+ scopedPaths: config.loaderConfig.scopedPaths
2401
+ });
2402
+ }
2403
+ if (this.config.mode === "mock" && !config.persistence) {
2404
+ this.persistence = new MockPersistenceAdapter({
2405
+ seed: config.mockSeed,
2406
+ defaultSeedCount: config.mockSeedCount ?? 6,
2407
+ debug: config.debug
2408
+ });
2409
+ if (config.debug) {
2410
+ console.log("[OrbitalRuntime] Using mock persistence with faker data");
2411
+ }
2412
+ } else {
2413
+ this.persistence = config.persistence || new InMemoryPersistence();
2414
+ }
2415
+ }
2416
+ // ==========================================================================
2417
+ // Schema Registration
2418
+ // ==========================================================================
2419
+ /**
2420
+ * Register an OrbitalSchema for execution.
2421
+ *
2422
+ * If `autoPreprocess` is enabled in config and schema has `uses` declarations,
2423
+ * it will be preprocessed first to resolve imports.
2424
+ *
2425
+ * For explicit preprocessing control, use `registerWithPreprocess()`.
2426
+ */
2427
+ register(schema) {
2428
+ if (this.config.debug) {
2429
+ console.log(`[OrbitalRuntime] Registering schema: ${schema.name}`);
2430
+ }
2431
+ for (const orbital of schema.orbitals) {
2432
+ this.registerOrbital(orbital);
2433
+ }
2434
+ this.setupEventListeners();
2435
+ this.setupTicks();
2436
+ }
2437
+ /**
2438
+ * Register an OrbitalSchema with preprocessing to resolve `uses` imports.
2439
+ *
2440
+ * This method:
2441
+ * 1. Loads all external orbitals referenced in `uses` declarations
2442
+ * 2. Expands entity/trait/page references to inline definitions
2443
+ * 3. Builds entity sharing and event namespace maps
2444
+ * 4. Caches the preprocessed result
2445
+ * 5. Registers the resolved schema
2446
+ *
2447
+ * @param schema - Schema with potential `uses` declarations
2448
+ * @param options - Optional preprocessing options
2449
+ * @returns Preprocessing result with entity sharing info
2450
+ *
2451
+ * @example
2452
+ * ```typescript
2453
+ * const runtime = new OrbitalServerRuntime({
2454
+ * loaderConfig: {
2455
+ * basePath: '/schemas',
2456
+ * stdLibPath: '/std',
2457
+ * },
2458
+ * });
2459
+ *
2460
+ * const result = await runtime.registerWithPreprocess(schema);
2461
+ * if (result.success) {
2462
+ * console.log('Registered with', Object.keys(result.entitySharing).length, 'orbitals');
2463
+ * }
2464
+ * ```
2465
+ */
2466
+ async registerWithPreprocess(schema, options) {
2467
+ if (!this.loader && !this.config.loaderConfig) {
2468
+ return {
2469
+ success: false,
2470
+ errors: ["Loader not configured. Set loaderConfig in OrbitalServerRuntimeConfig."]
2471
+ };
2472
+ }
2473
+ if (!this.loader && this.config.loaderConfig) {
2474
+ this.loader = this.config.loaderConfig.loader ?? createUnifiedLoader({
2475
+ basePath: this.config.loaderConfig.basePath,
2476
+ stdLibPath: this.config.loaderConfig.stdLibPath,
2477
+ scopedPaths: this.config.loaderConfig.scopedPaths
2478
+ });
2479
+ }
2480
+ const cacheKey = `${schema.name}:${schema.version || "1.0.0"}`;
2481
+ const cached = this.preprocessedCache.get(cacheKey);
2482
+ if (cached) {
2483
+ if (this.config.debug) {
2484
+ console.log(`[OrbitalRuntime] Using cached preprocessed schema: ${schema.name}`);
2485
+ }
2486
+ this.register(cached.schema);
2487
+ this.entitySharingMap = { ...this.entitySharingMap, ...cached.entitySharing };
2488
+ this.eventNamespaceMap = { ...this.eventNamespaceMap, ...cached.eventNamespaces };
2489
+ return {
2490
+ success: true,
2491
+ entitySharing: cached.entitySharing,
2492
+ eventNamespaces: cached.eventNamespaces,
2493
+ warnings: cached.warnings
2494
+ };
2495
+ }
2496
+ if (this.config.debug) {
2497
+ console.log(`[OrbitalRuntime] Preprocessing schema: ${schema.name}`);
2498
+ }
2499
+ const result = await preprocessSchema(schema, {
2500
+ basePath: this.config.loaderConfig?.basePath || ".",
2501
+ stdLibPath: this.config.loaderConfig?.stdLibPath,
2502
+ scopedPaths: this.config.loaderConfig?.scopedPaths,
2503
+ loader: this.loader,
2504
+ namespaceEvents: this.config.namespaceEvents
2505
+ });
2506
+ if (!result.success) {
2507
+ return {
2508
+ success: false,
2509
+ errors: result.errors
2510
+ };
2511
+ }
2512
+ this.preprocessedCache.set(cacheKey, result.data);
2513
+ this.entitySharingMap = { ...this.entitySharingMap, ...result.data.entitySharing };
2514
+ this.eventNamespaceMap = { ...this.eventNamespaceMap, ...result.data.eventNamespaces };
2515
+ this.register(result.data.schema);
2516
+ return {
2517
+ success: true,
2518
+ entitySharing: result.data.entitySharing,
2519
+ eventNamespaces: result.data.eventNamespaces,
2520
+ warnings: result.data.warnings
2521
+ };
2522
+ }
2523
+ /**
2524
+ * Get entity sharing information for registered orbitals.
2525
+ * Useful for determining entity isolation and collection names.
2526
+ */
2527
+ getEntitySharing() {
2528
+ return { ...this.entitySharingMap };
2529
+ }
2530
+ /**
2531
+ * Get event namespace mapping for registered orbitals.
2532
+ * Useful for debugging cross-orbital event routing.
2533
+ */
2534
+ getEventNamespaces() {
2535
+ return { ...this.eventNamespaceMap };
2536
+ }
2537
+ /**
2538
+ * Clear the preprocessing cache.
2539
+ */
2540
+ clearPreprocessCache() {
2541
+ this.preprocessedCache.clear();
2542
+ }
2543
+ /**
2544
+ * Register a single orbital
2545
+ */
2546
+ registerOrbital(orbital) {
2547
+ const traitDefs = orbital.traits.map((t) => {
2548
+ const stateMachine = t.stateMachine;
2549
+ const states = t.states || stateMachine?.states || [];
2550
+ const transitions = t.transitions || stateMachine?.transitions || [];
2551
+ return {
2552
+ name: t.name,
2553
+ states,
2554
+ transitions,
2555
+ listens: t.listens
2556
+ };
2557
+ });
2558
+ const manager = new StateMachineManager(traitDefs);
2559
+ this.orbitals.set(orbital.name, {
2560
+ schema: orbital,
2561
+ manager,
2562
+ entityData: /* @__PURE__ */ new Map()
2563
+ });
2564
+ console.log(`[OrbitalRuntime] Checking mock seed: mode=${this.config.mode}, isMockAdapter=${this.persistence instanceof MockPersistenceAdapter}`);
2565
+ if (this.config.mode === "mock" && this.persistence instanceof MockPersistenceAdapter) {
2566
+ const entity = orbital.entity;
2567
+ console.log(`[OrbitalRuntime] Entity:`, entity?.name, "fields:", entity?.fields?.length);
2568
+ if (entity?.name && entity.fields) {
2569
+ const fields = entity.fields.map((f) => ({
2570
+ name: f.name,
2571
+ type: f.type,
2572
+ required: f.required,
2573
+ values: f.values,
2574
+ default: f.default
2575
+ }));
2576
+ this.persistence.registerEntity({ name: entity.name, fields });
2577
+ console.log(`[OrbitalRuntime] Seeded mock data for entity: ${entity.name}, count: ${this.persistence.count(entity.name)}`);
2578
+ }
2579
+ } else {
2580
+ console.log(`[OrbitalRuntime] Mock seeding SKIPPED`);
2581
+ }
2582
+ if (this.config.debug) {
2583
+ console.log(
2584
+ `[OrbitalRuntime] Registered orbital: ${orbital.name} with ${orbital.traits.length} trait(s)`
2585
+ );
2586
+ }
2587
+ }
2588
+ /**
2589
+ * Set up event listeners for cross-orbital communication
2590
+ */
2591
+ setupEventListeners() {
2592
+ for (const cleanup of this.listenerCleanups) {
2593
+ cleanup();
2594
+ }
2595
+ this.listenerCleanups = [];
2596
+ for (const [orbitalName, registered] of this.orbitals) {
2597
+ for (const trait of registered.schema.traits) {
2598
+ if (!trait.listens) continue;
2599
+ for (const listener of trait.listens) {
2600
+ const cleanup = this.eventBus.on(listener.event, async (event) => {
2601
+ if (this.config.debug) {
2602
+ console.log(
2603
+ `[OrbitalRuntime] ${orbitalName}.${trait.name} received: ${listener.event}`
2604
+ );
2605
+ }
2606
+ let mappedPayload = event.payload;
2607
+ if (listener.payloadMapping && event.payload) {
2608
+ mappedPayload = {};
2609
+ for (const [key, expr] of Object.entries(
2610
+ listener.payloadMapping
2611
+ )) {
2612
+ if (typeof expr === "string" && expr.startsWith("@payload.")) {
2613
+ const field = expr.slice("@payload.".length);
2614
+ mappedPayload[key] = event.payload[field];
2615
+ } else {
2616
+ mappedPayload[key] = expr;
2617
+ }
2618
+ }
2619
+ }
2620
+ await this.processOrbitalEvent(orbitalName, {
2621
+ event: listener.triggers,
2622
+ payload: mappedPayload
2623
+ });
2624
+ });
2625
+ this.listenerCleanups.push(cleanup);
2626
+ }
2627
+ }
2628
+ }
2629
+ }
2630
+ /**
2631
+ * Set up scheduled ticks for all traits
2632
+ */
2633
+ setupTicks() {
2634
+ this.cleanupTicks();
2635
+ for (const [orbitalName, registered] of this.orbitals) {
2636
+ for (const trait of registered.schema.traits) {
2637
+ if (!trait.ticks || trait.ticks.length === 0) continue;
2638
+ for (const tick of trait.ticks) {
2639
+ this.registerTick(orbitalName, trait.name, tick, registered);
2640
+ }
2641
+ }
2642
+ }
2643
+ if (this.config.debug && this.tickBindings.length > 0) {
2644
+ console.log(
2645
+ `[OrbitalRuntime] Registered ${this.tickBindings.length} tick(s)`
2646
+ );
2647
+ }
2648
+ }
2649
+ /**
2650
+ * Register a single tick
2651
+ */
2652
+ registerTick(orbitalName, traitName, tick, registered) {
2653
+ let intervalMs;
2654
+ if (typeof tick.interval === "number") {
2655
+ intervalMs = tick.interval;
2656
+ } else if (typeof tick.interval === "string") {
2657
+ intervalMs = this.parseIntervalString(tick.interval);
2658
+ } else {
2659
+ intervalMs = 1e3;
2660
+ }
2661
+ if (this.config.debug) {
2662
+ console.log(
2663
+ `[OrbitalRuntime] Registering tick: ${orbitalName}.${traitName}.${tick.name} (${intervalMs}ms)`
2664
+ );
2665
+ }
2666
+ const timerId = setInterval(async () => {
2667
+ await this.executeTick(orbitalName, traitName, tick, registered);
2668
+ }, intervalMs);
2669
+ this.tickBindings.push({
2670
+ orbitalName,
2671
+ traitName,
2672
+ tick,
2673
+ timerId
2674
+ });
2675
+ }
2676
+ /**
2677
+ * Parse interval string to milliseconds
2678
+ * Supports: '5s', '1m', '1h', '30000' (ms)
2679
+ */
2680
+ parseIntervalString(interval) {
2681
+ const match = interval.match(/^(\d+)(ms|s|m|h)?$/);
2682
+ if (!match) {
2683
+ console.warn(
2684
+ `[OrbitalRuntime] Invalid interval format: ${interval}, defaulting to 1000ms`
2685
+ );
2686
+ return 1e3;
2687
+ }
2688
+ const value = parseInt(match[1], 10);
2689
+ const unit = match[2] || "ms";
2690
+ switch (unit) {
2691
+ case "ms":
2692
+ return value;
2693
+ case "s":
2694
+ return value * 1e3;
2695
+ case "m":
2696
+ return value * 60 * 1e3;
2697
+ case "h":
2698
+ return value * 60 * 60 * 1e3;
2699
+ default:
2700
+ return value;
2701
+ }
2702
+ }
2703
+ /**
2704
+ * Execute a tick for all applicable entities
2705
+ */
2706
+ async executeTick(orbitalName, traitName, tick, registered) {
2707
+ const entityType = registered.schema.entity.name;
2708
+ const emittedEvents = [];
2709
+ try {
2710
+ let entities = await this.persistence.list(entityType);
2711
+ if (tick.appliesTo && tick.appliesTo.length > 0) {
2712
+ const appliesToSet = new Set(tick.appliesTo);
2713
+ entities = entities.filter((e) => appliesToSet.has(e.id));
2714
+ }
2715
+ if (this.config.debug && entities.length > 0) {
2716
+ console.log(
2717
+ `[OrbitalRuntime] Tick ${orbitalName}.${traitName}.${tick.name}: processing ${entities.length} entities`
2718
+ );
2719
+ }
2720
+ for (const entity of entities) {
2721
+ if (tick.guard) {
2722
+ try {
2723
+ const ctx = createContextFromBindings({
2724
+ entity,
2725
+ payload: {},
2726
+ state: registered.manager.getState(traitName)?.currentState || "unknown"
2727
+ });
2728
+ const guardPasses = evaluateGuard(
2729
+ tick.guard,
2730
+ ctx
2731
+ );
2732
+ if (!guardPasses) {
2733
+ if (this.config.debug) {
2734
+ console.log(
2735
+ `[OrbitalRuntime] Tick ${tick.name}: guard failed for entity ${entity.id}`
2736
+ );
2737
+ }
2738
+ continue;
2739
+ }
2740
+ } catch (error) {
2741
+ console.error(
2742
+ `[OrbitalRuntime] Tick ${tick.name}: guard evaluation error for entity ${entity.id}:`,
2743
+ error
2744
+ );
2745
+ continue;
2746
+ }
2747
+ }
2748
+ if (tick.effects && tick.effects.length > 0) {
2749
+ const fetchedData = {};
2750
+ const clientEffects = [];
2751
+ const tickEffectResults = [];
2752
+ await this.executeEffects(
2753
+ registered,
2754
+ traitName,
2755
+ tick.effects,
2756
+ {},
2757
+ // No payload for ticks
2758
+ entity,
2759
+ entity.id,
2760
+ emittedEvents,
2761
+ fetchedData,
2762
+ clientEffects,
2763
+ tickEffectResults
2764
+ );
2765
+ if (this.config.debug) {
2766
+ console.log(
2767
+ `[OrbitalRuntime] Tick ${tick.name}: executed effects for entity ${entity.id}`
2768
+ );
2769
+ }
2770
+ }
2771
+ }
2772
+ } catch (error) {
2773
+ console.error(
2774
+ `[OrbitalRuntime] Tick ${tick.name} execution error:`,
2775
+ error
2776
+ );
2777
+ }
2778
+ }
2779
+ /**
2780
+ * Clean up all active ticks
2781
+ */
2782
+ cleanupTicks() {
2783
+ for (const binding of this.tickBindings) {
2784
+ clearInterval(binding.timerId);
2785
+ }
2786
+ this.tickBindings = [];
2787
+ }
2788
+ /**
2789
+ * Unregister all orbitals and clean up
2790
+ */
2791
+ unregisterAll() {
2792
+ this.cleanupTicks();
2793
+ for (const cleanup of this.listenerCleanups) {
2794
+ cleanup();
2795
+ }
2796
+ this.listenerCleanups = [];
2797
+ this.orbitals.clear();
2798
+ this.eventBus.clear();
2799
+ }
2800
+ // ==========================================================================
2801
+ // Event Processing
2802
+ // ==========================================================================
2803
+ /**
2804
+ * Process an event for an orbital
2805
+ */
2806
+ async processOrbitalEvent(orbitalName, request) {
2807
+ const registered = this.orbitals.get(orbitalName);
2808
+ if (!registered) {
2809
+ return {
2810
+ success: false,
2811
+ transitioned: false,
2812
+ states: {},
2813
+ emittedEvents: [],
2814
+ error: `Orbital not found: ${orbitalName}`
2815
+ };
2816
+ }
2817
+ const { event, payload, entityId, user } = request;
2818
+ const emittedEvents = [];
2819
+ const fetchedData = {};
2820
+ const clientEffects = [];
2821
+ const effectResults = [];
2822
+ const activeTraits = payload?._activeTraits;
2823
+ const cleanPayload = payload ? { ...payload } : void 0;
2824
+ if (cleanPayload) {
2825
+ delete cleanPayload._activeTraits;
2826
+ }
2827
+ let entityData = {};
2828
+ if (entityId) {
2829
+ const stored = await this.persistence.getById(
2830
+ registered.schema.entity.name,
2831
+ entityId
2832
+ );
2833
+ if (stored) {
2834
+ entityData = stored;
2835
+ }
2836
+ }
2837
+ const results = registered.manager.sendEvent(event, cleanPayload, entityData);
2838
+ const filteredResults = activeTraits && activeTraits.length > 0 ? results.filter(({ traitName }) => activeTraits.includes(traitName)) : results;
2839
+ if (this.config.debug && activeTraits) {
2840
+ console.log(`[OrbitalRuntime] Filtering traits: ${results.length} total, ${filteredResults.length} active (${activeTraits.join(", ")})`);
2841
+ }
2842
+ for (const { traitName, result } of filteredResults) {
2843
+ if (result.effects.length > 0) {
2844
+ await this.executeEffects(
2845
+ registered,
2846
+ traitName,
2847
+ result.effects,
2848
+ cleanPayload,
2849
+ entityData,
2850
+ entityId,
2851
+ emittedEvents,
2852
+ fetchedData,
2853
+ clientEffects,
2854
+ effectResults,
2855
+ user
2856
+ );
2857
+ }
2858
+ }
2859
+ const states = {};
2860
+ for (const [name, state] of registered.manager.getAllStates()) {
2861
+ states[name] = state.currentState;
2862
+ }
2863
+ const response = {
2864
+ success: true,
2865
+ transitioned: results.length > 0,
2866
+ states,
2867
+ emittedEvents
2868
+ };
2869
+ if (Object.keys(fetchedData).length > 0) {
2870
+ response.data = fetchedData;
2871
+ }
2872
+ if (clientEffects.length > 0) {
2873
+ response.clientEffects = clientEffects;
2874
+ }
2875
+ if (effectResults.length > 0) {
2876
+ response.effectResults = effectResults;
2877
+ }
2878
+ return response;
2879
+ }
2880
+ /**
2881
+ * Execute effects from a transition
2882
+ */
2883
+ async executeEffects(registered, traitName, effects, payload, entityData, entityId, emittedEvents, fetchedData, clientEffects, effectResults, user) {
2884
+ const entityType = registered.schema.entity.name;
2885
+ const handlers = {
2886
+ emit: (event, eventPayload) => {
2887
+ if (this.config.debug) {
2888
+ console.log(`[OrbitalRuntime] Emitting: ${event}`, eventPayload);
2889
+ }
2890
+ this.eventBus.emit(event, eventPayload);
2891
+ emittedEvents.push({ event, payload: eventPayload });
2892
+ },
2893
+ set: async (targetId, field, value) => {
2894
+ const id = targetId || entityId;
2895
+ if (id) {
2896
+ try {
2897
+ await this.persistence.update(entityType, id, { [field]: value });
2898
+ effectResults.push({
2899
+ effect: "set",
2900
+ entityType,
2901
+ data: { id, field, value },
2902
+ success: true
2903
+ });
2904
+ } catch (err) {
2905
+ effectResults.push({
2906
+ effect: "set",
2907
+ entityType,
2908
+ data: { id, field, value },
2909
+ success: false,
2910
+ error: err instanceof Error ? err.message : String(err)
2911
+ });
2912
+ }
2913
+ }
2914
+ },
2915
+ persist: async (action, targetEntityType, data) => {
2916
+ const type = targetEntityType || entityType;
2917
+ let resultData;
2918
+ try {
2919
+ switch (action) {
2920
+ case "create": {
2921
+ const { id } = await this.persistence.create(type, data || {});
2922
+ resultData = { id, ...data || {} };
2923
+ break;
2924
+ }
2925
+ case "update":
2926
+ if (data?.id || entityId) {
2927
+ const updateId = data?.id || entityId;
2928
+ await this.persistence.update(type, updateId, data || {});
2929
+ const updated = await this.persistence.getById(type, updateId);
2930
+ resultData = updated || { id: updateId, ...data || {} };
2931
+ }
2932
+ break;
2933
+ case "delete":
2934
+ if (data?.id || entityId) {
2935
+ const deleteId = data?.id || entityId;
2936
+ await this.persistence.delete(type, deleteId);
2937
+ resultData = { id: deleteId, deleted: true };
2938
+ }
2939
+ break;
2940
+ }
2941
+ effectResults.push({
2942
+ effect: "persist",
2943
+ action,
2944
+ entityType: type,
2945
+ data: resultData,
2946
+ success: true
2947
+ });
2948
+ } catch (err) {
2949
+ effectResults.push({
2950
+ effect: "persist",
2951
+ action,
2952
+ entityType: type,
2953
+ success: false,
2954
+ error: err instanceof Error ? err.message : String(err)
2955
+ });
2956
+ }
2957
+ },
2958
+ callService: async (service, action, params) => {
2959
+ try {
2960
+ let result = null;
2961
+ if (this.config.effectHandlers?.callService) {
2962
+ result = await this.config.effectHandlers.callService(
2963
+ service,
2964
+ action,
2965
+ params
2966
+ );
2967
+ } else {
2968
+ console.warn(
2969
+ `[OrbitalRuntime] call-service not configured: ${service}.${action}`
2970
+ );
2971
+ }
2972
+ effectResults.push({
2973
+ effect: "call-service",
2974
+ action: `${service}.${action}`,
2975
+ data: result,
2976
+ success: true
2977
+ });
2978
+ return result;
2979
+ } catch (err) {
2980
+ effectResults.push({
2981
+ effect: "call-service",
2982
+ action: `${service}.${action}`,
2983
+ success: false,
2984
+ error: err instanceof Error ? err.message : String(err)
2985
+ });
2986
+ return null;
2987
+ }
2988
+ },
2989
+ fetch: async (fetchEntityType, options) => {
2990
+ try {
2991
+ if (options?.id) {
2992
+ const entity = await this.persistence.getById(fetchEntityType, options.id);
2993
+ if (entity) {
2994
+ fetchedData[fetchEntityType] = [entity];
2995
+ }
2996
+ return entity;
2997
+ } else {
2998
+ let entities = await this.persistence.list(fetchEntityType);
2999
+ if (options?.offset && options.offset > 0) {
3000
+ entities = entities.slice(options.offset);
3001
+ }
3002
+ if (options?.limit && options.limit > 0) {
3003
+ entities = entities.slice(0, options.limit);
3004
+ }
3005
+ fetchedData[fetchEntityType] = entities;
3006
+ return entities;
3007
+ }
3008
+ } catch (error) {
3009
+ console.error(`[OrbitalRuntime] Fetch error for ${fetchEntityType}:`, error);
3010
+ return null;
3011
+ }
3012
+ },
3013
+ // Client-side effects - collect for forwarding to client
3014
+ renderUI: (slot, pattern, props, priority) => {
3015
+ clientEffects.push(["render-ui", slot, pattern, props, priority]);
3016
+ },
3017
+ navigate: (path2, params) => {
3018
+ clientEffects.push(["navigate", path2, params]);
3019
+ },
3020
+ notify: (message, type) => {
3021
+ if (this.config.debug) {
3022
+ console.log(`[OrbitalRuntime] Notification (${type}): ${message}`);
3023
+ }
3024
+ clientEffects.push(["notify", message, { type }]);
3025
+ },
3026
+ log: (message, level) => {
3027
+ const logFn = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
3028
+ logFn(`[OrbitalRuntime] ${message}`);
3029
+ },
3030
+ // Allow custom handlers to override
3031
+ ...this.config.effectHandlers
3032
+ };
3033
+ const state = registered.manager.getState(traitName);
3034
+ const bindings = {
3035
+ entity: entityData,
3036
+ payload,
3037
+ state: state?.currentState || "unknown",
3038
+ user
3039
+ // @user bindings from Firebase auth
3040
+ };
3041
+ const context = {
3042
+ traitName,
3043
+ state: state?.currentState || "unknown",
3044
+ transition: "unknown",
3045
+ entityId
3046
+ };
3047
+ const executor = new EffectExecutor({
3048
+ handlers,
3049
+ bindings,
3050
+ context,
3051
+ debug: this.config.debug
3052
+ });
3053
+ await executor.executeAll(effects);
3054
+ }
3055
+ // ==========================================================================
3056
+ // Express Router
3057
+ // ==========================================================================
3058
+ /**
3059
+ * Create Express router for orbital API endpoints
3060
+ *
3061
+ * All data access goes through trait events with guards.
3062
+ * No direct CRUD routes - use events with `fetch` effects.
3063
+ *
3064
+ * Routes:
3065
+ * - GET / - List registered orbitals
3066
+ * - GET /:orbital - Get orbital info and current states
3067
+ * - POST /:orbital/events - Send event to orbital (includes data from `fetch` effects)
3068
+ */
3069
+ router() {
3070
+ const router = Router();
3071
+ router.get("/", (_req, res) => {
3072
+ const orbitals = Array.from(this.orbitals.entries()).map(
3073
+ ([name, reg]) => ({
3074
+ name,
3075
+ entity: reg.schema.entity.name,
3076
+ traits: reg.schema.traits.map((t) => t.name)
3077
+ })
3078
+ );
3079
+ res.json({ success: true, orbitals });
3080
+ });
3081
+ router.get("/:orbital", (req, res) => {
3082
+ const orbitalName = req.params.orbital;
3083
+ const registered = this.orbitals.get(orbitalName);
3084
+ if (!registered) {
3085
+ res.status(404).json({ success: false, error: "Orbital not found" });
3086
+ return;
3087
+ }
3088
+ const states = {};
3089
+ for (const [name, state] of registered.manager.getAllStates()) {
3090
+ states[name] = state.currentState;
3091
+ }
3092
+ res.json({
3093
+ success: true,
3094
+ orbital: {
3095
+ name: orbitalName,
3096
+ entity: registered.schema.entity,
3097
+ traits: registered.schema.traits.map((t) => ({
3098
+ name: t.name,
3099
+ currentState: states[t.name],
3100
+ states: t.states.map((s) => s.name),
3101
+ events: [...new Set(t.transitions.map((tr) => tr.event))]
3102
+ }))
3103
+ }
3104
+ });
3105
+ });
3106
+ router.post(
3107
+ "/:orbital/events",
3108
+ async (req, res, next) => {
3109
+ try {
3110
+ const orbitalName = req.params.orbital;
3111
+ const firebaseUser = req.firebaseUser;
3112
+ const user = firebaseUser ? {
3113
+ uid: firebaseUser.uid,
3114
+ email: firebaseUser.email,
3115
+ displayName: firebaseUser.name,
3116
+ ...firebaseUser
3117
+ } : void 0;
3118
+ const result = await this.processOrbitalEvent(orbitalName, {
3119
+ ...req.body,
3120
+ user
3121
+ });
3122
+ res.json(result);
3123
+ } catch (error) {
3124
+ next(error);
3125
+ }
3126
+ }
3127
+ );
3128
+ return router;
3129
+ }
3130
+ // ==========================================================================
3131
+ // Direct API (for programmatic use)
3132
+ // ==========================================================================
3133
+ /**
3134
+ * Get the event bus for manual event emission
3135
+ */
3136
+ getEventBus() {
3137
+ return this.eventBus;
3138
+ }
3139
+ /**
3140
+ * Get state for a specific orbital/trait
3141
+ */
3142
+ getState(orbitalName, traitName) {
3143
+ const registered = this.orbitals.get(orbitalName);
3144
+ if (!registered) return void 0;
3145
+ if (traitName) {
3146
+ return registered.manager.getState(traitName);
3147
+ }
3148
+ const states = {};
3149
+ for (const [name, state] of registered.manager.getAllStates()) {
3150
+ states[name] = state;
3151
+ }
3152
+ return states;
3153
+ }
3154
+ /**
3155
+ * List registered orbitals
3156
+ */
3157
+ listOrbitals() {
3158
+ return Array.from(this.orbitals.keys());
3159
+ }
3160
+ /**
3161
+ * Check if an orbital is registered
3162
+ */
3163
+ hasOrbital(name) {
3164
+ return this.orbitals.has(name);
3165
+ }
3166
+ /**
3167
+ * Get information about active ticks
3168
+ */
3169
+ getActiveTicks() {
3170
+ return this.tickBindings.map((binding) => ({
3171
+ orbital: binding.orbitalName,
3172
+ trait: binding.traitName,
3173
+ tick: binding.tick.name,
3174
+ interval: binding.tick.interval,
3175
+ hasGuard: !!binding.tick.guard
3176
+ }));
3177
+ }
3178
+ };
3179
+ function createOrbitalServerRuntime(config) {
3180
+ return new OrbitalServerRuntime(config);
3181
+ }
3182
+
3183
+ export { OrbitalServerRuntime, createOrbitalServerRuntime };
3184
+ //# sourceMappingURL=OrbitalServerRuntime.js.map
3185
+ //# sourceMappingURL=OrbitalServerRuntime.js.map