@jxsuite/runtime 6.2.2 → 6.3.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/runtime.js +231 -28
- package/dist/runtime.js.map +7 -5
- package/package.json +3 -2
- package/src/body-eraser.ts +201 -0
- package/src/runtime.ts +72 -46
package/dist/runtime.js
CHANGED
|
@@ -1491,6 +1491,9 @@ function isTemplateString(value) {
|
|
|
1491
1491
|
function bodyReturnsValue(body) {
|
|
1492
1492
|
return /\breturn\b[^\S\n]*(?![;}\s]|$)/.test(maskCode(body));
|
|
1493
1493
|
}
|
|
1494
|
+
function isFunctionEntry(value) {
|
|
1495
|
+
return value.$prototype === "Function" || isString2(value.role) && value.role !== "field";
|
|
1496
|
+
}
|
|
1494
1497
|
function maskCode(body) {
|
|
1495
1498
|
if (!/[/'"`]/u.test(body)) {
|
|
1496
1499
|
return body;
|
|
@@ -2573,6 +2576,96 @@ function evaluateOperand(operand, state, event, iterCtx) {
|
|
|
2573
2576
|
return resolveOperand(operand, state, event, iterCtx);
|
|
2574
2577
|
}
|
|
2575
2578
|
|
|
2579
|
+
// ../schema/src/function-role.ts
|
|
2580
|
+
var LIFECYCLE_KEYS = new Set(["onMount", "onUnmount", "onAdopted"]);
|
|
2581
|
+
function collectCallableKeys(doc) {
|
|
2582
|
+
const keys = new Set;
|
|
2583
|
+
const seen = new Set;
|
|
2584
|
+
const addStateRef = (ref) => {
|
|
2585
|
+
if (ref.startsWith("#/state/")) {
|
|
2586
|
+
keys.add(ref.slice("#/state/".length).split("/")[0]);
|
|
2587
|
+
}
|
|
2588
|
+
};
|
|
2589
|
+
const visit = (node) => {
|
|
2590
|
+
if (isString2(node)) {
|
|
2591
|
+
for (const call of node.matchAll(/\bstate\.([A-Za-z_$][\w$]*)\s*\(/gu)) {
|
|
2592
|
+
keys.add(call[1]);
|
|
2593
|
+
}
|
|
2594
|
+
return;
|
|
2595
|
+
}
|
|
2596
|
+
if (Array.isArray(node)) {
|
|
2597
|
+
if (!seen.has(node)) {
|
|
2598
|
+
seen.add(node);
|
|
2599
|
+
for (const item of node) {
|
|
2600
|
+
visit(item);
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
return;
|
|
2604
|
+
}
|
|
2605
|
+
if (!isJsonObject(node) || seen.has(node)) {
|
|
2606
|
+
return;
|
|
2607
|
+
}
|
|
2608
|
+
seen.add(node);
|
|
2609
|
+
if (node.operator === "call" && isRef2(node.target)) {
|
|
2610
|
+
addStateRef(node.target.$ref);
|
|
2611
|
+
}
|
|
2612
|
+
for (const [key, value] of Object.entries(node)) {
|
|
2613
|
+
if (key === "$defs") {
|
|
2614
|
+
continue;
|
|
2615
|
+
}
|
|
2616
|
+
if (key.startsWith("on") && isRef2(value)) {
|
|
2617
|
+
addStateRef(value.$ref);
|
|
2618
|
+
}
|
|
2619
|
+
visit(value);
|
|
2620
|
+
}
|
|
2621
|
+
};
|
|
2622
|
+
visit(doc);
|
|
2623
|
+
for (const hook of LIFECYCLE_KEYS) {
|
|
2624
|
+
if (isJsonObject(doc.state) && hook in doc.state) {
|
|
2625
|
+
keys.add(hook);
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
return keys;
|
|
2629
|
+
}
|
|
2630
|
+
function declaresBodyAndSrc(def) {
|
|
2631
|
+
return "body" in def && "$src" in def;
|
|
2632
|
+
}
|
|
2633
|
+
function handlerParamNames(def) {
|
|
2634
|
+
const list = ["state"];
|
|
2635
|
+
for (const name of paramNames(def.parameters ?? def.arguments)) {
|
|
2636
|
+
if (name !== "state") {
|
|
2637
|
+
list.push(name);
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
return list;
|
|
2641
|
+
}
|
|
2642
|
+
function functionEntryRole(key, def, callableKeys) {
|
|
2643
|
+
if (hasStructuredBody(def)) {
|
|
2644
|
+
return "callable";
|
|
2645
|
+
}
|
|
2646
|
+
if (paramNames(def.parameters ?? def.arguments).length > 0) {
|
|
2647
|
+
return "callable";
|
|
2648
|
+
}
|
|
2649
|
+
if (isString2(def.body)) {
|
|
2650
|
+
return bodyReturnsValue(def.body) ? "computed" : "callable";
|
|
2651
|
+
}
|
|
2652
|
+
if (def.$src) {
|
|
2653
|
+
return callableKeys().has(key) ? "callable" : "computed";
|
|
2654
|
+
}
|
|
2655
|
+
return "callable";
|
|
2656
|
+
}
|
|
2657
|
+
function classifyFunctionEntries(doc) {
|
|
2658
|
+
const roles = new Map;
|
|
2659
|
+
let callable = null;
|
|
2660
|
+
const callableKeys = () => callable ??= collectCallableKeys(doc);
|
|
2661
|
+
for (const [key, def] of Object.entries(doc.state ?? {})) {
|
|
2662
|
+
if (isFunctionDef(def)) {
|
|
2663
|
+
roles.set(key, functionEntryRole(key, def, callableKeys));
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
return roles;
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2576
2669
|
// ../schema/src/asset-paths.ts
|
|
2577
2670
|
var URL_TERMINATOR = String.raw`"'\s<>()\\`;
|
|
2578
2671
|
function parseSrcset(value) {
|
|
@@ -2701,6 +2794,111 @@ async function runStatements(statements, state, event, opts = {}) {
|
|
|
2701
2794
|
}
|
|
2702
2795
|
}
|
|
2703
2796
|
|
|
2797
|
+
// src/body-eraser.ts
|
|
2798
|
+
var eraser = null;
|
|
2799
|
+
var loader = null;
|
|
2800
|
+
var loading = null;
|
|
2801
|
+
var erased = new Map;
|
|
2802
|
+
function setBodyEraser(fn) {
|
|
2803
|
+
eraser = fn;
|
|
2804
|
+
erased.clear();
|
|
2805
|
+
if (!fn) {
|
|
2806
|
+
loading = null;
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
function setBodyEraserLoader(load) {
|
|
2810
|
+
loader = load;
|
|
2811
|
+
loading = null;
|
|
2812
|
+
}
|
|
2813
|
+
function compiles(text) {
|
|
2814
|
+
for (const source of [text, `return async function () {
|
|
2815
|
+
${text}
|
|
2816
|
+
}`]) {
|
|
2817
|
+
try {
|
|
2818
|
+
Reflect.construct(Function, [source]);
|
|
2819
|
+
return true;
|
|
2820
|
+
} catch {}
|
|
2821
|
+
}
|
|
2822
|
+
return false;
|
|
2823
|
+
}
|
|
2824
|
+
var GENERIC_CALL = /<[^<>]*>\s*[(`]/u;
|
|
2825
|
+
function bodyNeedsEraser(text) {
|
|
2826
|
+
return GENERIC_CALL.test(text) || !compiles(text);
|
|
2827
|
+
}
|
|
2828
|
+
function* bodiesOf(value) {
|
|
2829
|
+
if (Array.isArray(value)) {
|
|
2830
|
+
for (const item of value) {
|
|
2831
|
+
yield* bodiesOf(item);
|
|
2832
|
+
}
|
|
2833
|
+
return;
|
|
2834
|
+
}
|
|
2835
|
+
if (!isJsonObject(value)) {
|
|
2836
|
+
return;
|
|
2837
|
+
}
|
|
2838
|
+
const isFunction = isFunctionEntry(value);
|
|
2839
|
+
for (const [key, child] of Object.entries(value)) {
|
|
2840
|
+
if (isFunction && key === "body") {
|
|
2841
|
+
if (isString2(child)) {
|
|
2842
|
+
yield child;
|
|
2843
|
+
} else if (Array.isArray(child) && child.every((line) => isString2(line))) {
|
|
2844
|
+
yield child.join(`
|
|
2845
|
+
`);
|
|
2846
|
+
} else {
|
|
2847
|
+
yield* bodiesOf(child);
|
|
2848
|
+
}
|
|
2849
|
+
} else if (isFunction && (key === "getter" || key === "setter") && isJsonObject(child)) {
|
|
2850
|
+
if (isString2(child.body)) {
|
|
2851
|
+
yield child.body;
|
|
2852
|
+
}
|
|
2853
|
+
} else {
|
|
2854
|
+
yield* bodiesOf(child);
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
async function prepareBodies(doc) {
|
|
2859
|
+
const load = loader;
|
|
2860
|
+
if (eraser || !load) {
|
|
2861
|
+
return;
|
|
2862
|
+
}
|
|
2863
|
+
const tree = doc;
|
|
2864
|
+
if (!bodiesOf(tree).some((body) => bodyNeedsEraser(body))) {
|
|
2865
|
+
return;
|
|
2866
|
+
}
|
|
2867
|
+
loading ??= load().then((module) => {
|
|
2868
|
+
if (loader === load) {
|
|
2869
|
+
setBodyEraser(module.eraseBody);
|
|
2870
|
+
}
|
|
2871
|
+
});
|
|
2872
|
+
await loading;
|
|
2873
|
+
}
|
|
2874
|
+
function javaScriptOf(body, where) {
|
|
2875
|
+
if (!eraser) {
|
|
2876
|
+
return body;
|
|
2877
|
+
}
|
|
2878
|
+
let code = erased.get(body);
|
|
2879
|
+
if (code === undefined) {
|
|
2880
|
+
const result = eraser(body);
|
|
2881
|
+
const refused = result.problems.find((problem) => problem.kind === "unsupported");
|
|
2882
|
+
if (refused) {
|
|
2883
|
+
throw new SyntaxError(`Jx: ${where} cannot run: ${refused.message}`);
|
|
2884
|
+
}
|
|
2885
|
+
({ code } = result);
|
|
2886
|
+
erased.set(body, code);
|
|
2887
|
+
}
|
|
2888
|
+
return code;
|
|
2889
|
+
}
|
|
2890
|
+
function compileBody(params, body, where) {
|
|
2891
|
+
const code = javaScriptOf(body, where);
|
|
2892
|
+
try {
|
|
2893
|
+
return new Function(...params, code);
|
|
2894
|
+
} catch (error) {
|
|
2895
|
+
if (!(error instanceof SyntaxError) || eraser) {
|
|
2896
|
+
throw error;
|
|
2897
|
+
}
|
|
2898
|
+
throw new SyntaxError(`Jx: ${where} does not compile as JavaScript (${error.message}). A function body may be ` + `TypeScript: give this page an eraser with setBodyEraser(eraseBody), eraseBody from ` + `@jxsuite/erase/browser.`, { cause: error });
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2704
2902
|
// src/cookie.ts
|
|
2705
2903
|
var HOST_PREFIX = "__Host-";
|
|
2706
2904
|
var SECURE_PREFIX = "__Secure-";
|
|
@@ -2739,7 +2937,6 @@ function serializeCookie(name, value, options) {
|
|
|
2739
2937
|
}
|
|
2740
2938
|
return parts.join("; ");
|
|
2741
2939
|
}
|
|
2742
|
-
|
|
2743
2940
|
// src/runtime.ts
|
|
2744
2941
|
var SUPPORTED_SCHEMA_VERSION = 1;
|
|
2745
2942
|
var _schemaVersionWarned = false;
|
|
@@ -2863,6 +3060,7 @@ function responseContentType(res) {
|
|
|
2863
3060
|
}
|
|
2864
3061
|
async function resolve(source, ctx) {
|
|
2865
3062
|
if (!isString2(source)) {
|
|
3063
|
+
await prepareBodies(source);
|
|
2866
3064
|
return source;
|
|
2867
3065
|
}
|
|
2868
3066
|
const served = ctx?.resolver?.(source);
|
|
@@ -2872,7 +3070,7 @@ async function resolve(source, ctx) {
|
|
|
2872
3070
|
if (_resolveCache.has(source)) {
|
|
2873
3071
|
return _resolveCache.get(source);
|
|
2874
3072
|
}
|
|
2875
|
-
const
|
|
3073
|
+
const fetched = fetch(source).then(async (res) => {
|
|
2876
3074
|
if (!res.ok) {
|
|
2877
3075
|
throw new Error(`Jx: failed to fetch ${source} (${res.status})`);
|
|
2878
3076
|
}
|
|
@@ -2888,6 +3086,10 @@ async function resolve(source, ctx) {
|
|
|
2888
3086
|
}
|
|
2889
3087
|
return await res.json();
|
|
2890
3088
|
});
|
|
3089
|
+
const p = fetched.then(async (doc) => {
|
|
3090
|
+
await prepareBodies(doc);
|
|
3091
|
+
return doc;
|
|
3092
|
+
});
|
|
2891
3093
|
_resolveCache.set(source, p);
|
|
2892
3094
|
return p;
|
|
2893
3095
|
}
|
|
@@ -3159,7 +3361,11 @@ async function buildScope(doc, parentScope = {}, base = location.href, ctx) {
|
|
|
3159
3361
|
}
|
|
3160
3362
|
}
|
|
3161
3363
|
}
|
|
3364
|
+
const roles = classifyFunctionEntries(doc);
|
|
3162
3365
|
for (const [key, def] of Object.entries(defs)) {
|
|
3366
|
+
if (isFunctionDef(def) && declaresBodyAndSrc(def)) {
|
|
3367
|
+
throw new Error(`Jx: '${key}' declares both body and $src — these are mutually exclusive`);
|
|
3368
|
+
}
|
|
3163
3369
|
if (hasStructuredBody(def)) {
|
|
3164
3370
|
const { body } = def;
|
|
3165
3371
|
if (Array.isArray(def.parameters) && def.parameters.length > 0) {
|
|
@@ -3175,7 +3381,7 @@ async function buildScope(doc, parentScope = {}, base = location.href, ctx) {
|
|
|
3175
3381
|
state[key] = handler;
|
|
3176
3382
|
}
|
|
3177
3383
|
} else if (isFunctionDef(def)) {
|
|
3178
|
-
state[key] = await resolveFunction(def, state, key, base);
|
|
3384
|
+
state[key] = await resolveFunction(def, state, key, roles.get(key) ?? "callable", base);
|
|
3179
3385
|
}
|
|
3180
3386
|
}
|
|
3181
3387
|
for (const [key, def] of Object.entries(defs)) {
|
|
@@ -3388,12 +3594,9 @@ function seededModule(src) {
|
|
|
3388
3594
|
_moduleLoaders.set(src, load);
|
|
3389
3595
|
return load;
|
|
3390
3596
|
}
|
|
3391
|
-
async function resolveFunction(def, state, key, base) {
|
|
3392
|
-
if (def.body && def.$src) {
|
|
3393
|
-
throw new Error(`Jx: '${key}' declares both body and $src — these are mutually exclusive`);
|
|
3394
|
-
}
|
|
3597
|
+
async function resolveFunction(def, state, key, role, base) {
|
|
3395
3598
|
if (!def.body && !def.$src) {
|
|
3396
|
-
const params =
|
|
3599
|
+
const params = handlerParamNames(def);
|
|
3397
3600
|
const noop = new Function(...params, "");
|
|
3398
3601
|
Object.defineProperty(noop, "name", {
|
|
3399
3602
|
configurable: true,
|
|
@@ -3403,8 +3606,8 @@ async function resolveFunction(def, state, key, base) {
|
|
|
3403
3606
|
}
|
|
3404
3607
|
let fn;
|
|
3405
3608
|
if (isString2(def.body)) {
|
|
3406
|
-
const params =
|
|
3407
|
-
fn =
|
|
3609
|
+
const params = handlerParamNames(def);
|
|
3610
|
+
fn = compileBody(params, def.body, `'${key}'`);
|
|
3408
3611
|
Object.defineProperty(fn, "name", {
|
|
3409
3612
|
configurable: true,
|
|
3410
3613
|
value: def.name ?? key
|
|
@@ -3431,20 +3634,11 @@ async function resolveFunction(def, state, key, base) {
|
|
|
3431
3634
|
}
|
|
3432
3635
|
fn = candidate;
|
|
3433
3636
|
}
|
|
3434
|
-
|
|
3435
|
-
let isComputed = false;
|
|
3436
|
-
if (!hasParams) {
|
|
3437
|
-
isComputed = isString2(def.body) ? bodyReturnsValue(def.body) : fn.length <= 1 && bodyReturnsValue(fn.toString());
|
|
3438
|
-
}
|
|
3439
|
-
if (isComputed) {
|
|
3637
|
+
if (role === "computed") {
|
|
3440
3638
|
return computed(() => fn(state));
|
|
3441
3639
|
}
|
|
3442
3640
|
return fn;
|
|
3443
3641
|
}
|
|
3444
|
-
function resolveParamNames(def) {
|
|
3445
|
-
const names = def.parameters ? paramNames(def.parameters) : def.arguments ?? [];
|
|
3446
|
-
return names.length > 0 && names[0] === "state" ? names : ["state", ...names];
|
|
3447
|
-
}
|
|
3448
3642
|
var RESERVED_KEYS = new Set([
|
|
3449
3643
|
"$schema",
|
|
3450
3644
|
"$id",
|
|
@@ -3659,6 +3853,9 @@ function bindHandler(el, key, val, state) {
|
|
|
3659
3853
|
}
|
|
3660
3854
|
return true;
|
|
3661
3855
|
}
|
|
3856
|
+
if (isFunctionDef(val) && declaresBodyAndSrc(val)) {
|
|
3857
|
+
throw new Error(`Jx: inline '${key}' handler declares both body and $src — these are mutually exclusive`);
|
|
3858
|
+
}
|
|
3662
3859
|
if (hasStructuredBody(val)) {
|
|
3663
3860
|
const { body } = val;
|
|
3664
3861
|
el.addEventListener(type, (e) => {
|
|
@@ -3667,8 +3864,8 @@ function bindHandler(el, key, val, state) {
|
|
|
3667
3864
|
return true;
|
|
3668
3865
|
}
|
|
3669
3866
|
if (isFunctionDef(val) && isString2(val.body)) {
|
|
3670
|
-
const params =
|
|
3671
|
-
const fn =
|
|
3867
|
+
const params = handlerParamNames(val);
|
|
3868
|
+
const fn = compileBody(params, val.body, `the ${type} handler`);
|
|
3672
3869
|
el.addEventListener(type, (e) => fn(scope, e));
|
|
3673
3870
|
return true;
|
|
3674
3871
|
}
|
|
@@ -4534,6 +4731,7 @@ async function resolveClassJson(def, state, key, base) {
|
|
|
4534
4731
|
} catch {
|
|
4535
4732
|
return resolveViaDevProxy(def, state, key, base);
|
|
4536
4733
|
}
|
|
4734
|
+
await prepareBodies(classDef);
|
|
4537
4735
|
if (classDef.$implementation) {
|
|
4538
4736
|
const schemaStr = JSON.stringify(classDef);
|
|
4539
4737
|
if (schemaStr.includes('"#/$context/')) {
|
|
@@ -4567,6 +4765,7 @@ function classFromSchema(classDef) {
|
|
|
4567
4765
|
const rawCtor = classDef.$defs?.constructor;
|
|
4568
4766
|
const ctor = isCtorDef(rawCtor) ? rawCtor : undefined;
|
|
4569
4767
|
const methods = classDef.$defs?.methods ?? {};
|
|
4768
|
+
const className = classDef.title ?? "a class";
|
|
4570
4769
|
|
|
4571
4770
|
class DynClass {
|
|
4572
4771
|
constructor(config = {}) {
|
|
@@ -4586,7 +4785,7 @@ function classFromSchema(classDef) {
|
|
|
4586
4785
|
if (ctor?.body) {
|
|
4587
4786
|
const bodyStr = Array.isArray(ctor.body) ? ctor.body.join(`
|
|
4588
4787
|
`) : ctor.body;
|
|
4589
|
-
const init =
|
|
4788
|
+
const init = compileBody(["config"], bodyStr, `the constructor of ${className}`);
|
|
4590
4789
|
init.call(this, config);
|
|
4591
4790
|
}
|
|
4592
4791
|
}
|
|
@@ -4605,20 +4804,20 @@ function classFromSchema(classDef) {
|
|
|
4605
4804
|
if (typedMethod.role === "accessor") {
|
|
4606
4805
|
const descriptor = {};
|
|
4607
4806
|
if (typedMethod.getter) {
|
|
4608
|
-
descriptor.get =
|
|
4807
|
+
descriptor.get = compileBody([], typedMethod.getter.body ?? "", `the getter ${name} of ${className}`);
|
|
4609
4808
|
}
|
|
4610
4809
|
if (typedMethod.setter) {
|
|
4611
4810
|
const sp = (typedMethod.setter.parameters ?? []).map((p) => p.$ref?.split("/").pop() ?? "v");
|
|
4612
|
-
descriptor.set =
|
|
4811
|
+
descriptor.set = compileBody(sp, typedMethod.setter.body ?? "", `the setter ${name} of ${className}`);
|
|
4613
4812
|
}
|
|
4614
4813
|
Object.defineProperty(DynClass.prototype, name, {
|
|
4615
4814
|
...descriptor,
|
|
4616
4815
|
configurable: true
|
|
4617
4816
|
});
|
|
4618
4817
|
} else if (typedMethod.scope === "static") {
|
|
4619
|
-
DynClass[name] =
|
|
4818
|
+
DynClass[name] = compileBody(params, bodyStr, `${name} of ${className}`);
|
|
4620
4819
|
} else {
|
|
4621
|
-
DynClass.prototype[name] =
|
|
4820
|
+
DynClass.prototype[name] = compileBody(params, bodyStr, `${name} of ${className}`);
|
|
4622
4821
|
}
|
|
4623
4822
|
}
|
|
4624
4823
|
Object.defineProperty(DynClass, "name", {
|
|
@@ -5397,6 +5596,7 @@ export {
|
|
|
5397
5596
|
SUPPORTED_SCHEMA_VERSION,
|
|
5398
5597
|
_resetPrivatePropWarnings,
|
|
5399
5598
|
applyStyle,
|
|
5599
|
+
bodyNeedsEraser,
|
|
5400
5600
|
booleanAttrValue,
|
|
5401
5601
|
buildScope,
|
|
5402
5602
|
buildStyleRules,
|
|
@@ -5422,6 +5622,7 @@ export {
|
|
|
5422
5622
|
observeScope,
|
|
5423
5623
|
preloadDocument,
|
|
5424
5624
|
preloadModule,
|
|
5625
|
+
prepareBodies,
|
|
5425
5626
|
pureSchemeOf,
|
|
5426
5627
|
reapplyStyle,
|
|
5427
5628
|
redefineElement,
|
|
@@ -5436,6 +5637,8 @@ export {
|
|
|
5436
5637
|
resolveTagName,
|
|
5437
5638
|
runScoped,
|
|
5438
5639
|
schemeSelectors,
|
|
5640
|
+
setBodyEraser,
|
|
5641
|
+
setBodyEraserLoader,
|
|
5439
5642
|
setCanvasAssetResolver,
|
|
5440
5643
|
setCanvasDelinkAnchors,
|
|
5441
5644
|
setCanvasDelinkCommands,
|
|
@@ -5457,5 +5660,5 @@ export {
|
|
|
5457
5660
|
transposeCanvasUnits
|
|
5458
5661
|
};
|
|
5459
5662
|
|
|
5460
|
-
//# debugId=
|
|
5663
|
+
//# debugId=E573B4C42927B2CC64756E2164756E21
|
|
5461
5664
|
//# sourceMappingURL=runtime.js.map
|