@jxsuite/runtime 4.0.2 → 4.1.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/LICENSE +1 -1
- package/dist/runtime.js +30 -32
- package/dist/runtime.js.map +4 -4
- package/package.json +3 -3
- package/src/runtime.ts +90 -54
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/runtime",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "JSON-native reactive web component runtime",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
"upgrade": "bun update --latest && bun install"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@jxsuite/schema": "^
|
|
36
|
+
"@jxsuite/schema": "^3.0.0",
|
|
37
37
|
"@vue/reactivity": "3.5.43"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@happy-dom/global-registrator": "^20.14.
|
|
40
|
+
"@happy-dom/global-registrator": "^20.14.6"
|
|
41
41
|
}
|
|
42
42
|
}
|
package/src/runtime.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
/**
|
|
3
3
|
* Jx — JSON-native reactive web component runtime
|
|
4
|
-
* @version 3.0.0
|
|
5
|
-
* @license MIT
|
|
6
4
|
*
|
|
7
5
|
* Four-step pipeline:
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
6
|
+
*
|
|
7
|
+
* 1. `resolve` — fetch JSON source (or accept raw object)
|
|
8
|
+
* 2. `buildScope` — state detection + reactive proxy construction
|
|
9
|
+
* 3. `render` — walk resolved tree, build DOM, wire reactive effects
|
|
10
|
+
* 4. `output` — append to target
|
|
12
11
|
*
|
|
13
12
|
* @module jx
|
|
13
|
+
* @license MIT
|
|
14
|
+
* @version 3.0.0
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
import {
|
|
@@ -939,7 +940,8 @@ export async function buildScope(
|
|
|
939
940
|
}
|
|
940
941
|
}
|
|
941
942
|
|
|
942
|
-
|
|
943
|
+
/* Fifth pass: timing: "server" entries, each called through the host's /__jx_server__ proxy, or
|
|
944
|
+
in process only when the host registered the module itself (spec.md §11.4) */
|
|
943
945
|
if (!(ctx?.skipServerFunctions ?? _serverFnConfig.skip)) {
|
|
944
946
|
for (const [key, def] of Object.entries(defs)) {
|
|
945
947
|
if (isServerFnDef(def)) {
|
|
@@ -1083,6 +1085,14 @@ type ModuleLoader = () => Promise<Record<string, unknown>>;
|
|
|
1083
1085
|
*/
|
|
1084
1086
|
const _moduleLoaders = new Map<string, ModuleLoader | Promise<ImportedModule>>();
|
|
1085
1087
|
|
|
1088
|
+
/**
|
|
1089
|
+
* The specifiers a host registered through `preloadModule`, in either form. Kept apart from
|
|
1090
|
+
* `_moduleCache` because that cache is shared: a Function entry's `$src` import lands there too,
|
|
1091
|
+
* and a `timing: "server"` entry naming the same specifier must still not run in the browser
|
|
1092
|
+
* (spec.md §11.4). Only the host's own registration admits a server module in process.
|
|
1093
|
+
*/
|
|
1094
|
+
const _hostSeeded = new Set<string>();
|
|
1095
|
+
|
|
1086
1096
|
/**
|
|
1087
1097
|
* Seed the `$src` module cache with a module the host already imported, under the specifier the
|
|
1088
1098
|
* document spells. A bundled document cannot have its sidecar fetched by URL — the bundler saw
|
|
@@ -1095,6 +1105,9 @@ const _moduleLoaders = new Map<string, ModuleLoader | Promise<ImportedModule>>()
|
|
|
1095
1105
|
* canvas frame registers every kit behaviour this way, so a page that uses no kit element loads
|
|
1096
1106
|
* none of them (embedding.md §6).
|
|
1097
1107
|
*
|
|
1108
|
+
* A registered module is also how a host runs a `timing: "server"` entry in process; for any
|
|
1109
|
+
* specifier not registered here, the runtime calls the host's `/__jx_server__` proxy instead.
|
|
1110
|
+
*
|
|
1098
1111
|
* @param {string} specifier - The `$src` value as written in the document
|
|
1099
1112
|
* @param {Record<string, unknown> | (() => Promise<Record<string, unknown>>)} mod - The imported
|
|
1100
1113
|
* module namespace, or a loader that imports it on first use
|
|
@@ -1103,6 +1116,7 @@ export function preloadModule(
|
|
|
1103
1116
|
specifier: string,
|
|
1104
1117
|
mod: Record<string, unknown> | ModuleLoader,
|
|
1105
1118
|
): void {
|
|
1119
|
+
_hostSeeded.add(specifier);
|
|
1106
1120
|
if (typeof mod === "function") {
|
|
1107
1121
|
_moduleCache.delete(specifier);
|
|
1108
1122
|
_moduleLoaders.set(specifier, mod);
|
|
@@ -3368,13 +3382,59 @@ async function resolveViaDevProxy(def: JxPrototypeDef, state: JxScope, key: stri
|
|
|
3368
3382
|
return s;
|
|
3369
3383
|
}
|
|
3370
3384
|
|
|
3371
|
-
// ─── Server function resolution
|
|
3385
|
+
// ─── Server function resolution ───────────────────────────────────────────────
|
|
3372
3386
|
|
|
3373
3387
|
/**
|
|
3374
|
-
*
|
|
3375
|
-
*
|
|
3376
|
-
*
|
|
3377
|
-
*
|
|
3388
|
+
* Start one `timing: "server"` entry's calls, wherever they run. Each call takes the next number,
|
|
3389
|
+
* and only the latest may write the entry or report a failure: the answer to a call whose arguments
|
|
3390
|
+
* have since changed is dropped whenever it arrives, and a failed call keeps the entry's last
|
|
3391
|
+
* value. A call that throws before it returns a promise (an argument the proxy cannot serialize, or
|
|
3392
|
+
* a host function that throws synchronously) fails the same way, rather than out of whichever state
|
|
3393
|
+
* write started it. The proxy and a host-registered module share it, so a reactive entry's calls
|
|
3394
|
+
* are ordered and reported the same way on either path.
|
|
3395
|
+
*
|
|
3396
|
+
* @param {Ref<unknown>} s - The entry's value
|
|
3397
|
+
* @param {(args: JxScope) => unknown} invoke - One call, with resolved arguments: it may return a
|
|
3398
|
+
* value or a promise, or throw
|
|
3399
|
+
* @param {string} label - The `console.error` prefix a failed call is reported under
|
|
3400
|
+
* @returns {(args: JxScope) => void} Starts a call
|
|
3401
|
+
*/
|
|
3402
|
+
function latestServerCall(
|
|
3403
|
+
s: Ref<unknown>,
|
|
3404
|
+
invoke: (args: JxScope) => unknown,
|
|
3405
|
+
label: string,
|
|
3406
|
+
): (args: JxScope) => void {
|
|
3407
|
+
let seq = 0;
|
|
3408
|
+
return (args: JxScope) => {
|
|
3409
|
+
seq += 1;
|
|
3410
|
+
const n = seq;
|
|
3411
|
+
/* The executor runs before the constructor returns, so the call still starts synchronously,
|
|
3412
|
+
and a throw inside it rejects this promise instead of escaping to the caller. */
|
|
3413
|
+
new Promise<unknown>((settle) => {
|
|
3414
|
+
settle(invoke(args));
|
|
3415
|
+
})
|
|
3416
|
+
.then((result: unknown) => {
|
|
3417
|
+
if (n === seq) {
|
|
3418
|
+
s.value = result;
|
|
3419
|
+
}
|
|
3420
|
+
})
|
|
3421
|
+
.catch((error: unknown) => {
|
|
3422
|
+
if (n === seq) {
|
|
3423
|
+
console.error(label, error);
|
|
3424
|
+
}
|
|
3425
|
+
});
|
|
3426
|
+
};
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3429
|
+
/**
|
|
3430
|
+
* Resolve a timing: "server" entry. The runtime never imports a server module itself: every call is
|
|
3431
|
+
* a POST to the host's `/__jx_server__` proxy, which runs the function server-side with `env`, so
|
|
3432
|
+
* the runtime never requests the module's source (spec.md §11.4). A host may still serve that file
|
|
3433
|
+
* at its URL, which is why a secret belongs in `env` and never in the module body. The one
|
|
3434
|
+
* exception is the host's own act: a module it registered with `preloadModule` (embedding.md §6) is
|
|
3435
|
+
* called in process, and a reactive entry's calls are ordered there as the proxy orders them.
|
|
3436
|
+
*
|
|
3437
|
+
* @param {JxServerFnDef} def
|
|
3378
3438
|
* @param {JxScope} state
|
|
3379
3439
|
* @param {string} key
|
|
3380
3440
|
* @param {string} [base]
|
|
@@ -3389,30 +3449,13 @@ async function resolveServerFunction(
|
|
|
3389
3449
|
const src = def.$src;
|
|
3390
3450
|
const exportName = def.$export;
|
|
3391
3451
|
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
mod = seeded;
|
|
3398
|
-
} else {
|
|
3399
|
-
try {
|
|
3400
|
-
mod = (await import(src)) as ImportedModule;
|
|
3401
|
-
} catch {
|
|
3402
|
-
if (base) {
|
|
3403
|
-
try {
|
|
3404
|
-
const resolvedSrc = new URL(src, base).href;
|
|
3405
|
-
mod = (await import(resolvedSrc)) as ImportedModule;
|
|
3406
|
-
} catch {
|
|
3407
|
-
// Module cannot run in the browser — fall back to dev server proxy
|
|
3408
|
-
return resolveServerFunctionViaProxy(def, state, key, base);
|
|
3409
|
-
}
|
|
3410
|
-
} else {
|
|
3411
|
-
return resolveServerFunctionViaProxy(def, state, key, base);
|
|
3412
|
-
}
|
|
3413
|
-
}
|
|
3414
|
-
_moduleCache.set(src, mod);
|
|
3452
|
+
/* The host-seeded set, not the module cache: a Function entry's own `$src` import lands in that
|
|
3453
|
+
cache too, and reusing it here would put a server module in the browser after all. */
|
|
3454
|
+
const seeded = _hostSeeded.has(src) ? seededModule(src) : undefined;
|
|
3455
|
+
if (!seeded) {
|
|
3456
|
+
return resolveServerFunctionViaProxy(def, state, key, base);
|
|
3415
3457
|
}
|
|
3458
|
+
const mod = seeded instanceof Promise ? await seeded : seeded;
|
|
3416
3459
|
|
|
3417
3460
|
const candidate = mod[exportName] ?? mod.default?.[exportName];
|
|
3418
3461
|
if (!candidate) {
|
|
@@ -3421,7 +3464,7 @@ async function resolveServerFunction(
|
|
|
3421
3464
|
if (typeof candidate !== "function") {
|
|
3422
3465
|
throw new TypeError(`Jx: "${exportName}" from "${src}" is not a function`);
|
|
3423
3466
|
}
|
|
3424
|
-
const fn = candidate as (args: JxScope) =>
|
|
3467
|
+
const fn = candidate as (args: JxScope) => unknown;
|
|
3425
3468
|
|
|
3426
3469
|
const rawArgs = def.arguments ?? {};
|
|
3427
3470
|
const hasReactiveArg = Object.values(rawArgs).some((v: unknown) => isRefObj(v));
|
|
@@ -3436,14 +3479,11 @@ async function resolveServerFunction(
|
|
|
3436
3479
|
// Always wrap in ref for reactivity
|
|
3437
3480
|
const s: Ref<unknown> = ref(null);
|
|
3438
3481
|
if (hasReactiveArg) {
|
|
3482
|
+
const call = latestServerCall(s, fn, "Jx server function:");
|
|
3439
3483
|
effect(() => {
|
|
3440
3484
|
const args = resolveArgs();
|
|
3441
3485
|
onEffectCleanup(() => {});
|
|
3442
|
-
|
|
3443
|
-
.then((result: unknown) => {
|
|
3444
|
-
s.value = result;
|
|
3445
|
-
})
|
|
3446
|
-
.catch(() => {});
|
|
3486
|
+
call(args);
|
|
3447
3487
|
});
|
|
3448
3488
|
} else {
|
|
3449
3489
|
s.value = await fn(resolveArgs());
|
|
@@ -3452,9 +3492,12 @@ async function resolveServerFunction(
|
|
|
3452
3492
|
}
|
|
3453
3493
|
|
|
3454
3494
|
/**
|
|
3455
|
-
*
|
|
3456
|
-
*
|
|
3457
|
-
*
|
|
3495
|
+
* Call a timing: "server" entry through the host's `/__jx_server__` proxy, which imports the module
|
|
3496
|
+
* server-side and calls the export with `env` (server.md §3.3). This is the only path an
|
|
3497
|
+
* interpreting runtime takes for a module the host did not register. A `$ref` argument makes the
|
|
3498
|
+
* call reactive: it re-posts from an `effect()` when the reference changes. A failed call leaves
|
|
3499
|
+
* the entry's last value and logs, and the answer to a superseded call is dropped, so a slow answer
|
|
3500
|
+
* to an earlier call can never overwrite a later one (`latestServerCall`).
|
|
3458
3501
|
*
|
|
3459
3502
|
* @param {JxScope} def
|
|
3460
3503
|
* @param {JxScope} state
|
|
@@ -3499,22 +3542,15 @@ async function resolveServerFunctionViaProxy(
|
|
|
3499
3542
|
|
|
3500
3543
|
// Always wrap in ref for reactivity
|
|
3501
3544
|
const s: Ref<unknown> = ref(null);
|
|
3545
|
+
const call = latestServerCall(s, doResolve, "Jx server proxy:");
|
|
3502
3546
|
if (hasReactiveArg) {
|
|
3503
3547
|
effect(() => {
|
|
3504
3548
|
const args = resolveArgs();
|
|
3505
3549
|
onEffectCleanup(() => {});
|
|
3506
|
-
|
|
3507
|
-
.then((result: unknown) => {
|
|
3508
|
-
s.value = result;
|
|
3509
|
-
})
|
|
3510
|
-
.catch((error: unknown) => console.error("Jx server proxy:", error));
|
|
3550
|
+
call(args);
|
|
3511
3551
|
});
|
|
3512
3552
|
} else {
|
|
3513
|
-
|
|
3514
|
-
.then((result: unknown) => {
|
|
3515
|
-
s.value = result;
|
|
3516
|
-
})
|
|
3517
|
-
.catch((error: unknown) => console.error("Jx server proxy:", error));
|
|
3553
|
+
call(resolveArgs());
|
|
3518
3554
|
}
|
|
3519
3555
|
return s;
|
|
3520
3556
|
}
|