@almadar/runtime 6.9.3 → 6.9.4
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-CvWqVb68.d.ts → OrbitalServerRuntime-BNRZpSZm.d.ts} +53 -45
- package/dist/OrbitalServerRuntime.d.ts +2 -2
- package/dist/OrbitalServerRuntime.js +1852 -1
- package/dist/OrbitalServerRuntime.js.map +1 -1
- package/dist/{chunk-VUXJJPIQ.js → chunk-R6Y4IJ7I.js} +1219 -3063
- package/dist/chunk-R6Y4IJ7I.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-VUXJJPIQ.js.map +0 -1
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { createLogger } from '@almadar/logger';
|
|
2
2
|
import { resolveBinding, evaluate, createMinimalContext, evaluateGuard } from '@almadar/evaluator';
|
|
3
3
|
export { createMinimalContext } from '@almadar/evaluator';
|
|
4
|
-
import {
|
|
5
|
-
import * as nodeModule from 'module';
|
|
6
|
-
import { isInlineTrait, isEntityCall, OrbitalSchemaSchema, isEntityReference, parseEntityRef, parseImportedTraitRef, isPageReference, isPageReferenceString, isPageReferenceObject, parsePageRef } from '@almadar/core';
|
|
4
|
+
import { isKnownStdOperator } from '@almadar/std/registry';
|
|
7
5
|
import { faker } from '@faker-js/faker';
|
|
6
|
+
import { OrbitalSchemaSchema, isInlineTrait, isEntityCall, isEntityReference, parseEntityRef, parseImportedTraitRef, isPageReference, isPageReferenceString, isPageReferenceObject, parsePageRef } from '@almadar/core';
|
|
8
7
|
|
|
9
8
|
// src/EventBus.ts
|
|
10
9
|
var log = createLogger("almadar:runtime:eventbus");
|
|
@@ -261,7 +260,7 @@ function isSExpression(value) {
|
|
|
261
260
|
if (value.length === 0) return false;
|
|
262
261
|
const first = value[0];
|
|
263
262
|
if (typeof first !== "string") return false;
|
|
264
|
-
if (
|
|
263
|
+
if (isKnownStdOperator(first)) return true;
|
|
265
264
|
if (first.includes("/")) return true;
|
|
266
265
|
if (first === "lambda" || first === "let") return true;
|
|
267
266
|
return false;
|
|
@@ -1696,6 +1695,25 @@ function buildEmitsFromTraits(traits, explicitEmits) {
|
|
|
1696
1695
|
}
|
|
1697
1696
|
return result;
|
|
1698
1697
|
}
|
|
1698
|
+
|
|
1699
|
+
// src/config-defaults.ts
|
|
1700
|
+
function collectDeclaredConfigDefaults(trait) {
|
|
1701
|
+
if (!trait) return void 0;
|
|
1702
|
+
const schema = trait.config;
|
|
1703
|
+
if (!schema || typeof schema !== "object") return void 0;
|
|
1704
|
+
const defaults = {};
|
|
1705
|
+
let hasAny = false;
|
|
1706
|
+
for (const [key, field] of Object.entries(schema)) {
|
|
1707
|
+
if (field && typeof field === "object" && !Array.isArray(field) && "default" in field) {
|
|
1708
|
+
const def = field.default;
|
|
1709
|
+
if (def !== void 0) {
|
|
1710
|
+
defaults[key] = def;
|
|
1711
|
+
hasAny = true;
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
return hasAny ? defaults : void 0;
|
|
1716
|
+
}
|
|
1699
1717
|
var mockLog = createLogger("almadar:runtime:mock");
|
|
1700
1718
|
var DEFAULT_MOCK_SEED = 42;
|
|
1701
1719
|
function picsumUrl(entityName, fieldName, width = 400, height = 400) {
|
|
@@ -2116,820 +2134,509 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2116
2134
|
function createMockPersistence(config) {
|
|
2117
2135
|
return new MockPersistenceAdapter(config);
|
|
2118
2136
|
}
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
return node.map((item) => renameEventsInRenderUiConfig(item, rename));
|
|
2124
|
-
}
|
|
2125
|
-
if (typeof node !== "object") return node;
|
|
2126
|
-
const obj = node;
|
|
2127
|
-
const next = { ...obj };
|
|
2128
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
2129
|
-
if (key === "action" && typeof value === "string" && !value.startsWith("@")) {
|
|
2130
|
-
next[key] = rename(value) ?? value;
|
|
2131
|
-
continue;
|
|
2132
|
-
}
|
|
2133
|
-
if (/^on[A-Z]/.test(key) && typeof value === "string" && !value.startsWith("@")) {
|
|
2134
|
-
next[key] = rename(value) ?? value;
|
|
2135
|
-
continue;
|
|
2136
|
-
}
|
|
2137
|
-
if (key.endsWith("Event") && typeof value === "string" && !value.startsWith("@")) {
|
|
2138
|
-
next[key] = rename(value) ?? value;
|
|
2139
|
-
continue;
|
|
2140
|
-
}
|
|
2141
|
-
if ((key === "actions" || key === "itemActions") && Array.isArray(value)) {
|
|
2142
|
-
const rewrittenArray = value.map((entry) => {
|
|
2143
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return entry;
|
|
2144
|
-
const action = entry;
|
|
2145
|
-
if (typeof action.event === "string" && !action.event.startsWith("@")) {
|
|
2146
|
-
return { ...action, event: rename(action.event) ?? action.event };
|
|
2147
|
-
}
|
|
2148
|
-
return action;
|
|
2149
|
-
});
|
|
2150
|
-
next[key] = rewrittenArray;
|
|
2151
|
-
continue;
|
|
2152
|
-
}
|
|
2153
|
-
next[key] = renameEventsInRenderUiConfig(value, rename);
|
|
2154
|
-
}
|
|
2155
|
-
return next;
|
|
2137
|
+
|
|
2138
|
+
// src/loader/schema-loader.ts
|
|
2139
|
+
function isElectron() {
|
|
2140
|
+
return typeof process !== "undefined" && !!process.versions?.electron;
|
|
2156
2141
|
}
|
|
2157
|
-
function
|
|
2158
|
-
return
|
|
2159
|
-
if (!Array.isArray(effect)) return effect;
|
|
2160
|
-
if (effect[0] === "render-ui" && effect.length >= 3) {
|
|
2161
|
-
const slot = effect[1];
|
|
2162
|
-
const config = effect[2];
|
|
2163
|
-
const nextConfig = renameEventsInRenderUiConfig(config, rename);
|
|
2164
|
-
return [effect[0], slot, nextConfig, ...effect.slice(3)];
|
|
2165
|
-
}
|
|
2166
|
-
return effect;
|
|
2167
|
-
});
|
|
2142
|
+
function isBrowser() {
|
|
2143
|
+
return typeof window !== "undefined" && !isElectron();
|
|
2168
2144
|
}
|
|
2169
|
-
function
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2145
|
+
function isNode() {
|
|
2146
|
+
return typeof process !== "undefined" && !isBrowser();
|
|
2147
|
+
}
|
|
2148
|
+
var HttpImportChain = class _HttpImportChain {
|
|
2149
|
+
chain = [];
|
|
2150
|
+
/**
|
|
2151
|
+
* Try to add a path to the chain.
|
|
2152
|
+
* @returns Error message if circular, null if OK
|
|
2153
|
+
*/
|
|
2154
|
+
push(absolutePath) {
|
|
2155
|
+
if (this.chain.includes(absolutePath)) {
|
|
2156
|
+
const cycle = [
|
|
2157
|
+
...this.chain.slice(this.chain.indexOf(absolutePath)),
|
|
2158
|
+
absolutePath
|
|
2159
|
+
];
|
|
2160
|
+
return `Circular import detected: ${cycle.join(" -> ")}`;
|
|
2181
2161
|
}
|
|
2182
|
-
|
|
2162
|
+
this.chain.push(absolutePath);
|
|
2163
|
+
return null;
|
|
2183
2164
|
}
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
var ENTITY_AT_POS_1 = /* @__PURE__ */ new Set(["fetch", "ref", "deref", "spawn"]);
|
|
2190
|
-
var ALL_ARGS_ARE_EFFECTS = /* @__PURE__ */ new Set([
|
|
2191
|
-
"do",
|
|
2192
|
-
"atomic",
|
|
2193
|
-
"async/race",
|
|
2194
|
-
"async/all",
|
|
2195
|
-
"async/sequence"
|
|
2196
|
-
]);
|
|
2197
|
-
var ARGS_FROM_POS_2_ARE_EFFECTS = /* @__PURE__ */ new Set([
|
|
2198
|
-
"if",
|
|
2199
|
-
"when",
|
|
2200
|
-
"let",
|
|
2201
|
-
"async/delay",
|
|
2202
|
-
"async/debounce",
|
|
2203
|
-
"async/throttle",
|
|
2204
|
-
"async/interval"
|
|
2205
|
-
]);
|
|
2206
|
-
function renameEntityInEffect(effect, oldName, newName) {
|
|
2207
|
-
if (!Array.isArray(effect) || effect.length === 0) return effect;
|
|
2208
|
-
const op = effect[0];
|
|
2209
|
-
if (typeof op !== "string") return effect;
|
|
2210
|
-
if (op === "render-ui" && effect.length >= 3) {
|
|
2211
|
-
const [, slot, config, ...rest] = effect;
|
|
2212
|
-
const nextConfig = renameEntityInRenderUiConfig(config, oldName, newName);
|
|
2213
|
-
return [op, slot, nextConfig, ...rest];
|
|
2165
|
+
/**
|
|
2166
|
+
* Remove the last path from the chain.
|
|
2167
|
+
*/
|
|
2168
|
+
pop() {
|
|
2169
|
+
this.chain.pop();
|
|
2214
2170
|
}
|
|
2215
|
-
|
|
2216
|
-
|
|
2171
|
+
/**
|
|
2172
|
+
* Clone the chain for nested loading.
|
|
2173
|
+
*/
|
|
2174
|
+
clone() {
|
|
2175
|
+
const newChain = new _HttpImportChain();
|
|
2176
|
+
newChain.chain = [...this.chain];
|
|
2177
|
+
return newChain;
|
|
2217
2178
|
}
|
|
2218
|
-
|
|
2219
|
-
|
|
2179
|
+
};
|
|
2180
|
+
var HttpLoaderCache = class {
|
|
2181
|
+
cache = /* @__PURE__ */ new Map();
|
|
2182
|
+
get(url) {
|
|
2183
|
+
return this.cache.get(url);
|
|
2220
2184
|
}
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
if (recurseAll || skipFirstNonEffectArg) {
|
|
2224
|
-
const startIndex = skipFirstNonEffectArg ? 2 : 1;
|
|
2225
|
-
return effect.map((arg, i) => {
|
|
2226
|
-
if (i < startIndex) return arg;
|
|
2227
|
-
if (Array.isArray(arg)) {
|
|
2228
|
-
return renameEntityInEffect(arg, oldName, newName);
|
|
2229
|
-
}
|
|
2230
|
-
return arg;
|
|
2231
|
-
});
|
|
2185
|
+
set(url, schema) {
|
|
2186
|
+
this.cache.set(url, schema);
|
|
2232
2187
|
}
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
linkedEntity
|
|
2245
|
-
) : t.effects;
|
|
2246
|
-
return { ...t, effects: nextEffects };
|
|
2247
|
-
});
|
|
2248
|
-
refResolverLog.info("linkedEntity:rename", {
|
|
2249
|
-
trait: trait.name,
|
|
2250
|
-
from: atomLinked,
|
|
2251
|
-
to: linkedEntity,
|
|
2252
|
-
transitionCount: nextTransitions.length
|
|
2253
|
-
});
|
|
2254
|
-
return {
|
|
2255
|
-
...trait,
|
|
2256
|
-
linkedEntity,
|
|
2257
|
-
stateMachine: { ...sm, transitions: nextTransitions }
|
|
2258
|
-
};
|
|
2259
|
-
}
|
|
2260
|
-
function applyEventRenames(trait, renames) {
|
|
2261
|
-
if (!renames || Object.keys(renames).length === 0) return trait;
|
|
2262
|
-
const rename = (k) => k !== void 0 && k in renames ? renames[k] : k;
|
|
2263
|
-
const sm = trait.stateMachine;
|
|
2264
|
-
if (!sm) return trait;
|
|
2265
|
-
const nextTransitions = (sm.transitions ?? []).map((t) => {
|
|
2266
|
-
const nextEvent = rename(t.event) ?? t.event;
|
|
2267
|
-
const nextEffects = t.effects ? renameEventsInEffects(t.effects, rename) : t.effects;
|
|
2268
|
-
return { ...t, event: nextEvent, effects: nextEffects };
|
|
2269
|
-
});
|
|
2270
|
-
const nextEvents = (sm.events ?? []).map((e) => {
|
|
2271
|
-
const newKey = rename(e.key);
|
|
2272
|
-
if (newKey === e.key) return e;
|
|
2273
|
-
return { ...e, key: newKey ?? e.key };
|
|
2274
|
-
});
|
|
2275
|
-
const nextEmits = (trait.emits ?? []).map((em) => {
|
|
2276
|
-
if (typeof em === "string") return rename(em) ?? em;
|
|
2277
|
-
const newEvent = rename(em.event);
|
|
2278
|
-
return newEvent === em.event ? em : { ...em, event: newEvent ?? em.event };
|
|
2279
|
-
});
|
|
2280
|
-
return {
|
|
2281
|
-
...trait,
|
|
2282
|
-
stateMachine: {
|
|
2283
|
-
...sm,
|
|
2284
|
-
transitions: nextTransitions,
|
|
2285
|
-
events: nextEvents
|
|
2286
|
-
},
|
|
2287
|
-
emits: nextEmits
|
|
2288
|
-
};
|
|
2289
|
-
}
|
|
2290
|
-
var ReferenceResolver = class {
|
|
2291
|
-
loader;
|
|
2188
|
+
has(url) {
|
|
2189
|
+
return this.cache.has(url);
|
|
2190
|
+
}
|
|
2191
|
+
clear() {
|
|
2192
|
+
this.cache.clear();
|
|
2193
|
+
}
|
|
2194
|
+
get size() {
|
|
2195
|
+
return this.cache.size;
|
|
2196
|
+
}
|
|
2197
|
+
};
|
|
2198
|
+
var HttpLoader = class {
|
|
2292
2199
|
options;
|
|
2293
|
-
|
|
2294
|
-
loaderInitialized = false;
|
|
2200
|
+
cache;
|
|
2295
2201
|
constructor(options) {
|
|
2296
|
-
this.options =
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
this.loader = new ExternalOrbitalLoader(this.options);
|
|
2306
|
-
} catch {
|
|
2307
|
-
}
|
|
2202
|
+
this.options = {
|
|
2203
|
+
basePath: options.basePath,
|
|
2204
|
+
stdLibPath: options.stdLibPath ?? "",
|
|
2205
|
+
scopedPaths: options.scopedPaths ?? {},
|
|
2206
|
+
fetchOptions: options.fetchOptions,
|
|
2207
|
+
timeout: options.timeout ?? 3e4,
|
|
2208
|
+
credentials: options.credentials ?? "same-origin"
|
|
2209
|
+
};
|
|
2210
|
+
this.cache = new HttpLoaderCache();
|
|
2308
2211
|
}
|
|
2309
2212
|
/**
|
|
2310
|
-
*
|
|
2213
|
+
* Load a schema from an import path.
|
|
2311
2214
|
*/
|
|
2312
|
-
async
|
|
2313
|
-
const
|
|
2314
|
-
const
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
return this;
|
|
2318
|
-
} };
|
|
2319
|
-
const traitsList = orbital.traits ?? [];
|
|
2320
|
-
const alreadyResolved = traitsList.length > 0 && traitsList.every((t) => isInlineTrait(t));
|
|
2321
|
-
const importsResult = alreadyResolved ? { success: true, data: { orbitals: /* @__PURE__ */ new Map() }} : await this.resolveImports(orbital.uses ?? [], sourcePath, importChain);
|
|
2322
|
-
if (!importsResult.success) {
|
|
2323
|
-
return { success: false, errors: importsResult.errors };
|
|
2324
|
-
}
|
|
2325
|
-
const imports = importsResult.data;
|
|
2326
|
-
const entityResult = this.resolveEntity(orbital.entity, imports);
|
|
2327
|
-
if (!entityResult.success) {
|
|
2328
|
-
errors.push(...entityResult.errors);
|
|
2215
|
+
async load(importPath, fromPath, chain) {
|
|
2216
|
+
const importChain = chain ?? new HttpImportChain();
|
|
2217
|
+
const resolveResult = this.resolvePath(importPath, fromPath);
|
|
2218
|
+
if (!resolveResult.success) {
|
|
2219
|
+
return resolveResult;
|
|
2329
2220
|
}
|
|
2330
|
-
const
|
|
2331
|
-
|
|
2332
|
-
|
|
2221
|
+
const absoluteUrl = resolveResult.data;
|
|
2222
|
+
const circularError = importChain.push(absoluteUrl);
|
|
2223
|
+
if (circularError) {
|
|
2224
|
+
return { success: false, error: circularError };
|
|
2333
2225
|
}
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2226
|
+
try {
|
|
2227
|
+
const cached = this.cache.get(absoluteUrl);
|
|
2228
|
+
if (cached) {
|
|
2229
|
+
return { success: true, data: cached };
|
|
2230
|
+
}
|
|
2231
|
+
const loadResult = await this.fetchSchema(absoluteUrl);
|
|
2232
|
+
if (!loadResult.success) {
|
|
2233
|
+
return loadResult;
|
|
2234
|
+
}
|
|
2235
|
+
const loaded = {
|
|
2236
|
+
schema: loadResult.data,
|
|
2237
|
+
sourcePath: absoluteUrl,
|
|
2238
|
+
importPath
|
|
2239
|
+
};
|
|
2240
|
+
this.cache.set(absoluteUrl, loaded);
|
|
2241
|
+
return { success: true, data: loaded };
|
|
2242
|
+
} finally {
|
|
2243
|
+
importChain.pop();
|
|
2337
2244
|
}
|
|
2338
|
-
|
|
2339
|
-
|
|
2245
|
+
}
|
|
2246
|
+
/**
|
|
2247
|
+
* Load a specific orbital from a schema by name.
|
|
2248
|
+
*/
|
|
2249
|
+
async loadOrbital(importPath, orbitalName, fromPath, chain) {
|
|
2250
|
+
const schemaResult = await this.load(importPath, fromPath, chain);
|
|
2251
|
+
if (!schemaResult.success) {
|
|
2252
|
+
return schemaResult;
|
|
2340
2253
|
}
|
|
2341
|
-
|
|
2342
|
-
|
|
2254
|
+
const schema = schemaResult.data.schema;
|
|
2255
|
+
let orbital;
|
|
2256
|
+
if (orbitalName) {
|
|
2257
|
+
const found = schema.orbitals.find(
|
|
2258
|
+
(o) => o.name === orbitalName
|
|
2259
|
+
);
|
|
2260
|
+
if (!found) {
|
|
2261
|
+
return {
|
|
2262
|
+
success: false,
|
|
2263
|
+
error: `Orbital "${orbitalName}" not found in ${importPath}. Available: ${schema.orbitals.map((o) => o.name).join(", ")}`
|
|
2264
|
+
};
|
|
2265
|
+
}
|
|
2266
|
+
orbital = found;
|
|
2267
|
+
} else {
|
|
2268
|
+
if (schema.orbitals.length === 0) {
|
|
2269
|
+
return {
|
|
2270
|
+
success: false,
|
|
2271
|
+
error: `No orbitals found in ${importPath}`
|
|
2272
|
+
};
|
|
2273
|
+
}
|
|
2274
|
+
orbital = schema.orbitals[0];
|
|
2343
2275
|
}
|
|
2344
2276
|
return {
|
|
2345
2277
|
success: true,
|
|
2346
2278
|
data: {
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
pages: pagesResult.data,
|
|
2352
|
-
imports,
|
|
2353
|
-
original: orbital
|
|
2354
|
-
},
|
|
2355
|
-
warnings
|
|
2279
|
+
orbital,
|
|
2280
|
+
sourcePath: schemaResult.data.sourcePath,
|
|
2281
|
+
importPath
|
|
2282
|
+
}
|
|
2356
2283
|
};
|
|
2357
2284
|
}
|
|
2358
2285
|
/**
|
|
2359
|
-
* Resolve
|
|
2286
|
+
* Resolve an import path to an absolute URL.
|
|
2360
2287
|
*/
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
if (this.options.skipExternalLoading) {
|
|
2365
|
-
return {
|
|
2366
|
-
success: true,
|
|
2367
|
-
data: { orbitals },
|
|
2368
|
-
warnings: ["External loading skipped"]
|
|
2369
|
-
};
|
|
2288
|
+
resolvePath(importPath, fromPath) {
|
|
2289
|
+
if (importPath.startsWith("http://") || importPath.startsWith("https://")) {
|
|
2290
|
+
return { success: true, data: importPath };
|
|
2370
2291
|
}
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
errors.push(`Duplicate import alias: ${use.as}`);
|
|
2374
|
-
continue;
|
|
2375
|
-
}
|
|
2376
|
-
await this.ensureLoader();
|
|
2377
|
-
if (!this.loader) {
|
|
2378
|
-
errors.push(`No loader available to resolve import: ${use.from}`);
|
|
2379
|
-
continue;
|
|
2380
|
-
}
|
|
2381
|
-
const loadResult = await this.loader.loadOrbital(
|
|
2382
|
-
use.from,
|
|
2383
|
-
void 0,
|
|
2384
|
-
sourcePath,
|
|
2385
|
-
chain
|
|
2386
|
-
);
|
|
2387
|
-
if (!loadResult.success) {
|
|
2388
|
-
errors.push(`Failed to load "${use.from}" as "${use.as}": ${loadResult.error}`);
|
|
2389
|
-
continue;
|
|
2390
|
-
}
|
|
2391
|
-
orbitals.set(use.as, {
|
|
2392
|
-
alias: use.as,
|
|
2393
|
-
from: use.from,
|
|
2394
|
-
orbital: loadResult.data.orbital,
|
|
2395
|
-
sourcePath: loadResult.data.sourcePath
|
|
2396
|
-
});
|
|
2292
|
+
if (importPath.startsWith("std/")) {
|
|
2293
|
+
return this.resolveStdPath(importPath);
|
|
2397
2294
|
}
|
|
2398
|
-
if (
|
|
2399
|
-
return
|
|
2295
|
+
if (importPath.startsWith("@")) {
|
|
2296
|
+
return this.resolveScopedPath(importPath);
|
|
2400
2297
|
}
|
|
2401
|
-
|
|
2298
|
+
if (importPath.startsWith("./") || importPath.startsWith("../")) {
|
|
2299
|
+
return this.resolveRelativePath(importPath, fromPath);
|
|
2300
|
+
}
|
|
2301
|
+
return this.resolveRelativePath(`./${importPath}`, fromPath);
|
|
2402
2302
|
}
|
|
2403
2303
|
/**
|
|
2404
|
-
* Resolve
|
|
2304
|
+
* Resolve a standard library path.
|
|
2405
2305
|
*/
|
|
2406
|
-
|
|
2407
|
-
if (
|
|
2408
|
-
const fallbackName = entityRef.name ?? entityRef.extends.replace(/\.entity$/, "");
|
|
2306
|
+
resolveStdPath(importPath) {
|
|
2307
|
+
if (!this.options.stdLibPath) {
|
|
2409
2308
|
return {
|
|
2410
|
-
success:
|
|
2411
|
-
|
|
2412
|
-
entity: {
|
|
2413
|
-
name: fallbackName,
|
|
2414
|
-
fields: entityRef.fields ?? [],
|
|
2415
|
-
...entityRef.persistence ? { persistence: entityRef.persistence } : {},
|
|
2416
|
-
...entityRef.collection ? { collection: entityRef.collection } : {}
|
|
2417
|
-
}
|
|
2418
|
-
},
|
|
2419
|
-
warnings: []
|
|
2309
|
+
success: false,
|
|
2310
|
+
error: `Standard library URL not configured. Cannot load: ${importPath}`
|
|
2420
2311
|
};
|
|
2421
2312
|
}
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
warnings: []
|
|
2427
|
-
};
|
|
2313
|
+
const relativePath = importPath.slice(4);
|
|
2314
|
+
let absoluteUrl = this.joinUrl(this.options.stdLibPath, relativePath);
|
|
2315
|
+
if (!absoluteUrl.endsWith(".orb")) {
|
|
2316
|
+
absoluteUrl += ".orb";
|
|
2428
2317
|
}
|
|
2429
|
-
|
|
2430
|
-
|
|
2318
|
+
return { success: true, data: absoluteUrl };
|
|
2319
|
+
}
|
|
2320
|
+
/**
|
|
2321
|
+
* Resolve a scoped package path.
|
|
2322
|
+
*/
|
|
2323
|
+
resolveScopedPath(importPath) {
|
|
2324
|
+
const match = importPath.match(/^(@[^/]+)/);
|
|
2325
|
+
if (!match) {
|
|
2431
2326
|
return {
|
|
2432
2327
|
success: false,
|
|
2433
|
-
|
|
2328
|
+
error: `Invalid scoped package path: ${importPath}`
|
|
2434
2329
|
};
|
|
2435
2330
|
}
|
|
2436
|
-
const
|
|
2437
|
-
|
|
2331
|
+
const scope = match[1];
|
|
2332
|
+
const scopeRoot = this.options.scopedPaths[scope];
|
|
2333
|
+
if (!scopeRoot) {
|
|
2438
2334
|
return {
|
|
2439
2335
|
success: false,
|
|
2440
|
-
|
|
2441
|
-
`Unknown import alias in entity reference: ${parsed.alias}. Available aliases: ${Array.from(imports.orbitals.keys()).join(", ") || "none"}`
|
|
2442
|
-
]
|
|
2336
|
+
error: `Scoped package "${scope}" not configured. Available: ${Object.keys(this.options.scopedPaths).join(", ") || "none"}`
|
|
2443
2337
|
};
|
|
2444
2338
|
}
|
|
2445
|
-
const
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
errors: [
|
|
2450
|
-
`Imported orbital "${parsed.alias}" does not have an inline entity. Entity references cannot be chained.`
|
|
2451
|
-
]
|
|
2452
|
-
};
|
|
2339
|
+
const relativePath = importPath.slice(scope.length + 1);
|
|
2340
|
+
let absoluteUrl = this.joinUrl(scopeRoot, relativePath);
|
|
2341
|
+
if (!absoluteUrl.endsWith(".orb")) {
|
|
2342
|
+
absoluteUrl += ".orb";
|
|
2453
2343
|
}
|
|
2454
|
-
|
|
2455
|
-
return {
|
|
2456
|
-
success: true,
|
|
2457
|
-
data: {
|
|
2458
|
-
entity: importedEntity,
|
|
2459
|
-
source: {
|
|
2460
|
-
alias: parsed.alias,
|
|
2461
|
-
persistence
|
|
2462
|
-
}
|
|
2463
|
-
},
|
|
2464
|
-
warnings: []
|
|
2465
|
-
};
|
|
2344
|
+
return { success: true, data: absoluteUrl };
|
|
2466
2345
|
}
|
|
2467
2346
|
/**
|
|
2468
|
-
*
|
|
2347
|
+
* Resolve a relative path.
|
|
2469
2348
|
*/
|
|
2470
|
-
|
|
2471
|
-
const
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
if (isEntityCall(entityRef)) {
|
|
2476
|
-
const fallbackName = entityRef.name ?? entityRef.extends.replace(/\.entity$/, "");
|
|
2477
|
-
return {
|
|
2478
|
-
name: fallbackName,
|
|
2479
|
-
fields: entityRef.fields ?? [],
|
|
2480
|
-
...entityRef.persistence ? { persistence: entityRef.persistence } : {},
|
|
2481
|
-
...entityRef.collection ? { collection: entityRef.collection } : {}
|
|
2482
|
-
};
|
|
2349
|
+
resolveRelativePath(importPath, fromPath) {
|
|
2350
|
+
const baseUrl = fromPath ? this.getParentUrl(fromPath) : this.options.basePath;
|
|
2351
|
+
let absoluteUrl = this.joinUrl(baseUrl, importPath);
|
|
2352
|
+
if (!absoluteUrl.endsWith(".orb")) {
|
|
2353
|
+
absoluteUrl += ".orb";
|
|
2483
2354
|
}
|
|
2484
|
-
return
|
|
2355
|
+
return { success: true, data: absoluteUrl };
|
|
2485
2356
|
}
|
|
2486
2357
|
/**
|
|
2487
|
-
*
|
|
2358
|
+
* Fetch and parse an OrbitalSchema from a URL.
|
|
2488
2359
|
*/
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
errors.push(...result.errors);
|
|
2496
|
-
} else {
|
|
2497
|
-
resolved.push(result.data);
|
|
2498
|
-
}
|
|
2499
|
-
}
|
|
2500
|
-
if (errors.length > 0) {
|
|
2501
|
-
return { success: false, errors };
|
|
2502
|
-
}
|
|
2503
|
-
return { success: true, data: resolved, warnings: [] };
|
|
2504
|
-
}
|
|
2505
|
-
/**
|
|
2506
|
-
* Resolve a single trait reference.
|
|
2507
|
-
*/
|
|
2508
|
-
resolveTraitRef(traitRef, imports) {
|
|
2509
|
-
if (typeof traitRef !== "string" && "stateMachine" in traitRef) {
|
|
2510
|
-
return {
|
|
2511
|
-
success: true,
|
|
2512
|
-
data: {
|
|
2513
|
-
trait: traitRef,
|
|
2514
|
-
source: { type: "inline" }
|
|
2515
|
-
},
|
|
2516
|
-
warnings: []
|
|
2517
|
-
};
|
|
2518
|
-
}
|
|
2519
|
-
if (typeof traitRef !== "string" && "ref" in traitRef) {
|
|
2520
|
-
const refObj = traitRef;
|
|
2521
|
-
return this.resolveTraitRefString(
|
|
2522
|
-
refObj.ref,
|
|
2523
|
-
imports,
|
|
2524
|
-
refObj.config,
|
|
2525
|
-
refObj.linkedEntity,
|
|
2526
|
-
refObj.name,
|
|
2527
|
-
refObj.events,
|
|
2528
|
-
refObj.listens
|
|
2360
|
+
async fetchSchema(url) {
|
|
2361
|
+
try {
|
|
2362
|
+
const controller = new AbortController();
|
|
2363
|
+
const timeoutId = setTimeout(
|
|
2364
|
+
() => controller.abort(),
|
|
2365
|
+
this.options.timeout
|
|
2529
2366
|
);
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2367
|
+
try {
|
|
2368
|
+
const response = await fetch(url, {
|
|
2369
|
+
...this.options.fetchOptions,
|
|
2370
|
+
credentials: this.options.credentials,
|
|
2371
|
+
signal: controller.signal,
|
|
2372
|
+
headers: {
|
|
2373
|
+
Accept: "application/json",
|
|
2374
|
+
...this.options.fetchOptions?.headers
|
|
2375
|
+
}
|
|
2376
|
+
});
|
|
2377
|
+
if (!response.ok) {
|
|
2378
|
+
return {
|
|
2379
|
+
success: false,
|
|
2380
|
+
error: `HTTP ${response.status}: ${response.statusText} for ${url}`
|
|
2381
|
+
};
|
|
2382
|
+
}
|
|
2383
|
+
const text = await response.text();
|
|
2384
|
+
let data;
|
|
2385
|
+
try {
|
|
2386
|
+
data = JSON.parse(text);
|
|
2387
|
+
} catch (e) {
|
|
2388
|
+
return {
|
|
2389
|
+
success: false,
|
|
2390
|
+
error: `Invalid JSON in ${url}: ${e instanceof Error ? e.message : String(e)}`
|
|
2391
|
+
};
|
|
2392
|
+
}
|
|
2393
|
+
const parseResult = OrbitalSchemaSchema.safeParse(data);
|
|
2394
|
+
if (!parseResult.success) {
|
|
2395
|
+
const errors = parseResult.error.errors.map((e) => ` - ${e.path.join(".")}: ${e.message}`).join("\n");
|
|
2396
|
+
return {
|
|
2397
|
+
success: false,
|
|
2398
|
+
error: `Invalid schema in ${url}:
|
|
2399
|
+
${errors}`
|
|
2400
|
+
};
|
|
2401
|
+
}
|
|
2402
|
+
return { success: true, data: parseResult.data };
|
|
2403
|
+
} finally {
|
|
2404
|
+
clearTimeout(timeoutId);
|
|
2553
2405
|
}
|
|
2554
|
-
|
|
2555
|
-
if (
|
|
2406
|
+
} catch (e) {
|
|
2407
|
+
if (e instanceof Error && e.name === "AbortError") {
|
|
2556
2408
|
return {
|
|
2557
2409
|
success: false,
|
|
2558
|
-
|
|
2559
|
-
`Trait "${parsed.traitName}" not found in imported orbital "${parsed.alias}". Available traits: ${this.listTraitsInOrbital(imported.orbital).join(", ") || "none"}`
|
|
2560
|
-
]
|
|
2410
|
+
error: `Request timeout for ${url} (${this.options.timeout}ms)`
|
|
2561
2411
|
};
|
|
2562
2412
|
}
|
|
2563
|
-
const baseTrait = overrideName ? { ...trait, name: overrideName } : trait;
|
|
2564
|
-
const reboundTrait = applyLinkedEntityRename(baseTrait, linkedEntity);
|
|
2565
|
-
const renamedTrait = applyEventRenames(reboundTrait, eventRenames);
|
|
2566
|
-
const finalTrait = listensOverride !== void 0 ? { ...renamedTrait, listens: listensOverride } : renamedTrait;
|
|
2567
|
-
if (listensOverride !== void 0) {
|
|
2568
|
-
refResolverLog.info("listens-override:imported", {
|
|
2569
|
-
trait: finalTrait.name,
|
|
2570
|
-
ref,
|
|
2571
|
-
atomListens: trait.listens?.length ?? 0,
|
|
2572
|
-
callSiteListens: listensOverride.length
|
|
2573
|
-
});
|
|
2574
|
-
}
|
|
2575
|
-
return {
|
|
2576
|
-
success: true,
|
|
2577
|
-
data: {
|
|
2578
|
-
trait: finalTrait,
|
|
2579
|
-
source: { type: "imported", alias: parsed.alias, traitName: parsed.traitName },
|
|
2580
|
-
config,
|
|
2581
|
-
linkedEntity
|
|
2582
|
-
},
|
|
2583
|
-
warnings: []
|
|
2584
|
-
};
|
|
2585
|
-
}
|
|
2586
|
-
const localTrait = this.localTraits.get(ref);
|
|
2587
|
-
if (localTrait) {
|
|
2588
|
-
const baseLocal = overrideName ? { ...localTrait, name: overrideName } : localTrait;
|
|
2589
|
-
const reboundLocal = applyLinkedEntityRename(baseLocal, linkedEntity);
|
|
2590
|
-
const renamedLocalTrait = applyEventRenames(reboundLocal, eventRenames);
|
|
2591
|
-
const finalLocalTrait = listensOverride !== void 0 ? { ...renamedLocalTrait, listens: listensOverride } : renamedLocalTrait;
|
|
2592
|
-
if (listensOverride !== void 0) {
|
|
2593
|
-
refResolverLog.info("listens-override:local", {
|
|
2594
|
-
trait: finalLocalTrait.name,
|
|
2595
|
-
ref,
|
|
2596
|
-
atomListens: localTrait.listens?.length ?? 0,
|
|
2597
|
-
callSiteListens: listensOverride.length
|
|
2598
|
-
});
|
|
2599
|
-
}
|
|
2600
2413
|
return {
|
|
2601
|
-
success:
|
|
2602
|
-
|
|
2603
|
-
trait: finalLocalTrait,
|
|
2604
|
-
source: { type: "local", name: ref },
|
|
2605
|
-
config,
|
|
2606
|
-
linkedEntity
|
|
2607
|
-
},
|
|
2608
|
-
warnings: []
|
|
2414
|
+
success: false,
|
|
2415
|
+
error: `Failed to fetch ${url}: ${e instanceof Error ? e.message : String(e)}`
|
|
2609
2416
|
};
|
|
2610
2417
|
}
|
|
2611
|
-
return {
|
|
2612
|
-
success: false,
|
|
2613
|
-
errors: [
|
|
2614
|
-
`Trait "${ref}" not found. For imported traits, use format "Alias.traits.TraitName". Local traits available: ${Array.from(this.localTraits.keys()).join(", ") || "none"}`
|
|
2615
|
-
]
|
|
2616
|
-
};
|
|
2617
2418
|
}
|
|
2618
2419
|
/**
|
|
2619
|
-
*
|
|
2420
|
+
* Join URL parts, handling relative paths and trailing slashes.
|
|
2620
2421
|
*/
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
if (refObj.ref === traitName || refObj.name === traitName) ;
|
|
2422
|
+
joinUrl(base, path) {
|
|
2423
|
+
if (path.startsWith("http://") || path.startsWith("https://")) {
|
|
2424
|
+
return path;
|
|
2425
|
+
}
|
|
2426
|
+
if (typeof URL !== "undefined") {
|
|
2427
|
+
try {
|
|
2428
|
+
const baseUrl = base.endsWith("/") ? base : base + "/";
|
|
2429
|
+
return new URL(path, baseUrl).href;
|
|
2430
|
+
} catch {
|
|
2631
2431
|
}
|
|
2632
2432
|
}
|
|
2633
|
-
|
|
2433
|
+
const normalizedBase = base.endsWith("/") ? base.slice(0, -1) : base;
|
|
2434
|
+
const normalizedPath = path.startsWith("/") ? path : "/" + path;
|
|
2435
|
+
if (normalizedPath.startsWith("/./")) {
|
|
2436
|
+
return normalizedBase + normalizedPath.slice(2);
|
|
2437
|
+
}
|
|
2438
|
+
if (normalizedPath.startsWith("/../")) {
|
|
2439
|
+
const baseParts = normalizedBase.split("/");
|
|
2440
|
+
baseParts.pop();
|
|
2441
|
+
return baseParts.join("/") + normalizedPath.slice(3);
|
|
2442
|
+
}
|
|
2443
|
+
return normalizedBase + normalizedPath;
|
|
2634
2444
|
}
|
|
2635
2445
|
/**
|
|
2636
|
-
*
|
|
2446
|
+
* Get parent URL (directory) from a URL.
|
|
2637
2447
|
*/
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2448
|
+
getParentUrl(url) {
|
|
2449
|
+
if (typeof URL !== "undefined") {
|
|
2450
|
+
try {
|
|
2451
|
+
const urlObj = new URL(url);
|
|
2452
|
+
const pathParts = urlObj.pathname.split("/");
|
|
2453
|
+
pathParts.pop();
|
|
2454
|
+
urlObj.pathname = pathParts.join("/");
|
|
2455
|
+
return urlObj.href;
|
|
2456
|
+
} catch {
|
|
2643
2457
|
}
|
|
2644
2458
|
}
|
|
2645
|
-
|
|
2459
|
+
const lastSlash = url.lastIndexOf("/");
|
|
2460
|
+
if (lastSlash !== -1) {
|
|
2461
|
+
return url.slice(0, lastSlash);
|
|
2462
|
+
}
|
|
2463
|
+
return url;
|
|
2646
2464
|
}
|
|
2647
2465
|
/**
|
|
2648
|
-
*
|
|
2466
|
+
* Clear the cache.
|
|
2649
2467
|
*/
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
const resolved = [];
|
|
2653
|
-
for (const pageRef of pageRefs) {
|
|
2654
|
-
const result = this.resolvePageRef(pageRef, imports);
|
|
2655
|
-
if (!result.success) {
|
|
2656
|
-
errors.push(...result.errors);
|
|
2657
|
-
} else {
|
|
2658
|
-
resolved.push(result.data);
|
|
2659
|
-
}
|
|
2660
|
-
}
|
|
2661
|
-
if (errors.length > 0) {
|
|
2662
|
-
return { success: false, errors };
|
|
2663
|
-
}
|
|
2664
|
-
return { success: true, data: resolved, warnings: [] };
|
|
2468
|
+
clearCache() {
|
|
2469
|
+
this.cache.clear();
|
|
2665
2470
|
}
|
|
2666
2471
|
/**
|
|
2667
|
-
*
|
|
2472
|
+
* Get cache statistics.
|
|
2668
2473
|
*/
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
return {
|
|
2672
|
-
success: true,
|
|
2673
|
-
data: {
|
|
2674
|
-
page: pageRef,
|
|
2675
|
-
source: { type: "inline" },
|
|
2676
|
-
pathOverridden: false
|
|
2677
|
-
},
|
|
2678
|
-
warnings: []
|
|
2679
|
-
};
|
|
2680
|
-
}
|
|
2681
|
-
if (isPageReferenceString(pageRef)) {
|
|
2682
|
-
return this.resolvePageRefString(pageRef, imports);
|
|
2683
|
-
}
|
|
2684
|
-
if (isPageReferenceObject(pageRef)) {
|
|
2685
|
-
return this.resolvePageRefObject(pageRef, imports);
|
|
2686
|
-
}
|
|
2687
|
-
return {
|
|
2688
|
-
success: false,
|
|
2689
|
-
errors: [`Unknown page reference format: ${JSON.stringify(pageRef)}`]
|
|
2690
|
-
};
|
|
2691
|
-
}
|
|
2692
|
-
/**
|
|
2693
|
-
* Resolve a page reference string.
|
|
2694
|
-
*/
|
|
2695
|
-
resolvePageRefString(ref, imports) {
|
|
2696
|
-
const parsed = parsePageRef(ref);
|
|
2697
|
-
if (!parsed) {
|
|
2698
|
-
return {
|
|
2699
|
-
success: false,
|
|
2700
|
-
errors: [`Invalid page reference format: ${ref}. Expected "Alias.pages.PageName"`]
|
|
2701
|
-
};
|
|
2702
|
-
}
|
|
2703
|
-
const imported = imports.orbitals.get(parsed.alias);
|
|
2704
|
-
if (!imported) {
|
|
2705
|
-
return {
|
|
2706
|
-
success: false,
|
|
2707
|
-
errors: [
|
|
2708
|
-
`Unknown import alias in page reference: ${parsed.alias}. Available aliases: ${Array.from(imports.orbitals.keys()).join(", ") || "none"}`
|
|
2709
|
-
]
|
|
2710
|
-
};
|
|
2711
|
-
}
|
|
2712
|
-
const page = this.findPageInOrbital(imported.orbital, parsed.pageName);
|
|
2713
|
-
if (!page) {
|
|
2714
|
-
return {
|
|
2715
|
-
success: false,
|
|
2716
|
-
errors: [
|
|
2717
|
-
`Page "${parsed.pageName}" not found in imported orbital "${parsed.alias}". Available pages: ${this.listPagesInOrbital(imported.orbital).join(", ") || "none"}`
|
|
2718
|
-
]
|
|
2719
|
-
};
|
|
2720
|
-
}
|
|
2721
|
-
return {
|
|
2722
|
-
success: true,
|
|
2723
|
-
data: {
|
|
2724
|
-
page,
|
|
2725
|
-
source: { type: "imported", alias: parsed.alias, pageName: parsed.pageName },
|
|
2726
|
-
pathOverridden: false
|
|
2727
|
-
},
|
|
2728
|
-
warnings: []
|
|
2729
|
-
};
|
|
2730
|
-
}
|
|
2731
|
-
/**
|
|
2732
|
-
* Resolve a page reference object with optional path override.
|
|
2733
|
-
*/
|
|
2734
|
-
resolvePageRefObject(refObj, imports) {
|
|
2735
|
-
const baseResult = this.resolvePageRefString(refObj.ref, imports);
|
|
2736
|
-
if (!baseResult.success) {
|
|
2737
|
-
return baseResult;
|
|
2738
|
-
}
|
|
2739
|
-
const resolved = baseResult.data;
|
|
2740
|
-
if (refObj.path) {
|
|
2741
|
-
const originalPath = resolved.page.path;
|
|
2742
|
-
resolved.page = {
|
|
2743
|
-
...resolved.page,
|
|
2744
|
-
path: refObj.path
|
|
2745
|
-
};
|
|
2746
|
-
resolved.pathOverridden = true;
|
|
2747
|
-
resolved.originalPath = originalPath;
|
|
2748
|
-
}
|
|
2749
|
-
return {
|
|
2750
|
-
success: true,
|
|
2751
|
-
data: resolved,
|
|
2752
|
-
warnings: baseResult.warnings
|
|
2753
|
-
};
|
|
2754
|
-
}
|
|
2755
|
-
/**
|
|
2756
|
-
* Find a page in an orbital by name.
|
|
2757
|
-
*/
|
|
2758
|
-
findPageInOrbital(orbital, pageName) {
|
|
2759
|
-
const pages = orbital.pages;
|
|
2760
|
-
if (!pages) return null;
|
|
2761
|
-
for (const pageRef of pages) {
|
|
2762
|
-
if (typeof pageRef !== "string" && !("ref" in pageRef)) {
|
|
2763
|
-
const page = pageRef;
|
|
2764
|
-
if (page.name === pageName) {
|
|
2765
|
-
return { ...page };
|
|
2766
|
-
}
|
|
2767
|
-
}
|
|
2768
|
-
}
|
|
2769
|
-
return null;
|
|
2770
|
-
}
|
|
2771
|
-
/**
|
|
2772
|
-
* List page names in an orbital.
|
|
2773
|
-
*/
|
|
2774
|
-
listPagesInOrbital(orbital) {
|
|
2775
|
-
const pages = orbital.pages;
|
|
2776
|
-
if (!pages) return [];
|
|
2777
|
-
const names = [];
|
|
2778
|
-
for (const pageRef of pages) {
|
|
2779
|
-
if (typeof pageRef !== "string" && !("ref" in pageRef)) {
|
|
2780
|
-
names.push(pageRef.name);
|
|
2781
|
-
}
|
|
2782
|
-
}
|
|
2783
|
-
return names;
|
|
2784
|
-
}
|
|
2785
|
-
/**
|
|
2786
|
-
* Add local traits for resolution.
|
|
2787
|
-
*/
|
|
2788
|
-
addLocalTraits(traits) {
|
|
2789
|
-
for (const trait of traits) {
|
|
2790
|
-
this.localTraits.set(trait.name, trait);
|
|
2791
|
-
}
|
|
2792
|
-
}
|
|
2793
|
-
/**
|
|
2794
|
-
* Clear loader cache.
|
|
2795
|
-
*/
|
|
2796
|
-
clearCache() {
|
|
2797
|
-
this.loader?.clearCache();
|
|
2474
|
+
getCacheStats() {
|
|
2475
|
+
return { size: this.cache.size };
|
|
2798
2476
|
}
|
|
2799
2477
|
};
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
const inlineTraits = orbital.traits.filter(
|
|
2807
|
-
(t) => typeof t !== "string" && "stateMachine" in t
|
|
2808
|
-
);
|
|
2809
|
-
resolver.addLocalTraits(inlineTraits);
|
|
2478
|
+
|
|
2479
|
+
// src/loader/unified-loader.ts
|
|
2480
|
+
var externalLoaderModule = null;
|
|
2481
|
+
async function getExternalLoaderModule() {
|
|
2482
|
+
if (externalLoaderModule) {
|
|
2483
|
+
return externalLoaderModule;
|
|
2810
2484
|
}
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
if (!result.success) {
|
|
2814
|
-
errors.push(`Orbital "${orbital.name}": ${result.errors.join(", ")}`);
|
|
2815
|
-
} else {
|
|
2816
|
-
resolved.push(result.data);
|
|
2817
|
-
warnings.push(...result.warnings.map((w) => `Orbital "${orbital.name}": ${w}`));
|
|
2818
|
-
}
|
|
2485
|
+
if (isBrowser()) {
|
|
2486
|
+
return null;
|
|
2819
2487
|
}
|
|
2820
|
-
|
|
2821
|
-
|
|
2488
|
+
try {
|
|
2489
|
+
externalLoaderModule = await import('./external-loader-IN246DQM.js');
|
|
2490
|
+
return externalLoaderModule;
|
|
2491
|
+
} catch {
|
|
2492
|
+
return null;
|
|
2822
2493
|
}
|
|
2823
|
-
return { success: true, data: resolved, warnings };
|
|
2824
|
-
}
|
|
2825
|
-
|
|
2826
|
-
// src/loader/schema-loader.ts
|
|
2827
|
-
function isElectron() {
|
|
2828
|
-
return typeof process !== "undefined" && !!process.versions?.electron;
|
|
2829
|
-
}
|
|
2830
|
-
function isBrowser() {
|
|
2831
|
-
return typeof window !== "undefined" && !isElectron();
|
|
2832
|
-
}
|
|
2833
|
-
function isNode() {
|
|
2834
|
-
return typeof process !== "undefined" && !isBrowser();
|
|
2835
2494
|
}
|
|
2836
|
-
var
|
|
2495
|
+
var UnifiedImportChain = class _UnifiedImportChain {
|
|
2837
2496
|
chain = [];
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
*/
|
|
2842
|
-
push(absolutePath) {
|
|
2843
|
-
if (this.chain.includes(absolutePath)) {
|
|
2497
|
+
push(path) {
|
|
2498
|
+
const normalized = this.normalizePath(path);
|
|
2499
|
+
if (this.chain.includes(normalized)) {
|
|
2844
2500
|
const cycle = [
|
|
2845
|
-
...this.chain.slice(this.chain.indexOf(
|
|
2846
|
-
|
|
2501
|
+
...this.chain.slice(this.chain.indexOf(normalized)),
|
|
2502
|
+
normalized
|
|
2847
2503
|
];
|
|
2848
2504
|
return `Circular import detected: ${cycle.join(" -> ")}`;
|
|
2849
2505
|
}
|
|
2850
|
-
this.chain.push(
|
|
2506
|
+
this.chain.push(normalized);
|
|
2851
2507
|
return null;
|
|
2852
2508
|
}
|
|
2853
|
-
/**
|
|
2854
|
-
* Remove the last path from the chain.
|
|
2855
|
-
*/
|
|
2856
2509
|
pop() {
|
|
2857
2510
|
this.chain.pop();
|
|
2858
2511
|
}
|
|
2859
|
-
/**
|
|
2860
|
-
* Clone the chain for nested loading.
|
|
2861
|
-
*/
|
|
2862
2512
|
clone() {
|
|
2863
|
-
const newChain = new
|
|
2513
|
+
const newChain = new _UnifiedImportChain();
|
|
2864
2514
|
newChain.chain = [...this.chain];
|
|
2865
2515
|
return newChain;
|
|
2866
2516
|
}
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
return
|
|
2872
|
-
}
|
|
2873
|
-
set(url, schema) {
|
|
2874
|
-
this.cache.set(url, schema);
|
|
2875
|
-
}
|
|
2876
|
-
has(url) {
|
|
2877
|
-
return this.cache.has(url);
|
|
2878
|
-
}
|
|
2879
|
-
clear() {
|
|
2880
|
-
this.cache.clear();
|
|
2881
|
-
}
|
|
2882
|
-
get size() {
|
|
2883
|
-
return this.cache.size;
|
|
2517
|
+
normalizePath(path) {
|
|
2518
|
+
if (path.startsWith("http://") || path.startsWith("https://")) {
|
|
2519
|
+
return path;
|
|
2520
|
+
}
|
|
2521
|
+
return path.replace(/\\/g, "/");
|
|
2884
2522
|
}
|
|
2885
2523
|
};
|
|
2886
|
-
var
|
|
2524
|
+
var UnifiedLoader = class {
|
|
2887
2525
|
options;
|
|
2888
|
-
|
|
2526
|
+
httpLoader = null;
|
|
2527
|
+
fsLoader = null;
|
|
2528
|
+
fsLoaderInitialized = false;
|
|
2529
|
+
cache = /* @__PURE__ */ new Map();
|
|
2889
2530
|
constructor(options) {
|
|
2890
|
-
this.options =
|
|
2531
|
+
this.options = options;
|
|
2532
|
+
this.httpLoader = new HttpLoader({
|
|
2891
2533
|
basePath: options.basePath,
|
|
2892
|
-
stdLibPath: options.stdLibPath
|
|
2893
|
-
scopedPaths: options.scopedPaths
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
credentials: options.credentials ?? "same-origin"
|
|
2897
|
-
};
|
|
2898
|
-
this.cache = new HttpLoaderCache();
|
|
2534
|
+
stdLibPath: options.stdLibPath,
|
|
2535
|
+
scopedPaths: options.scopedPaths,
|
|
2536
|
+
...options.http
|
|
2537
|
+
});
|
|
2899
2538
|
}
|
|
2900
2539
|
/**
|
|
2901
|
-
*
|
|
2540
|
+
* Initialize the filesystem loader if available.
|
|
2902
2541
|
*/
|
|
2903
|
-
async
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
if (!resolveResult.success) {
|
|
2907
|
-
return resolveResult;
|
|
2542
|
+
async initFsLoader() {
|
|
2543
|
+
if (this.fsLoaderInitialized) {
|
|
2544
|
+
return;
|
|
2908
2545
|
}
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
return { success: false, error: circularError };
|
|
2546
|
+
this.fsLoaderInitialized = true;
|
|
2547
|
+
if (this.options.forceLoader === "http") {
|
|
2548
|
+
return;
|
|
2913
2549
|
}
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
return
|
|
2930
|
-
}
|
|
2931
|
-
|
|
2550
|
+
const module = await getExternalLoaderModule();
|
|
2551
|
+
if (module) {
|
|
2552
|
+
this.fsLoader = new module.ExternalOrbitalLoader({
|
|
2553
|
+
basePath: this.options.basePath,
|
|
2554
|
+
stdLibPath: this.options.stdLibPath,
|
|
2555
|
+
scopedPaths: this.options.scopedPaths,
|
|
2556
|
+
...this.options.fileSystem
|
|
2557
|
+
});
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
/**
|
|
2561
|
+
* Determine which loader to use for an import path.
|
|
2562
|
+
*/
|
|
2563
|
+
getLoaderForPath(importPath) {
|
|
2564
|
+
if (this.options.forceLoader) {
|
|
2565
|
+
return this.options.forceLoader;
|
|
2566
|
+
}
|
|
2567
|
+
if (importPath.startsWith("http://") || importPath.startsWith("https://")) {
|
|
2568
|
+
return "http";
|
|
2569
|
+
}
|
|
2570
|
+
if (importPath.startsWith("std/") && this.options.stdLibPath) {
|
|
2571
|
+
if (this.options.stdLibPath.startsWith("http://") || this.options.stdLibPath.startsWith("https://")) {
|
|
2572
|
+
return "http";
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
if (importPath.startsWith("@") && this.options.scopedPaths) {
|
|
2576
|
+
const match = importPath.match(/^(@[^/]+)/);
|
|
2577
|
+
if (match) {
|
|
2578
|
+
const scopePath = this.options.scopedPaths[match[1]];
|
|
2579
|
+
if (scopePath && (scopePath.startsWith("http://") || scopePath.startsWith("https://"))) {
|
|
2580
|
+
return "http";
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
if (isBrowser()) {
|
|
2585
|
+
return "http";
|
|
2586
|
+
}
|
|
2587
|
+
return "filesystem";
|
|
2588
|
+
}
|
|
2589
|
+
/**
|
|
2590
|
+
* Load a schema from an import path.
|
|
2591
|
+
*
|
|
2592
|
+
* Note: We delegate chain management to the inner loader (HttpLoader or FsLoader).
|
|
2593
|
+
* The inner loader handles circular import detection, so we don't push/pop here.
|
|
2594
|
+
* We only use the unified cache to avoid duplicate loads across loaders.
|
|
2595
|
+
*/
|
|
2596
|
+
async load(importPath, fromPath, chain) {
|
|
2597
|
+
await this.initFsLoader();
|
|
2598
|
+
const importChain = chain ?? new UnifiedImportChain();
|
|
2599
|
+
const loaderType = this.getLoaderForPath(importPath);
|
|
2600
|
+
const resolveResult = this.resolvePath(importPath, fromPath);
|
|
2601
|
+
if (!resolveResult.success) {
|
|
2602
|
+
return resolveResult;
|
|
2603
|
+
}
|
|
2604
|
+
const absolutePath = resolveResult.data;
|
|
2605
|
+
const cached = this.cache.get(absolutePath);
|
|
2606
|
+
if (cached) {
|
|
2607
|
+
return { success: true, data: cached };
|
|
2608
|
+
}
|
|
2609
|
+
let result;
|
|
2610
|
+
if (loaderType === "http") {
|
|
2611
|
+
if (!this.httpLoader) {
|
|
2612
|
+
return {
|
|
2613
|
+
success: false,
|
|
2614
|
+
error: "HTTP loader not available"
|
|
2615
|
+
};
|
|
2616
|
+
}
|
|
2617
|
+
result = await this.httpLoader.load(importPath, fromPath, importChain);
|
|
2618
|
+
} else {
|
|
2619
|
+
if (!this.fsLoader) {
|
|
2620
|
+
if (this.httpLoader) {
|
|
2621
|
+
result = await this.httpLoader.load(
|
|
2622
|
+
importPath,
|
|
2623
|
+
fromPath,
|
|
2624
|
+
importChain
|
|
2625
|
+
);
|
|
2626
|
+
} else {
|
|
2627
|
+
return {
|
|
2628
|
+
success: false,
|
|
2629
|
+
error: `Filesystem loader not available and import "${importPath}" cannot be loaded via HTTP. This typically happens when loading local files in a browser environment.`
|
|
2630
|
+
};
|
|
2631
|
+
}
|
|
2632
|
+
} else {
|
|
2633
|
+
result = await this.fsLoader.load(importPath, fromPath, importChain);
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
if (result.success) {
|
|
2637
|
+
this.cache.set(absolutePath, result.data);
|
|
2932
2638
|
}
|
|
2639
|
+
return result;
|
|
2933
2640
|
}
|
|
2934
2641
|
/**
|
|
2935
2642
|
* Load a specific orbital from a schema by name.
|
|
@@ -2940,2546 +2647,995 @@ var HttpLoader = class {
|
|
|
2940
2647
|
return schemaResult;
|
|
2941
2648
|
}
|
|
2942
2649
|
const schema = schemaResult.data.schema;
|
|
2943
|
-
let orbital;
|
|
2944
2650
|
if (orbitalName) {
|
|
2945
|
-
const found = schema.orbitals.find(
|
|
2946
|
-
(o) => o.name === orbitalName
|
|
2947
|
-
);
|
|
2651
|
+
const found = schema.orbitals.find((o) => o.name === orbitalName);
|
|
2948
2652
|
if (!found) {
|
|
2949
2653
|
return {
|
|
2950
2654
|
success: false,
|
|
2951
2655
|
error: `Orbital "${orbitalName}" not found in ${importPath}. Available: ${schema.orbitals.map((o) => o.name).join(", ")}`
|
|
2952
2656
|
};
|
|
2953
2657
|
}
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
}
|
|
2961
|
-
}
|
|
2962
|
-
|
|
2658
|
+
return {
|
|
2659
|
+
success: true,
|
|
2660
|
+
data: {
|
|
2661
|
+
orbital: found,
|
|
2662
|
+
sourcePath: schemaResult.data.sourcePath,
|
|
2663
|
+
importPath
|
|
2664
|
+
}
|
|
2665
|
+
};
|
|
2666
|
+
}
|
|
2667
|
+
if (schema.orbitals.length === 0) {
|
|
2668
|
+
return {
|
|
2669
|
+
success: false,
|
|
2670
|
+
error: `No orbitals found in ${importPath}`
|
|
2671
|
+
};
|
|
2963
2672
|
}
|
|
2964
2673
|
return {
|
|
2965
2674
|
success: true,
|
|
2966
2675
|
data: {
|
|
2967
|
-
orbital,
|
|
2676
|
+
orbital: schema.orbitals[0],
|
|
2968
2677
|
sourcePath: schemaResult.data.sourcePath,
|
|
2969
2678
|
importPath
|
|
2970
2679
|
}
|
|
2971
2680
|
};
|
|
2972
2681
|
}
|
|
2973
2682
|
/**
|
|
2974
|
-
* Resolve an import path to an absolute URL.
|
|
2683
|
+
* Resolve an import path to an absolute path/URL.
|
|
2975
2684
|
*/
|
|
2976
2685
|
resolvePath(importPath, fromPath) {
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
if (importPath.startsWith("std/")) {
|
|
2981
|
-
return this.resolveStdPath(importPath);
|
|
2686
|
+
const loaderType = this.getLoaderForPath(importPath);
|
|
2687
|
+
if (loaderType === "http" && this.httpLoader) {
|
|
2688
|
+
return this.httpLoader.resolvePath(importPath, fromPath);
|
|
2982
2689
|
}
|
|
2983
|
-
if (
|
|
2984
|
-
return this.
|
|
2690
|
+
if (this.fsLoader) {
|
|
2691
|
+
return this.fsLoader.resolvePath(importPath, fromPath);
|
|
2985
2692
|
}
|
|
2986
|
-
if (
|
|
2987
|
-
return this.
|
|
2693
|
+
if (this.httpLoader) {
|
|
2694
|
+
return this.httpLoader.resolvePath(importPath, fromPath);
|
|
2988
2695
|
}
|
|
2989
|
-
return
|
|
2696
|
+
return {
|
|
2697
|
+
success: false,
|
|
2698
|
+
error: "No loader available for path resolution"
|
|
2699
|
+
};
|
|
2990
2700
|
}
|
|
2991
2701
|
/**
|
|
2992
|
-
*
|
|
2702
|
+
* Clear all caches.
|
|
2993
2703
|
*/
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
error: `Standard library URL not configured. Cannot load: ${importPath}`
|
|
2999
|
-
};
|
|
3000
|
-
}
|
|
3001
|
-
const relativePath = importPath.slice(4);
|
|
3002
|
-
let absoluteUrl = this.joinUrl(this.options.stdLibPath, relativePath);
|
|
3003
|
-
if (!absoluteUrl.endsWith(".orb")) {
|
|
3004
|
-
absoluteUrl += ".orb";
|
|
3005
|
-
}
|
|
3006
|
-
return { success: true, data: absoluteUrl };
|
|
2704
|
+
clearCache() {
|
|
2705
|
+
this.cache.clear();
|
|
2706
|
+
this.httpLoader?.clearCache();
|
|
2707
|
+
this.fsLoader?.clearCache();
|
|
3007
2708
|
}
|
|
3008
2709
|
/**
|
|
3009
|
-
*
|
|
2710
|
+
* Get combined cache statistics.
|
|
3010
2711
|
*/
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
if (
|
|
3014
|
-
|
|
3015
|
-
success: false,
|
|
3016
|
-
error: `Invalid scoped package path: ${importPath}`
|
|
3017
|
-
};
|
|
3018
|
-
}
|
|
3019
|
-
const scope = match[1];
|
|
3020
|
-
const scopeRoot = this.options.scopedPaths[scope];
|
|
3021
|
-
if (!scopeRoot) {
|
|
3022
|
-
return {
|
|
3023
|
-
success: false,
|
|
3024
|
-
error: `Scoped package "${scope}" not configured. Available: ${Object.keys(this.options.scopedPaths).join(", ") || "none"}`
|
|
3025
|
-
};
|
|
2712
|
+
getCacheStats() {
|
|
2713
|
+
let size = this.cache.size;
|
|
2714
|
+
if (this.httpLoader) {
|
|
2715
|
+
size += this.httpLoader.getCacheStats().size;
|
|
3026
2716
|
}
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
if (!absoluteUrl.endsWith(".orb")) {
|
|
3030
|
-
absoluteUrl += ".orb";
|
|
2717
|
+
if (this.fsLoader) {
|
|
2718
|
+
size += this.fsLoader.getCacheStats().size;
|
|
3031
2719
|
}
|
|
3032
|
-
return {
|
|
2720
|
+
return { size };
|
|
3033
2721
|
}
|
|
3034
2722
|
/**
|
|
3035
|
-
*
|
|
2723
|
+
* Check if filesystem loading is available.
|
|
3036
2724
|
*/
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
if (!absoluteUrl.endsWith(".orb")) {
|
|
3041
|
-
absoluteUrl += ".orb";
|
|
3042
|
-
}
|
|
3043
|
-
return { success: true, data: absoluteUrl };
|
|
2725
|
+
async hasFilesystemAccess() {
|
|
2726
|
+
await this.initFsLoader();
|
|
2727
|
+
return this.fsLoader !== null;
|
|
3044
2728
|
}
|
|
3045
2729
|
/**
|
|
3046
|
-
*
|
|
2730
|
+
* Get current environment info.
|
|
3047
2731
|
*/
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
try {
|
|
3056
|
-
const response = await fetch(url, {
|
|
3057
|
-
...this.options.fetchOptions,
|
|
3058
|
-
credentials: this.options.credentials,
|
|
3059
|
-
signal: controller.signal,
|
|
3060
|
-
headers: {
|
|
3061
|
-
Accept: "application/json",
|
|
3062
|
-
...this.options.fetchOptions?.headers
|
|
3063
|
-
}
|
|
3064
|
-
});
|
|
3065
|
-
if (!response.ok) {
|
|
3066
|
-
return {
|
|
3067
|
-
success: false,
|
|
3068
|
-
error: `HTTP ${response.status}: ${response.statusText} for ${url}`
|
|
3069
|
-
};
|
|
3070
|
-
}
|
|
3071
|
-
const text = await response.text();
|
|
3072
|
-
let data;
|
|
3073
|
-
try {
|
|
3074
|
-
data = JSON.parse(text);
|
|
3075
|
-
} catch (e) {
|
|
3076
|
-
return {
|
|
3077
|
-
success: false,
|
|
3078
|
-
error: `Invalid JSON in ${url}: ${e instanceof Error ? e.message : String(e)}`
|
|
3079
|
-
};
|
|
3080
|
-
}
|
|
3081
|
-
const parseResult = OrbitalSchemaSchema.safeParse(data);
|
|
3082
|
-
if (!parseResult.success) {
|
|
3083
|
-
const errors = parseResult.error.errors.map((e) => ` - ${e.path.join(".")}: ${e.message}`).join("\n");
|
|
3084
|
-
return {
|
|
3085
|
-
success: false,
|
|
3086
|
-
error: `Invalid schema in ${url}:
|
|
3087
|
-
${errors}`
|
|
3088
|
-
};
|
|
3089
|
-
}
|
|
3090
|
-
return { success: true, data: parseResult.data };
|
|
3091
|
-
} finally {
|
|
3092
|
-
clearTimeout(timeoutId);
|
|
3093
|
-
}
|
|
3094
|
-
} catch (e) {
|
|
3095
|
-
if (e instanceof Error && e.name === "AbortError") {
|
|
3096
|
-
return {
|
|
3097
|
-
success: false,
|
|
3098
|
-
error: `Request timeout for ${url} (${this.options.timeout}ms)`
|
|
3099
|
-
};
|
|
3100
|
-
}
|
|
3101
|
-
return {
|
|
3102
|
-
success: false,
|
|
3103
|
-
error: `Failed to fetch ${url}: ${e instanceof Error ? e.message : String(e)}`
|
|
3104
|
-
};
|
|
3105
|
-
}
|
|
2732
|
+
getEnvironmentInfo() {
|
|
2733
|
+
return {
|
|
2734
|
+
isElectron: isElectron(),
|
|
2735
|
+
isBrowser: isBrowser(),
|
|
2736
|
+
isNode: isNode(),
|
|
2737
|
+
hasFilesystem: this.fsLoader !== null
|
|
2738
|
+
};
|
|
3106
2739
|
}
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
2740
|
+
};
|
|
2741
|
+
function createUnifiedLoader(options) {
|
|
2742
|
+
return new UnifiedLoader(options);
|
|
2743
|
+
}
|
|
2744
|
+
createLogger("almadar:runtime:studio-config");
|
|
2745
|
+
var refResolverLog = createLogger("almadar:runtime:ref-resolver");
|
|
2746
|
+
function renameEventsInRenderUiConfig(node, rename) {
|
|
2747
|
+
if (node === null || node === void 0) return node;
|
|
2748
|
+
if (Array.isArray(node)) {
|
|
2749
|
+
return node.map((item) => renameEventsInRenderUiConfig(item, rename));
|
|
2750
|
+
}
|
|
2751
|
+
if (typeof node !== "object") return node;
|
|
2752
|
+
const obj = node;
|
|
2753
|
+
const next = { ...obj };
|
|
2754
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
2755
|
+
if (key === "action" && typeof value === "string" && !value.startsWith("@")) {
|
|
2756
|
+
next[key] = rename(value) ?? value;
|
|
2757
|
+
continue;
|
|
3113
2758
|
}
|
|
3114
|
-
if (typeof
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
return new URL(path, baseUrl).href;
|
|
3118
|
-
} catch {
|
|
3119
|
-
}
|
|
2759
|
+
if (/^on[A-Z]/.test(key) && typeof value === "string" && !value.startsWith("@")) {
|
|
2760
|
+
next[key] = rename(value) ?? value;
|
|
2761
|
+
continue;
|
|
3120
2762
|
}
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
return normalizedBase + normalizedPath.slice(2);
|
|
2763
|
+
if (key.endsWith("Event") && typeof value === "string" && !value.startsWith("@")) {
|
|
2764
|
+
next[key] = rename(value) ?? value;
|
|
2765
|
+
continue;
|
|
3125
2766
|
}
|
|
3126
|
-
if (
|
|
3127
|
-
const
|
|
3128
|
-
|
|
3129
|
-
|
|
2767
|
+
if ((key === "actions" || key === "itemActions") && Array.isArray(value)) {
|
|
2768
|
+
const rewrittenArray = value.map((entry) => {
|
|
2769
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return entry;
|
|
2770
|
+
const action = entry;
|
|
2771
|
+
if (typeof action.event === "string" && !action.event.startsWith("@")) {
|
|
2772
|
+
return { ...action, event: rename(action.event) ?? action.event };
|
|
2773
|
+
}
|
|
2774
|
+
return action;
|
|
2775
|
+
});
|
|
2776
|
+
next[key] = rewrittenArray;
|
|
2777
|
+
continue;
|
|
3130
2778
|
}
|
|
3131
|
-
|
|
2779
|
+
next[key] = renameEventsInRenderUiConfig(value, rename);
|
|
3132
2780
|
}
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
if (
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
return urlObj.href;
|
|
3144
|
-
} catch {
|
|
3145
|
-
}
|
|
3146
|
-
}
|
|
3147
|
-
const lastSlash = url.lastIndexOf("/");
|
|
3148
|
-
if (lastSlash !== -1) {
|
|
3149
|
-
return url.slice(0, lastSlash);
|
|
2781
|
+
return next;
|
|
2782
|
+
}
|
|
2783
|
+
function renameEventsInEffects(effects, rename) {
|
|
2784
|
+
return effects.map((effect) => {
|
|
2785
|
+
if (!Array.isArray(effect)) return effect;
|
|
2786
|
+
if (effect[0] === "render-ui" && effect.length >= 3) {
|
|
2787
|
+
const slot = effect[1];
|
|
2788
|
+
const config = effect[2];
|
|
2789
|
+
const nextConfig = renameEventsInRenderUiConfig(config, rename);
|
|
2790
|
+
return [effect[0], slot, nextConfig, ...effect.slice(3)];
|
|
3150
2791
|
}
|
|
3151
|
-
return
|
|
3152
|
-
}
|
|
3153
|
-
/**
|
|
3154
|
-
* Clear the cache.
|
|
3155
|
-
*/
|
|
3156
|
-
clearCache() {
|
|
3157
|
-
this.cache.clear();
|
|
3158
|
-
}
|
|
3159
|
-
/**
|
|
3160
|
-
* Get cache statistics.
|
|
3161
|
-
*/
|
|
3162
|
-
getCacheStats() {
|
|
3163
|
-
return { size: this.cache.size };
|
|
3164
|
-
}
|
|
3165
|
-
};
|
|
3166
|
-
|
|
3167
|
-
// src/loader/unified-loader.ts
|
|
3168
|
-
var externalLoaderModule = null;
|
|
3169
|
-
async function getExternalLoaderModule() {
|
|
3170
|
-
if (externalLoaderModule) {
|
|
3171
|
-
return externalLoaderModule;
|
|
3172
|
-
}
|
|
3173
|
-
if (isBrowser()) {
|
|
3174
|
-
return null;
|
|
3175
|
-
}
|
|
3176
|
-
try {
|
|
3177
|
-
externalLoaderModule = await import('./external-loader-IN246DQM.js');
|
|
3178
|
-
return externalLoaderModule;
|
|
3179
|
-
} catch {
|
|
3180
|
-
return null;
|
|
3181
|
-
}
|
|
2792
|
+
return effect;
|
|
2793
|
+
});
|
|
3182
2794
|
}
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
2795
|
+
function renameEntityInRenderUiConfig(node, oldName, newName) {
|
|
2796
|
+
if (node === null || node === void 0) return node;
|
|
2797
|
+
if (Array.isArray(node)) {
|
|
2798
|
+
return node.map((item) => renameEntityInRenderUiConfig(item, oldName, newName));
|
|
2799
|
+
}
|
|
2800
|
+
if (typeof node !== "object") return node;
|
|
2801
|
+
const obj = node;
|
|
2802
|
+
const next = { ...obj };
|
|
2803
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
2804
|
+
if (key === "entity" && value === oldName) {
|
|
2805
|
+
next[key] = newName;
|
|
2806
|
+
continue;
|
|
3193
2807
|
}
|
|
3194
|
-
|
|
3195
|
-
return null;
|
|
2808
|
+
next[key] = renameEntityInRenderUiConfig(value, oldName, newName);
|
|
3196
2809
|
}
|
|
3197
|
-
|
|
3198
|
-
|
|
2810
|
+
return next;
|
|
2811
|
+
}
|
|
2812
|
+
function renameEntityInEffects(effects, oldName, newName) {
|
|
2813
|
+
return effects.map((effect) => renameEntityInEffect(effect, oldName, newName));
|
|
2814
|
+
}
|
|
2815
|
+
var ENTITY_AT_POS_1 = /* @__PURE__ */ new Set(["fetch", "ref", "deref", "spawn"]);
|
|
2816
|
+
var ALL_ARGS_ARE_EFFECTS = /* @__PURE__ */ new Set([
|
|
2817
|
+
"do",
|
|
2818
|
+
"atomic",
|
|
2819
|
+
"async/race",
|
|
2820
|
+
"async/all",
|
|
2821
|
+
"async/sequence"
|
|
2822
|
+
]);
|
|
2823
|
+
var ARGS_FROM_POS_2_ARE_EFFECTS = /* @__PURE__ */ new Set([
|
|
2824
|
+
"if",
|
|
2825
|
+
"when",
|
|
2826
|
+
"let",
|
|
2827
|
+
"async/delay",
|
|
2828
|
+
"async/debounce",
|
|
2829
|
+
"async/throttle",
|
|
2830
|
+
"async/interval"
|
|
2831
|
+
]);
|
|
2832
|
+
function renameEntityInEffect(effect, oldName, newName) {
|
|
2833
|
+
if (!Array.isArray(effect) || effect.length === 0) return effect;
|
|
2834
|
+
const op = effect[0];
|
|
2835
|
+
if (typeof op !== "string") return effect;
|
|
2836
|
+
if (op === "render-ui" && effect.length >= 3) {
|
|
2837
|
+
const [, slot, config, ...rest] = effect;
|
|
2838
|
+
const nextConfig = renameEntityInRenderUiConfig(config, oldName, newName);
|
|
2839
|
+
return [op, slot, nextConfig, ...rest];
|
|
3199
2840
|
}
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
newChain.chain = [...this.chain];
|
|
3203
|
-
return newChain;
|
|
2841
|
+
if (op === "persist" && effect.length >= 3 && effect[2] === oldName) {
|
|
2842
|
+
return [op, effect[1], newName, ...effect.slice(3)];
|
|
3204
2843
|
}
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
return path;
|
|
3208
|
-
}
|
|
3209
|
-
return path.replace(/\\/g, "/");
|
|
2844
|
+
if (ENTITY_AT_POS_1.has(op) && effect[1] === oldName) {
|
|
2845
|
+
return [op, newName, ...effect.slice(2)];
|
|
3210
2846
|
}
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
basePath: options.basePath,
|
|
3222
|
-
stdLibPath: options.stdLibPath,
|
|
3223
|
-
scopedPaths: options.scopedPaths,
|
|
3224
|
-
...options.http
|
|
2847
|
+
const skipFirstNonEffectArg = ARGS_FROM_POS_2_ARE_EFFECTS.has(op);
|
|
2848
|
+
const recurseAll = ALL_ARGS_ARE_EFFECTS.has(op);
|
|
2849
|
+
if (recurseAll || skipFirstNonEffectArg) {
|
|
2850
|
+
const startIndex = skipFirstNonEffectArg ? 2 : 1;
|
|
2851
|
+
return effect.map((arg, i) => {
|
|
2852
|
+
if (i < startIndex) return arg;
|
|
2853
|
+
if (Array.isArray(arg)) {
|
|
2854
|
+
return renameEntityInEffect(arg, oldName, newName);
|
|
2855
|
+
}
|
|
2856
|
+
return arg;
|
|
3225
2857
|
});
|
|
3226
2858
|
}
|
|
3227
|
-
|
|
3228
|
-
* Initialize the filesystem loader if available.
|
|
3229
|
-
*/
|
|
3230
|
-
async initFsLoader() {
|
|
3231
|
-
if (this.fsLoaderInitialized) {
|
|
3232
|
-
return;
|
|
3233
|
-
}
|
|
3234
|
-
this.fsLoaderInitialized = true;
|
|
3235
|
-
if (this.options.forceLoader === "http") {
|
|
3236
|
-
return;
|
|
3237
|
-
}
|
|
3238
|
-
const module = await getExternalLoaderModule();
|
|
3239
|
-
if (module) {
|
|
3240
|
-
this.fsLoader = new module.ExternalOrbitalLoader({
|
|
3241
|
-
basePath: this.options.basePath,
|
|
3242
|
-
stdLibPath: this.options.stdLibPath,
|
|
3243
|
-
scopedPaths: this.options.scopedPaths,
|
|
3244
|
-
...this.options.fileSystem
|
|
3245
|
-
});
|
|
3246
|
-
}
|
|
3247
|
-
}
|
|
3248
|
-
/**
|
|
3249
|
-
* Determine which loader to use for an import path.
|
|
3250
|
-
*/
|
|
3251
|
-
getLoaderForPath(importPath) {
|
|
3252
|
-
if (this.options.forceLoader) {
|
|
3253
|
-
return this.options.forceLoader;
|
|
3254
|
-
}
|
|
3255
|
-
if (importPath.startsWith("http://") || importPath.startsWith("https://")) {
|
|
3256
|
-
return "http";
|
|
3257
|
-
}
|
|
3258
|
-
if (importPath.startsWith("std/") && this.options.stdLibPath) {
|
|
3259
|
-
if (this.options.stdLibPath.startsWith("http://") || this.options.stdLibPath.startsWith("https://")) {
|
|
3260
|
-
return "http";
|
|
3261
|
-
}
|
|
3262
|
-
}
|
|
3263
|
-
if (importPath.startsWith("@") && this.options.scopedPaths) {
|
|
3264
|
-
const match = importPath.match(/^(@[^/]+)/);
|
|
3265
|
-
if (match) {
|
|
3266
|
-
const scopePath = this.options.scopedPaths[match[1]];
|
|
3267
|
-
if (scopePath && (scopePath.startsWith("http://") || scopePath.startsWith("https://"))) {
|
|
3268
|
-
return "http";
|
|
3269
|
-
}
|
|
3270
|
-
}
|
|
3271
|
-
}
|
|
3272
|
-
if (isBrowser()) {
|
|
3273
|
-
return "http";
|
|
3274
|
-
}
|
|
3275
|
-
return "filesystem";
|
|
3276
|
-
}
|
|
3277
|
-
/**
|
|
3278
|
-
* Load a schema from an import path.
|
|
3279
|
-
*
|
|
3280
|
-
* Note: We delegate chain management to the inner loader (HttpLoader or FsLoader).
|
|
3281
|
-
* The inner loader handles circular import detection, so we don't push/pop here.
|
|
3282
|
-
* We only use the unified cache to avoid duplicate loads across loaders.
|
|
3283
|
-
*/
|
|
3284
|
-
async load(importPath, fromPath, chain) {
|
|
3285
|
-
await this.initFsLoader();
|
|
3286
|
-
const importChain = chain ?? new UnifiedImportChain();
|
|
3287
|
-
const loaderType = this.getLoaderForPath(importPath);
|
|
3288
|
-
const resolveResult = this.resolvePath(importPath, fromPath);
|
|
3289
|
-
if (!resolveResult.success) {
|
|
3290
|
-
return resolveResult;
|
|
3291
|
-
}
|
|
3292
|
-
const absolutePath = resolveResult.data;
|
|
3293
|
-
const cached = this.cache.get(absolutePath);
|
|
3294
|
-
if (cached) {
|
|
3295
|
-
return { success: true, data: cached };
|
|
3296
|
-
}
|
|
3297
|
-
let result;
|
|
3298
|
-
if (loaderType === "http") {
|
|
3299
|
-
if (!this.httpLoader) {
|
|
3300
|
-
return {
|
|
3301
|
-
success: false,
|
|
3302
|
-
error: "HTTP loader not available"
|
|
3303
|
-
};
|
|
3304
|
-
}
|
|
3305
|
-
result = await this.httpLoader.load(importPath, fromPath, importChain);
|
|
3306
|
-
} else {
|
|
3307
|
-
if (!this.fsLoader) {
|
|
3308
|
-
if (this.httpLoader) {
|
|
3309
|
-
result = await this.httpLoader.load(
|
|
3310
|
-
importPath,
|
|
3311
|
-
fromPath,
|
|
3312
|
-
importChain
|
|
3313
|
-
);
|
|
3314
|
-
} else {
|
|
3315
|
-
return {
|
|
3316
|
-
success: false,
|
|
3317
|
-
error: `Filesystem loader not available and import "${importPath}" cannot be loaded via HTTP. This typically happens when loading local files in a browser environment.`
|
|
3318
|
-
};
|
|
3319
|
-
}
|
|
3320
|
-
} else {
|
|
3321
|
-
result = await this.fsLoader.load(importPath, fromPath, importChain);
|
|
3322
|
-
}
|
|
3323
|
-
}
|
|
3324
|
-
if (result.success) {
|
|
3325
|
-
this.cache.set(absolutePath, result.data);
|
|
3326
|
-
}
|
|
3327
|
-
return result;
|
|
3328
|
-
}
|
|
3329
|
-
/**
|
|
3330
|
-
* Load a specific orbital from a schema by name.
|
|
3331
|
-
*/
|
|
3332
|
-
async loadOrbital(importPath, orbitalName, fromPath, chain) {
|
|
3333
|
-
const schemaResult = await this.load(importPath, fromPath, chain);
|
|
3334
|
-
if (!schemaResult.success) {
|
|
3335
|
-
return schemaResult;
|
|
3336
|
-
}
|
|
3337
|
-
const schema = schemaResult.data.schema;
|
|
3338
|
-
if (orbitalName) {
|
|
3339
|
-
const found = schema.orbitals.find((o) => o.name === orbitalName);
|
|
3340
|
-
if (!found) {
|
|
3341
|
-
return {
|
|
3342
|
-
success: false,
|
|
3343
|
-
error: `Orbital "${orbitalName}" not found in ${importPath}. Available: ${schema.orbitals.map((o) => o.name).join(", ")}`
|
|
3344
|
-
};
|
|
3345
|
-
}
|
|
3346
|
-
return {
|
|
3347
|
-
success: true,
|
|
3348
|
-
data: {
|
|
3349
|
-
orbital: found,
|
|
3350
|
-
sourcePath: schemaResult.data.sourcePath,
|
|
3351
|
-
importPath
|
|
3352
|
-
}
|
|
3353
|
-
};
|
|
3354
|
-
}
|
|
3355
|
-
if (schema.orbitals.length === 0) {
|
|
3356
|
-
return {
|
|
3357
|
-
success: false,
|
|
3358
|
-
error: `No orbitals found in ${importPath}`
|
|
3359
|
-
};
|
|
3360
|
-
}
|
|
3361
|
-
return {
|
|
3362
|
-
success: true,
|
|
3363
|
-
data: {
|
|
3364
|
-
orbital: schema.orbitals[0],
|
|
3365
|
-
sourcePath: schemaResult.data.sourcePath,
|
|
3366
|
-
importPath
|
|
3367
|
-
}
|
|
3368
|
-
};
|
|
3369
|
-
}
|
|
3370
|
-
/**
|
|
3371
|
-
* Resolve an import path to an absolute path/URL.
|
|
3372
|
-
*/
|
|
3373
|
-
resolvePath(importPath, fromPath) {
|
|
3374
|
-
const loaderType = this.getLoaderForPath(importPath);
|
|
3375
|
-
if (loaderType === "http" && this.httpLoader) {
|
|
3376
|
-
return this.httpLoader.resolvePath(importPath, fromPath);
|
|
3377
|
-
}
|
|
3378
|
-
if (this.fsLoader) {
|
|
3379
|
-
return this.fsLoader.resolvePath(importPath, fromPath);
|
|
3380
|
-
}
|
|
3381
|
-
if (this.httpLoader) {
|
|
3382
|
-
return this.httpLoader.resolvePath(importPath, fromPath);
|
|
3383
|
-
}
|
|
3384
|
-
return {
|
|
3385
|
-
success: false,
|
|
3386
|
-
error: "No loader available for path resolution"
|
|
3387
|
-
};
|
|
3388
|
-
}
|
|
3389
|
-
/**
|
|
3390
|
-
* Clear all caches.
|
|
3391
|
-
*/
|
|
3392
|
-
clearCache() {
|
|
3393
|
-
this.cache.clear();
|
|
3394
|
-
this.httpLoader?.clearCache();
|
|
3395
|
-
this.fsLoader?.clearCache();
|
|
3396
|
-
}
|
|
3397
|
-
/**
|
|
3398
|
-
* Get combined cache statistics.
|
|
3399
|
-
*/
|
|
3400
|
-
getCacheStats() {
|
|
3401
|
-
let size = this.cache.size;
|
|
3402
|
-
if (this.httpLoader) {
|
|
3403
|
-
size += this.httpLoader.getCacheStats().size;
|
|
3404
|
-
}
|
|
3405
|
-
if (this.fsLoader) {
|
|
3406
|
-
size += this.fsLoader.getCacheStats().size;
|
|
3407
|
-
}
|
|
3408
|
-
return { size };
|
|
3409
|
-
}
|
|
3410
|
-
/**
|
|
3411
|
-
* Check if filesystem loading is available.
|
|
3412
|
-
*/
|
|
3413
|
-
async hasFilesystemAccess() {
|
|
3414
|
-
await this.initFsLoader();
|
|
3415
|
-
return this.fsLoader !== null;
|
|
3416
|
-
}
|
|
3417
|
-
/**
|
|
3418
|
-
* Get current environment info.
|
|
3419
|
-
*/
|
|
3420
|
-
getEnvironmentInfo() {
|
|
3421
|
-
return {
|
|
3422
|
-
isElectron: isElectron(),
|
|
3423
|
-
isBrowser: isBrowser(),
|
|
3424
|
-
isNode: isNode(),
|
|
3425
|
-
hasFilesystem: this.fsLoader !== null
|
|
3426
|
-
};
|
|
3427
|
-
}
|
|
3428
|
-
};
|
|
3429
|
-
function createUnifiedLoader(options) {
|
|
3430
|
-
return new UnifiedLoader(options);
|
|
2859
|
+
return effect;
|
|
3431
2860
|
}
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
const
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
persistence,
|
|
3452
|
-
isShared: persistence !== "runtime",
|
|
3453
|
-
sourceAlias: resolvedOrbital.entitySource?.alias,
|
|
3454
|
-
collectionName: resolvedOrbital.entity.collection
|
|
3455
|
-
};
|
|
3456
|
-
eventNamespaces[orbitalName] = {};
|
|
3457
|
-
for (const resolvedTrait of resolvedOrbital.traits) {
|
|
3458
|
-
const traitName = resolvedTrait.trait.name;
|
|
3459
|
-
const namespace = {
|
|
3460
|
-
emits: {},
|
|
3461
|
-
listens: {}
|
|
3462
|
-
};
|
|
3463
|
-
if (namespaceEvents && resolvedTrait.source.type === "imported") {
|
|
3464
|
-
const emits = resolvedTrait.trait.emits ?? [];
|
|
3465
|
-
for (const emit of emits) {
|
|
3466
|
-
const eventName = typeof emit === "string" ? emit : emit.event;
|
|
3467
|
-
namespace.emits[eventName] = `${orbitalName}.${traitName}.${eventName}`;
|
|
3468
|
-
}
|
|
3469
|
-
const listens = resolvedTrait.trait.listens ?? [];
|
|
3470
|
-
for (const listen of listens) {
|
|
3471
|
-
namespace.listens[listen.event] = listen.event;
|
|
3472
|
-
}
|
|
3473
|
-
}
|
|
3474
|
-
eventNamespaces[orbitalName][traitName] = namespace;
|
|
3475
|
-
}
|
|
3476
|
-
const preprocessedOrbital = {
|
|
3477
|
-
name: orbitalName,
|
|
3478
|
-
description: resolvedOrbital.original.description,
|
|
3479
|
-
visual_prompt: resolvedOrbital.original.visual_prompt,
|
|
3480
|
-
// Resolved entity (always inline now)
|
|
3481
|
-
entity: resolvedOrbital.entity,
|
|
3482
|
-
// Resolved traits (inline definitions)
|
|
3483
|
-
traits: (resolvedOrbital.traits || []).map((rt) => {
|
|
3484
|
-
if (rt.config || rt.linkedEntity) {
|
|
3485
|
-
return {
|
|
3486
|
-
ref: rt.trait.name,
|
|
3487
|
-
config: rt.config,
|
|
3488
|
-
linkedEntity: rt.linkedEntity,
|
|
3489
|
-
// Include the resolved trait definition for runtime
|
|
3490
|
-
_resolved: rt.trait
|
|
3491
|
-
};
|
|
3492
|
-
}
|
|
3493
|
-
return rt.trait;
|
|
3494
|
-
}),
|
|
3495
|
-
// Resolved pages (inline definitions with path overrides applied)
|
|
3496
|
-
pages: resolvedOrbital.pages.map((rp) => rp.page),
|
|
3497
|
-
// Preserve other fields
|
|
3498
|
-
exposes: resolvedOrbital.original.exposes,
|
|
3499
|
-
domainContext: resolvedOrbital.original.domainContext,
|
|
3500
|
-
design: resolvedOrbital.original.design,
|
|
3501
|
-
// Gap #22: pass through auxiliary entities so OrbitalServerRuntime's
|
|
3502
|
-
// mock-seed branch registers SearchResult / FilterTarget / PagedItem
|
|
3503
|
-
// alongside the molecule's primary entity. Without this, an inlined
|
|
3504
|
-
// .orb that has `auxiliaryEntities` populated by the Rust inline
|
|
3505
|
-
// phase still loses them here, and `(set @entity.searchTerm ...)` /
|
|
3506
|
-
// `(fetch SearchResult ...)` from no-rebind imports hit unregistered
|
|
3507
|
-
// persistence and silently no-op.
|
|
3508
|
-
auxiliaryEntities: resolvedOrbital.original.auxiliaryEntities
|
|
3509
|
-
};
|
|
3510
|
-
preprocessedOrbitals.push(preprocessedOrbital);
|
|
3511
|
-
}
|
|
3512
|
-
const preprocessedSchema = {
|
|
3513
|
-
...schema,
|
|
3514
|
-
orbitals: preprocessedOrbitals
|
|
3515
|
-
};
|
|
2861
|
+
function applyLinkedEntityRename(trait, linkedEntity) {
|
|
2862
|
+
const atomLinked = trait.linkedEntity;
|
|
2863
|
+
if (!linkedEntity || !atomLinked || linkedEntity === atomLinked) return trait;
|
|
2864
|
+
const sm = trait.stateMachine;
|
|
2865
|
+
if (!sm) return { ...trait, linkedEntity };
|
|
2866
|
+
const nextTransitions = (sm.transitions ?? []).map((t) => {
|
|
2867
|
+
const nextEffects = t.effects ? renameEntityInEffects(
|
|
2868
|
+
t.effects,
|
|
2869
|
+
atomLinked,
|
|
2870
|
+
linkedEntity
|
|
2871
|
+
) : t.effects;
|
|
2872
|
+
return { ...t, effects: nextEffects };
|
|
2873
|
+
});
|
|
2874
|
+
refResolverLog.info("linkedEntity:rename", {
|
|
2875
|
+
trait: trait.name,
|
|
2876
|
+
from: atomLinked,
|
|
2877
|
+
to: linkedEntity,
|
|
2878
|
+
transitionCount: nextTransitions.length
|
|
2879
|
+
});
|
|
3516
2880
|
return {
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
entitySharing,
|
|
3521
|
-
eventNamespaces,
|
|
3522
|
-
warnings
|
|
3523
|
-
}
|
|
2881
|
+
...trait,
|
|
2882
|
+
linkedEntity,
|
|
2883
|
+
stateMachine: { ...sm, transitions: nextTransitions }
|
|
3524
2884
|
};
|
|
3525
2885
|
}
|
|
3526
|
-
function
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
}
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
}
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
}
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
* of `[entityType, EntityRow[]]` entries. Rows without an `id` get one
|
|
3566
|
-
* generated at insert time; rows with an `id` keep it (so re-seeding
|
|
3567
|
-
* after a schema rebuild preserves identities used in render bindings).
|
|
3568
|
-
*/
|
|
3569
|
-
seed(seedData) {
|
|
3570
|
-
const entries = Symbol.iterator in Object(seedData) ? seedData : Object.entries(seedData);
|
|
3571
|
-
for (const [entityType, rows] of entries) {
|
|
3572
|
-
if (!this.data.has(entityType)) {
|
|
3573
|
-
this.data.set(entityType, /* @__PURE__ */ new Map());
|
|
3574
|
-
}
|
|
3575
|
-
const collection = this.data.get(entityType);
|
|
3576
|
-
for (const row of rows) {
|
|
3577
|
-
const id = row.id || `${entityType}-${++this.idCounter}`;
|
|
3578
|
-
collection.set(id, { ...row, id });
|
|
3579
|
-
}
|
|
3580
|
-
}
|
|
3581
|
-
}
|
|
3582
|
-
async create(entityType, data) {
|
|
3583
|
-
const id = data.id || `${entityType}-${++this.idCounter}`;
|
|
3584
|
-
if (!this.data.has(entityType)) {
|
|
3585
|
-
this.data.set(entityType, /* @__PURE__ */ new Map());
|
|
3586
|
-
}
|
|
3587
|
-
this.data.get(entityType).set(id, { ...data, id });
|
|
3588
|
-
return { id };
|
|
2886
|
+
function applyEventRenames(trait, renames) {
|
|
2887
|
+
if (!renames || Object.keys(renames).length === 0) return trait;
|
|
2888
|
+
const rename = (k) => k !== void 0 && k in renames ? renames[k] : k;
|
|
2889
|
+
const sm = trait.stateMachine;
|
|
2890
|
+
if (!sm) return trait;
|
|
2891
|
+
const nextTransitions = (sm.transitions ?? []).map((t) => {
|
|
2892
|
+
const nextEvent = rename(t.event) ?? t.event;
|
|
2893
|
+
const nextEffects = t.effects ? renameEventsInEffects(t.effects, rename) : t.effects;
|
|
2894
|
+
return { ...t, event: nextEvent, effects: nextEffects };
|
|
2895
|
+
});
|
|
2896
|
+
const nextEvents = (sm.events ?? []).map((e) => {
|
|
2897
|
+
const newKey = rename(e.key);
|
|
2898
|
+
if (newKey === e.key) return e;
|
|
2899
|
+
return { ...e, key: newKey ?? e.key };
|
|
2900
|
+
});
|
|
2901
|
+
const nextEmits = (trait.emits ?? []).map((em) => {
|
|
2902
|
+
if (typeof em === "string") return rename(em) ?? em;
|
|
2903
|
+
const newEvent = rename(em.event);
|
|
2904
|
+
return newEvent === em.event ? em : { ...em, event: newEvent ?? em.event };
|
|
2905
|
+
});
|
|
2906
|
+
return {
|
|
2907
|
+
...trait,
|
|
2908
|
+
stateMachine: {
|
|
2909
|
+
...sm,
|
|
2910
|
+
transitions: nextTransitions,
|
|
2911
|
+
events: nextEvents
|
|
2912
|
+
},
|
|
2913
|
+
emits: nextEmits
|
|
2914
|
+
};
|
|
2915
|
+
}
|
|
2916
|
+
var ReferenceResolver = class {
|
|
2917
|
+
loader;
|
|
2918
|
+
options;
|
|
2919
|
+
localTraits;
|
|
2920
|
+
loaderInitialized = false;
|
|
2921
|
+
constructor(options) {
|
|
2922
|
+
this.options = options;
|
|
2923
|
+
this.loader = options.loader;
|
|
2924
|
+
this.localTraits = options.localTraits ?? /* @__PURE__ */ new Map();
|
|
3589
2925
|
}
|
|
3590
|
-
async
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
2926
|
+
async ensureLoader() {
|
|
2927
|
+
if (this.loader || this.loaderInitialized) return;
|
|
2928
|
+
this.loaderInitialized = true;
|
|
2929
|
+
try {
|
|
2930
|
+
const { ExternalOrbitalLoader } = await import('./external-loader-IN246DQM.js');
|
|
2931
|
+
this.loader = new ExternalOrbitalLoader(this.options);
|
|
2932
|
+
} catch {
|
|
3595
2933
|
}
|
|
3596
2934
|
}
|
|
3597
|
-
async delete(entityType, id) {
|
|
3598
|
-
this.data.get(entityType)?.delete(id);
|
|
3599
|
-
}
|
|
3600
|
-
async getById(entityType, id) {
|
|
3601
|
-
return this.data.get(entityType)?.get(id) || null;
|
|
3602
|
-
}
|
|
3603
|
-
async list(entityType) {
|
|
3604
|
-
const collection = this.data.get(entityType);
|
|
3605
|
-
return collection ? Array.from(collection.values()) : [];
|
|
3606
|
-
}
|
|
3607
2935
|
/**
|
|
3608
|
-
*
|
|
3609
|
-
* Useful for feeding a fresh render-time binding layer with the
|
|
3610
|
-
* current persistence view.
|
|
2936
|
+
* Resolve all references in an orbital.
|
|
3611
2937
|
*/
|
|
3612
|
-
|
|
3613
|
-
const
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
}
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
if (!_resolvedNodeRequire) {
|
|
3625
|
-
const evalRequire = (0, eval)('typeof require !== "undefined" ? require : null');
|
|
3626
|
-
if (evalRequire) {
|
|
3627
|
-
_resolvedNodeRequire = evalRequire;
|
|
3628
|
-
} else {
|
|
3629
|
-
const createReq = nodeModule.createRequire;
|
|
3630
|
-
if (typeof createReq !== "function") {
|
|
3631
|
-
throw new Error(
|
|
3632
|
-
"[OrbitalServerRuntime] No synchronous require available. This branch is Node-only \u2014 invoking it from a browser indicates an isNodeEnv() guard regression upstream."
|
|
3633
|
-
);
|
|
3634
|
-
}
|
|
3635
|
-
_resolvedNodeRequire = createReq(import.meta.url);
|
|
3636
|
-
}
|
|
3637
|
-
}
|
|
3638
|
-
return _resolvedNodeRequire(modulePath);
|
|
3639
|
-
}
|
|
3640
|
-
var _nodeRequireExt = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
|
|
3641
|
-
var effectLog2 = createLogger("almadar:runtime:effects");
|
|
3642
|
-
var busLog = createLogger("almadar:runtime:bus");
|
|
3643
|
-
var renderLog2 = createLogger("almadar:runtime:render-ui");
|
|
3644
|
-
var xOrbitalLog = createLogger("almadar:runtime:cross-orbital");
|
|
3645
|
-
var persistLog = createLogger("almadar:runtime:persist");
|
|
3646
|
-
var registerLog = createLogger("almadar:runtime:register");
|
|
3647
|
-
var dynamicLog = createLogger("almadar:runtime:dynamic");
|
|
3648
|
-
function isNodeEnv() {
|
|
3649
|
-
return typeof process !== "undefined" && Boolean(process.versions?.node);
|
|
3650
|
-
}
|
|
3651
|
-
function collectDeclaredConfigDefaults(trait) {
|
|
3652
|
-
if (!trait) return void 0;
|
|
3653
|
-
const schema = trait.config;
|
|
3654
|
-
if (!schema || typeof schema !== "object") return void 0;
|
|
3655
|
-
const defaults = {};
|
|
3656
|
-
let hasAny = false;
|
|
3657
|
-
for (const [key, field] of Object.entries(schema)) {
|
|
3658
|
-
if (field && typeof field === "object" && !Array.isArray(field) && "default" in field) {
|
|
3659
|
-
const def = field.default;
|
|
3660
|
-
if (def !== void 0) {
|
|
3661
|
-
defaults[key] = def;
|
|
3662
|
-
hasAny = true;
|
|
3663
|
-
}
|
|
2938
|
+
async resolve(orbital, sourcePath, chain) {
|
|
2939
|
+
const errors = [];
|
|
2940
|
+
const warnings = [];
|
|
2941
|
+
const importChain = chain ?? { push: () => null, pop: () => {
|
|
2942
|
+
}, clone() {
|
|
2943
|
+
return this;
|
|
2944
|
+
} };
|
|
2945
|
+
const traitsList = orbital.traits ?? [];
|
|
2946
|
+
const alreadyResolved = traitsList.length > 0 && traitsList.every((t) => isInlineTrait(t));
|
|
2947
|
+
const importsResult = alreadyResolved ? { success: true, data: { orbitals: /* @__PURE__ */ new Map() }} : await this.resolveImports(orbital.uses ?? [], sourcePath, importChain);
|
|
2948
|
+
if (!importsResult.success) {
|
|
2949
|
+
return { success: false, errors: importsResult.errors };
|
|
3664
2950
|
}
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
for (const orbital of schema.orbitals) {
|
|
3670
|
-
const uses = orbital.uses;
|
|
3671
|
-
if (Array.isArray(uses) && uses.length > 0) {
|
|
3672
|
-
return true;
|
|
3673
|
-
}
|
|
3674
|
-
const traits = orbital.traits ?? [];
|
|
3675
|
-
for (const t of traits) {
|
|
3676
|
-
if (!t || typeof t !== "object") continue;
|
|
3677
|
-
const obj = t;
|
|
3678
|
-
if (typeof obj.ref === "string" && obj.ref.includes(".") && !obj.stateMachine) {
|
|
3679
|
-
return true;
|
|
3680
|
-
}
|
|
2951
|
+
const imports = importsResult.data;
|
|
2952
|
+
const entityResult = this.resolveEntity(orbital.entity, imports);
|
|
2953
|
+
if (!entityResult.success) {
|
|
2954
|
+
errors.push(...entityResult.errors);
|
|
3681
2955
|
}
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
var OrbitalServerRuntime = class {
|
|
3686
|
-
orbitals = /* @__PURE__ */ new Map();
|
|
3687
|
-
eventBus;
|
|
3688
|
-
config;
|
|
3689
|
-
persistence;
|
|
3690
|
-
listenerCleanups = [];
|
|
3691
|
-
tickBindings = [];
|
|
3692
|
-
loader = null;
|
|
3693
|
-
preprocessedCache = /* @__PURE__ */ new Map();
|
|
3694
|
-
entitySharingMap = {};
|
|
3695
|
-
eventNamespaceMap = {};
|
|
3696
|
-
osHandlers = null;
|
|
3697
|
-
localPersistence = null;
|
|
3698
|
-
resolvedSchema = null;
|
|
3699
|
-
constructor(config = {}) {
|
|
3700
|
-
this.config = {
|
|
3701
|
-
mode: "mock",
|
|
3702
|
-
// Default to mock mode for preview
|
|
3703
|
-
autoPreprocess: false,
|
|
3704
|
-
namespaceEvents: true,
|
|
3705
|
-
...config
|
|
3706
|
-
};
|
|
3707
|
-
this.eventBus = new EventBus();
|
|
3708
|
-
if (config.loaderConfig?.loader) {
|
|
3709
|
-
this.loader = config.loaderConfig.loader;
|
|
3710
|
-
} else if (config.loaderConfig?.stdLibPath) {
|
|
3711
|
-
this.loader = createUnifiedLoader({
|
|
3712
|
-
basePath: config.loaderConfig.basePath,
|
|
3713
|
-
stdLibPath: config.loaderConfig.stdLibPath,
|
|
3714
|
-
scopedPaths: config.loaderConfig.scopedPaths
|
|
3715
|
-
});
|
|
2956
|
+
const traitsResult = this.resolveTraits(orbital.traits, imports);
|
|
2957
|
+
if (!traitsResult.success) {
|
|
2958
|
+
errors.push(...traitsResult.errors);
|
|
3716
2959
|
}
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
defaultSeedCount: config.mockSeedCount ?? 6,
|
|
3721
|
-
debug: config.debug
|
|
3722
|
-
});
|
|
3723
|
-
if (config.debug) {
|
|
3724
|
-
persistLog.debug("mock:init", { adapter: "MockPersistenceAdapter" });
|
|
3725
|
-
}
|
|
3726
|
-
} else {
|
|
3727
|
-
this.persistence = config.persistence || new InMemoryPersistence();
|
|
2960
|
+
const pagesResult = this.resolvePages(orbital.pages, imports);
|
|
2961
|
+
if (!pagesResult.success) {
|
|
2962
|
+
errors.push(...pagesResult.errors);
|
|
3728
2963
|
}
|
|
3729
|
-
if (
|
|
3730
|
-
|
|
3731
|
-
this.localPersistence = new LocalPersistenceAdapter(config.localStorageRoot);
|
|
2964
|
+
if (errors.length > 0) {
|
|
2965
|
+
return { success: false, errors };
|
|
3732
2966
|
}
|
|
3733
|
-
if (
|
|
3734
|
-
|
|
3735
|
-
this.osHandlers = createOsHandlers({
|
|
3736
|
-
emitEvent: (type, payload) => this.eventBus.emit(type, payload)
|
|
3737
|
-
});
|
|
3738
|
-
} else {
|
|
3739
|
-
this.osHandlers = { handlers: {}, cleanup: () => {
|
|
3740
|
-
} };
|
|
2967
|
+
if (!entityResult.success || !traitsResult.success || !pagesResult.success) {
|
|
2968
|
+
return { success: false, errors: ["Internal error: unexpected failure state"] };
|
|
3741
2969
|
}
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
2970
|
+
return {
|
|
2971
|
+
success: true,
|
|
2972
|
+
data: {
|
|
2973
|
+
name: orbital.name,
|
|
2974
|
+
entity: entityResult.data.entity,
|
|
2975
|
+
entitySource: entityResult.data.source,
|
|
2976
|
+
traits: traitsResult.data,
|
|
2977
|
+
pages: pagesResult.data,
|
|
2978
|
+
imports,
|
|
2979
|
+
original: orbital
|
|
2980
|
+
},
|
|
2981
|
+
warnings
|
|
3745
2982
|
};
|
|
3746
2983
|
}
|
|
3747
2984
|
/**
|
|
3748
|
-
*
|
|
3749
|
-
* but `register()` needs to preprocess. Looks for `@almadar/std` in the
|
|
3750
|
-
* nearest `node_modules` so cross-orbital `std/behaviors/<name>` imports
|
|
3751
|
-
* resolve to the tiered registry on disk.
|
|
3752
|
-
*
|
|
3753
|
-
* Node only — browsers should receive already-preprocessed schemas from
|
|
3754
|
-
* their server.
|
|
2985
|
+
* Resolve `uses` declarations to loaded orbitals.
|
|
3755
2986
|
*/
|
|
3756
|
-
async
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
2987
|
+
async resolveImports(uses, sourcePath, chain) {
|
|
2988
|
+
const errors = [];
|
|
2989
|
+
const orbitals = /* @__PURE__ */ new Map();
|
|
2990
|
+
if (this.options.skipExternalLoading) {
|
|
2991
|
+
return {
|
|
2992
|
+
success: true,
|
|
2993
|
+
data: { orbitals },
|
|
2994
|
+
warnings: ["External loading skipped"]
|
|
2995
|
+
};
|
|
3760
2996
|
}
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
import
|
|
3764
|
-
|
|
3765
|
-
import('fs')
|
|
3766
|
-
]);
|
|
3767
|
-
const mainEntryUrl = import.meta.resolve("@almadar/std");
|
|
3768
|
-
const mainEntry = fileURLToPath(mainEntryUrl);
|
|
3769
|
-
let stdLibPath = path.dirname(mainEntry);
|
|
3770
|
-
while (stdLibPath !== path.dirname(stdLibPath)) {
|
|
3771
|
-
if (fs.existsSync(path.join(stdLibPath, "package.json"))) {
|
|
3772
|
-
const pkg = JSON.parse(
|
|
3773
|
-
fs.readFileSync(path.join(stdLibPath, "package.json"), "utf-8")
|
|
3774
|
-
);
|
|
3775
|
-
if (pkg.name === "@almadar/std") break;
|
|
3776
|
-
}
|
|
3777
|
-
stdLibPath = path.dirname(stdLibPath);
|
|
3778
|
-
}
|
|
3779
|
-
const basePath = this.config.loaderConfig?.basePath ?? process.cwd();
|
|
3780
|
-
this.loader = createUnifiedLoader({
|
|
3781
|
-
basePath,
|
|
3782
|
-
stdLibPath,
|
|
3783
|
-
scopedPaths: this.config.loaderConfig?.scopedPaths
|
|
3784
|
-
});
|
|
3785
|
-
if (this.config.debug) {
|
|
3786
|
-
registerLog.debug("loader:constructed", { basePath, stdLibPath });
|
|
3787
|
-
}
|
|
3788
|
-
} catch (err) {
|
|
3789
|
-
if (this.config.debug) {
|
|
3790
|
-
registerLog.warn("loader:construct-failed", { error: err instanceof Error ? err : String(err) });
|
|
2997
|
+
for (const use of uses) {
|
|
2998
|
+
if (orbitals.has(use.as)) {
|
|
2999
|
+
errors.push(`Duplicate import alias: ${use.as}`);
|
|
3000
|
+
continue;
|
|
3791
3001
|
}
|
|
3792
|
-
}
|
|
3793
|
-
}
|
|
3794
|
-
// ==========================================================================
|
|
3795
|
-
// Schema Registration
|
|
3796
|
-
// ==========================================================================
|
|
3797
|
-
/**
|
|
3798
|
-
* Register an OrbitalSchema for execution.
|
|
3799
|
-
*
|
|
3800
|
-
* Auto-preprocesses the schema when it contains `uses` declarations or
|
|
3801
|
-
* unresolved cross-orbital trait references (e.g. a trait with
|
|
3802
|
-
* `ref: "Modal.traits.ModalRecordModal"` and no inline `stateMachine`).
|
|
3803
|
-
* Without preprocessing, those refs arrive empty at the state machine and
|
|
3804
|
-
* button clicks silently do nothing — see Phase 9.5.H.
|
|
3805
|
-
*
|
|
3806
|
-
* Preprocessing needs a loader. If `loaderConfig` is set, that loader is
|
|
3807
|
-
* used. Otherwise, a default loader is constructed that points at
|
|
3808
|
-
* `<cwd>` (for `basePath`) and the nearest `node_modules/@almadar/std` (for
|
|
3809
|
-
* `stdLibPath`), which matches how every caller in this monorepo has the
|
|
3810
|
-
* std registry on disk.
|
|
3811
|
-
*/
|
|
3812
|
-
async register(schema) {
|
|
3813
|
-
if (this.config.debug) {
|
|
3814
|
-
registerLog.debug("register:schema", { name: schema.name });
|
|
3815
|
-
}
|
|
3816
|
-
if (needsPreprocessing(schema)) {
|
|
3817
3002
|
await this.ensureLoader();
|
|
3818
|
-
if (this.loader) {
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
}
|
|
3822
|
-
const result = await preprocessSchema(schema, {
|
|
3823
|
-
basePath: this.config.loaderConfig?.basePath || process.cwd(),
|
|
3824
|
-
stdLibPath: this.config.loaderConfig?.stdLibPath,
|
|
3825
|
-
scopedPaths: this.config.loaderConfig?.scopedPaths,
|
|
3826
|
-
loader: this.loader,
|
|
3827
|
-
namespaceEvents: this.config.namespaceEvents
|
|
3828
|
-
});
|
|
3829
|
-
if (!result.success) {
|
|
3830
|
-
throw new Error(
|
|
3831
|
-
`Schema preprocessing failed: ${result.errors.join("; ")}`
|
|
3832
|
-
);
|
|
3833
|
-
}
|
|
3834
|
-
schema = result.data.schema;
|
|
3835
|
-
this.entitySharingMap = {
|
|
3836
|
-
...this.entitySharingMap,
|
|
3837
|
-
...result.data.entitySharing
|
|
3838
|
-
};
|
|
3839
|
-
this.eventNamespaceMap = {
|
|
3840
|
-
...this.eventNamespaceMap,
|
|
3841
|
-
...result.data.eventNamespaces
|
|
3842
|
-
};
|
|
3843
|
-
} else if (this.config.debug) {
|
|
3844
|
-
registerLog.warn("register:no-loader", { name: schema.name });
|
|
3003
|
+
if (!this.loader) {
|
|
3004
|
+
errors.push(`No loader available to resolve import: ${use.from}`);
|
|
3005
|
+
continue;
|
|
3845
3006
|
}
|
|
3007
|
+
const loadResult = await this.loader.loadOrbital(
|
|
3008
|
+
use.from,
|
|
3009
|
+
void 0,
|
|
3010
|
+
sourcePath,
|
|
3011
|
+
chain
|
|
3012
|
+
);
|
|
3013
|
+
if (!loadResult.success) {
|
|
3014
|
+
errors.push(`Failed to load "${use.from}" as "${use.as}": ${loadResult.error}`);
|
|
3015
|
+
continue;
|
|
3016
|
+
}
|
|
3017
|
+
orbitals.set(use.as, {
|
|
3018
|
+
alias: use.as,
|
|
3019
|
+
from: use.from,
|
|
3020
|
+
orbital: loadResult.data.orbital,
|
|
3021
|
+
sourcePath: loadResult.data.sourcePath
|
|
3022
|
+
});
|
|
3846
3023
|
}
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
}
|
|
3850
|
-
this.setupEventListeners();
|
|
3851
|
-
this.setupTicks();
|
|
3852
|
-
this.resolvedSchema = schema;
|
|
3853
|
-
}
|
|
3854
|
-
/**
|
|
3855
|
-
* Register an OrbitalSchema synchronously (for backward compatibility).
|
|
3856
|
-
* Note: This version doesn't wait for instance seeding to complete.
|
|
3857
|
-
* Use async register() for guaranteed instance seeding.
|
|
3858
|
-
*/
|
|
3859
|
-
registerSync(schema) {
|
|
3860
|
-
if (this.config.debug) {
|
|
3861
|
-
registerLog.debug("register:schema-sync", { name: schema.name });
|
|
3862
|
-
}
|
|
3863
|
-
for (const orbital of schema.orbitals) {
|
|
3864
|
-
this.registerOrbital(orbital);
|
|
3024
|
+
if (errors.length > 0) {
|
|
3025
|
+
return { success: false, errors };
|
|
3865
3026
|
}
|
|
3866
|
-
|
|
3867
|
-
this.setupTicks();
|
|
3868
|
-
this.resolvedSchema = schema;
|
|
3869
|
-
}
|
|
3870
|
-
/**
|
|
3871
|
-
* Returns the schema that this runtime is currently executing, post-
|
|
3872
|
-
* preprocessing. Safe to expose from an HTTP `/api/schema` endpoint — every
|
|
3873
|
-
* cross-orbital trait ref will have an inline `stateMachine` already, which
|
|
3874
|
-
* is what the browser's `schema-to-ir` resolver needs to wire button clicks
|
|
3875
|
-
* back to state transitions.
|
|
3876
|
-
*
|
|
3877
|
-
* Returns `null` if `register()` hasn't run yet.
|
|
3878
|
-
*/
|
|
3879
|
-
getResolvedSchema() {
|
|
3880
|
-
return this.resolvedSchema;
|
|
3027
|
+
return { success: true, data: { orbitals }, warnings: [] };
|
|
3881
3028
|
}
|
|
3882
3029
|
/**
|
|
3883
|
-
*
|
|
3884
|
-
* cross-orbital imports, and register the result. Callers never touch raw
|
|
3885
|
-
* `.orb` bytes — `register()` handles preprocessing internally.
|
|
3886
|
-
*
|
|
3887
|
-
* Node only. Browsers must receive already-resolved schemas from their
|
|
3888
|
-
* server (see `getResolvedSchema()`).
|
|
3030
|
+
* Resolve entity reference.
|
|
3889
3031
|
*/
|
|
3890
|
-
|
|
3891
|
-
if (
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3032
|
+
resolveEntity(entityRef, imports) {
|
|
3033
|
+
if (isEntityCall(entityRef)) {
|
|
3034
|
+
const fallbackName = entityRef.name ?? entityRef.extends.replace(/\.entity$/, "");
|
|
3035
|
+
return {
|
|
3036
|
+
success: true,
|
|
3037
|
+
data: {
|
|
3038
|
+
entity: {
|
|
3039
|
+
name: fallbackName,
|
|
3040
|
+
fields: entityRef.fields ?? [],
|
|
3041
|
+
...entityRef.persistence ? { persistence: entityRef.persistence } : {},
|
|
3042
|
+
...entityRef.collection ? { collection: entityRef.collection } : {}
|
|
3043
|
+
}
|
|
3044
|
+
},
|
|
3045
|
+
warnings: []
|
|
3046
|
+
};
|
|
3895
3047
|
}
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3903
|
-
throw new Error(`registerFromFile: ${path} is not valid JSON: ${msg}`);
|
|
3048
|
+
if (!isEntityReference(entityRef)) {
|
|
3049
|
+
return {
|
|
3050
|
+
success: true,
|
|
3051
|
+
data: { entity: entityRef },
|
|
3052
|
+
warnings: []
|
|
3053
|
+
};
|
|
3904
3054
|
}
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
/**
|
|
3908
|
-
* Register an OrbitalSchema with preprocessing to resolve `uses` imports.
|
|
3909
|
-
*
|
|
3910
|
-
* This method:
|
|
3911
|
-
* 1. Loads all external orbitals referenced in `uses` declarations
|
|
3912
|
-
* 2. Expands entity/trait/page references to inline definitions
|
|
3913
|
-
* 3. Builds entity sharing and event namespace maps
|
|
3914
|
-
* 4. Caches the preprocessed result
|
|
3915
|
-
* 5. Registers the resolved schema
|
|
3916
|
-
*
|
|
3917
|
-
* @param schema - Schema with potential `uses` declarations
|
|
3918
|
-
* @param options - Optional preprocessing options
|
|
3919
|
-
* @returns Preprocessing result with entity sharing info
|
|
3920
|
-
*
|
|
3921
|
-
* @example
|
|
3922
|
-
* ```typescript
|
|
3923
|
-
* const runtime = new OrbitalServerRuntime({
|
|
3924
|
-
* loaderConfig: {
|
|
3925
|
-
* basePath: '/schemas',
|
|
3926
|
-
* stdLibPath: '/std',
|
|
3927
|
-
* },
|
|
3928
|
-
* });
|
|
3929
|
-
*
|
|
3930
|
-
* const result = await runtime.registerWithPreprocess(schema);
|
|
3931
|
-
* if (result.success) {
|
|
3932
|
-
* console.log('Registered with', Object.keys(result.entitySharing).length, 'orbitals');
|
|
3933
|
-
* }
|
|
3934
|
-
* ```
|
|
3935
|
-
*/
|
|
3936
|
-
async registerWithPreprocess(schema, options) {
|
|
3937
|
-
if (!this.loader && !this.config.loaderConfig) {
|
|
3055
|
+
const parsed = parseEntityRef(entityRef);
|
|
3056
|
+
if (!parsed) {
|
|
3938
3057
|
return {
|
|
3939
3058
|
success: false,
|
|
3940
|
-
errors: [
|
|
3059
|
+
errors: [`Invalid entity reference format: ${entityRef}. Expected "Alias.entity"`]
|
|
3941
3060
|
};
|
|
3942
3061
|
}
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
basePath: this.config.loaderConfig.basePath,
|
|
3946
|
-
stdLibPath: this.config.loaderConfig.stdLibPath,
|
|
3947
|
-
scopedPaths: this.config.loaderConfig.scopedPaths
|
|
3948
|
-
});
|
|
3949
|
-
}
|
|
3950
|
-
const cacheKey = `${schema.name}:${schema.version || "1.0.0"}`;
|
|
3951
|
-
const cached = this.preprocessedCache.get(cacheKey);
|
|
3952
|
-
if (cached) {
|
|
3953
|
-
if (this.config.debug) {
|
|
3954
|
-
registerLog.debug("preprocess:cache-hit", { name: schema.name });
|
|
3955
|
-
}
|
|
3956
|
-
this.register(cached.schema);
|
|
3957
|
-
this.entitySharingMap = { ...this.entitySharingMap, ...cached.entitySharing };
|
|
3958
|
-
this.eventNamespaceMap = { ...this.eventNamespaceMap, ...cached.eventNamespaces };
|
|
3062
|
+
const imported = imports.orbitals.get(parsed.alias);
|
|
3063
|
+
if (!imported) {
|
|
3959
3064
|
return {
|
|
3960
|
-
success:
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3065
|
+
success: false,
|
|
3066
|
+
errors: [
|
|
3067
|
+
`Unknown import alias in entity reference: ${parsed.alias}. Available aliases: ${Array.from(imports.orbitals.keys()).join(", ") || "none"}`
|
|
3068
|
+
]
|
|
3964
3069
|
};
|
|
3965
3070
|
}
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
}
|
|
3969
|
-
const result = await preprocessSchema(schema, {
|
|
3970
|
-
basePath: this.config.loaderConfig?.basePath || ".",
|
|
3971
|
-
stdLibPath: this.config.loaderConfig?.stdLibPath,
|
|
3972
|
-
scopedPaths: this.config.loaderConfig?.scopedPaths,
|
|
3973
|
-
loader: this.loader,
|
|
3974
|
-
namespaceEvents: this.config.namespaceEvents
|
|
3975
|
-
});
|
|
3976
|
-
if (!result.success) {
|
|
3071
|
+
const importedEntity = this.getEntityFromOrbital(imported.orbital);
|
|
3072
|
+
if (!importedEntity) {
|
|
3977
3073
|
return {
|
|
3978
3074
|
success: false,
|
|
3979
|
-
errors:
|
|
3075
|
+
errors: [
|
|
3076
|
+
`Imported orbital "${parsed.alias}" does not have an inline entity. Entity references cannot be chained.`
|
|
3077
|
+
]
|
|
3980
3078
|
};
|
|
3981
3079
|
}
|
|
3982
|
-
|
|
3983
|
-
this.entitySharingMap = { ...this.entitySharingMap, ...result.data.entitySharing };
|
|
3984
|
-
this.eventNamespaceMap = { ...this.eventNamespaceMap, ...result.data.eventNamespaces };
|
|
3985
|
-
this.register(result.data.schema);
|
|
3080
|
+
const persistence = importedEntity.persistence ?? "persistent";
|
|
3986
3081
|
return {
|
|
3987
3082
|
success: true,
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3083
|
+
data: {
|
|
3084
|
+
entity: importedEntity,
|
|
3085
|
+
source: {
|
|
3086
|
+
alias: parsed.alias,
|
|
3087
|
+
persistence
|
|
3088
|
+
}
|
|
3089
|
+
},
|
|
3090
|
+
warnings: []
|
|
3991
3091
|
};
|
|
3992
3092
|
}
|
|
3993
3093
|
/**
|
|
3994
|
-
* Get entity
|
|
3995
|
-
* Useful for determining entity isolation and collection names.
|
|
3996
|
-
*/
|
|
3997
|
-
getEntitySharing() {
|
|
3998
|
-
return { ...this.entitySharingMap };
|
|
3999
|
-
}
|
|
4000
|
-
/**
|
|
4001
|
-
* Get event namespace mapping for registered orbitals.
|
|
4002
|
-
* Useful for debugging cross-orbital event routing.
|
|
4003
|
-
*/
|
|
4004
|
-
getEventNamespaces() {
|
|
4005
|
-
return { ...this.eventNamespaceMap };
|
|
4006
|
-
}
|
|
4007
|
-
/**
|
|
4008
|
-
* Clear the preprocessing cache.
|
|
4009
|
-
*/
|
|
4010
|
-
clearPreprocessCache() {
|
|
4011
|
-
this.preprocessedCache.clear();
|
|
4012
|
-
}
|
|
4013
|
-
/**
|
|
4014
|
-
* Register a single orbital
|
|
3094
|
+
* Get the entity from an orbital (handling EntityRef).
|
|
4015
3095
|
*/
|
|
4016
|
-
|
|
4017
|
-
const configByTrait = /* @__PURE__ */ new Map();
|
|
4018
|
-
const unwrapped = (orbital.traits || []).map((t) => {
|
|
4019
|
-
if (t && typeof t === "object" && "ref" in t && "_resolved" in t) {
|
|
4020
|
-
const wrapper = t;
|
|
4021
|
-
const inner = wrapper._resolved;
|
|
4022
|
-
if (wrapper.config && inner?.name) {
|
|
4023
|
-
configByTrait.set(inner.name, wrapper.config);
|
|
4024
|
-
}
|
|
4025
|
-
return inner;
|
|
4026
|
-
}
|
|
4027
|
-
return t;
|
|
4028
|
-
});
|
|
4029
|
-
const inlineTraits = unwrapped.filter(isInlineTrait);
|
|
4030
|
-
const traitDefs = inlineTraits.map((t) => {
|
|
4031
|
-
const sm = t.stateMachine;
|
|
4032
|
-
const states = sm?.states || [];
|
|
4033
|
-
const transitions = sm?.transitions || [];
|
|
4034
|
-
return {
|
|
4035
|
-
name: t.name,
|
|
4036
|
-
states,
|
|
4037
|
-
transitions,
|
|
4038
|
-
listens: t.listens
|
|
4039
|
-
};
|
|
4040
|
-
});
|
|
4041
|
-
const manager = new StateMachineManager(traitDefs, {
|
|
4042
|
-
contextExtensions: this.config.contextExtensions
|
|
4043
|
-
});
|
|
4044
|
-
for (const [traitName, traitConfig] of configByTrait) {
|
|
4045
|
-
manager.setTraitConfig(traitName, traitConfig);
|
|
4046
|
-
}
|
|
3096
|
+
getEntityFromOrbital(orbital) {
|
|
4047
3097
|
const entityRef = orbital.entity;
|
|
4048
|
-
let entity;
|
|
4049
3098
|
if (typeof entityRef === "string") {
|
|
4050
|
-
|
|
4051
|
-
}
|
|
3099
|
+
return null;
|
|
3100
|
+
}
|
|
3101
|
+
if (isEntityCall(entityRef)) {
|
|
4052
3102
|
const fallbackName = entityRef.name ?? entityRef.extends.replace(/\.entity$/, "");
|
|
4053
|
-
|
|
3103
|
+
return {
|
|
4054
3104
|
name: fallbackName,
|
|
4055
3105
|
fields: entityRef.fields ?? [],
|
|
4056
3106
|
...entityRef.persistence ? { persistence: entityRef.persistence } : {},
|
|
4057
3107
|
...entityRef.collection ? { collection: entityRef.collection } : {}
|
|
4058
3108
|
};
|
|
4059
|
-
} else {
|
|
4060
|
-
entity = entityRef;
|
|
4061
|
-
}
|
|
4062
|
-
this.orbitals.set(orbital.name, {
|
|
4063
|
-
schema: orbital,
|
|
4064
|
-
entity,
|
|
4065
|
-
traits: inlineTraits,
|
|
4066
|
-
configByTrait,
|
|
4067
|
-
manager,
|
|
4068
|
-
entityData: /* @__PURE__ */ new Map(),
|
|
4069
|
-
traitFieldStates: /* @__PURE__ */ new Map()
|
|
4070
|
-
});
|
|
4071
|
-
if (entity?.name && entity.instances && Array.isArray(entity.instances)) {
|
|
4072
|
-
const instances = entity.instances;
|
|
4073
|
-
if (instances.length > 0) {
|
|
4074
|
-
persistLog.debug("seed:start", { entity: entity.name, count: instances.length });
|
|
4075
|
-
const results = await Promise.all(
|
|
4076
|
-
instances.map(async (instance) => {
|
|
4077
|
-
try {
|
|
4078
|
-
const result = await this.persistence.create(entity.name, instance);
|
|
4079
|
-
persistLog.debug("seed:instance", { entity: entity.name, id: instance.id ?? "no-id" });
|
|
4080
|
-
return result;
|
|
4081
|
-
} catch (err) {
|
|
4082
|
-
persistLog.error("seed:instance-error", {
|
|
4083
|
-
entity: entity.name,
|
|
4084
|
-
id: instance.id,
|
|
4085
|
-
error: err instanceof Error ? err : String(err)
|
|
4086
|
-
});
|
|
4087
|
-
return null;
|
|
4088
|
-
}
|
|
4089
|
-
})
|
|
4090
|
-
);
|
|
4091
|
-
const successCount = results.filter((r) => r !== null).length;
|
|
4092
|
-
persistLog.debug("seed:done", { entity: entity.name, success: successCount, total: instances.length });
|
|
4093
|
-
}
|
|
4094
|
-
} else if (this.config.mode === "mock" && this.persistence instanceof MockPersistenceAdapter) {
|
|
4095
|
-
if (this.config.debug) {
|
|
4096
|
-
persistLog.debug("mock:generate", { entity: entity?.name });
|
|
4097
|
-
}
|
|
4098
|
-
if (entity?.name && entity.fields) {
|
|
4099
|
-
const fields = entity.fields.filter(
|
|
4100
|
-
(f) => typeof f.name === "string" && f.name.length > 0
|
|
4101
|
-
);
|
|
4102
|
-
this.persistence.registerEntity({ name: entity.name, fields });
|
|
4103
|
-
if (this.config.debug) {
|
|
4104
|
-
persistLog.debug("mock:seeded", { entity: entity.name, count: this.persistence.count(entity.name) });
|
|
4105
|
-
}
|
|
4106
|
-
}
|
|
4107
|
-
}
|
|
4108
|
-
const auxiliaryEntities = orbital.auxiliaryEntities;
|
|
4109
|
-
if (auxiliaryEntities !== void 0 && auxiliaryEntities.length > 0 && this.config.mode === "mock" && this.persistence instanceof MockPersistenceAdapter) {
|
|
4110
|
-
for (const auxRef of auxiliaryEntities) {
|
|
4111
|
-
if (typeof auxRef === "string" || isEntityCall(auxRef)) continue;
|
|
4112
|
-
const auxEntity = auxRef;
|
|
4113
|
-
if (!auxEntity.name || !auxEntity.fields) continue;
|
|
4114
|
-
const auxFields = auxEntity.fields.filter(
|
|
4115
|
-
(f) => typeof f.name === "string" && f.name.length > 0
|
|
4116
|
-
);
|
|
4117
|
-
this.persistence.registerEntity({ name: auxEntity.name, fields: auxFields });
|
|
4118
|
-
if (this.config.debug) {
|
|
4119
|
-
persistLog.debug("mock:seeded-auxiliary", {
|
|
4120
|
-
entity: auxEntity.name,
|
|
4121
|
-
count: this.persistence.count(auxEntity.name)
|
|
4122
|
-
});
|
|
4123
|
-
}
|
|
4124
|
-
}
|
|
4125
|
-
}
|
|
4126
|
-
if (this.config.debug) {
|
|
4127
|
-
registerLog.debug("register:orbital", {
|
|
4128
|
-
name: orbital.name,
|
|
4129
|
-
traitCount: (orbital.traits || []).length
|
|
4130
|
-
});
|
|
4131
|
-
}
|
|
4132
|
-
}
|
|
4133
|
-
/**
|
|
4134
|
-
* Register a single orbital (sync wrapper for backward compatibility)
|
|
4135
|
-
*/
|
|
4136
|
-
registerOrbital(orbital) {
|
|
4137
|
-
this.registerOrbitalAsync(orbital).catch((err) => {
|
|
4138
|
-
registerLog.error("register:failed", {
|
|
4139
|
-
name: orbital.name,
|
|
4140
|
-
error: err instanceof Error ? err : String(err)
|
|
4141
|
-
});
|
|
4142
|
-
});
|
|
4143
|
-
}
|
|
4144
|
-
/**
|
|
4145
|
-
* Set up event listeners for cross-orbital communication
|
|
4146
|
-
*/
|
|
4147
|
-
setupEventListeners() {
|
|
4148
|
-
for (const cleanup of this.listenerCleanups) {
|
|
4149
|
-
cleanup();
|
|
4150
|
-
}
|
|
4151
|
-
this.listenerCleanups = [];
|
|
4152
|
-
for (const [orbitalName, registered] of this.orbitals) {
|
|
4153
|
-
for (const trait of registered.traits) {
|
|
4154
|
-
if (!trait.listens) continue;
|
|
4155
|
-
for (const listener of trait.listens) {
|
|
4156
|
-
const { bareEvent, matcher } = parseListenSource(listener, orbitalName);
|
|
4157
|
-
const cleanup = this.eventBus.on(bareEvent, async (event) => {
|
|
4158
|
-
if (!matcher(event.source)) return;
|
|
4159
|
-
if (this.config.debug) {
|
|
4160
|
-
xOrbitalLog.debug("listen:received", () => ({
|
|
4161
|
-
receiverOrbital: orbitalName,
|
|
4162
|
-
receiverTrait: trait.name,
|
|
4163
|
-
event: listener.event,
|
|
4164
|
-
sourceOrbital: event.source?.orbital ?? "?",
|
|
4165
|
-
sourceTrait: event.source?.trait ?? "?"
|
|
4166
|
-
}));
|
|
4167
|
-
}
|
|
4168
|
-
let mappedPayload = event.payload;
|
|
4169
|
-
if (listener.payloadMapping && event.payload) {
|
|
4170
|
-
mappedPayload = {};
|
|
4171
|
-
for (const [key, expr] of Object.entries(
|
|
4172
|
-
listener.payloadMapping
|
|
4173
|
-
)) {
|
|
4174
|
-
if (typeof expr === "string" && expr.startsWith("@payload.")) {
|
|
4175
|
-
const field = expr.slice("@payload.".length);
|
|
4176
|
-
mappedPayload[key] = event.payload[field];
|
|
4177
|
-
} else {
|
|
4178
|
-
mappedPayload[key] = expr;
|
|
4179
|
-
}
|
|
4180
|
-
}
|
|
4181
|
-
}
|
|
4182
|
-
const raw = event.payload;
|
|
4183
|
-
const mapped = mappedPayload;
|
|
4184
|
-
const pickId = (field) => mapped?.[field] ?? raw?.[field];
|
|
4185
|
-
const forwardedEntityId = pickId("entityId") ?? pickId("orbitalName");
|
|
4186
|
-
await this.processOrbitalEvent(orbitalName, {
|
|
4187
|
-
event: listener.triggers,
|
|
4188
|
-
payload: mappedPayload,
|
|
4189
|
-
entityId: forwardedEntityId
|
|
4190
|
-
});
|
|
4191
|
-
});
|
|
4192
|
-
this.listenerCleanups.push(cleanup);
|
|
4193
|
-
}
|
|
4194
|
-
}
|
|
4195
3109
|
}
|
|
3110
|
+
return entityRef;
|
|
4196
3111
|
}
|
|
4197
3112
|
/**
|
|
4198
|
-
*
|
|
3113
|
+
* Resolve trait references.
|
|
4199
3114
|
*/
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
3115
|
+
resolveTraits(traitRefs, imports) {
|
|
3116
|
+
const errors = [];
|
|
3117
|
+
const resolved = [];
|
|
3118
|
+
for (const traitRef of traitRefs) {
|
|
3119
|
+
const result = this.resolveTraitRef(traitRef, imports);
|
|
3120
|
+
if (!result.success) {
|
|
3121
|
+
errors.push(...result.errors);
|
|
3122
|
+
} else {
|
|
3123
|
+
resolved.push(result.data);
|
|
4208
3124
|
}
|
|
4209
3125
|
}
|
|
4210
|
-
if (
|
|
4211
|
-
|
|
3126
|
+
if (errors.length > 0) {
|
|
3127
|
+
return { success: false, errors };
|
|
4212
3128
|
}
|
|
3129
|
+
return { success: true, data: resolved, warnings: [] };
|
|
4213
3130
|
}
|
|
4214
3131
|
/**
|
|
4215
|
-
*
|
|
3132
|
+
* Resolve a single trait reference.
|
|
4216
3133
|
*/
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
3134
|
+
resolveTraitRef(traitRef, imports) {
|
|
3135
|
+
if (typeof traitRef !== "string" && "stateMachine" in traitRef) {
|
|
3136
|
+
return {
|
|
3137
|
+
success: true,
|
|
3138
|
+
data: {
|
|
3139
|
+
trait: traitRef,
|
|
3140
|
+
source: { type: "inline" }
|
|
3141
|
+
},
|
|
3142
|
+
warnings: []
|
|
3143
|
+
};
|
|
4225
3144
|
}
|
|
4226
|
-
if (
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
3145
|
+
if (typeof traitRef !== "string" && "ref" in traitRef) {
|
|
3146
|
+
const refObj = traitRef;
|
|
3147
|
+
return this.resolveTraitRefString(
|
|
3148
|
+
refObj.ref,
|
|
3149
|
+
imports,
|
|
3150
|
+
refObj.config,
|
|
3151
|
+
refObj.linkedEntity,
|
|
3152
|
+
refObj.name,
|
|
3153
|
+
refObj.events,
|
|
3154
|
+
refObj.listens
|
|
3155
|
+
);
|
|
4233
3156
|
}
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
}
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
timerId
|
|
4242
|
-
});
|
|
3157
|
+
if (typeof traitRef === "string") {
|
|
3158
|
+
return this.resolveTraitRefString(traitRef, imports);
|
|
3159
|
+
}
|
|
3160
|
+
return {
|
|
3161
|
+
success: false,
|
|
3162
|
+
errors: [`Unknown trait reference format: ${JSON.stringify(traitRef)}`]
|
|
3163
|
+
};
|
|
4243
3164
|
}
|
|
4244
3165
|
/**
|
|
4245
|
-
*
|
|
4246
|
-
* Supports: '5s', '1m', '1h', '30000' (ms)
|
|
3166
|
+
* Resolve a trait reference string.
|
|
4247
3167
|
*/
|
|
4248
|
-
|
|
4249
|
-
const
|
|
4250
|
-
if (
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
return
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
3168
|
+
resolveTraitRefString(ref, imports, config, linkedEntity, overrideName, eventRenames, listensOverride) {
|
|
3169
|
+
const parsed = parseImportedTraitRef(ref);
|
|
3170
|
+
if (parsed) {
|
|
3171
|
+
const imported = imports.orbitals.get(parsed.alias);
|
|
3172
|
+
if (!imported) {
|
|
3173
|
+
return {
|
|
3174
|
+
success: false,
|
|
3175
|
+
errors: [
|
|
3176
|
+
`Unknown import alias in trait reference: ${parsed.alias}. Available aliases: ${Array.from(imports.orbitals.keys()).join(", ") || "none"}`
|
|
3177
|
+
]
|
|
3178
|
+
};
|
|
3179
|
+
}
|
|
3180
|
+
const trait = this.findTraitInOrbital(imported.orbital, parsed.traitName);
|
|
3181
|
+
if (!trait) {
|
|
3182
|
+
return {
|
|
3183
|
+
success: false,
|
|
3184
|
+
errors: [
|
|
3185
|
+
`Trait "${parsed.traitName}" not found in imported orbital "${parsed.alias}". Available traits: ${this.listTraitsInOrbital(imported.orbital).join(", ") || "none"}`
|
|
3186
|
+
]
|
|
3187
|
+
};
|
|
3188
|
+
}
|
|
3189
|
+
const baseTrait = overrideName ? { ...trait, name: overrideName } : trait;
|
|
3190
|
+
const reboundTrait = applyLinkedEntityRename(baseTrait, linkedEntity);
|
|
3191
|
+
const renamedTrait = applyEventRenames(reboundTrait, eventRenames);
|
|
3192
|
+
const finalTrait = listensOverride !== void 0 ? { ...renamedTrait, listens: listensOverride } : renamedTrait;
|
|
3193
|
+
if (listensOverride !== void 0) {
|
|
3194
|
+
refResolverLog.info("listens-override:imported", {
|
|
3195
|
+
trait: finalTrait.name,
|
|
3196
|
+
ref,
|
|
3197
|
+
atomListens: trait.listens?.length ?? 0,
|
|
3198
|
+
callSiteListens: listensOverride.length
|
|
3199
|
+
});
|
|
3200
|
+
}
|
|
3201
|
+
return {
|
|
3202
|
+
success: true,
|
|
3203
|
+
data: {
|
|
3204
|
+
trait: finalTrait,
|
|
3205
|
+
source: { type: "imported", alias: parsed.alias, traitName: parsed.traitName },
|
|
3206
|
+
config,
|
|
3207
|
+
linkedEntity
|
|
3208
|
+
},
|
|
3209
|
+
warnings: []
|
|
3210
|
+
};
|
|
3211
|
+
}
|
|
3212
|
+
const localTrait = this.localTraits.get(ref);
|
|
3213
|
+
if (localTrait) {
|
|
3214
|
+
const baseLocal = overrideName ? { ...localTrait, name: overrideName } : localTrait;
|
|
3215
|
+
const reboundLocal = applyLinkedEntityRename(baseLocal, linkedEntity);
|
|
3216
|
+
const renamedLocalTrait = applyEventRenames(reboundLocal, eventRenames);
|
|
3217
|
+
const finalLocalTrait = listensOverride !== void 0 ? { ...renamedLocalTrait, listens: listensOverride } : renamedLocalTrait;
|
|
3218
|
+
if (listensOverride !== void 0) {
|
|
3219
|
+
refResolverLog.info("listens-override:local", {
|
|
3220
|
+
trait: finalLocalTrait.name,
|
|
3221
|
+
ref,
|
|
3222
|
+
atomListens: localTrait.listens?.length ?? 0,
|
|
3223
|
+
callSiteListens: listensOverride.length
|
|
3224
|
+
});
|
|
3225
|
+
}
|
|
3226
|
+
return {
|
|
3227
|
+
success: true,
|
|
3228
|
+
data: {
|
|
3229
|
+
trait: finalLocalTrait,
|
|
3230
|
+
source: { type: "local", name: ref },
|
|
3231
|
+
config,
|
|
3232
|
+
linkedEntity
|
|
3233
|
+
},
|
|
3234
|
+
warnings: []
|
|
3235
|
+
};
|
|
4267
3236
|
}
|
|
3237
|
+
return {
|
|
3238
|
+
success: false,
|
|
3239
|
+
errors: [
|
|
3240
|
+
`Trait "${ref}" not found. For imported traits, use format "Alias.traits.TraitName". Local traits available: ${Array.from(this.localTraits.keys()).join(", ") || "none"}`
|
|
3241
|
+
]
|
|
3242
|
+
};
|
|
4268
3243
|
}
|
|
4269
3244
|
/**
|
|
4270
|
-
*
|
|
3245
|
+
* Find a trait in an orbital by name.
|
|
4271
3246
|
*/
|
|
4272
|
-
|
|
4273
|
-
const
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
if (tick.appliesTo && tick.appliesTo.length > 0) {
|
|
4278
|
-
const appliesToSet = new Set(tick.appliesTo);
|
|
4279
|
-
entities = entities.filter((e) => appliesToSet.has(e.id));
|
|
4280
|
-
}
|
|
4281
|
-
if (this.config.debug && entities.length > 0) {
|
|
4282
|
-
effectLog2.debug("tick:processing", () => ({
|
|
4283
|
-
orbital: orbitalName,
|
|
4284
|
-
trait: traitName,
|
|
4285
|
-
tick: tick.name,
|
|
4286
|
-
entityCount: entities.length
|
|
4287
|
-
}));
|
|
4288
|
-
}
|
|
4289
|
-
for (const entity of entities) {
|
|
4290
|
-
if (tick.guard) {
|
|
4291
|
-
try {
|
|
4292
|
-
const ctx = createContextFromBindings({
|
|
4293
|
-
entity,
|
|
4294
|
-
payload: {},
|
|
4295
|
-
state: registered.manager.getState(traitName)?.currentState || "unknown"
|
|
4296
|
-
}, false, this.config.contextExtensions);
|
|
4297
|
-
const guardPasses = evaluateGuard(
|
|
4298
|
-
tick.guard,
|
|
4299
|
-
ctx
|
|
4300
|
-
);
|
|
4301
|
-
if (!guardPasses) {
|
|
4302
|
-
if (this.config.debug) {
|
|
4303
|
-
effectLog2.debug("tick:guard-failed", () => ({
|
|
4304
|
-
tick: tick.name,
|
|
4305
|
-
entityId: typeof entity.id === "string" ? entity.id : void 0
|
|
4306
|
-
}));
|
|
4307
|
-
}
|
|
4308
|
-
continue;
|
|
4309
|
-
}
|
|
4310
|
-
} catch (error) {
|
|
4311
|
-
effectLog2.error("tick:guard-error", {
|
|
4312
|
-
tick: tick.name,
|
|
4313
|
-
entityId: typeof entity.id === "string" ? entity.id : void 0,
|
|
4314
|
-
error: error instanceof Error ? error : String(error)
|
|
4315
|
-
});
|
|
4316
|
-
continue;
|
|
4317
|
-
}
|
|
4318
|
-
}
|
|
4319
|
-
if (tick.effects && tick.effects.length > 0) {
|
|
4320
|
-
const fetchedData = {};
|
|
4321
|
-
const clientEffects = [];
|
|
4322
|
-
const tickEffectResults = [];
|
|
4323
|
-
await this.executeEffects(
|
|
4324
|
-
registered,
|
|
4325
|
-
traitName,
|
|
4326
|
-
tick.effects,
|
|
4327
|
-
{},
|
|
4328
|
-
// No payload for ticks
|
|
4329
|
-
entity,
|
|
4330
|
-
entity.id,
|
|
4331
|
-
emittedEvents,
|
|
4332
|
-
fetchedData,
|
|
4333
|
-
clientEffects,
|
|
4334
|
-
tickEffectResults
|
|
4335
|
-
);
|
|
4336
|
-
if (this.config.debug) {
|
|
4337
|
-
effectLog2.debug("tick:effects-executed", () => ({
|
|
4338
|
-
tick: tick.name,
|
|
4339
|
-
entityId: typeof entity.id === "string" ? entity.id : void 0
|
|
4340
|
-
}));
|
|
4341
|
-
}
|
|
3247
|
+
findTraitInOrbital(orbital, traitName) {
|
|
3248
|
+
for (const traitRef of orbital.traits) {
|
|
3249
|
+
if (typeof traitRef !== "string" && "stateMachine" in traitRef) {
|
|
3250
|
+
if (traitRef.name === traitName) {
|
|
3251
|
+
return traitRef;
|
|
4342
3252
|
}
|
|
4343
3253
|
}
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
});
|
|
3254
|
+
if (typeof traitRef !== "string" && "ref" in traitRef) {
|
|
3255
|
+
const refObj = traitRef;
|
|
3256
|
+
if (refObj.ref === traitName || refObj.name === traitName) ;
|
|
3257
|
+
}
|
|
4349
3258
|
}
|
|
3259
|
+
return null;
|
|
4350
3260
|
}
|
|
4351
3261
|
/**
|
|
4352
|
-
*
|
|
3262
|
+
* List trait names in an orbital.
|
|
4353
3263
|
*/
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
3264
|
+
listTraitsInOrbital(orbital) {
|
|
3265
|
+
const names = [];
|
|
3266
|
+
for (const traitRef of orbital.traits) {
|
|
3267
|
+
if (typeof traitRef !== "string" && "stateMachine" in traitRef) {
|
|
3268
|
+
names.push(traitRef.name);
|
|
3269
|
+
}
|
|
4357
3270
|
}
|
|
4358
|
-
|
|
3271
|
+
return names;
|
|
4359
3272
|
}
|
|
4360
3273
|
/**
|
|
4361
|
-
*
|
|
3274
|
+
* Resolve page references.
|
|
4362
3275
|
*/
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
3276
|
+
resolvePages(pageRefs, imports) {
|
|
3277
|
+
const errors = [];
|
|
3278
|
+
const resolved = [];
|
|
3279
|
+
for (const pageRef of pageRefs) {
|
|
3280
|
+
const result = this.resolvePageRef(pageRef, imports);
|
|
3281
|
+
if (!result.success) {
|
|
3282
|
+
errors.push(...result.errors);
|
|
3283
|
+
} else {
|
|
3284
|
+
resolved.push(result.data);
|
|
3285
|
+
}
|
|
4373
3286
|
}
|
|
4374
|
-
if (
|
|
4375
|
-
|
|
4376
|
-
this.osHandlers = null;
|
|
3287
|
+
if (errors.length > 0) {
|
|
3288
|
+
return { success: false, errors };
|
|
4377
3289
|
}
|
|
3290
|
+
return { success: true, data: resolved, warnings: [] };
|
|
4378
3291
|
}
|
|
4379
3292
|
/**
|
|
4380
|
-
*
|
|
4381
|
-
* unregistering orbitals. Exposed for verifier tools that want to
|
|
4382
|
-
* start each test with deterministic seeded rows, not the residue of
|
|
4383
|
-
* the previous walk's persist-creates. No-op when the persistence
|
|
4384
|
-
* layer is not MockPersistenceAdapter.
|
|
3293
|
+
* Resolve a single page reference.
|
|
4385
3294
|
*/
|
|
4386
|
-
|
|
4387
|
-
if (!(
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
(f) => typeof f.name === "string" && f.name.length > 0
|
|
4398
|
-
);
|
|
4399
|
-
this.persistence.registerEntity({ name: entity.name, fields });
|
|
4400
|
-
}
|
|
3295
|
+
resolvePageRef(pageRef, imports) {
|
|
3296
|
+
if (!isPageReference(pageRef)) {
|
|
3297
|
+
return {
|
|
3298
|
+
success: true,
|
|
3299
|
+
data: {
|
|
3300
|
+
page: pageRef,
|
|
3301
|
+
source: { type: "inline" },
|
|
3302
|
+
pathOverridden: false
|
|
3303
|
+
},
|
|
3304
|
+
warnings: []
|
|
3305
|
+
};
|
|
4401
3306
|
}
|
|
3307
|
+
if (isPageReferenceString(pageRef)) {
|
|
3308
|
+
return this.resolvePageRefString(pageRef, imports);
|
|
3309
|
+
}
|
|
3310
|
+
if (isPageReferenceObject(pageRef)) {
|
|
3311
|
+
return this.resolvePageRefObject(pageRef, imports);
|
|
3312
|
+
}
|
|
3313
|
+
return {
|
|
3314
|
+
success: false,
|
|
3315
|
+
errors: [`Unknown page reference format: ${JSON.stringify(pageRef)}`]
|
|
3316
|
+
};
|
|
4402
3317
|
}
|
|
4403
|
-
// ==========================================================================
|
|
4404
|
-
// Event Processing
|
|
4405
|
-
// ==========================================================================
|
|
4406
3318
|
/**
|
|
4407
|
-
*
|
|
3319
|
+
* Resolve a page reference string.
|
|
4408
3320
|
*/
|
|
4409
|
-
|
|
4410
|
-
const
|
|
4411
|
-
if (!
|
|
3321
|
+
resolvePageRefString(ref, imports) {
|
|
3322
|
+
const parsed = parsePageRef(ref);
|
|
3323
|
+
if (!parsed) {
|
|
4412
3324
|
return {
|
|
4413
3325
|
success: false,
|
|
4414
|
-
|
|
4415
|
-
states: {},
|
|
4416
|
-
emittedEvents: [],
|
|
4417
|
-
error: `Orbital not found: ${orbitalName}`
|
|
3326
|
+
errors: [`Invalid page reference format: ${ref}. Expected "Alias.pages.PageName"`]
|
|
4418
3327
|
};
|
|
4419
3328
|
}
|
|
4420
|
-
const
|
|
4421
|
-
|
|
4422
|
-
const payloadRowId = payloadRowAsPayload?.["id"];
|
|
4423
|
-
renderLog2.debug("processOrbitalEvent:enter", {
|
|
4424
|
-
orbital: orbitalName,
|
|
4425
|
-
event: request.event,
|
|
4426
|
-
hasPayloadRow: payloadRowAsPayload !== void 0,
|
|
4427
|
-
payloadRowId: typeof payloadRowId === "string" || typeof payloadRowId === "number" ? payloadRowId : void 0,
|
|
4428
|
-
entityId: request.entityId
|
|
4429
|
-
});
|
|
4430
|
-
busLog.debug("bus:incoming", () => ({
|
|
4431
|
-
orbital: orbitalName,
|
|
4432
|
-
event: request.event,
|
|
4433
|
-
payload: JSON.stringify(request.payload ?? null),
|
|
4434
|
-
entityId: request.entityId,
|
|
4435
|
-
traitStates: JSON.stringify(
|
|
4436
|
-
Array.from(registered.manager.getAllStates().entries()).map(([traitName, state]) => ({
|
|
4437
|
-
traitName,
|
|
4438
|
-
currentState: state.currentState
|
|
4439
|
-
}))
|
|
4440
|
-
)
|
|
4441
|
-
}));
|
|
4442
|
-
xOrbitalLog.info("processOrbitalEvent:enter", () => ({
|
|
4443
|
-
orbital: orbitalName,
|
|
4444
|
-
event: request.event,
|
|
4445
|
-
traitsInOrbital: registered.traits.map((t) => t.name).join(","),
|
|
4446
|
-
payloadActiveTraits: JSON.stringify(
|
|
4447
|
-
request.payload?.["_activeTraits"] ?? null
|
|
4448
|
-
)
|
|
4449
|
-
}));
|
|
4450
|
-
const { event, payload, entityId, user } = request;
|
|
4451
|
-
const validationFailures = [];
|
|
4452
|
-
for (const trait of registered.traits) {
|
|
4453
|
-
const eventSchema = trait.stateMachine?.events?.find((e) => e.key === event);
|
|
4454
|
-
if (eventSchema?.payloadSchema && eventSchema.payloadSchema.length > 0) {
|
|
4455
|
-
validationFailures.push(
|
|
4456
|
-
...validateEventPayload(event, payload, eventSchema.payloadSchema)
|
|
4457
|
-
);
|
|
4458
|
-
}
|
|
4459
|
-
}
|
|
4460
|
-
if (validationFailures.length > 0) {
|
|
3329
|
+
const imported = imports.orbitals.get(parsed.alias);
|
|
3330
|
+
if (!imported) {
|
|
4461
3331
|
return {
|
|
4462
3332
|
success: false,
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
error: formatPayloadValidationError(validationFailures)
|
|
3333
|
+
errors: [
|
|
3334
|
+
`Unknown import alias in page reference: ${parsed.alias}. Available aliases: ${Array.from(imports.orbitals.keys()).join(", ") || "none"}`
|
|
3335
|
+
]
|
|
4467
3336
|
};
|
|
4468
3337
|
}
|
|
4469
|
-
const
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
delete cleanPayload._activeTraits;
|
|
4478
|
-
}
|
|
4479
|
-
let entityData = {};
|
|
4480
|
-
if (entityId) {
|
|
4481
|
-
const stored = await this.persistence.getById(
|
|
4482
|
-
registered.entity.name,
|
|
4483
|
-
entityId
|
|
4484
|
-
);
|
|
4485
|
-
if (stored) {
|
|
4486
|
-
entityData = stored;
|
|
4487
|
-
}
|
|
4488
|
-
}
|
|
4489
|
-
const entityByTrait = {};
|
|
4490
|
-
for (const [name, fields] of registered.traitFieldStates) {
|
|
4491
|
-
if (fields && Object.keys(fields).length > 0) {
|
|
4492
|
-
entityByTrait[name] = fields;
|
|
4493
|
-
}
|
|
4494
|
-
}
|
|
4495
|
-
const results = registered.manager.sendEvent(
|
|
4496
|
-
event,
|
|
4497
|
-
cleanPayload,
|
|
4498
|
-
entityData,
|
|
4499
|
-
entityByTrait
|
|
4500
|
-
);
|
|
4501
|
-
const filteredResults = activeTraits && activeTraits.length > 0 ? results.filter(({ traitName }) => activeTraits.includes(traitName)) : results;
|
|
4502
|
-
if (this.config.debug && activeTraits) {
|
|
4503
|
-
busLog.debug("dispatch:filter-traits", () => ({
|
|
4504
|
-
total: results.length,
|
|
4505
|
-
active: filteredResults.length,
|
|
4506
|
-
activeTraits: activeTraits.join(",")
|
|
4507
|
-
}));
|
|
4508
|
-
}
|
|
4509
|
-
for (const { traitName, result } of filteredResults) {
|
|
4510
|
-
if (result.effects.length > 0) {
|
|
4511
|
-
await this.executeEffects(
|
|
4512
|
-
registered,
|
|
4513
|
-
traitName,
|
|
4514
|
-
result.effects,
|
|
4515
|
-
cleanPayload,
|
|
4516
|
-
entityData,
|
|
4517
|
-
entityId,
|
|
4518
|
-
emittedEvents,
|
|
4519
|
-
fetchedData,
|
|
4520
|
-
clientEffects,
|
|
4521
|
-
effectResults,
|
|
4522
|
-
user,
|
|
4523
|
-
clientEffectsByTrait
|
|
4524
|
-
);
|
|
4525
|
-
}
|
|
4526
|
-
}
|
|
4527
|
-
const states = {};
|
|
4528
|
-
for (const [name, state] of registered.manager.getAllStates()) {
|
|
4529
|
-
states[name] = state.currentState;
|
|
3338
|
+
const page = this.findPageInOrbital(imported.orbital, parsed.pageName);
|
|
3339
|
+
if (!page) {
|
|
3340
|
+
return {
|
|
3341
|
+
success: false,
|
|
3342
|
+
errors: [
|
|
3343
|
+
`Page "${parsed.pageName}" not found in imported orbital "${parsed.alias}". Available pages: ${this.listPagesInOrbital(imported.orbital).join(", ") || "none"}`
|
|
3344
|
+
]
|
|
3345
|
+
};
|
|
4530
3346
|
}
|
|
4531
|
-
|
|
3347
|
+
return {
|
|
4532
3348
|
success: true,
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
3349
|
+
data: {
|
|
3350
|
+
page,
|
|
3351
|
+
source: { type: "imported", alias: parsed.alias, pageName: parsed.pageName },
|
|
3352
|
+
pathOverridden: false
|
|
3353
|
+
},
|
|
3354
|
+
warnings: []
|
|
4536
3355
|
};
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
3356
|
+
}
|
|
3357
|
+
/**
|
|
3358
|
+
* Resolve a page reference object with optional path override.
|
|
3359
|
+
*/
|
|
3360
|
+
resolvePageRefObject(refObj, imports) {
|
|
3361
|
+
const baseResult = this.resolvePageRefString(refObj.ref, imports);
|
|
3362
|
+
if (!baseResult.success) {
|
|
3363
|
+
return baseResult;
|
|
4542
3364
|
}
|
|
4543
|
-
|
|
4544
|
-
|
|
3365
|
+
const resolved = baseResult.data;
|
|
3366
|
+
if (refObj.path) {
|
|
3367
|
+
const originalPath = resolved.page.path;
|
|
3368
|
+
resolved.page = {
|
|
3369
|
+
...resolved.page,
|
|
3370
|
+
path: refObj.path
|
|
3371
|
+
};
|
|
3372
|
+
resolved.pathOverridden = true;
|
|
3373
|
+
resolved.originalPath = originalPath;
|
|
4545
3374
|
}
|
|
4546
|
-
return
|
|
3375
|
+
return {
|
|
3376
|
+
success: true,
|
|
3377
|
+
data: resolved,
|
|
3378
|
+
warnings: baseResult.warnings
|
|
3379
|
+
};
|
|
4547
3380
|
}
|
|
4548
3381
|
/**
|
|
4549
|
-
*
|
|
3382
|
+
* Find a page in an orbital by name.
|
|
4550
3383
|
*/
|
|
4551
|
-
|
|
4552
|
-
const
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
const handlers = {
|
|
4560
|
-
emit: (event, eventPayload, source) => {
|
|
4561
|
-
if (this.config.debug) {
|
|
4562
|
-
busLog.debug("emit:dispatch", () => ({
|
|
4563
|
-
event,
|
|
4564
|
-
payloadJson: JSON.stringify(eventPayload ?? null),
|
|
4565
|
-
sourceOrbital: source?.orbital,
|
|
4566
|
-
sourceTrait: source?.trait
|
|
4567
|
-
}));
|
|
4568
|
-
}
|
|
4569
|
-
const stamp = source ?? {
|
|
4570
|
-
orbital: registered.schema.name,
|
|
4571
|
-
trait: traitName
|
|
4572
|
-
};
|
|
4573
|
-
this.eventBus.emit(event, eventPayload, stamp);
|
|
4574
|
-
emittedEvents.push({ event, payload: eventPayload, source: stamp });
|
|
4575
|
-
effectLog2.debug("emit:push", {
|
|
4576
|
-
event,
|
|
4577
|
-
cumulativeEmittedCount: emittedEvents.length,
|
|
4578
|
-
sourceTrait: stamp.trait,
|
|
4579
|
-
sourceOrbital: stamp.orbital
|
|
4580
|
-
});
|
|
4581
|
-
xOrbitalLog.info("emit:server", {
|
|
4582
|
-
event,
|
|
4583
|
-
sourceOrbital: stamp.orbital,
|
|
4584
|
-
sourceTrait: stamp.trait,
|
|
4585
|
-
dispatchOrbital: registered.schema.name
|
|
4586
|
-
});
|
|
4587
|
-
},
|
|
4588
|
-
set: async (targetId, field, value) => {
|
|
4589
|
-
let fieldState = registered.traitFieldStates.get(traitName);
|
|
4590
|
-
if (!fieldState) {
|
|
4591
|
-
fieldState = {};
|
|
4592
|
-
registered.traitFieldStates.set(traitName, fieldState);
|
|
4593
|
-
}
|
|
4594
|
-
fieldState[field] = value;
|
|
4595
|
-
effectResults.push({
|
|
4596
|
-
effect: "set",
|
|
4597
|
-
entityType,
|
|
4598
|
-
data: { id: targetId || entityId || "", field, value },
|
|
4599
|
-
success: true
|
|
4600
|
-
});
|
|
4601
|
-
},
|
|
4602
|
-
persist: async (action, targetEntityType, data) => {
|
|
4603
|
-
if (action === "batch") {
|
|
4604
|
-
const operations = data?.operations;
|
|
4605
|
-
if (!Array.isArray(operations) || operations.length === 0) {
|
|
4606
|
-
effectResults.push({
|
|
4607
|
-
effect: "persist",
|
|
4608
|
-
action: "batch",
|
|
4609
|
-
success: false,
|
|
4610
|
-
error: "Batch requires a non-empty operations array"
|
|
4611
|
-
});
|
|
4612
|
-
return;
|
|
4613
|
-
}
|
|
4614
|
-
const batchResults = [];
|
|
4615
|
-
const completed = [];
|
|
4616
|
-
let batchFailed = false;
|
|
4617
|
-
let batchError = "";
|
|
4618
|
-
for (const op of operations) {
|
|
4619
|
-
if (!Array.isArray(op) || op.length < 2) {
|
|
4620
|
-
batchFailed = true;
|
|
4621
|
-
batchError = `Invalid batch operation format: ${JSON.stringify(op)}`;
|
|
4622
|
-
break;
|
|
4623
|
-
}
|
|
4624
|
-
const [opAction, opEntityType, ...opRest] = op;
|
|
4625
|
-
try {
|
|
4626
|
-
switch (opAction) {
|
|
4627
|
-
case "create": {
|
|
4628
|
-
const createData = opRest[0] || {};
|
|
4629
|
-
const { id: newId } = await this.persistence.create(opEntityType, createData);
|
|
4630
|
-
batchResults.push({ action: "create", entityType: opEntityType, id: newId, ...createData });
|
|
4631
|
-
completed.push({ action: "create", entityType: opEntityType, id: newId });
|
|
4632
|
-
break;
|
|
4633
|
-
}
|
|
4634
|
-
case "update": {
|
|
4635
|
-
const updateId = opRest[0];
|
|
4636
|
-
const updateData = opRest[1] || {};
|
|
4637
|
-
await this.persistence.update(opEntityType, updateId, updateData);
|
|
4638
|
-
const updated = await this.persistence.getById(opEntityType, updateId);
|
|
4639
|
-
batchResults.push({ action: "update", entityType: opEntityType, id: updateId, ...updated || updateData });
|
|
4640
|
-
completed.push({ action: "update", entityType: opEntityType, id: updateId });
|
|
4641
|
-
break;
|
|
4642
|
-
}
|
|
4643
|
-
case "delete": {
|
|
4644
|
-
const deleteId = opRest[0];
|
|
4645
|
-
await this.persistence.delete(opEntityType, deleteId);
|
|
4646
|
-
batchResults.push({ action: "delete", entityType: opEntityType, id: deleteId, deleted: true });
|
|
4647
|
-
completed.push({ action: "delete", entityType: opEntityType, id: deleteId });
|
|
4648
|
-
break;
|
|
4649
|
-
}
|
|
4650
|
-
default:
|
|
4651
|
-
batchFailed = true;
|
|
4652
|
-
batchError = `Unknown batch operation action: ${opAction}`;
|
|
4653
|
-
break;
|
|
4654
|
-
}
|
|
4655
|
-
} catch (err) {
|
|
4656
|
-
batchFailed = true;
|
|
4657
|
-
batchError = `Batch operation [${opAction}, ${opEntityType}] failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
4658
|
-
break;
|
|
4659
|
-
}
|
|
4660
|
-
if (batchFailed) break;
|
|
4661
|
-
}
|
|
4662
|
-
effectResults.push({
|
|
4663
|
-
effect: "persist",
|
|
4664
|
-
action: "batch",
|
|
4665
|
-
data: {
|
|
4666
|
-
operations: batchResults,
|
|
4667
|
-
completedCount: completed.length,
|
|
4668
|
-
totalCount: operations.length
|
|
4669
|
-
},
|
|
4670
|
-
success: !batchFailed,
|
|
4671
|
-
...batchFailed ? { error: batchError } : {}
|
|
4672
|
-
});
|
|
4673
|
-
return;
|
|
4674
|
-
}
|
|
4675
|
-
const type = targetEntityType || entityType;
|
|
4676
|
-
let resultData;
|
|
4677
|
-
const sizeBefore = (await this.persistence.list(type)).length;
|
|
4678
|
-
try {
|
|
4679
|
-
if (action === "create" || action === "update") {
|
|
4680
|
-
this.validateRelationCardinality(type, data || {});
|
|
4681
|
-
}
|
|
4682
|
-
switch (action) {
|
|
4683
|
-
case "create": {
|
|
4684
|
-
const { id } = await this.persistence.create(type, data || {});
|
|
4685
|
-
resultData = { id, ...data || {} };
|
|
4686
|
-
break;
|
|
4687
|
-
}
|
|
4688
|
-
case "update":
|
|
4689
|
-
if (data?.id || entityId) {
|
|
4690
|
-
const updateId = data?.id || entityId;
|
|
4691
|
-
await this.persistence.update(type, updateId, data || {});
|
|
4692
|
-
const updated = await this.persistence.getById(type, updateId);
|
|
4693
|
-
resultData = updated || { id: updateId, ...data || {} };
|
|
4694
|
-
}
|
|
4695
|
-
break;
|
|
4696
|
-
case "delete": {
|
|
4697
|
-
const directId = typeof data === "string" ? data : void 0;
|
|
4698
|
-
const nestedId = typeof data === "object" && data !== null ? data.id : void 0;
|
|
4699
|
-
const deleteId = directId ?? nestedId ?? entityId;
|
|
4700
|
-
if (deleteId) {
|
|
4701
|
-
await this.enforceOnDeleteRules(type, deleteId);
|
|
4702
|
-
await this.persistence.delete(type, deleteId);
|
|
4703
|
-
resultData = { id: deleteId, deleted: true };
|
|
4704
|
-
}
|
|
4705
|
-
break;
|
|
4706
|
-
}
|
|
4707
|
-
}
|
|
4708
|
-
const sizeAfter = (await this.persistence.list(type)).length;
|
|
4709
|
-
effectLog2.debug("persist:store-mutate", {
|
|
4710
|
-
action,
|
|
4711
|
-
entityType: type,
|
|
4712
|
-
resultId: resultData?.id,
|
|
4713
|
-
sizeBefore,
|
|
4714
|
-
sizeAfter,
|
|
4715
|
-
delta: sizeAfter - sizeBefore
|
|
4716
|
-
});
|
|
4717
|
-
effectResults.push({
|
|
4718
|
-
effect: "persist",
|
|
4719
|
-
action,
|
|
4720
|
-
entityType: type,
|
|
4721
|
-
data: resultData,
|
|
4722
|
-
success: true
|
|
4723
|
-
});
|
|
4724
|
-
} catch (err) {
|
|
4725
|
-
effectLog2.error("persist:store-mutate-error", {
|
|
4726
|
-
action,
|
|
4727
|
-
entityType: type,
|
|
4728
|
-
error: err instanceof Error ? err.message : String(err)
|
|
4729
|
-
});
|
|
4730
|
-
effectResults.push({
|
|
4731
|
-
effect: "persist",
|
|
4732
|
-
action,
|
|
4733
|
-
entityType: type,
|
|
4734
|
-
success: false,
|
|
4735
|
-
error: err instanceof Error ? err.message : String(err)
|
|
4736
|
-
});
|
|
4737
|
-
}
|
|
4738
|
-
},
|
|
4739
|
-
callService: async (service, action, params) => {
|
|
4740
|
-
try {
|
|
4741
|
-
let result = null;
|
|
4742
|
-
if (this.config.effectHandlers?.callService) {
|
|
4743
|
-
result = await this.config.effectHandlers.callService(
|
|
4744
|
-
service,
|
|
4745
|
-
action,
|
|
4746
|
-
params
|
|
4747
|
-
);
|
|
4748
|
-
} else if (this.config.mode === "mock") {
|
|
4749
|
-
const mockId = `mock_${service}_${action}_${Math.random().toString(36).slice(2, 10)}`;
|
|
4750
|
-
const paramsEcho = {};
|
|
4751
|
-
if (params) {
|
|
4752
|
-
for (const [k, v] of Object.entries(params)) {
|
|
4753
|
-
if (v !== void 0 && (typeof v === "string" || typeof v === "number" || typeof v === "boolean" || v === null || v instanceof Date)) {
|
|
4754
|
-
paramsEcho[k] = v;
|
|
4755
|
-
}
|
|
4756
|
-
}
|
|
4757
|
-
}
|
|
4758
|
-
result = {
|
|
4759
|
-
id: mockId,
|
|
4760
|
-
clientSecret: `secret_${mockId}`,
|
|
4761
|
-
success: true,
|
|
4762
|
-
status: "succeeded",
|
|
4763
|
-
...paramsEcho
|
|
4764
|
-
};
|
|
4765
|
-
} else {
|
|
4766
|
-
effectLog2.warn("call-service:not-configured", { service, action });
|
|
4767
|
-
}
|
|
4768
|
-
effectResults.push({
|
|
4769
|
-
effect: "call-service",
|
|
4770
|
-
action: `${service}.${action}`,
|
|
4771
|
-
data: result,
|
|
4772
|
-
success: true
|
|
4773
|
-
});
|
|
4774
|
-
return result;
|
|
4775
|
-
} catch (err) {
|
|
4776
|
-
effectResults.push({
|
|
4777
|
-
effect: "call-service",
|
|
4778
|
-
action: `${service}.${action}`,
|
|
4779
|
-
success: false,
|
|
4780
|
-
error: err instanceof Error ? err.message : String(err)
|
|
4781
|
-
});
|
|
4782
|
-
return null;
|
|
4783
|
-
}
|
|
4784
|
-
},
|
|
4785
|
-
fetch: async (fetchEntityType, options) => {
|
|
4786
|
-
try {
|
|
4787
|
-
xOrbitalLog.info("fetch:enter", () => ({
|
|
4788
|
-
entityType: fetchEntityType,
|
|
4789
|
-
hasOptions: options !== void 0 && options !== null,
|
|
4790
|
-
optionsKeys: options ? Object.keys(options).join(",") : "",
|
|
4791
|
-
filterType: typeof options?.filter,
|
|
4792
|
-
filterIsArray: Array.isArray(options?.filter),
|
|
4793
|
-
filterJson: JSON.stringify(options?.filter ?? null).slice(0, 300),
|
|
4794
|
-
payloadJson: JSON.stringify(bindingsRef?.payload ?? null).slice(0, 300)
|
|
4795
|
-
}));
|
|
4796
|
-
let result = null;
|
|
4797
|
-
let total = 0;
|
|
4798
|
-
if (options?.id) {
|
|
4799
|
-
const entity = await this.persistence.getById(fetchEntityType, options.id);
|
|
4800
|
-
if (entity) {
|
|
4801
|
-
if (options?.include && options.include.length > 0) {
|
|
4802
|
-
await this.populateRelations([entity], fetchEntityType, options.include);
|
|
4803
|
-
}
|
|
4804
|
-
fetchedData[fetchEntityType] = [entity];
|
|
4805
|
-
result = entity;
|
|
4806
|
-
total = 1;
|
|
4807
|
-
}
|
|
4808
|
-
} else {
|
|
4809
|
-
let entities = await this.persistence.list(fetchEntityType);
|
|
4810
|
-
if (options?.filter !== void 0 && options.filter !== null) {
|
|
4811
|
-
const predicate = options.filter;
|
|
4812
|
-
entities = entities.filter((entity) => {
|
|
4813
|
-
const ctx = createContextFromBindings(
|
|
4814
|
-
{ entity, payload: bindingsRef?.payload, current: entity },
|
|
4815
|
-
false
|
|
4816
|
-
);
|
|
4817
|
-
try {
|
|
4818
|
-
return Boolean(evaluate(predicate, ctx));
|
|
4819
|
-
} catch (err) {
|
|
4820
|
-
effectLog2.error("fetch:filter-eval-error", {
|
|
4821
|
-
entityType: fetchEntityType,
|
|
4822
|
-
error: err instanceof Error ? err : String(err)
|
|
4823
|
-
});
|
|
4824
|
-
return false;
|
|
4825
|
-
}
|
|
4826
|
-
});
|
|
4827
|
-
}
|
|
4828
|
-
total = entities.length;
|
|
4829
|
-
if (options?.offset && options.offset > 0) {
|
|
4830
|
-
entities = entities.slice(options.offset);
|
|
4831
|
-
}
|
|
4832
|
-
if (options?.limit && options.limit > 0) {
|
|
4833
|
-
entities = entities.slice(0, options.limit);
|
|
4834
|
-
}
|
|
4835
|
-
if (options?.include && options.include.length > 0) {
|
|
4836
|
-
await this.populateRelations(entities, fetchEntityType, options.include);
|
|
4837
|
-
}
|
|
4838
|
-
fetchedData[fetchEntityType] = entities;
|
|
4839
|
-
result = entities;
|
|
4840
|
-
}
|
|
4841
|
-
return result === null ? null : { rows: result, total };
|
|
4842
|
-
} catch (error) {
|
|
4843
|
-
effectLog2.error("fetch:error", {
|
|
4844
|
-
entityType: fetchEntityType,
|
|
4845
|
-
error: error instanceof Error ? error : String(error)
|
|
4846
|
-
});
|
|
4847
|
-
return null;
|
|
4848
|
-
}
|
|
4849
|
-
},
|
|
4850
|
-
// Resource operators: ref, deref, swap, watch, atomic
|
|
4851
|
-
ref: async (refEntityType, options) => {
|
|
4852
|
-
try {
|
|
4853
|
-
return await handlers.fetch(refEntityType, options);
|
|
4854
|
-
} catch (error) {
|
|
4855
|
-
effectLog2.error("ref:error", {
|
|
4856
|
-
entityType: refEntityType,
|
|
4857
|
-
error: error instanceof Error ? error : String(error)
|
|
4858
|
-
});
|
|
4859
|
-
return null;
|
|
4860
|
-
}
|
|
4861
|
-
},
|
|
4862
|
-
deref: async (derefEntityType, options) => {
|
|
4863
|
-
try {
|
|
4864
|
-
let result = null;
|
|
4865
|
-
let total = 0;
|
|
4866
|
-
if (options?.id) {
|
|
4867
|
-
const entity = await this.persistence.getById(derefEntityType, options.id);
|
|
4868
|
-
if (entity) {
|
|
4869
|
-
fetchedData[derefEntityType] = [entity];
|
|
4870
|
-
result = entity;
|
|
4871
|
-
total = 1;
|
|
4872
|
-
}
|
|
4873
|
-
} else {
|
|
4874
|
-
const entities = await this.persistence.list(derefEntityType);
|
|
4875
|
-
fetchedData[derefEntityType] = entities;
|
|
4876
|
-
result = entities;
|
|
4877
|
-
total = entities.length;
|
|
4878
|
-
}
|
|
4879
|
-
effectResults.push({
|
|
4880
|
-
effect: "deref",
|
|
4881
|
-
entityType: derefEntityType,
|
|
4882
|
-
success: true
|
|
4883
|
-
});
|
|
4884
|
-
return result === null ? null : { rows: result, total };
|
|
4885
|
-
} catch (error) {
|
|
4886
|
-
effectResults.push({
|
|
4887
|
-
effect: "deref",
|
|
4888
|
-
entityType: derefEntityType,
|
|
4889
|
-
success: false,
|
|
4890
|
-
error: error instanceof Error ? error.message : String(error)
|
|
4891
|
-
});
|
|
4892
|
-
return null;
|
|
4893
|
-
}
|
|
4894
|
-
},
|
|
4895
|
-
swap: async (swapEntityType, swapEntityId, transform) => {
|
|
4896
|
-
try {
|
|
4897
|
-
const current = await this.persistence.getById(swapEntityType, swapEntityId);
|
|
4898
|
-
if (!current) {
|
|
4899
|
-
effectResults.push({
|
|
4900
|
-
effect: "swap",
|
|
4901
|
-
entityType: swapEntityType,
|
|
4902
|
-
success: false,
|
|
4903
|
-
error: `Entity ${swapEntityType}/${swapEntityId} not found`
|
|
4904
|
-
});
|
|
4905
|
-
return null;
|
|
4906
|
-
}
|
|
4907
|
-
const ctx = createContextFromBindings({
|
|
4908
|
-
current,
|
|
4909
|
-
entity: entityData,
|
|
4910
|
-
payload
|
|
4911
|
-
}, false, this.config.contextExtensions);
|
|
4912
|
-
let newData;
|
|
4913
|
-
if (Array.isArray(transform)) {
|
|
4914
|
-
const result = evaluate(
|
|
4915
|
-
transform,
|
|
4916
|
-
ctx
|
|
4917
|
-
);
|
|
4918
|
-
if (result && typeof result === "object" && !Array.isArray(result)) {
|
|
4919
|
-
newData = result;
|
|
4920
|
-
} else {
|
|
4921
|
-
newData = current;
|
|
4922
|
-
}
|
|
4923
|
-
} else if (typeof transform === "object" && transform !== null) {
|
|
4924
|
-
newData = { ...current, ...transform };
|
|
4925
|
-
} else {
|
|
4926
|
-
effectResults.push({
|
|
4927
|
-
effect: "swap",
|
|
4928
|
-
entityType: swapEntityType,
|
|
4929
|
-
success: false,
|
|
4930
|
-
error: "swap! transform must be an S-expression or object"
|
|
4931
|
-
});
|
|
4932
|
-
return null;
|
|
4933
|
-
}
|
|
4934
|
-
await this.persistence.update(swapEntityType, swapEntityId, newData);
|
|
4935
|
-
effectResults.push({
|
|
4936
|
-
effect: "swap",
|
|
4937
|
-
entityType: swapEntityType,
|
|
4938
|
-
data: { id: swapEntityId, ...newData },
|
|
4939
|
-
success: true
|
|
4940
|
-
});
|
|
4941
|
-
return newData;
|
|
4942
|
-
} catch (error) {
|
|
4943
|
-
effectResults.push({
|
|
4944
|
-
effect: "swap",
|
|
4945
|
-
entityType: swapEntityType,
|
|
4946
|
-
success: false,
|
|
4947
|
-
error: error instanceof Error ? error.message : String(error)
|
|
4948
|
-
});
|
|
4949
|
-
return null;
|
|
4950
|
-
}
|
|
4951
|
-
},
|
|
4952
|
-
watch: (_watchEntityType, _watchOptions) => {
|
|
4953
|
-
if (this.config.debug) {
|
|
4954
|
-
effectLog2.debug("watch:noop-server", { entityType: _watchEntityType });
|
|
4955
|
-
}
|
|
4956
|
-
},
|
|
4957
|
-
atomic: async (atomicEffects) => {
|
|
4958
|
-
let atomicFailed = false;
|
|
4959
|
-
let atomicError = "";
|
|
4960
|
-
const atomicExecutor = new EffectExecutor({
|
|
4961
|
-
handlers,
|
|
4962
|
-
bindings: bindingsRef ?? {},
|
|
4963
|
-
context: contextRef ?? { traitName, orbitalName: registered.schema.name, state: "unknown", transition: "unknown" },
|
|
4964
|
-
debug: this.config.debug,
|
|
4965
|
-
contextExtensions: this.config.contextExtensions
|
|
4966
|
-
});
|
|
4967
|
-
for (const innerEffect of atomicEffects) {
|
|
4968
|
-
if (atomicFailed) break;
|
|
4969
|
-
try {
|
|
4970
|
-
await atomicExecutor.execute(innerEffect);
|
|
4971
|
-
} catch (err) {
|
|
4972
|
-
atomicFailed = true;
|
|
4973
|
-
atomicError = err instanceof Error ? err.message : String(err);
|
|
4974
|
-
}
|
|
4975
|
-
}
|
|
4976
|
-
if (atomicFailed) {
|
|
4977
|
-
effectResults.push({
|
|
4978
|
-
effect: "atomic",
|
|
4979
|
-
success: false,
|
|
4980
|
-
error: `Atomic block failed: ${atomicError}`
|
|
4981
|
-
});
|
|
4982
|
-
} else {
|
|
4983
|
-
effectResults.push({
|
|
4984
|
-
effect: "atomic",
|
|
4985
|
-
success: true,
|
|
4986
|
-
data: { innerCount: atomicEffects.length }
|
|
4987
|
-
});
|
|
4988
|
-
}
|
|
4989
|
-
},
|
|
4990
|
-
// Client-side effects - collect for forwarding to client
|
|
4991
|
-
renderUI: (slot, pattern, props, priority) => {
|
|
4992
|
-
const patternNode = pattern !== null && typeof pattern === "object" && !Array.isArray(pattern) ? pattern : null;
|
|
4993
|
-
const patternEntity = patternNode?.entity;
|
|
4994
|
-
const entityRow = patternEntity !== null && typeof patternEntity === "object" && !Array.isArray(patternEntity) ? patternEntity : null;
|
|
4995
|
-
const patternTypeRaw = patternNode?.["type"];
|
|
4996
|
-
renderLog2.debug("renderUI:push", {
|
|
4997
|
-
trait: traitName,
|
|
4998
|
-
slot,
|
|
4999
|
-
patternType: typeof patternTypeRaw === "string" ? patternTypeRaw : void 0,
|
|
5000
|
-
entityRowId: typeof entityRow?.id === "string" ? entityRow.id : void 0,
|
|
5001
|
-
entityIsObject: entityRow !== null
|
|
5002
|
-
});
|
|
5003
|
-
pushClientEffect(["render-ui", slot, pattern, props, priority]);
|
|
5004
|
-
},
|
|
5005
|
-
navigate: (path, params) => {
|
|
5006
|
-
pushClientEffect(["navigate", path, params]);
|
|
5007
|
-
},
|
|
5008
|
-
notify: (message, type) => {
|
|
5009
|
-
if (this.config.debug) {
|
|
5010
|
-
effectLog2.info("notify", { type, message });
|
|
5011
|
-
}
|
|
5012
|
-
pushClientEffect(["notify", message, { type }]);
|
|
5013
|
-
},
|
|
5014
|
-
log: (message, level) => {
|
|
5015
|
-
if (level === "error") {
|
|
5016
|
-
dynamicLog.error(message);
|
|
5017
|
-
} else if (level === "warn") {
|
|
5018
|
-
dynamicLog.warn(message);
|
|
5019
|
-
} else {
|
|
5020
|
-
dynamicLog.debug(message);
|
|
3384
|
+
findPageInOrbital(orbital, pageName) {
|
|
3385
|
+
const pages = orbital.pages;
|
|
3386
|
+
if (!pages) return null;
|
|
3387
|
+
for (const pageRef of pages) {
|
|
3388
|
+
if (typeof pageRef !== "string" && !("ref" in pageRef)) {
|
|
3389
|
+
const page = pageRef;
|
|
3390
|
+
if (page.name === pageName) {
|
|
3391
|
+
return { ...page };
|
|
5021
3392
|
}
|
|
5022
|
-
}
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
};
|
|
5026
|
-
const state = registered.manager.getState(traitName);
|
|
5027
|
-
const bindings = {
|
|
5028
|
-
entity: entityData,
|
|
5029
|
-
payload,
|
|
5030
|
-
state: state?.currentState || "unknown",
|
|
5031
|
-
user
|
|
5032
|
-
// @user bindings from Firebase auth
|
|
5033
|
-
};
|
|
5034
|
-
const traitDef = registered.traits.find((t) => t.name === traitName);
|
|
5035
|
-
const declaredDefaults = collectDeclaredConfigDefaults(traitDef);
|
|
5036
|
-
const callSiteOverride = registered.configByTrait.get(traitName);
|
|
5037
|
-
if (declaredDefaults || callSiteOverride) {
|
|
5038
|
-
bindings.config = { ...declaredDefaults ?? {}, ...callSiteOverride ?? {} };
|
|
5039
|
-
}
|
|
5040
|
-
const traitFieldState = registered.traitFieldStates.get(traitName);
|
|
5041
|
-
if (traitFieldState) {
|
|
5042
|
-
bindings.entity = traitFieldState;
|
|
5043
|
-
}
|
|
5044
|
-
if (entityType) {
|
|
5045
|
-
bindings[entityType] = bindings.entity ?? entityData;
|
|
5046
|
-
}
|
|
5047
|
-
bindingsRef = bindings;
|
|
5048
|
-
const context = {
|
|
5049
|
-
traitName,
|
|
5050
|
-
orbitalName: registered.schema.name,
|
|
5051
|
-
state: state?.currentState || "unknown",
|
|
5052
|
-
transition: "unknown",
|
|
5053
|
-
entityId
|
|
5054
|
-
};
|
|
5055
|
-
contextRef = context;
|
|
5056
|
-
const executor = new EffectExecutor({
|
|
5057
|
-
handlers,
|
|
5058
|
-
bindings,
|
|
5059
|
-
context,
|
|
5060
|
-
debug: this.config.debug,
|
|
5061
|
-
contextExtensions: this.config.contextExtensions
|
|
5062
|
-
});
|
|
5063
|
-
await executor.executeAll(effects);
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
return null;
|
|
5064
3396
|
}
|
|
5065
|
-
// ==========================================================================
|
|
5066
|
-
// Relation Population
|
|
5067
|
-
// ==========================================================================
|
|
5068
3397
|
/**
|
|
5069
|
-
*
|
|
5070
|
-
*
|
|
5071
|
-
* For each field in `include`, find the relation field configuration and
|
|
5072
|
-
* fetch the related entity, attaching it to the parent entity.
|
|
5073
|
-
*
|
|
5074
|
-
* @param entities - Entities to populate
|
|
5075
|
-
* @param entityType - Entity type name
|
|
5076
|
-
* @param include - Relation field names to populate
|
|
3398
|
+
* List page names in an orbital.
|
|
5077
3399
|
*/
|
|
3400
|
+
listPagesInOrbital(orbital) {
|
|
3401
|
+
const pages = orbital.pages;
|
|
3402
|
+
if (!pages) return [];
|
|
3403
|
+
const names = [];
|
|
3404
|
+
for (const pageRef of pages) {
|
|
3405
|
+
if (typeof pageRef !== "string" && !("ref" in pageRef)) {
|
|
3406
|
+
names.push(pageRef.name);
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
return names;
|
|
3410
|
+
}
|
|
5078
3411
|
/**
|
|
5079
|
-
*
|
|
5080
|
-
* Called before create/update to ensure data integrity.
|
|
3412
|
+
* Add local traits for resolution.
|
|
5081
3413
|
*/
|
|
5082
|
-
|
|
5083
|
-
for (const
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
3414
|
+
addLocalTraits(traits) {
|
|
3415
|
+
for (const trait of traits) {
|
|
3416
|
+
this.localTraits.set(trait.name, trait);
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
3419
|
+
/**
|
|
3420
|
+
* Clear loader cache.
|
|
3421
|
+
*/
|
|
3422
|
+
clearCache() {
|
|
3423
|
+
this.loader?.clearCache();
|
|
3424
|
+
}
|
|
3425
|
+
};
|
|
3426
|
+
async function resolveSchema(schema, options) {
|
|
3427
|
+
const resolver = new ReferenceResolver(options);
|
|
3428
|
+
const errors = [];
|
|
3429
|
+
const warnings = [];
|
|
3430
|
+
const resolved = [];
|
|
3431
|
+
for (const orbital of schema.orbitals) {
|
|
3432
|
+
const inlineTraits = orbital.traits.filter(
|
|
3433
|
+
(t) => typeof t !== "string" && "stateMachine" in t
|
|
3434
|
+
);
|
|
3435
|
+
resolver.addLocalTraits(inlineTraits);
|
|
3436
|
+
}
|
|
3437
|
+
for (const orbital of schema.orbitals) {
|
|
3438
|
+
const result = await resolver.resolve(orbital);
|
|
3439
|
+
if (!result.success) {
|
|
3440
|
+
errors.push(`Orbital "${orbital.name}": ${result.errors.join(", ")}`);
|
|
3441
|
+
} else {
|
|
3442
|
+
resolved.push(result.data);
|
|
3443
|
+
warnings.push(...result.warnings.map((w) => `Orbital "${orbital.name}": ${w}`));
|
|
5112
3444
|
}
|
|
5113
3445
|
}
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
* Scans all registered entities for relation fields targeting the given entity type,
|
|
5117
|
-
* finds records referencing the ID being deleted, and applies cascade/nullify/restrict.
|
|
5118
|
-
*/
|
|
5119
|
-
async enforceOnDeleteRules(entityType, deletedId) {
|
|
5120
|
-
for (const [, registered] of this.orbitals) {
|
|
5121
|
-
const entity = registered.entity;
|
|
5122
|
-
const fields = entity.fields ?? [];
|
|
5123
|
-
for (const field of fields) {
|
|
5124
|
-
if (field.type !== "relation") continue;
|
|
5125
|
-
if (field.relation?.entity !== entityType) continue;
|
|
5126
|
-
if (field.name === void 0) continue;
|
|
5127
|
-
const fieldName = field.name;
|
|
5128
|
-
const onDelete = field.relation.onDelete || "restrict";
|
|
5129
|
-
const referringEntityType = entity.name;
|
|
5130
|
-
const allRecords = await this.persistence.list(referringEntityType);
|
|
5131
|
-
const affectedRecords = allRecords.filter((record) => {
|
|
5132
|
-
const fkValue = record[fieldName];
|
|
5133
|
-
if (typeof fkValue === "string") return fkValue === deletedId;
|
|
5134
|
-
if (Array.isArray(fkValue)) return fkValue.includes(deletedId);
|
|
5135
|
-
return false;
|
|
5136
|
-
});
|
|
5137
|
-
if (affectedRecords.length === 0) continue;
|
|
5138
|
-
switch (onDelete) {
|
|
5139
|
-
case "restrict":
|
|
5140
|
-
throw new Error(
|
|
5141
|
-
`Cannot delete ${entityType} ${deletedId}: ${affectedRecords.length} ${referringEntityType} record(s) reference it via ${field.name}. Rule: restrict.`
|
|
5142
|
-
);
|
|
5143
|
-
case "cascade":
|
|
5144
|
-
for (const record of affectedRecords) {
|
|
5145
|
-
const recordId = record.id;
|
|
5146
|
-
if (recordId) {
|
|
5147
|
-
await this.persistence.delete(referringEntityType, recordId);
|
|
5148
|
-
}
|
|
5149
|
-
}
|
|
5150
|
-
if (this.config.debug) {
|
|
5151
|
-
persistLog.debug("cascade-delete", {
|
|
5152
|
-
count: affectedRecords.length,
|
|
5153
|
-
entityType: referringEntityType
|
|
5154
|
-
});
|
|
5155
|
-
}
|
|
5156
|
-
break;
|
|
5157
|
-
case "nullify":
|
|
5158
|
-
for (const record of affectedRecords) {
|
|
5159
|
-
const recordId = record.id;
|
|
5160
|
-
if (recordId && field.name !== void 0) {
|
|
5161
|
-
const fieldName2 = field.name;
|
|
5162
|
-
const update = {};
|
|
5163
|
-
const fkValue = record[fieldName2];
|
|
5164
|
-
if (Array.isArray(fkValue)) {
|
|
5165
|
-
update[fieldName2] = fkValue.filter((id) => id !== deletedId);
|
|
5166
|
-
} else {
|
|
5167
|
-
update[fieldName2] = null;
|
|
5168
|
-
}
|
|
5169
|
-
await this.persistence.update(referringEntityType, recordId, update);
|
|
5170
|
-
}
|
|
5171
|
-
}
|
|
5172
|
-
if (this.config.debug) {
|
|
5173
|
-
persistLog.debug("nullify", {
|
|
5174
|
-
field: field.name,
|
|
5175
|
-
count: affectedRecords.length,
|
|
5176
|
-
entityType: referringEntityType
|
|
5177
|
-
});
|
|
5178
|
-
}
|
|
5179
|
-
break;
|
|
5180
|
-
}
|
|
5181
|
-
}
|
|
5182
|
-
}
|
|
3446
|
+
if (errors.length > 0) {
|
|
3447
|
+
return { success: false, errors };
|
|
5183
3448
|
}
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
continue;
|
|
5222
|
-
}
|
|
5223
|
-
const foreignKeyField = relationField.name;
|
|
5224
|
-
const relatedEntityType = relationField.relation.entity;
|
|
5225
|
-
const cardinality = relationField.relation.cardinality || "one";
|
|
5226
|
-
const foreignKeyIds = /* @__PURE__ */ new Set();
|
|
5227
|
-
for (const entity of entities) {
|
|
5228
|
-
const fkValue = entity[foreignKeyField];
|
|
5229
|
-
if (fkValue && typeof fkValue === "string") {
|
|
5230
|
-
foreignKeyIds.add(fkValue);
|
|
5231
|
-
} else if (Array.isArray(fkValue)) {
|
|
5232
|
-
for (const id of fkValue) {
|
|
5233
|
-
if (id && typeof id === "string") {
|
|
5234
|
-
foreignKeyIds.add(id);
|
|
5235
|
-
}
|
|
5236
|
-
}
|
|
3449
|
+
return { success: true, data: resolved, warnings };
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3452
|
+
// src/UsesIntegration.ts
|
|
3453
|
+
async function preprocessSchema(schema, options) {
|
|
3454
|
+
const namespaceEvents = options.namespaceEvents ?? true;
|
|
3455
|
+
const resolveResult = await resolveSchema(schema, options);
|
|
3456
|
+
if (!resolveResult.success) {
|
|
3457
|
+
return { success: false, errors: resolveResult.errors };
|
|
3458
|
+
}
|
|
3459
|
+
const resolved = resolveResult.data;
|
|
3460
|
+
const warnings = resolveResult.warnings;
|
|
3461
|
+
const preprocessedOrbitals = [];
|
|
3462
|
+
const entitySharing = {};
|
|
3463
|
+
const eventNamespaces = {};
|
|
3464
|
+
for (const resolvedOrbital of resolved) {
|
|
3465
|
+
const orbitalName = resolvedOrbital.name;
|
|
3466
|
+
const persistence = resolvedOrbital.entitySource?.persistence ?? resolvedOrbital.entity.persistence ?? "persistent";
|
|
3467
|
+
entitySharing[orbitalName] = {
|
|
3468
|
+
entityName: resolvedOrbital.entity.name,
|
|
3469
|
+
persistence,
|
|
3470
|
+
isShared: persistence !== "runtime",
|
|
3471
|
+
sourceAlias: resolvedOrbital.entitySource?.alias,
|
|
3472
|
+
collectionName: resolvedOrbital.entity.collection
|
|
3473
|
+
};
|
|
3474
|
+
eventNamespaces[orbitalName] = {};
|
|
3475
|
+
for (const resolvedTrait of resolvedOrbital.traits) {
|
|
3476
|
+
const traitName = resolvedTrait.trait.name;
|
|
3477
|
+
const namespace = {
|
|
3478
|
+
emits: {},
|
|
3479
|
+
listens: {}
|
|
3480
|
+
};
|
|
3481
|
+
if (namespaceEvents && resolvedTrait.source.type === "imported") {
|
|
3482
|
+
const emits = resolvedTrait.trait.emits ?? [];
|
|
3483
|
+
for (const emit of emits) {
|
|
3484
|
+
const eventName = typeof emit === "string" ? emit : emit.event;
|
|
3485
|
+
namespace.emits[eventName] = `${orbitalName}.${traitName}.${eventName}`;
|
|
5237
3486
|
}
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
for (const fkId of foreignKeyIds) {
|
|
5242
|
-
try {
|
|
5243
|
-
const related = await this.persistence.getById(relatedEntityType, fkId);
|
|
5244
|
-
if (related) {
|
|
5245
|
-
relatedEntities.set(fkId, related);
|
|
5246
|
-
}
|
|
5247
|
-
} catch (error) {
|
|
5248
|
-
if (this.config.debug) {
|
|
5249
|
-
persistLog.error("populate:fetch-related-error", {
|
|
5250
|
-
entityType: relatedEntityType,
|
|
5251
|
-
error: error instanceof Error ? error : String(error)
|
|
5252
|
-
});
|
|
5253
|
-
}
|
|
3487
|
+
const listens = resolvedTrait.trait.listens ?? [];
|
|
3488
|
+
for (const listen of listens) {
|
|
3489
|
+
namespace.listens[listen.event] = listen.event;
|
|
5254
3490
|
}
|
|
5255
3491
|
}
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
});
|
|
5275
|
-
}
|
|
5276
|
-
} else {
|
|
5277
|
-
if (Array.isArray(fkValue)) {
|
|
5278
|
-
const fkIds = fkValue.filter((id) => typeof id === "string");
|
|
5279
|
-
Object.defineProperty(entity, populatedFieldName, {
|
|
5280
|
-
value: fkIds.map(hydrateClone).filter(Boolean),
|
|
5281
|
-
writable: true,
|
|
5282
|
-
enumerable: true,
|
|
5283
|
-
configurable: true
|
|
5284
|
-
});
|
|
5285
|
-
} else if (typeof fkValue === "string" && relatedEntities.has(fkValue)) {
|
|
5286
|
-
Object.defineProperty(entity, populatedFieldName, {
|
|
5287
|
-
value: [hydrateClone(fkValue)],
|
|
5288
|
-
writable: true,
|
|
5289
|
-
enumerable: true,
|
|
5290
|
-
configurable: true
|
|
5291
|
-
});
|
|
5292
|
-
}
|
|
3492
|
+
eventNamespaces[orbitalName][traitName] = namespace;
|
|
3493
|
+
}
|
|
3494
|
+
const preprocessedOrbital = {
|
|
3495
|
+
name: orbitalName,
|
|
3496
|
+
description: resolvedOrbital.original.description,
|
|
3497
|
+
visual_prompt: resolvedOrbital.original.visual_prompt,
|
|
3498
|
+
// Resolved entity (always inline now)
|
|
3499
|
+
entity: resolvedOrbital.entity,
|
|
3500
|
+
// Resolved traits (inline definitions)
|
|
3501
|
+
traits: (resolvedOrbital.traits || []).map((rt) => {
|
|
3502
|
+
if (rt.config || rt.linkedEntity) {
|
|
3503
|
+
return {
|
|
3504
|
+
ref: rt.trait.name,
|
|
3505
|
+
config: rt.config,
|
|
3506
|
+
linkedEntity: rt.linkedEntity,
|
|
3507
|
+
// Include the resolved trait definition for runtime
|
|
3508
|
+
_resolved: rt.trait
|
|
3509
|
+
};
|
|
5293
3510
|
}
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
3511
|
+
return rt.trait;
|
|
3512
|
+
}),
|
|
3513
|
+
// Resolved pages (inline definitions with path overrides applied)
|
|
3514
|
+
pages: resolvedOrbital.pages.map((rp) => rp.page),
|
|
3515
|
+
// Preserve other fields
|
|
3516
|
+
exposes: resolvedOrbital.original.exposes,
|
|
3517
|
+
domainContext: resolvedOrbital.original.domainContext,
|
|
3518
|
+
design: resolvedOrbital.original.design,
|
|
3519
|
+
// Gap #22: pass through auxiliary entities so OrbitalServerRuntime's
|
|
3520
|
+
// mock-seed branch registers SearchResult / FilterTarget / PagedItem
|
|
3521
|
+
// alongside the molecule's primary entity. Without this, an inlined
|
|
3522
|
+
// .orb that has `auxiliaryEntities` populated by the Rust inline
|
|
3523
|
+
// phase still loses them here, and `(set @entity.searchTerm ...)` /
|
|
3524
|
+
// `(fetch SearchResult ...)` from no-rebind imports hit unregistered
|
|
3525
|
+
// persistence and silently no-op.
|
|
3526
|
+
auxiliaryEntities: resolvedOrbital.original.auxiliaryEntities
|
|
3527
|
+
};
|
|
3528
|
+
preprocessedOrbitals.push(preprocessedOrbital);
|
|
3529
|
+
}
|
|
3530
|
+
const preprocessedSchema = {
|
|
3531
|
+
...schema,
|
|
3532
|
+
orbitals: preprocessedOrbitals
|
|
3533
|
+
};
|
|
3534
|
+
return {
|
|
3535
|
+
success: true,
|
|
3536
|
+
data: {
|
|
3537
|
+
schema: preprocessedSchema,
|
|
3538
|
+
entitySharing,
|
|
3539
|
+
eventNamespaces,
|
|
3540
|
+
warnings
|
|
5302
3541
|
}
|
|
3542
|
+
};
|
|
3543
|
+
}
|
|
3544
|
+
function getIsolatedCollectionName(orbitalName, entitySharing) {
|
|
3545
|
+
const info = entitySharing[orbitalName];
|
|
3546
|
+
if (!info) {
|
|
3547
|
+
throw new Error(`Unknown orbital: ${orbitalName}`);
|
|
5303
3548
|
}
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
3549
|
+
if (info.persistence === "runtime") {
|
|
3550
|
+
return `${orbitalName}_${info.entityName}`;
|
|
3551
|
+
}
|
|
3552
|
+
return info.collectionName || info.entityName.toLowerCase() + "s";
|
|
3553
|
+
}
|
|
3554
|
+
function getNamespacedEvent(orbitalName, traitName, eventName, eventNamespaces) {
|
|
3555
|
+
const orbitalNs = eventNamespaces[orbitalName];
|
|
3556
|
+
if (!orbitalNs) return eventName;
|
|
3557
|
+
const traitNs = orbitalNs[traitName];
|
|
3558
|
+
if (!traitNs) return eventName;
|
|
3559
|
+
return traitNs.emits[eventName] || eventName;
|
|
3560
|
+
}
|
|
3561
|
+
function isNamespacedEvent(eventName) {
|
|
3562
|
+
return eventName.includes(".");
|
|
3563
|
+
}
|
|
3564
|
+
function parseNamespacedEvent(eventName) {
|
|
3565
|
+
const parts = eventName.split(".");
|
|
3566
|
+
if (parts.length === 3) {
|
|
3567
|
+
return { orbital: parts[0], trait: parts[1], event: parts[2] };
|
|
3568
|
+
}
|
|
3569
|
+
if (parts.length === 2) {
|
|
3570
|
+
return { trait: parts[0], event: parts[1] };
|
|
3571
|
+
}
|
|
3572
|
+
return { event: eventName };
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3575
|
+
// src/PersistenceAdapter.ts
|
|
3576
|
+
var InMemoryPersistence = class {
|
|
3577
|
+
data = /* @__PURE__ */ new Map();
|
|
3578
|
+
idCounter = 0;
|
|
5307
3579
|
/**
|
|
5308
|
-
*
|
|
5309
|
-
*
|
|
5310
|
-
* All data access goes through trait events with guards.
|
|
5311
|
-
* No direct CRUD routes - use events with `fetch` effects.
|
|
3580
|
+
* Seed the store with pre-existing rows.
|
|
5312
3581
|
*
|
|
5313
|
-
*
|
|
5314
|
-
*
|
|
5315
|
-
*
|
|
5316
|
-
*
|
|
3582
|
+
* Accepts either a plain `Record<entityType, EntityRow[]>` or an iterable
|
|
3583
|
+
* of `[entityType, EntityRow[]]` entries. Rows without an `id` get one
|
|
3584
|
+
* generated at insert time; rows with an `id` keep it (so re-seeding
|
|
3585
|
+
* after a schema rebuild preserves identities used in render bindings).
|
|
5317
3586
|
*/
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
}
|
|
5324
|
-
const { Router } = nodeRequire("express");
|
|
5325
|
-
const router = Router();
|
|
5326
|
-
router.get("/", (_req, res) => {
|
|
5327
|
-
const orbitals = Array.from(this.orbitals.entries()).map(
|
|
5328
|
-
([name, reg]) => ({
|
|
5329
|
-
name,
|
|
5330
|
-
entity: reg.entity?.name,
|
|
5331
|
-
traits: (reg.traits || []).map((t) => t.name)
|
|
5332
|
-
})
|
|
5333
|
-
);
|
|
5334
|
-
res.json({ success: true, orbitals });
|
|
5335
|
-
});
|
|
5336
|
-
router.get("/:orbital", (req, res) => {
|
|
5337
|
-
const orbitalName = req.params.orbital;
|
|
5338
|
-
const registered = this.orbitals.get(orbitalName);
|
|
5339
|
-
if (!registered) {
|
|
5340
|
-
res.status(404).json({ success: false, error: "Orbital not found" });
|
|
5341
|
-
return;
|
|
5342
|
-
}
|
|
5343
|
-
const states = {};
|
|
5344
|
-
for (const [name, state] of registered.manager.getAllStates()) {
|
|
5345
|
-
states[name] = state.currentState;
|
|
3587
|
+
seed(seedData) {
|
|
3588
|
+
const entries = Symbol.iterator in Object(seedData) ? seedData : Object.entries(seedData);
|
|
3589
|
+
for (const [entityType, rows] of entries) {
|
|
3590
|
+
if (!this.data.has(entityType)) {
|
|
3591
|
+
this.data.set(entityType, /* @__PURE__ */ new Map());
|
|
5346
3592
|
}
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
entity: registered.entity,
|
|
5352
|
-
traits: registered.traits.map((t) => ({
|
|
5353
|
-
name: t.name,
|
|
5354
|
-
currentState: states[t.name],
|
|
5355
|
-
states: (t.stateMachine?.states || []).map((s) => s.name),
|
|
5356
|
-
events: [...new Set((t.stateMachine?.transitions || []).map((tr) => tr.event))]
|
|
5357
|
-
}))
|
|
5358
|
-
}
|
|
5359
|
-
});
|
|
5360
|
-
});
|
|
5361
|
-
router.post(
|
|
5362
|
-
"/:orbital/events",
|
|
5363
|
-
async (req, res, next) => {
|
|
5364
|
-
try {
|
|
5365
|
-
const orbitalName = req.params.orbital;
|
|
5366
|
-
const firebaseUser = req.firebaseUser;
|
|
5367
|
-
const user = firebaseUser ? {
|
|
5368
|
-
...firebaseUser,
|
|
5369
|
-
displayName: firebaseUser.name ?? firebaseUser.displayName
|
|
5370
|
-
} : void 0;
|
|
5371
|
-
const result = await this.processOrbitalEvent(orbitalName, {
|
|
5372
|
-
...req.body,
|
|
5373
|
-
user
|
|
5374
|
-
});
|
|
5375
|
-
res.json(result);
|
|
5376
|
-
} catch (error) {
|
|
5377
|
-
next(error);
|
|
5378
|
-
}
|
|
3593
|
+
const collection = this.data.get(entityType);
|
|
3594
|
+
for (const row of rows) {
|
|
3595
|
+
const id = row.id || `${entityType}-${++this.idCounter}`;
|
|
3596
|
+
collection.set(id, { ...row, id });
|
|
5379
3597
|
}
|
|
5380
|
-
|
|
5381
|
-
return router;
|
|
5382
|
-
}
|
|
5383
|
-
// ==========================================================================
|
|
5384
|
-
// Direct API (for programmatic use)
|
|
5385
|
-
// ==========================================================================
|
|
5386
|
-
/**
|
|
5387
|
-
* Get the event bus for manual event emission
|
|
5388
|
-
*/
|
|
5389
|
-
getEventBus() {
|
|
5390
|
-
return this.eventBus;
|
|
3598
|
+
}
|
|
5391
3599
|
}
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
const registered = this.orbitals.get(orbitalName);
|
|
5397
|
-
if (!registered) return void 0;
|
|
5398
|
-
if (traitName) {
|
|
5399
|
-
return registered.manager.getState(traitName);
|
|
3600
|
+
async create(entityType, data) {
|
|
3601
|
+
const id = data.id || `${entityType}-${++this.idCounter}`;
|
|
3602
|
+
if (!this.data.has(entityType)) {
|
|
3603
|
+
this.data.set(entityType, /* @__PURE__ */ new Map());
|
|
5400
3604
|
}
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
3605
|
+
this.data.get(entityType).set(id, { ...data, id });
|
|
3606
|
+
return { id };
|
|
3607
|
+
}
|
|
3608
|
+
async update(entityType, id, data) {
|
|
3609
|
+
const collection = this.data.get(entityType);
|
|
3610
|
+
if (collection?.has(id)) {
|
|
3611
|
+
const existing = collection.get(id);
|
|
3612
|
+
collection.set(id, { ...existing, ...data });
|
|
5404
3613
|
}
|
|
5405
|
-
return states;
|
|
5406
3614
|
}
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
*/
|
|
5410
|
-
listOrbitals() {
|
|
5411
|
-
return Array.from(this.orbitals.keys());
|
|
3615
|
+
async delete(entityType, id) {
|
|
3616
|
+
this.data.get(entityType)?.delete(id);
|
|
5412
3617
|
}
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
3618
|
+
async getById(entityType, id) {
|
|
3619
|
+
return this.data.get(entityType)?.get(id) || null;
|
|
3620
|
+
}
|
|
3621
|
+
async list(entityType) {
|
|
3622
|
+
const collection = this.data.get(entityType);
|
|
3623
|
+
return collection ? Array.from(collection.values()) : [];
|
|
5418
3624
|
}
|
|
5419
3625
|
/**
|
|
5420
|
-
*
|
|
3626
|
+
* Snapshot the entire store as a plain object (entityType → rows).
|
|
3627
|
+
* Useful for feeding a fresh render-time binding layer with the
|
|
3628
|
+
* current persistence view.
|
|
5421
3629
|
*/
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
tick: binding.tick.name,
|
|
5427
|
-
interval: binding.tick.interval,
|
|
5428
|
-
hasGuard: !!binding.tick.guard
|
|
5429
|
-
}));
|
|
5430
|
-
}
|
|
5431
|
-
};
|
|
5432
|
-
function createOrbitalServerRuntime(config) {
|
|
5433
|
-
return new OrbitalServerRuntime(config);
|
|
5434
|
-
}
|
|
5435
|
-
function parseListenSource(listener, listenerOrbital) {
|
|
5436
|
-
const explicit = listener.source;
|
|
5437
|
-
if (explicit && typeof explicit === "object") {
|
|
5438
|
-
return {
|
|
5439
|
-
bareEvent: listener.event,
|
|
5440
|
-
matcher: buildMatcher(explicit, listenerOrbital)
|
|
5441
|
-
};
|
|
5442
|
-
}
|
|
5443
|
-
const key = listener.event;
|
|
5444
|
-
const parts = key.split(".");
|
|
5445
|
-
if (parts.length === 1) {
|
|
5446
|
-
return { bareEvent: key, matcher: () => true };
|
|
5447
|
-
}
|
|
5448
|
-
if (parts.length === 2) {
|
|
5449
|
-
const [sourceOrStar, eventName] = parts;
|
|
5450
|
-
if (sourceOrStar === "*") {
|
|
5451
|
-
return { bareEvent: eventName, matcher: () => true };
|
|
3630
|
+
snapshot() {
|
|
3631
|
+
const out = {};
|
|
3632
|
+
for (const [entityType, collection] of this.data) {
|
|
3633
|
+
out[entityType] = Array.from(collection.values());
|
|
5452
3634
|
}
|
|
5453
|
-
return
|
|
5454
|
-
bareEvent: eventName,
|
|
5455
|
-
matcher: buildMatcher(
|
|
5456
|
-
{ kind: "trait", trait: sourceOrStar },
|
|
5457
|
-
listenerOrbital
|
|
5458
|
-
)
|
|
5459
|
-
};
|
|
5460
|
-
}
|
|
5461
|
-
if (parts.length >= 3) {
|
|
5462
|
-
const eventName = parts[parts.length - 1];
|
|
5463
|
-
const trait = parts[parts.length - 2];
|
|
5464
|
-
const orbital = parts.slice(0, parts.length - 2).join(".");
|
|
5465
|
-
return {
|
|
5466
|
-
bareEvent: eventName,
|
|
5467
|
-
matcher: buildMatcher({ kind: "orbital", orbital, trait }, listenerOrbital)
|
|
5468
|
-
};
|
|
3635
|
+
return out;
|
|
5469
3636
|
}
|
|
5470
|
-
|
|
5471
|
-
}
|
|
5472
|
-
function buildMatcher(src, listenerOrbital) {
|
|
5473
|
-
if (src.kind === "any") return () => true;
|
|
5474
|
-
if (src.kind === "trait") {
|
|
5475
|
-
const wantedTrait2 = src.trait;
|
|
5476
|
-
return (source) => !!source && source.orbital === listenerOrbital && source.trait === wantedTrait2;
|
|
5477
|
-
}
|
|
5478
|
-
const wantedOrbital = src.orbital;
|
|
5479
|
-
const wantedTrait = src.trait;
|
|
5480
|
-
return (source) => !!source && source.orbital === wantedOrbital && source.trait === wantedTrait;
|
|
5481
|
-
}
|
|
3637
|
+
};
|
|
5482
3638
|
|
|
5483
|
-
export { EffectExecutor, EventBus, HANDLER_MANIFEST, InMemoryPersistence, MockPersistenceAdapter,
|
|
5484
|
-
//# sourceMappingURL=chunk-
|
|
5485
|
-
//# sourceMappingURL=chunk-
|
|
3639
|
+
export { EffectExecutor, EventBus, HANDLER_MANIFEST, InMemoryPersistence, MockPersistenceAdapter, StateMachineManager, buildEmitsFromTraits, collectDeclaredConfigDefaults, containsBindings, createContextFromBindings, createInitialTraitState, createMockPersistence, createTestExecutor, createUnifiedLoader, extractBindings, findInitialState, findTransition, formatPayloadValidationError, getIsolatedCollectionName, getNamespacedEvent, interpolateProps, interpolateValue, isBrowser, isElectron, isNamespacedEvent, isNode, normalizeEventKey, parseNamespacedEvent, preprocessSchema, processEvent, validateEventPayload, validatePayloadShapes };
|
|
3640
|
+
//# sourceMappingURL=chunk-R6Y4IJ7I.js.map
|
|
3641
|
+
//# sourceMappingURL=chunk-R6Y4IJ7I.js.map
|