@almadar/runtime 6.31.0 → 6.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{OrbitalServerRuntime-DpAwIrmv.d.ts → OrbitalServerRuntime-Cep380it.d.ts} +13 -14
- package/dist/OrbitalServerRuntime.d.ts +2 -2
- package/dist/OrbitalServerRuntime.js +181 -137
- package/dist/ServerBridge.d.ts +1 -1
- package/dist/{chunk-YFH577FH.js → chunk-JZDLN2FS.js} +415 -50
- package/dist/chunk-O5VGKPTG.js +435 -0
- package/dist/chunk-OQJIK6PZ.js +163 -0
- package/dist/chunk-SCRAHWOC.js +82 -0
- package/dist/createOsHandlers.d.ts +1 -1
- package/dist/index.d.ts +37 -18
- package/dist/index.js +53 -3
- package/dist/mockRandom.d.ts +54 -0
- package/dist/mockRandom.js +2 -0
- package/dist/{types-D-9feVsj.d.ts → types-ConZnrpe.d.ts} +41 -1
- package/dist/ui/index.d.ts +419 -0
- package/dist/ui/index.js +3 -0
- package/package.json +17 -7
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { parseCron, cronMinuteKey, cronMatches } from './chunk-OU3ITB5S.js';
|
|
2
|
+
import { seedRandom, randomArrayElement, randomInt, shuffleArray, randomPastDate, randomBoolean, randomRecentDate, randomUuid, randomPhone, randomUrl, randomEmail, randomWords } from './chunk-OQJIK6PZ.js';
|
|
3
|
+
import { collectTraitRefsFromValue, collectTraitRefsFromEffects } from './chunk-SCRAHWOC.js';
|
|
2
4
|
import { createLogger, setNamespaceLevel } from '@almadar/logger';
|
|
3
|
-
import { resolveBinding, evaluate,
|
|
5
|
+
import { createMinimalContext, resolveBinding, evaluate, evaluateGuard, SExpressionEvaluator } from '@almadar/evaluator';
|
|
4
6
|
export { createMinimalContext } from '@almadar/evaluator';
|
|
5
7
|
import { isKnownStdOperator } from '@almadar/std/registry';
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
+
import { OrbitalSchemaSchema, isInlineTrait, isEntityCall, isEntityReference, parseEntityRef, parseImportedTraitRef, isPageReference, isPageReferenceString, isPageReferenceObject, parsePageRef, configRefEventKnob, normalizeCallSiteConfigToValues, resolveConfigRefEventName } from '@almadar/core';
|
|
9
|
+
export { normalizeCallSiteConfigToValues } from '@almadar/core';
|
|
8
10
|
|
|
9
11
|
var log = createLogger("almadar:runtime:eventbus");
|
|
10
12
|
var EventBus = class {
|
|
@@ -289,6 +291,33 @@ var bindLog = createLogger("almadar:runtime:bindings");
|
|
|
289
291
|
setNamespaceLevel("almadar:runtime:bindings", "WARN");
|
|
290
292
|
var renderLog = createLogger("almadar:runtime:render-ui");
|
|
291
293
|
var CLIENT_ONLY_BINDING_ROOTS = /* @__PURE__ */ new Set(["trait"]);
|
|
294
|
+
var CALLSITE_PAYLOAD_PREFIX = "@callsitePayload.";
|
|
295
|
+
function payloadValueToConfigValue(v) {
|
|
296
|
+
if (v === null || v === void 0) return null;
|
|
297
|
+
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") return v;
|
|
298
|
+
if (v instanceof Date) return v.toISOString();
|
|
299
|
+
if (Array.isArray(v)) return v.map(payloadValueToConfigValue);
|
|
300
|
+
if (typeof v === "object") {
|
|
301
|
+
const obj = {};
|
|
302
|
+
for (const [k, val] of Object.entries(v)) obj[k] = payloadValueToConfigValue(val);
|
|
303
|
+
return obj;
|
|
304
|
+
}
|
|
305
|
+
return String(v);
|
|
306
|
+
}
|
|
307
|
+
function resolveCallSitePayloadCaptures(config, payload) {
|
|
308
|
+
let ctx;
|
|
309
|
+
const out = {};
|
|
310
|
+
for (const [key, value] of Object.entries(config)) {
|
|
311
|
+
if (typeof value === "string" && value.startsWith(CALLSITE_PAYLOAD_PREFIX)) {
|
|
312
|
+
const field = value.slice(CALLSITE_PAYLOAD_PREFIX.length);
|
|
313
|
+
if (!ctx) ctx = createMinimalContext({}, payload ?? {}, "idle");
|
|
314
|
+
out[key] = payloadValueToConfigValue(resolveBinding(`@payload.${field}`, ctx));
|
|
315
|
+
} else {
|
|
316
|
+
out[key] = value;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return out;
|
|
320
|
+
}
|
|
292
321
|
function isClientOnlyBinding(value) {
|
|
293
322
|
if (!value.startsWith("@")) return false;
|
|
294
323
|
const afterAt = value.slice(1);
|
|
@@ -982,7 +1011,7 @@ var StateMachineManager = class {
|
|
|
982
1011
|
|
|
983
1012
|
// src/types.ts
|
|
984
1013
|
var HANDLER_MANIFEST = {
|
|
985
|
-
client: ["render-ui", "render", "navigate", "notify", "emit", "set", "log", "ref", "deref", "watch", "send-server"],
|
|
1014
|
+
client: ["render-ui", "render", "navigate", "notify", "emit", "set", "log", "ref", "deref", "watch", "send-server", "browser/open-file-picker", "browser/clipboard-read", "browser/clipboard-write", "browser/geolocation-current"],
|
|
986
1015
|
server: ["persist", "fetch", "fetch-stream", "call-service", "emit", "set", "spawn", "despawn", "log", "ref", "deref", "swap!", "atomic", "os/watch-files", "os/watch-process", "os/watch-port", "os/watch-http", "os/watch-cron", "os/watch-signal", "os/watch-env", "os/debounce"],
|
|
987
1016
|
test: [
|
|
988
1017
|
"render-ui",
|
|
@@ -1002,7 +1031,11 @@ var HANDLER_MANIFEST = {
|
|
|
1002
1031
|
"swap!",
|
|
1003
1032
|
"watch",
|
|
1004
1033
|
"atomic",
|
|
1005
|
-
"send-server"
|
|
1034
|
+
"send-server",
|
|
1035
|
+
"browser/open-file-picker",
|
|
1036
|
+
"browser/clipboard-read",
|
|
1037
|
+
"browser/clipboard-write",
|
|
1038
|
+
"browser/geolocation-current"
|
|
1006
1039
|
],
|
|
1007
1040
|
ssr: ["render-ui", "render", "fetch", "emit", "set", "log", "ref", "deref"]
|
|
1008
1041
|
};
|
|
@@ -1938,6 +1971,58 @@ var EffectExecutor = class _EffectExecutor {
|
|
|
1938
1971
|
}, emitCfg);
|
|
1939
1972
|
break;
|
|
1940
1973
|
}
|
|
1974
|
+
// === Browser device operators (client-side, user-gesture) ===
|
|
1975
|
+
// Async host APIs routed to dedicated handler methods. Uniform
|
|
1976
|
+
// `{ result }` / `{ error }` payload via runSubstrate. On the
|
|
1977
|
+
// server (or any host without the API) the handler is absent →
|
|
1978
|
+
// unsupported warning; a thrown error fires `emit.failure`.
|
|
1979
|
+
case "browser/open-file-picker": {
|
|
1980
|
+
const [positional, emitCfg] = this.splitSubstrateEmit(args);
|
|
1981
|
+
const options = positional[0];
|
|
1982
|
+
await this.runSubstrate(async () => {
|
|
1983
|
+
if (!this.handlers.browserOpenFilePicker) {
|
|
1984
|
+
this.logUnsupported("browser/open-file-picker");
|
|
1985
|
+
return null;
|
|
1986
|
+
}
|
|
1987
|
+
return this.handlers.browserOpenFilePicker(options);
|
|
1988
|
+
}, emitCfg);
|
|
1989
|
+
break;
|
|
1990
|
+
}
|
|
1991
|
+
case "browser/clipboard-read": {
|
|
1992
|
+
const [, emitCfg] = this.splitSubstrateEmit(args);
|
|
1993
|
+
await this.runSubstrate(async () => {
|
|
1994
|
+
if (!this.handlers.browserClipboardRead) {
|
|
1995
|
+
this.logUnsupported("browser/clipboard-read");
|
|
1996
|
+
return null;
|
|
1997
|
+
}
|
|
1998
|
+
return this.handlers.browserClipboardRead();
|
|
1999
|
+
}, emitCfg);
|
|
2000
|
+
break;
|
|
2001
|
+
}
|
|
2002
|
+
case "browser/clipboard-write": {
|
|
2003
|
+
const [positional, emitCfg] = this.splitSubstrateEmit(args);
|
|
2004
|
+
const text = positional[0];
|
|
2005
|
+
await this.runSubstrate(async () => {
|
|
2006
|
+
if (!this.handlers.browserClipboardWrite) {
|
|
2007
|
+
this.logUnsupported("browser/clipboard-write");
|
|
2008
|
+
return null;
|
|
2009
|
+
}
|
|
2010
|
+
return this.handlers.browserClipboardWrite(text);
|
|
2011
|
+
}, emitCfg);
|
|
2012
|
+
break;
|
|
2013
|
+
}
|
|
2014
|
+
case "browser/geolocation-current": {
|
|
2015
|
+
const [positional, emitCfg] = this.splitSubstrateEmit(args);
|
|
2016
|
+
const options = positional[0];
|
|
2017
|
+
await this.runSubstrate(async () => {
|
|
2018
|
+
if (!this.handlers.browserGeolocationCurrent) {
|
|
2019
|
+
this.logUnsupported("browser/geolocation-current");
|
|
2020
|
+
return null;
|
|
2021
|
+
}
|
|
2022
|
+
return this.handlers.browserGeolocationCurrent(options);
|
|
2023
|
+
}, emitCfg);
|
|
2024
|
+
break;
|
|
2025
|
+
}
|
|
1941
2026
|
default: {
|
|
1942
2027
|
if (operator.includes("/")) {
|
|
1943
2028
|
const [positional, emitCfg] = this.splitSubstrateEmit(args);
|
|
@@ -2104,21 +2189,6 @@ function collectDeclaredConfigDefaults(trait) {
|
|
|
2104
2189
|
}
|
|
2105
2190
|
return hasAny ? defaults : void 0;
|
|
2106
2191
|
}
|
|
2107
|
-
function normalizeCallSiteConfigToValues(config) {
|
|
2108
|
-
if (config === void 0) {
|
|
2109
|
-
return void 0;
|
|
2110
|
-
}
|
|
2111
|
-
const out = {};
|
|
2112
|
-
let hasAny = false;
|
|
2113
|
-
for (const [key, entry] of Object.entries(config)) {
|
|
2114
|
-
const value = isCallSiteConfigDeclaration(entry) ? entry.default : entry;
|
|
2115
|
-
if (value !== void 0) {
|
|
2116
|
-
out[key] = value;
|
|
2117
|
-
hasAny = true;
|
|
2118
|
-
}
|
|
2119
|
-
}
|
|
2120
|
-
return hasAny ? out : void 0;
|
|
2121
|
-
}
|
|
2122
2192
|
function collectDeclaredEntityDefaults(entity) {
|
|
2123
2193
|
if (!entity) return void 0;
|
|
2124
2194
|
const defaults = {};
|
|
@@ -2134,7 +2204,7 @@ function collectDeclaredEntityDefaults(entity) {
|
|
|
2134
2204
|
var mockLog = createLogger("almadar:runtime:mock");
|
|
2135
2205
|
var DEFAULT_MOCK_SEED = 42;
|
|
2136
2206
|
function picsumUrl(entityName, fieldName, width = 400, height = 400) {
|
|
2137
|
-
const seed = `${entityName}-${fieldName}-${
|
|
2207
|
+
const seed = `${entityName}-${fieldName}-${randomInt({ min: 0, max: 1e3 })}`;
|
|
2138
2208
|
return `https://picsum.photos/seed/${encodeURIComponent(seed)}/${width}/${height}`;
|
|
2139
2209
|
}
|
|
2140
2210
|
var SEED_REFERENCE_TIMESTAMP = "2024-01-01T00:00:00.000Z";
|
|
@@ -2152,17 +2222,17 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2152
2222
|
// input doesn't overwrite the default.
|
|
2153
2223
|
seed: config.seed ?? DEFAULT_MOCK_SEED
|
|
2154
2224
|
};
|
|
2155
|
-
|
|
2225
|
+
seedRandom(this.config.seed);
|
|
2156
2226
|
mockLog.debug("mock:adapter:init", { seed: this.config.seed });
|
|
2157
2227
|
}
|
|
2158
|
-
/** Re-anchor
|
|
2228
|
+
/** Re-anchor the PRNG to the configured seed. Called before every
|
|
2159
2229
|
* re-seed loop so identical reseed sequences produce identical rows
|
|
2160
|
-
* (timestamps +
|
|
2230
|
+
* (timestamps + generated fields). Without this, the first
|
|
2161
2231
|
* reseed produces row set A, the second produces row set B, and
|
|
2162
2232
|
* diff observers see all rows as "changed" between frames. */
|
|
2163
2233
|
resetFakerSeed() {
|
|
2164
2234
|
if (this.config.seed !== void 0) {
|
|
2165
|
-
|
|
2235
|
+
seedRandom(this.config.seed);
|
|
2166
2236
|
}
|
|
2167
2237
|
}
|
|
2168
2238
|
// ============================================================================
|
|
@@ -2188,7 +2258,7 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2188
2258
|
/**
|
|
2189
2259
|
* Register an entity schema and seed mock data.
|
|
2190
2260
|
* If the schema has seedData, those instances are used directly.
|
|
2191
|
-
* Otherwise, random mock data is generated with
|
|
2261
|
+
* Otherwise, random mock data is generated with the seeded PRNG.
|
|
2192
2262
|
*/
|
|
2193
2263
|
registerEntity(schema, seedCount) {
|
|
2194
2264
|
const normalized = schema.name.toLowerCase();
|
|
@@ -2236,10 +2306,10 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2236
2306
|
if (eligible.length === 0) continue;
|
|
2237
2307
|
const cardinality = field.relation.cardinality ?? "many";
|
|
2238
2308
|
if (cardinality === "one" || cardinality === "many-to-one") {
|
|
2239
|
-
row[field.name] =
|
|
2309
|
+
row[field.name] = randomArrayElement(eligible);
|
|
2240
2310
|
} else {
|
|
2241
|
-
const pickCount = Math.min(eligible.length,
|
|
2242
|
-
row[field.name] =
|
|
2311
|
+
const pickCount = Math.min(eligible.length, randomInt({ min: 2, max: 4 }));
|
|
2312
|
+
row[field.name] = shuffleArray(eligible.slice()).slice(0, pickCount);
|
|
2243
2313
|
}
|
|
2244
2314
|
}
|
|
2245
2315
|
}
|
|
@@ -2291,7 +2361,7 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2291
2361
|
const id = this.nextId(entityName);
|
|
2292
2362
|
const item = {
|
|
2293
2363
|
id,
|
|
2294
|
-
createdAt:
|
|
2364
|
+
createdAt: randomPastDate({ years: 1 }).toISOString(),
|
|
2295
2365
|
updatedAt: SEED_REFERENCE_TIMESTAMP
|
|
2296
2366
|
};
|
|
2297
2367
|
for (const field of fields) {
|
|
@@ -2333,16 +2403,16 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2333
2403
|
case "string":
|
|
2334
2404
|
return this.generateStringValue(entityName, field, index);
|
|
2335
2405
|
case "number":
|
|
2336
|
-
return
|
|
2406
|
+
return randomInt({ min: 0, max: 100 });
|
|
2337
2407
|
case "boolean":
|
|
2338
|
-
return
|
|
2408
|
+
return randomBoolean();
|
|
2339
2409
|
case "date":
|
|
2340
2410
|
case "timestamp":
|
|
2341
2411
|
case "datetime":
|
|
2342
2412
|
return this.generateDateValue(field);
|
|
2343
2413
|
case "enum":
|
|
2344
2414
|
if (field.values && field.values.length > 0) {
|
|
2345
|
-
return
|
|
2415
|
+
return randomArrayElement(field.values);
|
|
2346
2416
|
}
|
|
2347
2417
|
return null;
|
|
2348
2418
|
case "relation":
|
|
@@ -2367,7 +2437,7 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2367
2437
|
generateArrayValue(entityName, field, index, depth = 0) {
|
|
2368
2438
|
if (field.type !== "array" || !field.items) return [];
|
|
2369
2439
|
if (depth >= _MockPersistenceAdapter.MAX_NESTED_DEPTH) return [];
|
|
2370
|
-
const count =
|
|
2440
|
+
const count = randomInt({ min: 3, max: 5 });
|
|
2371
2441
|
const out = [];
|
|
2372
2442
|
const elementName = field.name ?? "item";
|
|
2373
2443
|
for (let i = 0; i < count; i++) {
|
|
@@ -2381,7 +2451,7 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2381
2451
|
}
|
|
2382
2452
|
/**
|
|
2383
2453
|
* Generate a single object value with each declared property populated
|
|
2384
|
-
* by
|
|
2454
|
+
* by the seeded PRNG. Walks `properties` and recursively delegates to
|
|
2385
2455
|
* `generateFieldValue` per property so nested objects-of-arrays-of-objects
|
|
2386
2456
|
* compose correctly.
|
|
2387
2457
|
*/
|
|
@@ -2398,29 +2468,29 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2398
2468
|
/**
|
|
2399
2469
|
* Generate a string value based on the field's declared schema metadata.
|
|
2400
2470
|
* Reads `values` (enum) first, then `format` (email/url/phone/uuid/date/
|
|
2401
|
-
* datetime), then falls back to
|
|
2471
|
+
* datetime), then falls back to randomWords. No field-name heuristics
|
|
2402
2472
|
* — the schema is the source of truth. If a caller needs a real email, they
|
|
2403
2473
|
* declare `format: "email"`; if they need an enum, they declare `values: [...]`.
|
|
2404
2474
|
*/
|
|
2405
2475
|
generateStringValue(entityName, field, _index) {
|
|
2406
2476
|
const values = "values" in field ? field.values : void 0;
|
|
2407
2477
|
if (values && values.length > 0) {
|
|
2408
|
-
return
|
|
2478
|
+
return randomArrayElement(values);
|
|
2409
2479
|
}
|
|
2410
2480
|
const fieldName = field.name ?? "field";
|
|
2411
2481
|
switch (field.format) {
|
|
2412
2482
|
case "email":
|
|
2413
|
-
return
|
|
2483
|
+
return randomEmail();
|
|
2414
2484
|
case "url":
|
|
2415
|
-
return
|
|
2485
|
+
return randomUrl();
|
|
2416
2486
|
case "phone":
|
|
2417
|
-
return
|
|
2487
|
+
return randomPhone();
|
|
2418
2488
|
case "uuid":
|
|
2419
|
-
return
|
|
2489
|
+
return randomUuid();
|
|
2420
2490
|
case "date":
|
|
2421
|
-
return
|
|
2491
|
+
return randomRecentDate().toISOString().split("T")[0];
|
|
2422
2492
|
case "datetime":
|
|
2423
|
-
return
|
|
2493
|
+
return randomRecentDate().toISOString();
|
|
2424
2494
|
case "image":
|
|
2425
2495
|
case "avatar":
|
|
2426
2496
|
case "thumbnail":
|
|
@@ -2430,7 +2500,7 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2430
2500
|
if (lname === "image" || lname === "imageurl" || lname === "image_url" || lname === "photo" || lname === "photourl" || lname === "photo_url" || lname === "avatar" || lname === "avatarurl" || lname === "avatar_url" || lname === "thumbnail" || lname === "thumbnailurl" || lname === "thumbnail_url" || lname === "picture" || lname === "pictureurl" || lname === "cover" || lname === "coverurl" || lname === "banner" || lname === "bannerurl") {
|
|
2431
2501
|
return picsumUrl(entityName, fieldName);
|
|
2432
2502
|
}
|
|
2433
|
-
const value =
|
|
2503
|
+
const value = randomWords(2);
|
|
2434
2504
|
mockLog.debug("field:fallback-lorem", () => ({
|
|
2435
2505
|
entityName,
|
|
2436
2506
|
fieldName: field.name,
|
|
@@ -2446,7 +2516,7 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2446
2516
|
* field-name heuristics.
|
|
2447
2517
|
*/
|
|
2448
2518
|
generateDateValue(field) {
|
|
2449
|
-
const date =
|
|
2519
|
+
const date = randomRecentDate({ days: 30 });
|
|
2450
2520
|
if (field.format === "date") return date.toISOString().split("T")[0];
|
|
2451
2521
|
return date.toISOString();
|
|
2452
2522
|
}
|
|
@@ -2530,7 +2600,7 @@ var MockPersistenceAdapter = class _MockPersistenceAdapter {
|
|
|
2530
2600
|
this.stores.delete(normalized);
|
|
2531
2601
|
this.idCounters.delete(normalized);
|
|
2532
2602
|
}
|
|
2533
|
-
/** Clear all data + re-anchor
|
|
2603
|
+
/** Clear all data + re-anchor the PRNG so the next seed loop reproduces
|
|
2534
2604
|
* identical rows. Hermetic-frame mode calls this between every step
|
|
2535
2605
|
* via OrbitalServerRuntime.resetMockPersistence. */
|
|
2536
2606
|
clearAll() {
|
|
@@ -3158,6 +3228,254 @@ function createUnifiedLoader(options) {
|
|
|
3158
3228
|
return new UnifiedLoader(options);
|
|
3159
3229
|
}
|
|
3160
3230
|
createLogger("almadar:runtime:studio-config");
|
|
3231
|
+
|
|
3232
|
+
// src/ui/splice-lambda-traits.ts
|
|
3233
|
+
var TRAIT_BINDING_PREFIX = "@trait.";
|
|
3234
|
+
var CONFIG_BINDING_PREFIX = "@config.";
|
|
3235
|
+
var LambdaSpliceError = class extends Error {
|
|
3236
|
+
constructor(code, message) {
|
|
3237
|
+
super(message);
|
|
3238
|
+
this.code = code;
|
|
3239
|
+
this.name = "LambdaSpliceError";
|
|
3240
|
+
}
|
|
3241
|
+
code;
|
|
3242
|
+
};
|
|
3243
|
+
function isRenderUiEffect(effect) {
|
|
3244
|
+
return Array.isArray(effect) && effect.length >= 1 && effect[0] === "render-ui";
|
|
3245
|
+
}
|
|
3246
|
+
function renderOnlyTemplate(trait) {
|
|
3247
|
+
if (trait.listens && trait.listens.length > 0) return null;
|
|
3248
|
+
if (trait.ticks && trait.ticks.length > 0) return null;
|
|
3249
|
+
const sm = trait.stateMachine;
|
|
3250
|
+
const states = sm?.states ?? [];
|
|
3251
|
+
if (states.length > 1) return null;
|
|
3252
|
+
let template = null;
|
|
3253
|
+
for (const transition of sm?.transitions ?? []) {
|
|
3254
|
+
const selfLoop = transition.from === transition.to;
|
|
3255
|
+
const guarded = transition.guard !== void 0 && transition.guard !== null;
|
|
3256
|
+
if (!selfLoop || guarded) return null;
|
|
3257
|
+
for (const effect of transition.effects ?? []) {
|
|
3258
|
+
if (!isRenderUiEffect(effect) || !Array.isArray(effect)) return null;
|
|
3259
|
+
const pattern = effect[2];
|
|
3260
|
+
if (pattern === void 0) return null;
|
|
3261
|
+
if (template !== null) return null;
|
|
3262
|
+
template = pattern;
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
return template;
|
|
3266
|
+
}
|
|
3267
|
+
function resolveObjectPath(value, path) {
|
|
3268
|
+
let cur = value;
|
|
3269
|
+
for (const seg of path.split(".")) {
|
|
3270
|
+
if (cur === null || typeof cur !== "object") return void 0;
|
|
3271
|
+
cur = cur[seg];
|
|
3272
|
+
if (cur === void 0) return void 0;
|
|
3273
|
+
}
|
|
3274
|
+
return cur;
|
|
3275
|
+
}
|
|
3276
|
+
function substituteConfig(expr, subs) {
|
|
3277
|
+
if (typeof expr === "string") {
|
|
3278
|
+
if (!expr.startsWith(CONFIG_BINDING_PREFIX)) return expr;
|
|
3279
|
+
const rest = expr.slice(CONFIG_BINDING_PREFIX.length);
|
|
3280
|
+
const dot = rest.indexOf(".");
|
|
3281
|
+
const param = dot === -1 ? rest : rest.slice(0, dot);
|
|
3282
|
+
const trailing = dot === -1 ? void 0 : rest.slice(dot + 1);
|
|
3283
|
+
if (!(param in subs)) return expr;
|
|
3284
|
+
const value = subs[param];
|
|
3285
|
+
if (trailing === void 0) {
|
|
3286
|
+
if (typeof value === "string" && value === expr) return expr;
|
|
3287
|
+
return substituteConfig(value, subs);
|
|
3288
|
+
}
|
|
3289
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
3290
|
+
const leaf = resolveObjectPath(value, trailing);
|
|
3291
|
+
if (leaf !== void 0) return substituteConfig(leaf, subs);
|
|
3292
|
+
}
|
|
3293
|
+
return expr;
|
|
3294
|
+
}
|
|
3295
|
+
if (Array.isArray(expr)) {
|
|
3296
|
+
return expr.map((item) => substituteConfig(item, subs));
|
|
3297
|
+
}
|
|
3298
|
+
if (expr !== null && typeof expr === "object") {
|
|
3299
|
+
const out = {};
|
|
3300
|
+
for (const [k, v] of Object.entries(expr)) {
|
|
3301
|
+
out[k] = substituteConfig(v, subs);
|
|
3302
|
+
}
|
|
3303
|
+
return out;
|
|
3304
|
+
}
|
|
3305
|
+
return expr;
|
|
3306
|
+
}
|
|
3307
|
+
function buildTemplate(trait) {
|
|
3308
|
+
const raw = renderOnlyTemplate(trait);
|
|
3309
|
+
const emits = trait.emits ?? [];
|
|
3310
|
+
if (raw === null) return { template: null, emits };
|
|
3311
|
+
const defaults = collectDeclaredConfigDefaults(trait);
|
|
3312
|
+
const template = defaults ? substituteConfig(raw, defaults) : raw;
|
|
3313
|
+
return { template, emits };
|
|
3314
|
+
}
|
|
3315
|
+
function isLambdaForm(expr) {
|
|
3316
|
+
return expr.length === 3 && (expr[0] === "fn" || expr[0] === "lambda");
|
|
3317
|
+
}
|
|
3318
|
+
function spliceExpr(expr, inLambda, st) {
|
|
3319
|
+
if (typeof expr === "string") {
|
|
3320
|
+
if (!inLambda || !expr.startsWith(TRAIT_BINDING_PREFIX)) return expr;
|
|
3321
|
+
const name = expr.slice(TRAIT_BINDING_PREFIX.length);
|
|
3322
|
+
if (name.length === 0 || name.includes(".")) return expr;
|
|
3323
|
+
const entry = st.templates.get(name);
|
|
3324
|
+
if (entry === void 0) return expr;
|
|
3325
|
+
if (entry.template === null) {
|
|
3326
|
+
throw new LambdaSpliceError(
|
|
3327
|
+
"LAMBDA_STATEFUL_TRAIT",
|
|
3328
|
+
`Trait "${name}" keeps its own state machine and cannot be expanded per list item \u2014 use the list pattern's item events (referenced inside a lambda in trait "${st.hostName}")`
|
|
3329
|
+
);
|
|
3330
|
+
}
|
|
3331
|
+
if (st.visiting.includes(name)) return expr;
|
|
3332
|
+
st.visiting.push(name);
|
|
3333
|
+
const expanded = spliceExpr(entry.template, true, st);
|
|
3334
|
+
st.visiting.pop();
|
|
3335
|
+
for (const emit of entry.emits) st.merged.push([name, emit]);
|
|
3336
|
+
st.spliced.add(name);
|
|
3337
|
+
st.changed = true;
|
|
3338
|
+
return expanded;
|
|
3339
|
+
}
|
|
3340
|
+
if (Array.isArray(expr)) {
|
|
3341
|
+
if (isLambdaForm(expr)) {
|
|
3342
|
+
return [expr[0], expr[1], spliceExpr(expr[2], true, st)];
|
|
3343
|
+
}
|
|
3344
|
+
return expr.map((item) => spliceExpr(item, inLambda, st));
|
|
3345
|
+
}
|
|
3346
|
+
if (expr !== null && typeof expr === "object") {
|
|
3347
|
+
const out = {};
|
|
3348
|
+
for (const [k, v] of Object.entries(expr)) {
|
|
3349
|
+
out[k] = spliceExpr(v, inLambda, st);
|
|
3350
|
+
}
|
|
3351
|
+
return out;
|
|
3352
|
+
}
|
|
3353
|
+
return expr;
|
|
3354
|
+
}
|
|
3355
|
+
function canonical(value) {
|
|
3356
|
+
if (Array.isArray(value)) return value.map(canonical);
|
|
3357
|
+
if (value !== null && typeof value === "object") {
|
|
3358
|
+
const out = {};
|
|
3359
|
+
for (const key of Object.keys(value).sort()) {
|
|
3360
|
+
out[key] = canonical(value[key]);
|
|
3361
|
+
}
|
|
3362
|
+
return out;
|
|
3363
|
+
}
|
|
3364
|
+
return value;
|
|
3365
|
+
}
|
|
3366
|
+
function mergeWrapperEmits(host, incoming) {
|
|
3367
|
+
const emits = [...host.emits ?? []];
|
|
3368
|
+
for (const [wrapper, emit] of incoming) {
|
|
3369
|
+
const existing = emits.find((e) => e.event === emit.event);
|
|
3370
|
+
if (existing) {
|
|
3371
|
+
const same = JSON.stringify(canonical(existing)) === JSON.stringify(canonical(emit));
|
|
3372
|
+
if (!same) {
|
|
3373
|
+
throw new LambdaSpliceError(
|
|
3374
|
+
"EMIT_MERGE_CONFLICT",
|
|
3375
|
+
`Emit "${emit.event}" merged from spliced wrapper "${wrapper}" conflicts with an existing "${emit.event}" on host "${host.name}" (different payload shape)`
|
|
3376
|
+
);
|
|
3377
|
+
}
|
|
3378
|
+
} else {
|
|
3379
|
+
emits.push(emit);
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
return emits;
|
|
3383
|
+
}
|
|
3384
|
+
function removeConsumedWrappers(traits, pages, spliced) {
|
|
3385
|
+
for (; ; ) {
|
|
3386
|
+
const stillReferenced = /* @__PURE__ */ new Set();
|
|
3387
|
+
for (const { trait } of traits) {
|
|
3388
|
+
for (const t of trait.stateMachine?.transitions ?? []) {
|
|
3389
|
+
if (t.guard !== void 0 && t.guard !== null) {
|
|
3390
|
+
collectTraitRefsFromValue(t.guard, stillReferenced);
|
|
3391
|
+
}
|
|
3392
|
+
collectTraitRefsFromEffects(t.effects, stillReferenced);
|
|
3393
|
+
}
|
|
3394
|
+
for (const tick of trait.ticks ?? []) {
|
|
3395
|
+
if (tick.guard !== void 0 && tick.guard !== null) {
|
|
3396
|
+
collectTraitRefsFromValue(tick.guard, stillReferenced);
|
|
3397
|
+
}
|
|
3398
|
+
collectTraitRefsFromEffects(tick.effects, stillReferenced);
|
|
3399
|
+
}
|
|
3400
|
+
if (trait.config) {
|
|
3401
|
+
for (const field of Object.values(trait.config)) {
|
|
3402
|
+
if (field && typeof field === "object" && "default" in field && field.default !== void 0) {
|
|
3403
|
+
collectTraitRefsFromValue(field.default, stillReferenced);
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
const removable = /* @__PURE__ */ new Set();
|
|
3409
|
+
for (const { trait } of traits) {
|
|
3410
|
+
if (spliced.has(trait.name) && !stillReferenced.has(trait.name)) {
|
|
3411
|
+
removable.add(trait.name);
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
if (removable.size === 0) return;
|
|
3415
|
+
for (const { page } of pages) {
|
|
3416
|
+
const pageTraits = page.traits;
|
|
3417
|
+
if (!pageTraits) continue;
|
|
3418
|
+
const survivors2 = pageTraits.filter((pt) => !removable.has(pt.ref)).length;
|
|
3419
|
+
if (survivors2 === 0) {
|
|
3420
|
+
for (const pt of pageTraits) removable.delete(pt.ref);
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
if (removable.size === 0) return;
|
|
3424
|
+
for (const resolvedPage of pages) {
|
|
3425
|
+
const pageTraits = resolvedPage.page.traits;
|
|
3426
|
+
if (pageTraits) {
|
|
3427
|
+
resolvedPage.page.traits = pageTraits.filter((pt) => !removable.has(pt.ref));
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
const survivors = traits.filter(({ trait }) => !removable.has(trait.name));
|
|
3431
|
+
traits.length = 0;
|
|
3432
|
+
traits.push(...survivors);
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
function spliceLambdaTraitRefs(traits, pages) {
|
|
3436
|
+
if (traits.length === 0) return;
|
|
3437
|
+
const templates = /* @__PURE__ */ new Map();
|
|
3438
|
+
for (const { trait } of traits) {
|
|
3439
|
+
templates.set(trait.name, buildTemplate(trait));
|
|
3440
|
+
}
|
|
3441
|
+
const spliced = /* @__PURE__ */ new Set();
|
|
3442
|
+
for (const resolved of traits) {
|
|
3443
|
+
const host = resolved.trait;
|
|
3444
|
+
const st = {
|
|
3445
|
+
templates,
|
|
3446
|
+
// Seed with the host itself so a self-reference never self-splices.
|
|
3447
|
+
visiting: [host.name],
|
|
3448
|
+
merged: [],
|
|
3449
|
+
spliced,
|
|
3450
|
+
hostName: host.name,
|
|
3451
|
+
changed: false
|
|
3452
|
+
};
|
|
3453
|
+
const sm = host.stateMachine;
|
|
3454
|
+
const nextTransitions = sm?.transitions ? sm.transitions.map((t) => ({
|
|
3455
|
+
...t,
|
|
3456
|
+
effects: t.effects ? t.effects.map((e) => spliceExpr(e, false, st)) : t.effects
|
|
3457
|
+
})) : sm?.transitions;
|
|
3458
|
+
const nextTicks = host.ticks ? host.ticks.map((tick) => ({
|
|
3459
|
+
...tick,
|
|
3460
|
+
effects: tick.effects.map((e) => spliceExpr(e, false, st))
|
|
3461
|
+
})) : host.ticks;
|
|
3462
|
+
if (!st.changed) {
|
|
3463
|
+
continue;
|
|
3464
|
+
}
|
|
3465
|
+
const nextEmits = st.merged.length > 0 ? mergeWrapperEmits(host, st.merged) : host.emits;
|
|
3466
|
+
resolved.trait = {
|
|
3467
|
+
...host,
|
|
3468
|
+
...sm ? { stateMachine: { ...sm, transitions: nextTransitions ?? [] } } : {},
|
|
3469
|
+
...nextTicks !== void 0 ? { ticks: nextTicks } : {},
|
|
3470
|
+
...nextEmits !== void 0 ? { emits: nextEmits } : {}
|
|
3471
|
+
};
|
|
3472
|
+
}
|
|
3473
|
+
if (spliced.size > 0) {
|
|
3474
|
+
removeConsumedWrappers(traits, pages, spliced);
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
|
|
3478
|
+
// src/resolver/reference-resolver.ts
|
|
3161
3479
|
var refResolverLog = createLogger("almadar:runtime:ref-resolver");
|
|
3162
3480
|
function renameEventsInRenderUiConfig(node, rename) {
|
|
3163
3481
|
if (node === null || node === void 0) return node;
|
|
@@ -3329,6 +3647,33 @@ function applyEventRenames(trait, renames) {
|
|
|
3329
3647
|
emits: nextEmits
|
|
3330
3648
|
};
|
|
3331
3649
|
}
|
|
3650
|
+
function resolveConfigRefEmitNames(trait, callSiteConfig) {
|
|
3651
|
+
const emits = trait.emits ?? [];
|
|
3652
|
+
const hasRef = emits.some((em) => configRefEventKnob(em.event) !== void 0);
|
|
3653
|
+
if (!hasRef) return { trait, errors: [] };
|
|
3654
|
+
const effectiveConfig = {
|
|
3655
|
+
...normalizeCallSiteConfigToValues(trait.config) ?? {},
|
|
3656
|
+
...normalizeCallSiteConfigToValues(callSiteConfig) ?? {}
|
|
3657
|
+
};
|
|
3658
|
+
const errors = [];
|
|
3659
|
+
const nextEmits = emits.map((em) => {
|
|
3660
|
+
if (configRefEventKnob(em.event) === void 0) return em;
|
|
3661
|
+
const result = resolveConfigRefEventName(em.event, trait.config, effectiveConfig);
|
|
3662
|
+
if (!result.ok) {
|
|
3663
|
+
errors.push(
|
|
3664
|
+
`Trait "${trait.name}" emits \`${em.event}\` but the reference is invalid (${result.error}): the knob must be a declared string-typed config field with a default.`
|
|
3665
|
+
);
|
|
3666
|
+
return em;
|
|
3667
|
+
}
|
|
3668
|
+
refResolverLog.debug("emit-config-ref:resolved", {
|
|
3669
|
+
trait: trait.name,
|
|
3670
|
+
ref: em.event,
|
|
3671
|
+
resolved: result.value
|
|
3672
|
+
});
|
|
3673
|
+
return { ...em, event: result.value };
|
|
3674
|
+
});
|
|
3675
|
+
return { trait: { ...trait, emits: nextEmits }, errors };
|
|
3676
|
+
}
|
|
3332
3677
|
var ReferenceResolver = class {
|
|
3333
3678
|
loader;
|
|
3334
3679
|
options;
|
|
@@ -3383,6 +3728,14 @@ var ReferenceResolver = class {
|
|
|
3383
3728
|
if (!entityResult.success || !traitsResult.success || !pagesResult.success) {
|
|
3384
3729
|
return { success: false, errors: ["Internal error: unexpected failure state"] };
|
|
3385
3730
|
}
|
|
3731
|
+
try {
|
|
3732
|
+
spliceLambdaTraitRefs(traitsResult.data, pagesResult.data);
|
|
3733
|
+
} catch (e) {
|
|
3734
|
+
if (e instanceof LambdaSpliceError) {
|
|
3735
|
+
return { success: false, errors: [e.message] };
|
|
3736
|
+
}
|
|
3737
|
+
throw e;
|
|
3738
|
+
}
|
|
3386
3739
|
return {
|
|
3387
3740
|
success: true,
|
|
3388
3741
|
data: {
|
|
@@ -3549,10 +3902,14 @@ var ReferenceResolver = class {
|
|
|
3549
3902
|
*/
|
|
3550
3903
|
resolveTraitRef(traitRef, imports) {
|
|
3551
3904
|
if (typeof traitRef !== "string" && "stateMachine" in traitRef) {
|
|
3905
|
+
const { trait: resolvedInline, errors } = resolveConfigRefEmitNames(traitRef);
|
|
3906
|
+
if (errors.length > 0) {
|
|
3907
|
+
return { success: false, errors };
|
|
3908
|
+
}
|
|
3552
3909
|
return {
|
|
3553
3910
|
success: true,
|
|
3554
3911
|
data: {
|
|
3555
|
-
trait:
|
|
3912
|
+
trait: resolvedInline,
|
|
3556
3913
|
source: { type: "inline" }
|
|
3557
3914
|
},
|
|
3558
3915
|
warnings: []
|
|
@@ -3603,7 +3960,11 @@ var ReferenceResolver = class {
|
|
|
3603
3960
|
};
|
|
3604
3961
|
}
|
|
3605
3962
|
const baseTrait = overrideName ? { ...trait, name: overrideName } : trait;
|
|
3606
|
-
const
|
|
3963
|
+
const { trait: configResolvedTrait, errors: configRefErrors } = resolveConfigRefEmitNames(baseTrait, config);
|
|
3964
|
+
if (configRefErrors.length > 0) {
|
|
3965
|
+
return { success: false, errors: configRefErrors };
|
|
3966
|
+
}
|
|
3967
|
+
const reboundTrait = applyLinkedEntityRename(configResolvedTrait, linkedEntity);
|
|
3607
3968
|
const renamedTrait = applyEventRenames(reboundTrait, eventRenames);
|
|
3608
3969
|
const finalTrait = listensOverride !== void 0 ? { ...renamedTrait, listens: listensOverride } : renamedTrait;
|
|
3609
3970
|
if (listensOverride !== void 0) {
|
|
@@ -3628,7 +3989,11 @@ var ReferenceResolver = class {
|
|
|
3628
3989
|
const localTrait = this.localTraits.get(ref);
|
|
3629
3990
|
if (localTrait) {
|
|
3630
3991
|
const baseLocal = overrideName ? { ...localTrait, name: overrideName } : localTrait;
|
|
3631
|
-
const
|
|
3992
|
+
const { trait: configResolvedLocal, errors: localConfigRefErrors } = resolveConfigRefEmitNames(baseLocal, config);
|
|
3993
|
+
if (localConfigRefErrors.length > 0) {
|
|
3994
|
+
return { success: false, errors: localConfigRefErrors };
|
|
3995
|
+
}
|
|
3996
|
+
const reboundLocal = applyLinkedEntityRename(configResolvedLocal, linkedEntity);
|
|
3632
3997
|
const renamedLocalTrait = applyEventRenames(reboundLocal, eventRenames);
|
|
3633
3998
|
const finalLocalTrait = listensOverride !== void 0 ? { ...renamedLocalTrait, listens: listensOverride } : renamedLocalTrait;
|
|
3634
3999
|
if (listensOverride !== void 0) {
|
|
@@ -4052,4 +4417,4 @@ var InMemoryPersistence = class {
|
|
|
4052
4417
|
}
|
|
4053
4418
|
};
|
|
4054
4419
|
|
|
4055
|
-
export { EffectExecutor, EventBus, HANDLER_MANIFEST, InMemoryPersistence, MockPersistenceAdapter, StateMachineManager, TickScheduler, buildEmitsFromTraits, collectDeclaredConfigDefaults, collectDeclaredEntityDefaults, containsBindings, createContextFromBindings, createInitialTraitState, createMockPersistence, createTestExecutor, createTickScheduler, createUnifiedLoader, extractBindings, findInitialState, findTransition, formatPayloadValidationError, getIsolatedCollectionName, getNamespacedEvent, interpolateProps, interpolateValue, isBrowser, isElectron, isNamespacedEvent, isNode, isValidDurationString,
|
|
4420
|
+
export { CALLSITE_PAYLOAD_PREFIX, EffectExecutor, EventBus, HANDLER_MANIFEST, InMemoryPersistence, MockPersistenceAdapter, StateMachineManager, TickScheduler, buildEmitsFromTraits, collectDeclaredConfigDefaults, collectDeclaredEntityDefaults, containsBindings, createContextFromBindings, createInitialTraitState, createMockPersistence, createTestExecutor, createTickScheduler, createUnifiedLoader, extractBindings, findInitialState, findTransition, formatPayloadValidationError, getIsolatedCollectionName, getNamespacedEvent, interpolateProps, interpolateValue, isBrowser, isElectron, isNamespacedEvent, isNode, isValidDurationString, normalizeEventKey, parseDurationString, parseNamespacedEvent, preprocessSchema, processEvent, resolveCallSitePayloadCaptures, validateEventPayload, validatePayloadShapes };
|