@almadar/runtime 1.0.10 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/OrbitalServerRuntime.js +209 -2314
- package/dist/OrbitalServerRuntime.js.map +1 -1
- package/dist/chunk-VW6RWQA5.js +1870 -0
- package/dist/chunk-VW6RWQA5.js.map +1 -0
- package/dist/external-loader-FJVQACFN.js +319 -0
- package/dist/external-loader-FJVQACFN.js.map +1 -0
- package/dist/index.js +1 -1554
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,2373 +1,268 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import { OrbitalSchemaSchema, isEntityReference, parseEntityRef, parseImportedTraitRef, isPageReference, isPageReferenceString, isPageReferenceObject, parsePageRef } from '@almadar/core';
|
|
1
|
+
import { EventBus, createUnifiedLoader, preprocessSchema, StateMachineManager, createContextFromBindings, EffectExecutor } from './chunk-VW6RWQA5.js';
|
|
4
2
|
import { Router } from 'express';
|
|
5
|
-
import { evaluateGuard
|
|
3
|
+
import { evaluateGuard } from '@almadar/evaluator';
|
|
6
4
|
import { faker } from '@faker-js/faker';
|
|
7
5
|
|
|
8
|
-
var
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
* If the schema has seedData, those instances are used directly.
|
|
991
|
-
* Otherwise, random mock data is generated with faker.
|
|
992
|
-
*/
|
|
993
|
-
registerEntity(schema, seedCount) {
|
|
994
|
-
const normalized = schema.name.toLowerCase();
|
|
995
|
-
this.schemas.set(normalized, schema);
|
|
996
|
-
if (schema.seedData && schema.seedData.length > 0) {
|
|
997
|
-
this.seedFromInstances(schema.name, schema.seedData);
|
|
998
|
-
} else {
|
|
999
|
-
const count = seedCount ?? this.config.defaultSeedCount ?? 6;
|
|
1000
|
-
this.seed(schema.name, schema.fields, count);
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
/**
|
|
1004
|
-
* Seed an entity with pre-authored instance data.
|
|
1005
|
-
*/
|
|
1006
|
-
seedFromInstances(entityName, instances) {
|
|
1007
|
-
const store = this.getStore(entityName);
|
|
1008
|
-
if (this.config.debug) {
|
|
1009
|
-
console.log(`[MockPersistence] Seeding ${instances.length} ${entityName} from schema instances...`);
|
|
1010
|
-
}
|
|
1011
|
-
for (const instance of instances) {
|
|
1012
|
-
const id = instance.id || this.nextId(entityName);
|
|
1013
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1014
|
-
const item = {
|
|
1015
|
-
...instance,
|
|
1016
|
-
id,
|
|
1017
|
-
createdAt: instance.createdAt || now,
|
|
1018
|
-
updatedAt: now
|
|
1019
|
-
};
|
|
1020
|
-
store.set(id, item);
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
/**
|
|
1024
|
-
* Seed an entity with mock data.
|
|
1025
|
-
*/
|
|
1026
|
-
seed(entityName, fields, count) {
|
|
1027
|
-
const store = this.getStore(entityName);
|
|
1028
|
-
const normalized = entityName.toLowerCase();
|
|
1029
|
-
if (this.config.debug) {
|
|
1030
|
-
console.log(`[MockPersistence] Seeding ${count} ${entityName}...`);
|
|
1031
|
-
}
|
|
1032
|
-
for (let i = 0; i < count; i++) {
|
|
1033
|
-
const item = this.generateMockItem(normalized, entityName, fields, i + 1);
|
|
1034
|
-
store.set(item.id, item);
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
/**
|
|
1038
|
-
* Generate a single mock item based on field schemas.
|
|
1039
|
-
*/
|
|
1040
|
-
generateMockItem(normalizedName, entityName, fields, index) {
|
|
1041
|
-
const id = this.nextId(entityName);
|
|
1042
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1043
|
-
const item = {
|
|
1044
|
-
id,
|
|
1045
|
-
createdAt: faker.date.past({ years: 1 }).toISOString(),
|
|
1046
|
-
updatedAt: now
|
|
1047
|
-
};
|
|
1048
|
-
for (const field of fields) {
|
|
1049
|
-
if (field.name === "id" || field.name === "createdAt" || field.name === "updatedAt") {
|
|
1050
|
-
continue;
|
|
1051
|
-
}
|
|
1052
|
-
item[field.name] = this.generateFieldValue(entityName, field, index);
|
|
1053
|
-
}
|
|
1054
|
-
return item;
|
|
1055
|
-
}
|
|
1056
|
-
/**
|
|
1057
|
-
* Generate a mock value for a field based on its schema.
|
|
1058
|
-
*/
|
|
1059
|
-
generateFieldValue(entityName, field, index) {
|
|
1060
|
-
if (field.default !== void 0) {
|
|
1061
|
-
if (field.default === "@now") {
|
|
1062
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1063
|
-
}
|
|
1064
|
-
return field.default;
|
|
1065
|
-
}
|
|
1066
|
-
if (!field.required && Math.random() > 0.8) {
|
|
1067
|
-
return null;
|
|
1068
|
-
}
|
|
1069
|
-
const fieldType = field.type.toLowerCase();
|
|
1070
|
-
switch (fieldType) {
|
|
1071
|
-
case "string":
|
|
1072
|
-
return this.generateStringValue(entityName, field, index);
|
|
1073
|
-
case "number":
|
|
1074
|
-
return faker.number.int({ min: 0, max: 100 });
|
|
1075
|
-
case "boolean":
|
|
1076
|
-
return faker.datatype.boolean();
|
|
1077
|
-
case "date":
|
|
1078
|
-
case "timestamp":
|
|
1079
|
-
case "datetime":
|
|
1080
|
-
return this.generateDateValue(field);
|
|
1081
|
-
case "enum":
|
|
1082
|
-
if (field.values && field.values.length > 0) {
|
|
1083
|
-
return faker.helpers.arrayElement(field.values);
|
|
1084
|
-
}
|
|
1085
|
-
return null;
|
|
1086
|
-
case "relation":
|
|
1087
|
-
return null;
|
|
1088
|
-
// Relations need special handling
|
|
1089
|
-
case "array":
|
|
1090
|
-
case "object":
|
|
1091
|
-
return field.type === "array" ? [] : {};
|
|
1092
|
-
default:
|
|
1093
|
-
return this.generateStringValue(entityName, field, index);
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
/**
|
|
1097
|
-
* Generate a string value based on field name heuristics.
|
|
1098
|
-
*/
|
|
1099
|
-
generateStringValue(entityName, field, index) {
|
|
1100
|
-
const name = field.name.toLowerCase();
|
|
1101
|
-
if (field.values && field.values.length > 0) {
|
|
1102
|
-
return faker.helpers.arrayElement(field.values);
|
|
1103
|
-
}
|
|
1104
|
-
if (name.includes("email")) return faker.internet.email();
|
|
1105
|
-
if (name.includes("phone")) return faker.phone.number();
|
|
1106
|
-
if (name.includes("address")) return faker.location.streetAddress();
|
|
1107
|
-
if (name.includes("city")) return faker.location.city();
|
|
1108
|
-
if (name.includes("country")) return faker.location.country();
|
|
1109
|
-
if (name.includes("url") || name.includes("website")) return faker.internet.url();
|
|
1110
|
-
if (name.includes("avatar") || name.includes("image")) return faker.image.avatar();
|
|
1111
|
-
if (name.includes("color")) return faker.color.human();
|
|
1112
|
-
if (name.includes("uuid")) return faker.string.uuid();
|
|
1113
|
-
if (name.includes("description") || name.includes("bio")) return faker.lorem.paragraph();
|
|
1114
|
-
const entityLabel = this.capitalizeFirst(entityName);
|
|
1115
|
-
const fieldLabel = this.capitalizeFirst(field.name);
|
|
1116
|
-
return `${entityLabel} ${fieldLabel} ${index}`;
|
|
1117
|
-
}
|
|
1118
|
-
/**
|
|
1119
|
-
* Generate a date value based on field name heuristics.
|
|
1120
|
-
*/
|
|
1121
|
-
generateDateValue(field) {
|
|
1122
|
-
const name = field.name.toLowerCase();
|
|
1123
|
-
let date;
|
|
1124
|
-
if (name.includes("created") || name.includes("start") || name.includes("birth")) {
|
|
1125
|
-
date = faker.date.past({ years: 2 });
|
|
1126
|
-
} else if (name.includes("updated") || name.includes("modified")) {
|
|
1127
|
-
date = faker.date.recent({ days: 30 });
|
|
1128
|
-
} else if (name.includes("deadline") || name.includes("due") || name.includes("end") || name.includes("expires")) {
|
|
1129
|
-
date = faker.date.future({ years: 1 });
|
|
1130
|
-
} else {
|
|
1131
|
-
date = faker.date.anytime();
|
|
1132
|
-
}
|
|
1133
|
-
return date.toISOString();
|
|
1134
|
-
}
|
|
1135
|
-
capitalizeFirst(str) {
|
|
1136
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
1137
|
-
}
|
|
1138
|
-
// ============================================================================
|
|
1139
|
-
// PersistenceAdapter Implementation
|
|
1140
|
-
// ============================================================================
|
|
1141
|
-
async create(entityType, data) {
|
|
1142
|
-
const store = this.getStore(entityType);
|
|
1143
|
-
const id = this.nextId(entityType);
|
|
1144
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1145
|
-
const item = {
|
|
1146
|
-
...data,
|
|
1147
|
-
id,
|
|
1148
|
-
createdAt: now,
|
|
1149
|
-
updatedAt: now
|
|
1150
|
-
};
|
|
1151
|
-
store.set(id, item);
|
|
1152
|
-
return { id };
|
|
1153
|
-
}
|
|
1154
|
-
async update(entityType, id, data) {
|
|
1155
|
-
const store = this.getStore(entityType);
|
|
1156
|
-
const existing = store.get(id);
|
|
1157
|
-
if (!existing) {
|
|
1158
|
-
throw new Error(`Entity ${entityType} with id ${id} not found`);
|
|
1159
|
-
}
|
|
1160
|
-
const updated = {
|
|
1161
|
-
...existing,
|
|
1162
|
-
...data,
|
|
1163
|
-
id,
|
|
1164
|
-
// Preserve original ID
|
|
1165
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1166
|
-
};
|
|
1167
|
-
store.set(id, updated);
|
|
1168
|
-
}
|
|
1169
|
-
async delete(entityType, id) {
|
|
1170
|
-
const store = this.getStore(entityType);
|
|
1171
|
-
if (!store.has(id)) {
|
|
1172
|
-
throw new Error(`Entity ${entityType} with id ${id} not found`);
|
|
1173
|
-
}
|
|
1174
|
-
store.delete(id);
|
|
1175
|
-
}
|
|
1176
|
-
async getById(entityType, id) {
|
|
1177
|
-
const store = this.getStore(entityType);
|
|
1178
|
-
return store.get(id) ?? null;
|
|
1179
|
-
}
|
|
1180
|
-
async list(entityType) {
|
|
1181
|
-
const store = this.getStore(entityType);
|
|
1182
|
-
return Array.from(store.values());
|
|
1183
|
-
}
|
|
1184
|
-
// ============================================================================
|
|
1185
|
-
// Utilities
|
|
1186
|
-
// ============================================================================
|
|
1187
|
-
/**
|
|
1188
|
-
* Clear all data for an entity.
|
|
1189
|
-
*/
|
|
1190
|
-
clear(entityName) {
|
|
1191
|
-
const normalized = entityName.toLowerCase();
|
|
1192
|
-
this.stores.delete(normalized);
|
|
1193
|
-
this.idCounters.delete(normalized);
|
|
1194
|
-
}
|
|
1195
|
-
/**
|
|
1196
|
-
* Clear all data.
|
|
1197
|
-
*/
|
|
1198
|
-
clearAll() {
|
|
1199
|
-
this.stores.clear();
|
|
1200
|
-
this.idCounters.clear();
|
|
1201
|
-
}
|
|
1202
|
-
/**
|
|
1203
|
-
* Get count of items for an entity.
|
|
1204
|
-
*/
|
|
1205
|
-
count(entityName) {
|
|
1206
|
-
const store = this.getStore(entityName);
|
|
1207
|
-
return store.size;
|
|
1208
|
-
}
|
|
1209
|
-
};
|
|
1210
|
-
|
|
1211
|
-
// src/resolver/reference-resolver.ts
|
|
1212
|
-
init_external_loader();
|
|
1213
|
-
var ReferenceResolver = class {
|
|
1214
|
-
loader;
|
|
1215
|
-
options;
|
|
1216
|
-
localTraits;
|
|
1217
|
-
constructor(options) {
|
|
1218
|
-
this.options = options;
|
|
1219
|
-
this.loader = options.loader ?? new ExternalOrbitalLoader(options);
|
|
1220
|
-
this.localTraits = options.localTraits ?? /* @__PURE__ */ new Map();
|
|
1221
|
-
}
|
|
1222
|
-
/**
|
|
1223
|
-
* Resolve all references in an orbital.
|
|
1224
|
-
*/
|
|
1225
|
-
async resolve(orbital, sourcePath, chain) {
|
|
1226
|
-
const errors = [];
|
|
1227
|
-
const warnings = [];
|
|
1228
|
-
const importChain = chain ?? new ImportChain();
|
|
1229
|
-
const importsResult = await this.resolveImports(
|
|
1230
|
-
orbital.uses ?? [],
|
|
1231
|
-
sourcePath,
|
|
1232
|
-
importChain
|
|
1233
|
-
);
|
|
1234
|
-
if (!importsResult.success) {
|
|
1235
|
-
return { success: false, errors: importsResult.errors };
|
|
1236
|
-
}
|
|
1237
|
-
const imports = importsResult.data;
|
|
1238
|
-
const entityResult = this.resolveEntity(orbital.entity, imports);
|
|
1239
|
-
if (!entityResult.success) {
|
|
1240
|
-
errors.push(...entityResult.errors);
|
|
1241
|
-
}
|
|
1242
|
-
const traitsResult = this.resolveTraits(orbital.traits, imports);
|
|
1243
|
-
if (!traitsResult.success) {
|
|
1244
|
-
errors.push(...traitsResult.errors);
|
|
1245
|
-
}
|
|
1246
|
-
const pagesResult = this.resolvePages(orbital.pages, imports);
|
|
1247
|
-
if (!pagesResult.success) {
|
|
1248
|
-
errors.push(...pagesResult.errors);
|
|
1249
|
-
}
|
|
1250
|
-
if (errors.length > 0) {
|
|
1251
|
-
return { success: false, errors };
|
|
1252
|
-
}
|
|
1253
|
-
if (!entityResult.success || !traitsResult.success || !pagesResult.success) {
|
|
1254
|
-
return { success: false, errors: ["Internal error: unexpected failure state"] };
|
|
1255
|
-
}
|
|
1256
|
-
return {
|
|
1257
|
-
success: true,
|
|
1258
|
-
data: {
|
|
1259
|
-
name: orbital.name,
|
|
1260
|
-
entity: entityResult.data.entity,
|
|
1261
|
-
entitySource: entityResult.data.source,
|
|
1262
|
-
traits: traitsResult.data,
|
|
1263
|
-
pages: pagesResult.data,
|
|
1264
|
-
imports,
|
|
1265
|
-
original: orbital
|
|
1266
|
-
},
|
|
1267
|
-
warnings
|
|
1268
|
-
};
|
|
1269
|
-
}
|
|
1270
|
-
/**
|
|
1271
|
-
* Resolve `uses` declarations to loaded orbitals.
|
|
1272
|
-
*/
|
|
1273
|
-
async resolveImports(uses, sourcePath, chain) {
|
|
1274
|
-
const errors = [];
|
|
1275
|
-
const orbitals = /* @__PURE__ */ new Map();
|
|
1276
|
-
if (this.options.skipExternalLoading) {
|
|
1277
|
-
return {
|
|
1278
|
-
success: true,
|
|
1279
|
-
data: { orbitals },
|
|
1280
|
-
warnings: ["External loading skipped"]
|
|
1281
|
-
};
|
|
1282
|
-
}
|
|
1283
|
-
for (const use of uses) {
|
|
1284
|
-
if (orbitals.has(use.as)) {
|
|
1285
|
-
errors.push(`Duplicate import alias: ${use.as}`);
|
|
1286
|
-
continue;
|
|
1287
|
-
}
|
|
1288
|
-
const loadResult = await this.loader.loadOrbital(
|
|
1289
|
-
use.from,
|
|
1290
|
-
void 0,
|
|
1291
|
-
sourcePath,
|
|
1292
|
-
chain
|
|
1293
|
-
);
|
|
1294
|
-
if (!loadResult.success) {
|
|
1295
|
-
errors.push(`Failed to load "${use.from}" as "${use.as}": ${loadResult.error}`);
|
|
1296
|
-
continue;
|
|
1297
|
-
}
|
|
1298
|
-
orbitals.set(use.as, {
|
|
1299
|
-
alias: use.as,
|
|
1300
|
-
from: use.from,
|
|
1301
|
-
orbital: loadResult.data.orbital,
|
|
1302
|
-
sourcePath: loadResult.data.sourcePath
|
|
1303
|
-
});
|
|
1304
|
-
}
|
|
1305
|
-
if (errors.length > 0) {
|
|
1306
|
-
return { success: false, errors };
|
|
1307
|
-
}
|
|
1308
|
-
return { success: true, data: { orbitals }, warnings: [] };
|
|
1309
|
-
}
|
|
1310
|
-
/**
|
|
1311
|
-
* Resolve entity reference.
|
|
1312
|
-
*/
|
|
1313
|
-
resolveEntity(entityRef, imports) {
|
|
1314
|
-
if (!isEntityReference(entityRef)) {
|
|
1315
|
-
return {
|
|
1316
|
-
success: true,
|
|
1317
|
-
data: { entity: entityRef },
|
|
1318
|
-
warnings: []
|
|
1319
|
-
};
|
|
1320
|
-
}
|
|
1321
|
-
const parsed = parseEntityRef(entityRef);
|
|
1322
|
-
if (!parsed) {
|
|
1323
|
-
return {
|
|
1324
|
-
success: false,
|
|
1325
|
-
errors: [`Invalid entity reference format: ${entityRef}. Expected "Alias.entity"`]
|
|
1326
|
-
};
|
|
1327
|
-
}
|
|
1328
|
-
const imported = imports.orbitals.get(parsed.alias);
|
|
1329
|
-
if (!imported) {
|
|
1330
|
-
return {
|
|
1331
|
-
success: false,
|
|
1332
|
-
errors: [
|
|
1333
|
-
`Unknown import alias in entity reference: ${parsed.alias}. Available aliases: ${Array.from(imports.orbitals.keys()).join(", ") || "none"}`
|
|
1334
|
-
]
|
|
1335
|
-
};
|
|
1336
|
-
}
|
|
1337
|
-
const importedEntity = this.getEntityFromOrbital(imported.orbital);
|
|
1338
|
-
if (!importedEntity) {
|
|
1339
|
-
return {
|
|
1340
|
-
success: false,
|
|
1341
|
-
errors: [
|
|
1342
|
-
`Imported orbital "${parsed.alias}" does not have an inline entity. Entity references cannot be chained.`
|
|
1343
|
-
]
|
|
1344
|
-
};
|
|
1345
|
-
}
|
|
1346
|
-
const persistence = importedEntity.persistence ?? "persistent";
|
|
1347
|
-
return {
|
|
1348
|
-
success: true,
|
|
1349
|
-
data: {
|
|
1350
|
-
entity: importedEntity,
|
|
1351
|
-
source: {
|
|
1352
|
-
alias: parsed.alias,
|
|
1353
|
-
persistence
|
|
1354
|
-
}
|
|
1355
|
-
},
|
|
1356
|
-
warnings: []
|
|
1357
|
-
};
|
|
1358
|
-
}
|
|
1359
|
-
/**
|
|
1360
|
-
* Get the entity from an orbital (handling EntityRef).
|
|
1361
|
-
*/
|
|
1362
|
-
getEntityFromOrbital(orbital) {
|
|
1363
|
-
const entityRef = orbital.entity;
|
|
1364
|
-
if (typeof entityRef === "string") {
|
|
1365
|
-
return null;
|
|
1366
|
-
}
|
|
1367
|
-
return entityRef;
|
|
1368
|
-
}
|
|
1369
|
-
/**
|
|
1370
|
-
* Resolve trait references.
|
|
1371
|
-
*/
|
|
1372
|
-
resolveTraits(traitRefs, imports) {
|
|
1373
|
-
const errors = [];
|
|
1374
|
-
const resolved = [];
|
|
1375
|
-
for (const traitRef of traitRefs) {
|
|
1376
|
-
const result = this.resolveTraitRef(traitRef, imports);
|
|
1377
|
-
if (!result.success) {
|
|
1378
|
-
errors.push(...result.errors);
|
|
1379
|
-
} else {
|
|
1380
|
-
resolved.push(result.data);
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
if (errors.length > 0) {
|
|
1384
|
-
return { success: false, errors };
|
|
1385
|
-
}
|
|
1386
|
-
return { success: true, data: resolved, warnings: [] };
|
|
1387
|
-
}
|
|
1388
|
-
/**
|
|
1389
|
-
* Resolve a single trait reference.
|
|
1390
|
-
*/
|
|
1391
|
-
resolveTraitRef(traitRef, imports) {
|
|
1392
|
-
if (typeof traitRef !== "string" && "stateMachine" in traitRef) {
|
|
1393
|
-
return {
|
|
1394
|
-
success: true,
|
|
1395
|
-
data: {
|
|
1396
|
-
trait: traitRef,
|
|
1397
|
-
source: { type: "inline" }
|
|
1398
|
-
},
|
|
1399
|
-
warnings: []
|
|
1400
|
-
};
|
|
1401
|
-
}
|
|
1402
|
-
if (typeof traitRef !== "string" && "ref" in traitRef) {
|
|
1403
|
-
const refObj = traitRef;
|
|
1404
|
-
return this.resolveTraitRefString(refObj.ref, imports, refObj.config, refObj.linkedEntity);
|
|
1405
|
-
}
|
|
1406
|
-
if (typeof traitRef === "string") {
|
|
1407
|
-
return this.resolveTraitRefString(traitRef, imports);
|
|
1408
|
-
}
|
|
1409
|
-
return {
|
|
1410
|
-
success: false,
|
|
1411
|
-
errors: [`Unknown trait reference format: ${JSON.stringify(traitRef)}`]
|
|
1412
|
-
};
|
|
1413
|
-
}
|
|
1414
|
-
/**
|
|
1415
|
-
* Resolve a trait reference string.
|
|
1416
|
-
*/
|
|
1417
|
-
resolveTraitRefString(ref, imports, config, linkedEntity) {
|
|
1418
|
-
const parsed = parseImportedTraitRef(ref);
|
|
1419
|
-
if (parsed) {
|
|
1420
|
-
const imported = imports.orbitals.get(parsed.alias);
|
|
1421
|
-
if (!imported) {
|
|
1422
|
-
return {
|
|
1423
|
-
success: false,
|
|
1424
|
-
errors: [
|
|
1425
|
-
`Unknown import alias in trait reference: ${parsed.alias}. Available aliases: ${Array.from(imports.orbitals.keys()).join(", ") || "none"}`
|
|
1426
|
-
]
|
|
1427
|
-
};
|
|
1428
|
-
}
|
|
1429
|
-
const trait = this.findTraitInOrbital(imported.orbital, parsed.traitName);
|
|
1430
|
-
if (!trait) {
|
|
1431
|
-
return {
|
|
1432
|
-
success: false,
|
|
1433
|
-
errors: [
|
|
1434
|
-
`Trait "${parsed.traitName}" not found in imported orbital "${parsed.alias}". Available traits: ${this.listTraitsInOrbital(imported.orbital).join(", ") || "none"}`
|
|
1435
|
-
]
|
|
1436
|
-
};
|
|
1437
|
-
}
|
|
1438
|
-
return {
|
|
1439
|
-
success: true,
|
|
1440
|
-
data: {
|
|
1441
|
-
trait,
|
|
1442
|
-
source: { type: "imported", alias: parsed.alias, traitName: parsed.traitName },
|
|
1443
|
-
config,
|
|
1444
|
-
linkedEntity
|
|
1445
|
-
},
|
|
1446
|
-
warnings: []
|
|
1447
|
-
};
|
|
1448
|
-
}
|
|
1449
|
-
const localTrait = this.localTraits.get(ref);
|
|
1450
|
-
if (localTrait) {
|
|
1451
|
-
return {
|
|
1452
|
-
success: true,
|
|
1453
|
-
data: {
|
|
1454
|
-
trait: localTrait,
|
|
1455
|
-
source: { type: "local", name: ref },
|
|
1456
|
-
config,
|
|
1457
|
-
linkedEntity
|
|
1458
|
-
},
|
|
1459
|
-
warnings: []
|
|
1460
|
-
};
|
|
1461
|
-
}
|
|
1462
|
-
return {
|
|
1463
|
-
success: false,
|
|
1464
|
-
errors: [
|
|
1465
|
-
`Trait "${ref}" not found. For imported traits, use format "Alias.traits.TraitName". Local traits available: ${Array.from(this.localTraits.keys()).join(", ") || "none"}`
|
|
1466
|
-
]
|
|
1467
|
-
};
|
|
1468
|
-
}
|
|
1469
|
-
/**
|
|
1470
|
-
* Find a trait in an orbital by name.
|
|
1471
|
-
*/
|
|
1472
|
-
findTraitInOrbital(orbital, traitName) {
|
|
1473
|
-
for (const traitRef of orbital.traits) {
|
|
1474
|
-
if (typeof traitRef !== "string" && "stateMachine" in traitRef) {
|
|
1475
|
-
if (traitRef.name === traitName) {
|
|
1476
|
-
return traitRef;
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
if (typeof traitRef !== "string" && "ref" in traitRef) {
|
|
1480
|
-
const refObj = traitRef;
|
|
1481
|
-
if (refObj.ref === traitName || refObj.name === traitName) ;
|
|
1482
|
-
}
|
|
1483
|
-
}
|
|
1484
|
-
return null;
|
|
1485
|
-
}
|
|
1486
|
-
/**
|
|
1487
|
-
* List trait names in an orbital.
|
|
1488
|
-
*/
|
|
1489
|
-
listTraitsInOrbital(orbital) {
|
|
1490
|
-
const names = [];
|
|
1491
|
-
for (const traitRef of orbital.traits) {
|
|
1492
|
-
if (typeof traitRef !== "string" && "stateMachine" in traitRef) {
|
|
1493
|
-
names.push(traitRef.name);
|
|
1494
|
-
}
|
|
1495
|
-
}
|
|
1496
|
-
return names;
|
|
1497
|
-
}
|
|
1498
|
-
/**
|
|
1499
|
-
* Resolve page references.
|
|
1500
|
-
*/
|
|
1501
|
-
resolvePages(pageRefs, imports) {
|
|
1502
|
-
const errors = [];
|
|
1503
|
-
const resolved = [];
|
|
1504
|
-
for (const pageRef of pageRefs) {
|
|
1505
|
-
const result = this.resolvePageRef(pageRef, imports);
|
|
1506
|
-
if (!result.success) {
|
|
1507
|
-
errors.push(...result.errors);
|
|
1508
|
-
} else {
|
|
1509
|
-
resolved.push(result.data);
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
if (errors.length > 0) {
|
|
1513
|
-
return { success: false, errors };
|
|
1514
|
-
}
|
|
1515
|
-
return { success: true, data: resolved, warnings: [] };
|
|
1516
|
-
}
|
|
1517
|
-
/**
|
|
1518
|
-
* Resolve a single page reference.
|
|
1519
|
-
*/
|
|
1520
|
-
resolvePageRef(pageRef, imports) {
|
|
1521
|
-
if (!isPageReference(pageRef)) {
|
|
1522
|
-
return {
|
|
1523
|
-
success: true,
|
|
1524
|
-
data: {
|
|
1525
|
-
page: pageRef,
|
|
1526
|
-
source: { type: "inline" },
|
|
1527
|
-
pathOverridden: false
|
|
1528
|
-
},
|
|
1529
|
-
warnings: []
|
|
1530
|
-
};
|
|
1531
|
-
}
|
|
1532
|
-
if (isPageReferenceString(pageRef)) {
|
|
1533
|
-
return this.resolvePageRefString(pageRef, imports);
|
|
1534
|
-
}
|
|
1535
|
-
if (isPageReferenceObject(pageRef)) {
|
|
1536
|
-
return this.resolvePageRefObject(pageRef, imports);
|
|
1537
|
-
}
|
|
1538
|
-
return {
|
|
1539
|
-
success: false,
|
|
1540
|
-
errors: [`Unknown page reference format: ${JSON.stringify(pageRef)}`]
|
|
1541
|
-
};
|
|
1542
|
-
}
|
|
1543
|
-
/**
|
|
1544
|
-
* Resolve a page reference string.
|
|
1545
|
-
*/
|
|
1546
|
-
resolvePageRefString(ref, imports) {
|
|
1547
|
-
const parsed = parsePageRef(ref);
|
|
1548
|
-
if (!parsed) {
|
|
1549
|
-
return {
|
|
1550
|
-
success: false,
|
|
1551
|
-
errors: [`Invalid page reference format: ${ref}. Expected "Alias.pages.PageName"`]
|
|
1552
|
-
};
|
|
1553
|
-
}
|
|
1554
|
-
const imported = imports.orbitals.get(parsed.alias);
|
|
1555
|
-
if (!imported) {
|
|
1556
|
-
return {
|
|
1557
|
-
success: false,
|
|
1558
|
-
errors: [
|
|
1559
|
-
`Unknown import alias in page reference: ${parsed.alias}. Available aliases: ${Array.from(imports.orbitals.keys()).join(", ") || "none"}`
|
|
1560
|
-
]
|
|
1561
|
-
};
|
|
1562
|
-
}
|
|
1563
|
-
const page = this.findPageInOrbital(imported.orbital, parsed.pageName);
|
|
1564
|
-
if (!page) {
|
|
1565
|
-
return {
|
|
1566
|
-
success: false,
|
|
1567
|
-
errors: [
|
|
1568
|
-
`Page "${parsed.pageName}" not found in imported orbital "${parsed.alias}". Available pages: ${this.listPagesInOrbital(imported.orbital).join(", ") || "none"}`
|
|
1569
|
-
]
|
|
1570
|
-
};
|
|
1571
|
-
}
|
|
1572
|
-
return {
|
|
1573
|
-
success: true,
|
|
1574
|
-
data: {
|
|
1575
|
-
page,
|
|
1576
|
-
source: { type: "imported", alias: parsed.alias, pageName: parsed.pageName },
|
|
1577
|
-
pathOverridden: false
|
|
1578
|
-
},
|
|
1579
|
-
warnings: []
|
|
1580
|
-
};
|
|
1581
|
-
}
|
|
1582
|
-
/**
|
|
1583
|
-
* Resolve a page reference object with optional path override.
|
|
1584
|
-
*/
|
|
1585
|
-
resolvePageRefObject(refObj, imports) {
|
|
1586
|
-
const baseResult = this.resolvePageRefString(refObj.ref, imports);
|
|
1587
|
-
if (!baseResult.success) {
|
|
1588
|
-
return baseResult;
|
|
1589
|
-
}
|
|
1590
|
-
const resolved = baseResult.data;
|
|
1591
|
-
if (refObj.path) {
|
|
1592
|
-
const originalPath = resolved.page.path;
|
|
1593
|
-
resolved.page = {
|
|
1594
|
-
...resolved.page,
|
|
1595
|
-
path: refObj.path
|
|
1596
|
-
};
|
|
1597
|
-
resolved.pathOverridden = true;
|
|
1598
|
-
resolved.originalPath = originalPath;
|
|
1599
|
-
}
|
|
1600
|
-
return {
|
|
1601
|
-
success: true,
|
|
1602
|
-
data: resolved,
|
|
1603
|
-
warnings: baseResult.warnings
|
|
1604
|
-
};
|
|
1605
|
-
}
|
|
1606
|
-
/**
|
|
1607
|
-
* Find a page in an orbital by name.
|
|
1608
|
-
*/
|
|
1609
|
-
findPageInOrbital(orbital, pageName) {
|
|
1610
|
-
const pages = orbital.pages;
|
|
1611
|
-
if (!pages) return null;
|
|
1612
|
-
for (const pageRef of pages) {
|
|
1613
|
-
if (typeof pageRef !== "string" && !("ref" in pageRef)) {
|
|
1614
|
-
const page = pageRef;
|
|
1615
|
-
if (page.name === pageName) {
|
|
1616
|
-
return { ...page };
|
|
1617
|
-
}
|
|
1618
|
-
}
|
|
1619
|
-
}
|
|
1620
|
-
return null;
|
|
1621
|
-
}
|
|
1622
|
-
/**
|
|
1623
|
-
* List page names in an orbital.
|
|
1624
|
-
*/
|
|
1625
|
-
listPagesInOrbital(orbital) {
|
|
1626
|
-
const pages = orbital.pages;
|
|
1627
|
-
if (!pages) return [];
|
|
1628
|
-
const names = [];
|
|
1629
|
-
for (const pageRef of pages) {
|
|
1630
|
-
if (typeof pageRef !== "string" && !("ref" in pageRef)) {
|
|
1631
|
-
names.push(pageRef.name);
|
|
1632
|
-
}
|
|
1633
|
-
}
|
|
1634
|
-
return names;
|
|
1635
|
-
}
|
|
1636
|
-
/**
|
|
1637
|
-
* Add local traits for resolution.
|
|
1638
|
-
*/
|
|
1639
|
-
addLocalTraits(traits) {
|
|
1640
|
-
for (const trait of traits) {
|
|
1641
|
-
this.localTraits.set(trait.name, trait);
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
/**
|
|
1645
|
-
* Clear loader cache.
|
|
1646
|
-
*/
|
|
1647
|
-
clearCache() {
|
|
1648
|
-
this.loader.clearCache();
|
|
1649
|
-
}
|
|
1650
|
-
};
|
|
1651
|
-
async function resolveSchema(schema, options) {
|
|
1652
|
-
const resolver = new ReferenceResolver(options);
|
|
1653
|
-
const errors = [];
|
|
1654
|
-
const warnings = [];
|
|
1655
|
-
const resolved = [];
|
|
1656
|
-
for (const orbital of schema.orbitals) {
|
|
1657
|
-
const inlineTraits = orbital.traits.filter(
|
|
1658
|
-
(t) => typeof t !== "string" && "stateMachine" in t
|
|
1659
|
-
);
|
|
1660
|
-
resolver.addLocalTraits(inlineTraits);
|
|
1661
|
-
}
|
|
1662
|
-
for (const orbital of schema.orbitals) {
|
|
1663
|
-
const result = await resolver.resolve(orbital);
|
|
1664
|
-
if (!result.success) {
|
|
1665
|
-
errors.push(`Orbital "${orbital.name}": ${result.errors.join(", ")}`);
|
|
1666
|
-
} else {
|
|
1667
|
-
resolved.push(result.data);
|
|
1668
|
-
warnings.push(...result.warnings.map((w) => `Orbital "${orbital.name}": ${w}`));
|
|
1669
|
-
}
|
|
1670
|
-
}
|
|
1671
|
-
if (errors.length > 0) {
|
|
1672
|
-
return { success: false, errors };
|
|
1673
|
-
}
|
|
1674
|
-
return { success: true, data: resolved, warnings };
|
|
1675
|
-
}
|
|
1676
|
-
|
|
1677
|
-
// src/loader/schema-loader.ts
|
|
1678
|
-
function isElectron() {
|
|
1679
|
-
return typeof process !== "undefined" && !!process.versions?.electron;
|
|
1680
|
-
}
|
|
1681
|
-
function isBrowser() {
|
|
1682
|
-
return typeof window !== "undefined" && !isElectron();
|
|
1683
|
-
}
|
|
1684
|
-
function isNode() {
|
|
1685
|
-
return typeof process !== "undefined" && !isBrowser();
|
|
1686
|
-
}
|
|
1687
|
-
|
|
1688
|
-
// src/loader/index.ts
|
|
1689
|
-
init_external_loader();
|
|
1690
|
-
var HttpImportChain = class _HttpImportChain {
|
|
1691
|
-
chain = [];
|
|
1692
|
-
/**
|
|
1693
|
-
* Try to add a path to the chain.
|
|
1694
|
-
* @returns Error message if circular, null if OK
|
|
1695
|
-
*/
|
|
1696
|
-
push(absolutePath) {
|
|
1697
|
-
if (this.chain.includes(absolutePath)) {
|
|
1698
|
-
const cycle = [
|
|
1699
|
-
...this.chain.slice(this.chain.indexOf(absolutePath)),
|
|
1700
|
-
absolutePath
|
|
1701
|
-
];
|
|
1702
|
-
return `Circular import detected: ${cycle.join(" -> ")}`;
|
|
1703
|
-
}
|
|
1704
|
-
this.chain.push(absolutePath);
|
|
1705
|
-
return null;
|
|
1706
|
-
}
|
|
1707
|
-
/**
|
|
1708
|
-
* Remove the last path from the chain.
|
|
1709
|
-
*/
|
|
1710
|
-
pop() {
|
|
1711
|
-
this.chain.pop();
|
|
1712
|
-
}
|
|
1713
|
-
/**
|
|
1714
|
-
* Clone the chain for nested loading.
|
|
1715
|
-
*/
|
|
1716
|
-
clone() {
|
|
1717
|
-
const newChain = new _HttpImportChain();
|
|
1718
|
-
newChain.chain = [...this.chain];
|
|
1719
|
-
return newChain;
|
|
1720
|
-
}
|
|
1721
|
-
};
|
|
1722
|
-
var HttpLoaderCache = class {
|
|
1723
|
-
cache = /* @__PURE__ */ new Map();
|
|
1724
|
-
get(url) {
|
|
1725
|
-
return this.cache.get(url);
|
|
1726
|
-
}
|
|
1727
|
-
set(url, schema) {
|
|
1728
|
-
this.cache.set(url, schema);
|
|
1729
|
-
}
|
|
1730
|
-
has(url) {
|
|
1731
|
-
return this.cache.has(url);
|
|
1732
|
-
}
|
|
1733
|
-
clear() {
|
|
1734
|
-
this.cache.clear();
|
|
1735
|
-
}
|
|
1736
|
-
get size() {
|
|
1737
|
-
return this.cache.size;
|
|
1738
|
-
}
|
|
1739
|
-
};
|
|
1740
|
-
var HttpLoader = class {
|
|
1741
|
-
options;
|
|
1742
|
-
cache;
|
|
1743
|
-
constructor(options) {
|
|
1744
|
-
this.options = {
|
|
1745
|
-
basePath: options.basePath,
|
|
1746
|
-
stdLibPath: options.stdLibPath ?? "",
|
|
1747
|
-
scopedPaths: options.scopedPaths ?? {},
|
|
1748
|
-
fetchOptions: options.fetchOptions,
|
|
1749
|
-
timeout: options.timeout ?? 3e4,
|
|
1750
|
-
credentials: options.credentials ?? "same-origin"
|
|
6
|
+
var MockPersistenceAdapter = class {
|
|
7
|
+
stores = /* @__PURE__ */ new Map();
|
|
8
|
+
schemas = /* @__PURE__ */ new Map();
|
|
9
|
+
idCounters = /* @__PURE__ */ new Map();
|
|
10
|
+
config;
|
|
11
|
+
constructor(config = {}) {
|
|
12
|
+
this.config = {
|
|
13
|
+
defaultSeedCount: 6,
|
|
14
|
+
debug: false,
|
|
15
|
+
...config
|
|
1751
16
|
};
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
*/
|
|
1757
|
-
async load(importPath, fromPath, chain) {
|
|
1758
|
-
const importChain = chain ?? new HttpImportChain();
|
|
1759
|
-
const resolveResult = this.resolvePath(importPath, fromPath);
|
|
1760
|
-
if (!resolveResult.success) {
|
|
1761
|
-
return resolveResult;
|
|
1762
|
-
}
|
|
1763
|
-
const absoluteUrl = resolveResult.data;
|
|
1764
|
-
const circularError = importChain.push(absoluteUrl);
|
|
1765
|
-
if (circularError) {
|
|
1766
|
-
return { success: false, error: circularError };
|
|
1767
|
-
}
|
|
1768
|
-
try {
|
|
1769
|
-
const cached = this.cache.get(absoluteUrl);
|
|
1770
|
-
if (cached) {
|
|
1771
|
-
return { success: true, data: cached };
|
|
1772
|
-
}
|
|
1773
|
-
const loadResult = await this.fetchSchema(absoluteUrl);
|
|
1774
|
-
if (!loadResult.success) {
|
|
1775
|
-
return loadResult;
|
|
1776
|
-
}
|
|
1777
|
-
const loaded = {
|
|
1778
|
-
schema: loadResult.data,
|
|
1779
|
-
sourcePath: absoluteUrl,
|
|
1780
|
-
importPath
|
|
1781
|
-
};
|
|
1782
|
-
this.cache.set(absoluteUrl, loaded);
|
|
1783
|
-
return { success: true, data: loaded };
|
|
1784
|
-
} finally {
|
|
1785
|
-
importChain.pop();
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
1788
|
-
/**
|
|
1789
|
-
* Load a specific orbital from a schema by name.
|
|
1790
|
-
*/
|
|
1791
|
-
async loadOrbital(importPath, orbitalName, fromPath, chain) {
|
|
1792
|
-
const schemaResult = await this.load(importPath, fromPath, chain);
|
|
1793
|
-
if (!schemaResult.success) {
|
|
1794
|
-
return schemaResult;
|
|
1795
|
-
}
|
|
1796
|
-
const schema = schemaResult.data.schema;
|
|
1797
|
-
let orbital;
|
|
1798
|
-
if (orbitalName) {
|
|
1799
|
-
const found = schema.orbitals.find(
|
|
1800
|
-
(o) => o.name === orbitalName
|
|
1801
|
-
);
|
|
1802
|
-
if (!found) {
|
|
1803
|
-
return {
|
|
1804
|
-
success: false,
|
|
1805
|
-
error: `Orbital "${orbitalName}" not found in ${importPath}. Available: ${schema.orbitals.map((o) => o.name).join(", ")}`
|
|
1806
|
-
};
|
|
1807
|
-
}
|
|
1808
|
-
orbital = found;
|
|
1809
|
-
} else {
|
|
1810
|
-
if (schema.orbitals.length === 0) {
|
|
1811
|
-
return {
|
|
1812
|
-
success: false,
|
|
1813
|
-
error: `No orbitals found in ${importPath}`
|
|
1814
|
-
};
|
|
1815
|
-
}
|
|
1816
|
-
orbital = schema.orbitals[0];
|
|
1817
|
-
}
|
|
1818
|
-
return {
|
|
1819
|
-
success: true,
|
|
1820
|
-
data: {
|
|
1821
|
-
orbital,
|
|
1822
|
-
sourcePath: schemaResult.data.sourcePath,
|
|
1823
|
-
importPath
|
|
17
|
+
if (config.seed !== void 0) {
|
|
18
|
+
faker.seed(config.seed);
|
|
19
|
+
if (this.config.debug) {
|
|
20
|
+
console.log(`[MockPersistence] Using seed: ${config.seed}`);
|
|
1824
21
|
}
|
|
1825
|
-
};
|
|
1826
|
-
}
|
|
1827
|
-
/**
|
|
1828
|
-
* Resolve an import path to an absolute URL.
|
|
1829
|
-
*/
|
|
1830
|
-
resolvePath(importPath, fromPath) {
|
|
1831
|
-
if (importPath.startsWith("http://") || importPath.startsWith("https://")) {
|
|
1832
|
-
return { success: true, data: importPath };
|
|
1833
|
-
}
|
|
1834
|
-
if (importPath.startsWith("std/")) {
|
|
1835
|
-
return this.resolveStdPath(importPath);
|
|
1836
|
-
}
|
|
1837
|
-
if (importPath.startsWith("@")) {
|
|
1838
|
-
return this.resolveScopedPath(importPath);
|
|
1839
|
-
}
|
|
1840
|
-
if (importPath.startsWith("./") || importPath.startsWith("../")) {
|
|
1841
|
-
return this.resolveRelativePath(importPath, fromPath);
|
|
1842
|
-
}
|
|
1843
|
-
return this.resolveRelativePath(`./${importPath}`, fromPath);
|
|
1844
|
-
}
|
|
1845
|
-
/**
|
|
1846
|
-
* Resolve a standard library path.
|
|
1847
|
-
*/
|
|
1848
|
-
resolveStdPath(importPath) {
|
|
1849
|
-
if (!this.options.stdLibPath) {
|
|
1850
|
-
return {
|
|
1851
|
-
success: false,
|
|
1852
|
-
error: `Standard library URL not configured. Cannot load: ${importPath}`
|
|
1853
|
-
};
|
|
1854
22
|
}
|
|
1855
|
-
const relativePath = importPath.slice(4);
|
|
1856
|
-
let absoluteUrl = this.joinUrl(this.options.stdLibPath, relativePath);
|
|
1857
|
-
if (!absoluteUrl.endsWith(".orb")) {
|
|
1858
|
-
absoluteUrl += ".orb";
|
|
1859
|
-
}
|
|
1860
|
-
return { success: true, data: absoluteUrl };
|
|
1861
|
-
}
|
|
1862
|
-
/**
|
|
1863
|
-
* Resolve a scoped package path.
|
|
1864
|
-
*/
|
|
1865
|
-
resolveScopedPath(importPath) {
|
|
1866
|
-
const match = importPath.match(/^(@[^/]+)/);
|
|
1867
|
-
if (!match) {
|
|
1868
|
-
return {
|
|
1869
|
-
success: false,
|
|
1870
|
-
error: `Invalid scoped package path: ${importPath}`
|
|
1871
|
-
};
|
|
1872
|
-
}
|
|
1873
|
-
const scope = match[1];
|
|
1874
|
-
const scopeRoot = this.options.scopedPaths[scope];
|
|
1875
|
-
if (!scopeRoot) {
|
|
1876
|
-
return {
|
|
1877
|
-
success: false,
|
|
1878
|
-
error: `Scoped package "${scope}" not configured. Available: ${Object.keys(this.options.scopedPaths).join(", ") || "none"}`
|
|
1879
|
-
};
|
|
1880
|
-
}
|
|
1881
|
-
const relativePath = importPath.slice(scope.length + 1);
|
|
1882
|
-
let absoluteUrl = this.joinUrl(scopeRoot, relativePath);
|
|
1883
|
-
if (!absoluteUrl.endsWith(".orb")) {
|
|
1884
|
-
absoluteUrl += ".orb";
|
|
1885
|
-
}
|
|
1886
|
-
return { success: true, data: absoluteUrl };
|
|
1887
23
|
}
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
const
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// Store Management
|
|
26
|
+
// ============================================================================
|
|
27
|
+
getStore(entityName) {
|
|
28
|
+
const normalized = entityName.toLowerCase();
|
|
29
|
+
if (!this.stores.has(normalized)) {
|
|
30
|
+
this.stores.set(normalized, /* @__PURE__ */ new Map());
|
|
31
|
+
this.idCounters.set(normalized, 0);
|
|
1896
32
|
}
|
|
1897
|
-
return
|
|
33
|
+
return this.stores.get(normalized);
|
|
1898
34
|
}
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
const controller = new AbortController();
|
|
1905
|
-
const timeoutId = setTimeout(
|
|
1906
|
-
() => controller.abort(),
|
|
1907
|
-
this.options.timeout
|
|
1908
|
-
);
|
|
1909
|
-
try {
|
|
1910
|
-
const response = await fetch(url, {
|
|
1911
|
-
...this.options.fetchOptions,
|
|
1912
|
-
credentials: this.options.credentials,
|
|
1913
|
-
signal: controller.signal,
|
|
1914
|
-
headers: {
|
|
1915
|
-
Accept: "application/json",
|
|
1916
|
-
...this.options.fetchOptions?.headers
|
|
1917
|
-
}
|
|
1918
|
-
});
|
|
1919
|
-
if (!response.ok) {
|
|
1920
|
-
return {
|
|
1921
|
-
success: false,
|
|
1922
|
-
error: `HTTP ${response.status}: ${response.statusText} for ${url}`
|
|
1923
|
-
};
|
|
1924
|
-
}
|
|
1925
|
-
const text = await response.text();
|
|
1926
|
-
let data;
|
|
1927
|
-
try {
|
|
1928
|
-
data = JSON.parse(text);
|
|
1929
|
-
} catch (e) {
|
|
1930
|
-
return {
|
|
1931
|
-
success: false,
|
|
1932
|
-
error: `Invalid JSON in ${url}: ${e instanceof Error ? e.message : String(e)}`
|
|
1933
|
-
};
|
|
1934
|
-
}
|
|
1935
|
-
const parseResult = OrbitalSchemaSchema.safeParse(data);
|
|
1936
|
-
if (!parseResult.success) {
|
|
1937
|
-
const errors = parseResult.error.errors.map((e) => ` - ${e.path.join(".")}: ${e.message}`).join("\n");
|
|
1938
|
-
return {
|
|
1939
|
-
success: false,
|
|
1940
|
-
error: `Invalid schema in ${url}:
|
|
1941
|
-
${errors}`
|
|
1942
|
-
};
|
|
1943
|
-
}
|
|
1944
|
-
return { success: true, data: parseResult.data };
|
|
1945
|
-
} finally {
|
|
1946
|
-
clearTimeout(timeoutId);
|
|
1947
|
-
}
|
|
1948
|
-
} catch (e) {
|
|
1949
|
-
if (e instanceof Error && e.name === "AbortError") {
|
|
1950
|
-
return {
|
|
1951
|
-
success: false,
|
|
1952
|
-
error: `Request timeout for ${url} (${this.options.timeout}ms)`
|
|
1953
|
-
};
|
|
1954
|
-
}
|
|
1955
|
-
return {
|
|
1956
|
-
success: false,
|
|
1957
|
-
error: `Failed to fetch ${url}: ${e instanceof Error ? e.message : String(e)}`
|
|
1958
|
-
};
|
|
1959
|
-
}
|
|
35
|
+
nextId(entityName) {
|
|
36
|
+
const normalized = entityName.toLowerCase();
|
|
37
|
+
const counter = (this.idCounters.get(normalized) ?? 0) + 1;
|
|
38
|
+
this.idCounters.set(normalized, counter);
|
|
39
|
+
return `${this.capitalizeFirst(entityName)} Id ${counter}`;
|
|
1960
40
|
}
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// Schema & Seeding
|
|
43
|
+
// ============================================================================
|
|
1961
44
|
/**
|
|
1962
|
-
*
|
|
45
|
+
* Register an entity schema and seed mock data.
|
|
46
|
+
* If the schema has seedData, those instances are used directly.
|
|
47
|
+
* Otherwise, random mock data is generated with faker.
|
|
1963
48
|
*/
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
} catch {
|
|
1973
|
-
}
|
|
1974
|
-
}
|
|
1975
|
-
const normalizedBase = base.endsWith("/") ? base.slice(0, -1) : base;
|
|
1976
|
-
const normalizedPath = path2.startsWith("/") ? path2 : "/" + path2;
|
|
1977
|
-
if (normalizedPath.startsWith("/./")) {
|
|
1978
|
-
return normalizedBase + normalizedPath.slice(2);
|
|
1979
|
-
}
|
|
1980
|
-
if (normalizedPath.startsWith("/../")) {
|
|
1981
|
-
const baseParts = normalizedBase.split("/");
|
|
1982
|
-
baseParts.pop();
|
|
1983
|
-
return baseParts.join("/") + normalizedPath.slice(3);
|
|
49
|
+
registerEntity(schema, seedCount) {
|
|
50
|
+
const normalized = schema.name.toLowerCase();
|
|
51
|
+
this.schemas.set(normalized, schema);
|
|
52
|
+
if (schema.seedData && schema.seedData.length > 0) {
|
|
53
|
+
this.seedFromInstances(schema.name, schema.seedData);
|
|
54
|
+
} else {
|
|
55
|
+
const count = seedCount ?? this.config.defaultSeedCount ?? 6;
|
|
56
|
+
this.seed(schema.name, schema.fields, count);
|
|
1984
57
|
}
|
|
1985
|
-
return normalizedBase + normalizedPath;
|
|
1986
58
|
}
|
|
1987
59
|
/**
|
|
1988
|
-
*
|
|
60
|
+
* Seed an entity with pre-authored instance data.
|
|
1989
61
|
*/
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
const pathParts = urlObj.pathname.split("/");
|
|
1995
|
-
pathParts.pop();
|
|
1996
|
-
urlObj.pathname = pathParts.join("/");
|
|
1997
|
-
return urlObj.href;
|
|
1998
|
-
} catch {
|
|
1999
|
-
}
|
|
62
|
+
seedFromInstances(entityName, instances) {
|
|
63
|
+
const store = this.getStore(entityName);
|
|
64
|
+
if (this.config.debug) {
|
|
65
|
+
console.log(`[MockPersistence] Seeding ${instances.length} ${entityName} from schema instances...`);
|
|
2000
66
|
}
|
|
2001
|
-
const
|
|
2002
|
-
|
|
2003
|
-
|
|
67
|
+
for (const instance of instances) {
|
|
68
|
+
const id = instance.id || this.nextId(entityName);
|
|
69
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
70
|
+
const item = {
|
|
71
|
+
...instance,
|
|
72
|
+
id,
|
|
73
|
+
createdAt: instance.createdAt || now,
|
|
74
|
+
updatedAt: now
|
|
75
|
+
};
|
|
76
|
+
store.set(id, item);
|
|
2004
77
|
}
|
|
2005
|
-
return url;
|
|
2006
|
-
}
|
|
2007
|
-
/**
|
|
2008
|
-
* Clear the cache.
|
|
2009
|
-
*/
|
|
2010
|
-
clearCache() {
|
|
2011
|
-
this.cache.clear();
|
|
2012
78
|
}
|
|
2013
79
|
/**
|
|
2014
|
-
*
|
|
80
|
+
* Seed an entity with mock data.
|
|
2015
81
|
*/
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
// src/loader/unified-loader.ts
|
|
2022
|
-
var externalLoaderModule = null;
|
|
2023
|
-
async function getExternalLoaderModule() {
|
|
2024
|
-
if (externalLoaderModule) {
|
|
2025
|
-
return externalLoaderModule;
|
|
2026
|
-
}
|
|
2027
|
-
if (isBrowser()) {
|
|
2028
|
-
return null;
|
|
2029
|
-
}
|
|
2030
|
-
try {
|
|
2031
|
-
externalLoaderModule = await Promise.resolve().then(() => (init_external_loader(), external_loader_exports));
|
|
2032
|
-
return externalLoaderModule;
|
|
2033
|
-
} catch {
|
|
2034
|
-
return null;
|
|
2035
|
-
}
|
|
2036
|
-
}
|
|
2037
|
-
var UnifiedImportChain = class _UnifiedImportChain {
|
|
2038
|
-
chain = [];
|
|
2039
|
-
push(path2) {
|
|
2040
|
-
const normalized = this.normalizePath(path2);
|
|
2041
|
-
if (this.chain.includes(normalized)) {
|
|
2042
|
-
const cycle = [
|
|
2043
|
-
...this.chain.slice(this.chain.indexOf(normalized)),
|
|
2044
|
-
normalized
|
|
2045
|
-
];
|
|
2046
|
-
return `Circular import detected: ${cycle.join(" -> ")}`;
|
|
82
|
+
seed(entityName, fields, count) {
|
|
83
|
+
const store = this.getStore(entityName);
|
|
84
|
+
const normalized = entityName.toLowerCase();
|
|
85
|
+
if (this.config.debug) {
|
|
86
|
+
console.log(`[MockPersistence] Seeding ${count} ${entityName}...`);
|
|
2047
87
|
}
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
pop() {
|
|
2052
|
-
this.chain.pop();
|
|
2053
|
-
}
|
|
2054
|
-
clone() {
|
|
2055
|
-
const newChain = new _UnifiedImportChain();
|
|
2056
|
-
newChain.chain = [...this.chain];
|
|
2057
|
-
return newChain;
|
|
2058
|
-
}
|
|
2059
|
-
normalizePath(path2) {
|
|
2060
|
-
if (path2.startsWith("http://") || path2.startsWith("https://")) {
|
|
2061
|
-
return path2;
|
|
88
|
+
for (let i = 0; i < count; i++) {
|
|
89
|
+
const item = this.generateMockItem(normalized, entityName, fields, i + 1);
|
|
90
|
+
store.set(item.id, item);
|
|
2062
91
|
}
|
|
2063
|
-
return path2.replace(/\\/g, "/");
|
|
2064
|
-
}
|
|
2065
|
-
};
|
|
2066
|
-
var UnifiedLoader = class {
|
|
2067
|
-
options;
|
|
2068
|
-
httpLoader = null;
|
|
2069
|
-
fsLoader = null;
|
|
2070
|
-
fsLoaderInitialized = false;
|
|
2071
|
-
cache = /* @__PURE__ */ new Map();
|
|
2072
|
-
constructor(options) {
|
|
2073
|
-
this.options = options;
|
|
2074
|
-
this.httpLoader = new HttpLoader({
|
|
2075
|
-
basePath: options.basePath,
|
|
2076
|
-
stdLibPath: options.stdLibPath,
|
|
2077
|
-
scopedPaths: options.scopedPaths,
|
|
2078
|
-
...options.http
|
|
2079
|
-
});
|
|
2080
92
|
}
|
|
2081
93
|
/**
|
|
2082
|
-
*
|
|
94
|
+
* Generate a single mock item based on field schemas.
|
|
2083
95
|
*/
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
}
|
|
2092
|
-
const
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
scopedPaths: this.options.scopedPaths,
|
|
2098
|
-
...this.options.fileSystem
|
|
2099
|
-
});
|
|
96
|
+
generateMockItem(normalizedName, entityName, fields, index) {
|
|
97
|
+
const id = this.nextId(entityName);
|
|
98
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
99
|
+
const item = {
|
|
100
|
+
id,
|
|
101
|
+
createdAt: faker.date.past({ years: 1 }).toISOString(),
|
|
102
|
+
updatedAt: now
|
|
103
|
+
};
|
|
104
|
+
for (const field of fields) {
|
|
105
|
+
if (field.name === "id" || field.name === "createdAt" || field.name === "updatedAt") {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
item[field.name] = this.generateFieldValue(entityName, field, index);
|
|
2100
109
|
}
|
|
110
|
+
return item;
|
|
2101
111
|
}
|
|
2102
112
|
/**
|
|
2103
|
-
*
|
|
113
|
+
* Generate a mock value for a field based on its schema.
|
|
2104
114
|
*/
|
|
2105
|
-
|
|
2106
|
-
if (
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
if (importPath.startsWith("http://") || importPath.startsWith("https://")) {
|
|
2110
|
-
return "http";
|
|
2111
|
-
}
|
|
2112
|
-
if (importPath.startsWith("std/") && this.options.stdLibPath) {
|
|
2113
|
-
if (this.options.stdLibPath.startsWith("http://") || this.options.stdLibPath.startsWith("https://")) {
|
|
2114
|
-
return "http";
|
|
115
|
+
generateFieldValue(entityName, field, index) {
|
|
116
|
+
if (field.default !== void 0) {
|
|
117
|
+
if (field.default === "@now") {
|
|
118
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
2115
119
|
}
|
|
120
|
+
return field.default;
|
|
2116
121
|
}
|
|
2117
|
-
if (
|
|
2118
|
-
|
|
2119
|
-
if (match) {
|
|
2120
|
-
const scopePath = this.options.scopedPaths[match[1]];
|
|
2121
|
-
if (scopePath && (scopePath.startsWith("http://") || scopePath.startsWith("https://"))) {
|
|
2122
|
-
return "http";
|
|
2123
|
-
}
|
|
2124
|
-
}
|
|
122
|
+
if (!field.required && Math.random() > 0.8) {
|
|
123
|
+
return null;
|
|
2125
124
|
}
|
|
2126
|
-
|
|
2127
|
-
|
|
125
|
+
const fieldType = field.type.toLowerCase();
|
|
126
|
+
switch (fieldType) {
|
|
127
|
+
case "string":
|
|
128
|
+
return this.generateStringValue(entityName, field, index);
|
|
129
|
+
case "number":
|
|
130
|
+
return faker.number.int({ min: 0, max: 100 });
|
|
131
|
+
case "boolean":
|
|
132
|
+
return faker.datatype.boolean();
|
|
133
|
+
case "date":
|
|
134
|
+
case "timestamp":
|
|
135
|
+
case "datetime":
|
|
136
|
+
return this.generateDateValue(field);
|
|
137
|
+
case "enum":
|
|
138
|
+
if (field.values && field.values.length > 0) {
|
|
139
|
+
return faker.helpers.arrayElement(field.values);
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
case "relation":
|
|
143
|
+
return null;
|
|
144
|
+
// Relations need special handling
|
|
145
|
+
case "array":
|
|
146
|
+
case "object":
|
|
147
|
+
return field.type === "array" ? [] : {};
|
|
148
|
+
default:
|
|
149
|
+
return this.generateStringValue(entityName, field, index);
|
|
2128
150
|
}
|
|
2129
|
-
return "filesystem";
|
|
2130
151
|
}
|
|
2131
152
|
/**
|
|
2132
|
-
*
|
|
2133
|
-
*
|
|
2134
|
-
* Note: We delegate chain management to the inner loader (HttpLoader or FsLoader).
|
|
2135
|
-
* The inner loader handles circular import detection, so we don't push/pop here.
|
|
2136
|
-
* We only use the unified cache to avoid duplicate loads across loaders.
|
|
153
|
+
* Generate a string value based on field name heuristics.
|
|
2137
154
|
*/
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
const resolveResult = this.resolvePath(importPath, fromPath);
|
|
2143
|
-
if (!resolveResult.success) {
|
|
2144
|
-
return resolveResult;
|
|
2145
|
-
}
|
|
2146
|
-
const absolutePath = resolveResult.data;
|
|
2147
|
-
const cached = this.cache.get(absolutePath);
|
|
2148
|
-
if (cached) {
|
|
2149
|
-
return { success: true, data: cached };
|
|
2150
|
-
}
|
|
2151
|
-
let result;
|
|
2152
|
-
if (loaderType === "http") {
|
|
2153
|
-
if (!this.httpLoader) {
|
|
2154
|
-
return {
|
|
2155
|
-
success: false,
|
|
2156
|
-
error: "HTTP loader not available"
|
|
2157
|
-
};
|
|
2158
|
-
}
|
|
2159
|
-
result = await this.httpLoader.load(importPath, fromPath, importChain);
|
|
2160
|
-
} else {
|
|
2161
|
-
if (!this.fsLoader) {
|
|
2162
|
-
if (this.httpLoader) {
|
|
2163
|
-
result = await this.httpLoader.load(
|
|
2164
|
-
importPath,
|
|
2165
|
-
fromPath,
|
|
2166
|
-
importChain
|
|
2167
|
-
);
|
|
2168
|
-
} else {
|
|
2169
|
-
return {
|
|
2170
|
-
success: false,
|
|
2171
|
-
error: `Filesystem loader not available and import "${importPath}" cannot be loaded via HTTP. This typically happens when loading local files in a browser environment.`
|
|
2172
|
-
};
|
|
2173
|
-
}
|
|
2174
|
-
} else {
|
|
2175
|
-
result = await this.fsLoader.load(importPath, fromPath, importChain);
|
|
2176
|
-
}
|
|
2177
|
-
}
|
|
2178
|
-
if (result.success) {
|
|
2179
|
-
this.cache.set(absolutePath, result.data);
|
|
155
|
+
generateStringValue(entityName, field, index) {
|
|
156
|
+
const name = field.name.toLowerCase();
|
|
157
|
+
if (field.values && field.values.length > 0) {
|
|
158
|
+
return faker.helpers.arrayElement(field.values);
|
|
2180
159
|
}
|
|
2181
|
-
return
|
|
160
|
+
if (name.includes("email")) return faker.internet.email();
|
|
161
|
+
if (name.includes("phone")) return faker.phone.number();
|
|
162
|
+
if (name.includes("address")) return faker.location.streetAddress();
|
|
163
|
+
if (name.includes("city")) return faker.location.city();
|
|
164
|
+
if (name.includes("country")) return faker.location.country();
|
|
165
|
+
if (name.includes("url") || name.includes("website")) return faker.internet.url();
|
|
166
|
+
if (name.includes("avatar") || name.includes("image")) return faker.image.avatar();
|
|
167
|
+
if (name.includes("color")) return faker.color.human();
|
|
168
|
+
if (name.includes("uuid")) return faker.string.uuid();
|
|
169
|
+
if (name.includes("description") || name.includes("bio")) return faker.lorem.paragraph();
|
|
170
|
+
const entityLabel = this.capitalizeFirst(entityName);
|
|
171
|
+
const fieldLabel = this.capitalizeFirst(field.name);
|
|
172
|
+
return `${entityLabel} ${fieldLabel} ${index}`;
|
|
2182
173
|
}
|
|
2183
174
|
/**
|
|
2184
|
-
*
|
|
175
|
+
* Generate a date value based on field name heuristics.
|
|
2185
176
|
*/
|
|
2186
|
-
|
|
2187
|
-
const
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
error: `Orbital "${orbitalName}" not found in ${importPath}. Available: ${schema.orbitals.map((o) => o.name).join(", ")}`
|
|
2198
|
-
};
|
|
2199
|
-
}
|
|
2200
|
-
return {
|
|
2201
|
-
success: true,
|
|
2202
|
-
data: {
|
|
2203
|
-
orbital: found,
|
|
2204
|
-
sourcePath: schemaResult.data.sourcePath,
|
|
2205
|
-
importPath
|
|
2206
|
-
}
|
|
2207
|
-
};
|
|
2208
|
-
}
|
|
2209
|
-
if (schema.orbitals.length === 0) {
|
|
2210
|
-
return {
|
|
2211
|
-
success: false,
|
|
2212
|
-
error: `No orbitals found in ${importPath}`
|
|
2213
|
-
};
|
|
177
|
+
generateDateValue(field) {
|
|
178
|
+
const name = field.name.toLowerCase();
|
|
179
|
+
let date;
|
|
180
|
+
if (name.includes("created") || name.includes("start") || name.includes("birth")) {
|
|
181
|
+
date = faker.date.past({ years: 2 });
|
|
182
|
+
} else if (name.includes("updated") || name.includes("modified")) {
|
|
183
|
+
date = faker.date.recent({ days: 30 });
|
|
184
|
+
} else if (name.includes("deadline") || name.includes("due") || name.includes("end") || name.includes("expires")) {
|
|
185
|
+
date = faker.date.future({ years: 1 });
|
|
186
|
+
} else {
|
|
187
|
+
date = faker.date.anytime();
|
|
2214
188
|
}
|
|
2215
|
-
return
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
189
|
+
return date.toISOString();
|
|
190
|
+
}
|
|
191
|
+
capitalizeFirst(str) {
|
|
192
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
193
|
+
}
|
|
194
|
+
// ============================================================================
|
|
195
|
+
// PersistenceAdapter Implementation
|
|
196
|
+
// ============================================================================
|
|
197
|
+
async create(entityType, data) {
|
|
198
|
+
const store = this.getStore(entityType);
|
|
199
|
+
const id = this.nextId(entityType);
|
|
200
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
201
|
+
const item = {
|
|
202
|
+
...data,
|
|
203
|
+
id,
|
|
204
|
+
createdAt: now,
|
|
205
|
+
updatedAt: now
|
|
2222
206
|
};
|
|
207
|
+
store.set(id, item);
|
|
208
|
+
return { id };
|
|
2223
209
|
}
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
if (loaderType === "http" && this.httpLoader) {
|
|
2230
|
-
return this.httpLoader.resolvePath(importPath, fromPath);
|
|
2231
|
-
}
|
|
2232
|
-
if (this.fsLoader) {
|
|
2233
|
-
return this.fsLoader.resolvePath(importPath, fromPath);
|
|
2234
|
-
}
|
|
2235
|
-
if (this.httpLoader) {
|
|
2236
|
-
return this.httpLoader.resolvePath(importPath, fromPath);
|
|
210
|
+
async update(entityType, id, data) {
|
|
211
|
+
const store = this.getStore(entityType);
|
|
212
|
+
const existing = store.get(id);
|
|
213
|
+
if (!existing) {
|
|
214
|
+
throw new Error(`Entity ${entityType} with id ${id} not found`);
|
|
2237
215
|
}
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
216
|
+
const updated = {
|
|
217
|
+
...existing,
|
|
218
|
+
...data,
|
|
219
|
+
id,
|
|
220
|
+
// Preserve original ID
|
|
221
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2241
222
|
};
|
|
223
|
+
store.set(id, updated);
|
|
2242
224
|
}
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
225
|
+
async delete(entityType, id) {
|
|
226
|
+
const store = this.getStore(entityType);
|
|
227
|
+
if (!store.has(id)) {
|
|
228
|
+
throw new Error(`Entity ${entityType} with id ${id} not found`);
|
|
229
|
+
}
|
|
230
|
+
store.delete(id);
|
|
231
|
+
}
|
|
232
|
+
async getById(entityType, id) {
|
|
233
|
+
const store = this.getStore(entityType);
|
|
234
|
+
return store.get(id) ?? null;
|
|
235
|
+
}
|
|
236
|
+
async list(entityType) {
|
|
237
|
+
const store = this.getStore(entityType);
|
|
238
|
+
return Array.from(store.values());
|
|
2250
239
|
}
|
|
240
|
+
// ============================================================================
|
|
241
|
+
// Utilities
|
|
242
|
+
// ============================================================================
|
|
2251
243
|
/**
|
|
2252
|
-
*
|
|
244
|
+
* Clear all data for an entity.
|
|
2253
245
|
*/
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
}
|
|
2259
|
-
if (this.fsLoader) {
|
|
2260
|
-
size += this.fsLoader.getCacheStats().size;
|
|
2261
|
-
}
|
|
2262
|
-
return { size };
|
|
246
|
+
clear(entityName) {
|
|
247
|
+
const normalized = entityName.toLowerCase();
|
|
248
|
+
this.stores.delete(normalized);
|
|
249
|
+
this.idCounters.delete(normalized);
|
|
2263
250
|
}
|
|
2264
251
|
/**
|
|
2265
|
-
*
|
|
252
|
+
* Clear all data.
|
|
2266
253
|
*/
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
254
|
+
clearAll() {
|
|
255
|
+
this.stores.clear();
|
|
256
|
+
this.idCounters.clear();
|
|
2270
257
|
}
|
|
2271
258
|
/**
|
|
2272
|
-
* Get
|
|
259
|
+
* Get count of items for an entity.
|
|
2273
260
|
*/
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
isBrowser: isBrowser(),
|
|
2278
|
-
isNode: isNode(),
|
|
2279
|
-
hasFilesystem: this.fsLoader !== null
|
|
2280
|
-
};
|
|
261
|
+
count(entityName) {
|
|
262
|
+
const store = this.getStore(entityName);
|
|
263
|
+
return store.size;
|
|
2281
264
|
}
|
|
2282
265
|
};
|
|
2283
|
-
function createUnifiedLoader(options) {
|
|
2284
|
-
return new UnifiedLoader(options);
|
|
2285
|
-
}
|
|
2286
|
-
|
|
2287
|
-
// src/UsesIntegration.ts
|
|
2288
|
-
async function preprocessSchema(schema, options) {
|
|
2289
|
-
const namespaceEvents = options.namespaceEvents ?? true;
|
|
2290
|
-
const resolveResult = await resolveSchema(schema, options);
|
|
2291
|
-
if (!resolveResult.success) {
|
|
2292
|
-
return { success: false, errors: resolveResult.errors };
|
|
2293
|
-
}
|
|
2294
|
-
const resolved = resolveResult.data;
|
|
2295
|
-
const warnings = resolveResult.warnings;
|
|
2296
|
-
const preprocessedOrbitals = [];
|
|
2297
|
-
const entitySharing = {};
|
|
2298
|
-
const eventNamespaces = {};
|
|
2299
|
-
for (const resolvedOrbital of resolved) {
|
|
2300
|
-
const orbitalName = resolvedOrbital.name;
|
|
2301
|
-
const persistence = resolvedOrbital.entitySource?.persistence ?? resolvedOrbital.entity.persistence ?? "persistent";
|
|
2302
|
-
entitySharing[orbitalName] = {
|
|
2303
|
-
entityName: resolvedOrbital.entity.name,
|
|
2304
|
-
persistence,
|
|
2305
|
-
isShared: persistence !== "runtime",
|
|
2306
|
-
sourceAlias: resolvedOrbital.entitySource?.alias,
|
|
2307
|
-
collectionName: resolvedOrbital.entity.collection
|
|
2308
|
-
};
|
|
2309
|
-
eventNamespaces[orbitalName] = {};
|
|
2310
|
-
for (const resolvedTrait of resolvedOrbital.traits) {
|
|
2311
|
-
const traitName = resolvedTrait.trait.name;
|
|
2312
|
-
const namespace = {
|
|
2313
|
-
emits: {},
|
|
2314
|
-
listens: {}
|
|
2315
|
-
};
|
|
2316
|
-
if (namespaceEvents && resolvedTrait.source.type === "imported") {
|
|
2317
|
-
const emits = resolvedTrait.trait.emits ?? [];
|
|
2318
|
-
for (const emit of emits) {
|
|
2319
|
-
const eventName = typeof emit === "string" ? emit : emit.event;
|
|
2320
|
-
namespace.emits[eventName] = `${orbitalName}.${traitName}.${eventName}`;
|
|
2321
|
-
}
|
|
2322
|
-
const listens = resolvedTrait.trait.listens ?? [];
|
|
2323
|
-
for (const listen of listens) {
|
|
2324
|
-
namespace.listens[listen.event] = listen.event;
|
|
2325
|
-
}
|
|
2326
|
-
}
|
|
2327
|
-
eventNamespaces[orbitalName][traitName] = namespace;
|
|
2328
|
-
}
|
|
2329
|
-
const preprocessedOrbital = {
|
|
2330
|
-
name: orbitalName,
|
|
2331
|
-
description: resolvedOrbital.original.description,
|
|
2332
|
-
visual_prompt: resolvedOrbital.original.visual_prompt,
|
|
2333
|
-
// Resolved entity (always inline now)
|
|
2334
|
-
entity: resolvedOrbital.entity,
|
|
2335
|
-
// Resolved traits (inline definitions)
|
|
2336
|
-
traits: resolvedOrbital.traits.map((rt) => {
|
|
2337
|
-
if (rt.config || rt.linkedEntity) {
|
|
2338
|
-
return {
|
|
2339
|
-
ref: rt.trait.name,
|
|
2340
|
-
config: rt.config,
|
|
2341
|
-
linkedEntity: rt.linkedEntity,
|
|
2342
|
-
// Include the resolved trait definition for runtime
|
|
2343
|
-
_resolved: rt.trait
|
|
2344
|
-
};
|
|
2345
|
-
}
|
|
2346
|
-
return rt.trait;
|
|
2347
|
-
}),
|
|
2348
|
-
// Resolved pages (inline definitions with path overrides applied)
|
|
2349
|
-
pages: resolvedOrbital.pages.map((rp) => rp.page),
|
|
2350
|
-
// Preserve other fields
|
|
2351
|
-
exposes: resolvedOrbital.original.exposes,
|
|
2352
|
-
domainContext: resolvedOrbital.original.domainContext,
|
|
2353
|
-
design: resolvedOrbital.original.design
|
|
2354
|
-
};
|
|
2355
|
-
preprocessedOrbitals.push(preprocessedOrbital);
|
|
2356
|
-
}
|
|
2357
|
-
const preprocessedSchema = {
|
|
2358
|
-
...schema,
|
|
2359
|
-
orbitals: preprocessedOrbitals
|
|
2360
|
-
};
|
|
2361
|
-
return {
|
|
2362
|
-
success: true,
|
|
2363
|
-
data: {
|
|
2364
|
-
schema: preprocessedSchema,
|
|
2365
|
-
entitySharing,
|
|
2366
|
-
eventNamespaces,
|
|
2367
|
-
warnings
|
|
2368
|
-
}
|
|
2369
|
-
};
|
|
2370
|
-
}
|
|
2371
266
|
|
|
2372
267
|
// src/OrbitalServerRuntime.ts
|
|
2373
268
|
var InMemoryPersistence = class {
|
|
@@ -3091,8 +986,8 @@ var OrbitalServerRuntime = class {
|
|
|
3091
986
|
renderUI: (slot, pattern, props, priority) => {
|
|
3092
987
|
clientEffects.push(["render-ui", slot, pattern, props, priority]);
|
|
3093
988
|
},
|
|
3094
|
-
navigate: (
|
|
3095
|
-
clientEffects.push(["navigate",
|
|
989
|
+
navigate: (path, params) => {
|
|
990
|
+
clientEffects.push(["navigate", path, params]);
|
|
3096
991
|
},
|
|
3097
992
|
notify: (message, type) => {
|
|
3098
993
|
if (this.config.debug) {
|