@jxsuite/runtime 6.2.1 → 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 CHANGED
@@ -1489,7 +1489,100 @@ function isTemplateString(value) {
1489
1489
  return typeof value === "string" && value.includes("${");
1490
1490
  }
1491
1491
  function bodyReturnsValue(body) {
1492
- return /\breturn\b[^\S\n]*(?![;}\s]|$)/.test(body);
1492
+ return /\breturn\b[^\S\n]*(?![;}\s]|$)/.test(maskCode(body));
1493
+ }
1494
+ function isFunctionEntry(value) {
1495
+ return value.$prototype === "Function" || isString2(value.role) && value.role !== "field";
1496
+ }
1497
+ function maskCode(body) {
1498
+ if (!/[/'"`]/u.test(body)) {
1499
+ return body;
1500
+ }
1501
+ const known = masked.get(body);
1502
+ if (known !== undefined) {
1503
+ return known;
1504
+ }
1505
+ const result = scanCode(body);
1506
+ if (masked.size >= MASKED_LIMIT) {
1507
+ masked.clear();
1508
+ }
1509
+ masked.set(body, result);
1510
+ return result;
1511
+ }
1512
+ var masked = new Map;
1513
+ var MASKED_LIMIT = 2048;
1514
+ function scanCode(body) {
1515
+ const out = body.split("");
1516
+ const blank = (at) => {
1517
+ if (out[at] !== undefined && out[at] !== `
1518
+ `) {
1519
+ out[at] = " ";
1520
+ }
1521
+ };
1522
+ const substitutions = [];
1523
+ let inTemplate = false;
1524
+ let at = 0;
1525
+ while (at < body.length) {
1526
+ const char = body.charAt(at);
1527
+ if (inTemplate) {
1528
+ if (char === "\\") {
1529
+ blank(at);
1530
+ blank(at + 1);
1531
+ at += 2;
1532
+ } else if (char === "`") {
1533
+ inTemplate = false;
1534
+ at += 1;
1535
+ } else if (char === "$" && body.charAt(at + 1) === "{") {
1536
+ substitutions.push(0);
1537
+ inTemplate = false;
1538
+ at += 2;
1539
+ } else {
1540
+ blank(at);
1541
+ at += 1;
1542
+ }
1543
+ } else if (char === "/" && body.charAt(at + 1) === "/") {
1544
+ while (at < body.length && body.charAt(at) !== `
1545
+ `) {
1546
+ blank(at);
1547
+ at += 1;
1548
+ }
1549
+ } else if (char === "/" && body.charAt(at + 1) === "*") {
1550
+ const end = body.indexOf("*/", at + 2);
1551
+ const stop = end === -1 ? body.length : end + 2;
1552
+ for (;at < stop; at += 1) {
1553
+ blank(at);
1554
+ }
1555
+ } else if (char === "'" || char === '"') {
1556
+ at += 1;
1557
+ while (at < body.length && body.charAt(at) !== char && body.charAt(at) !== `
1558
+ `) {
1559
+ if (body.charAt(at) === "\\") {
1560
+ blank(at);
1561
+ at += 1;
1562
+ }
1563
+ blank(at);
1564
+ at += 1;
1565
+ }
1566
+ at += 1;
1567
+ } else if (char === "`") {
1568
+ inTemplate = true;
1569
+ at += 1;
1570
+ } else {
1571
+ const open = substitutions.length - 1;
1572
+ if (open >= 0 && char === "{") {
1573
+ substitutions[open] = (substitutions[open] ?? 0) + 1;
1574
+ } else if (open >= 0 && char === "}") {
1575
+ if (substitutions[open] === 0) {
1576
+ substitutions.pop();
1577
+ inTemplate = true;
1578
+ } else {
1579
+ substitutions[open] = (substitutions[open] ?? 1) - 1;
1580
+ }
1581
+ }
1582
+ at += 1;
1583
+ }
1584
+ }
1585
+ return out.join("");
1493
1586
  }
1494
1587
  function isNestedStyle(value) {
1495
1588
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -2483,6 +2576,96 @@ function evaluateOperand(operand, state, event, iterCtx) {
2483
2576
  return resolveOperand(operand, state, event, iterCtx);
2484
2577
  }
2485
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
+
2486
2669
  // ../schema/src/asset-paths.ts
2487
2670
  var URL_TERMINATOR = String.raw`"'\s<>()\\`;
2488
2671
  function parseSrcset(value) {
@@ -2611,6 +2794,111 @@ async function runStatements(statements, state, event, opts = {}) {
2611
2794
  }
2612
2795
  }
2613
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
+
2614
2902
  // src/cookie.ts
2615
2903
  var HOST_PREFIX = "__Host-";
2616
2904
  var SECURE_PREFIX = "__Secure-";
@@ -2649,7 +2937,6 @@ function serializeCookie(name, value, options) {
2649
2937
  }
2650
2938
  return parts.join("; ");
2651
2939
  }
2652
-
2653
2940
  // src/runtime.ts
2654
2941
  var SUPPORTED_SCHEMA_VERSION = 1;
2655
2942
  var _schemaVersionWarned = false;
@@ -2773,6 +3060,7 @@ function responseContentType(res) {
2773
3060
  }
2774
3061
  async function resolve(source, ctx) {
2775
3062
  if (!isString2(source)) {
3063
+ await prepareBodies(source);
2776
3064
  return source;
2777
3065
  }
2778
3066
  const served = ctx?.resolver?.(source);
@@ -2782,7 +3070,7 @@ async function resolve(source, ctx) {
2782
3070
  if (_resolveCache.has(source)) {
2783
3071
  return _resolveCache.get(source);
2784
3072
  }
2785
- const p = fetch(source).then(async (res) => {
3073
+ const fetched = fetch(source).then(async (res) => {
2786
3074
  if (!res.ok) {
2787
3075
  throw new Error(`Jx: failed to fetch ${source} (${res.status})`);
2788
3076
  }
@@ -2798,6 +3086,10 @@ async function resolve(source, ctx) {
2798
3086
  }
2799
3087
  return await res.json();
2800
3088
  });
3089
+ const p = fetched.then(async (doc) => {
3090
+ await prepareBodies(doc);
3091
+ return doc;
3092
+ });
2801
3093
  _resolveCache.set(source, p);
2802
3094
  return p;
2803
3095
  }
@@ -3069,7 +3361,11 @@ async function buildScope(doc, parentScope = {}, base = location.href, ctx) {
3069
3361
  }
3070
3362
  }
3071
3363
  }
3364
+ const roles = classifyFunctionEntries(doc);
3072
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
+ }
3073
3369
  if (hasStructuredBody(def)) {
3074
3370
  const { body } = def;
3075
3371
  if (Array.isArray(def.parameters) && def.parameters.length > 0) {
@@ -3085,7 +3381,7 @@ async function buildScope(doc, parentScope = {}, base = location.href, ctx) {
3085
3381
  state[key] = handler;
3086
3382
  }
3087
3383
  } else if (isFunctionDef(def)) {
3088
- state[key] = await resolveFunction(def, state, key, base);
3384
+ state[key] = await resolveFunction(def, state, key, roles.get(key) ?? "callable", base);
3089
3385
  }
3090
3386
  }
3091
3387
  for (const [key, def] of Object.entries(defs)) {
@@ -3298,12 +3594,9 @@ function seededModule(src) {
3298
3594
  _moduleLoaders.set(src, load);
3299
3595
  return load;
3300
3596
  }
3301
- async function resolveFunction(def, state, key, base) {
3302
- if (def.body && def.$src) {
3303
- throw new Error(`Jx: '${key}' declares both body and $src — these are mutually exclusive`);
3304
- }
3597
+ async function resolveFunction(def, state, key, role, base) {
3305
3598
  if (!def.body && !def.$src) {
3306
- const params = resolveParamNames(def);
3599
+ const params = handlerParamNames(def);
3307
3600
  const noop = new Function(...params, "");
3308
3601
  Object.defineProperty(noop, "name", {
3309
3602
  configurable: true,
@@ -3313,8 +3606,8 @@ async function resolveFunction(def, state, key, base) {
3313
3606
  }
3314
3607
  let fn;
3315
3608
  if (isString2(def.body)) {
3316
- const params = resolveParamNames(def);
3317
- fn = new Function(...params, def.body);
3609
+ const params = handlerParamNames(def);
3610
+ fn = compileBody(params, def.body, `'${key}'`);
3318
3611
  Object.defineProperty(fn, "name", {
3319
3612
  configurable: true,
3320
3613
  value: def.name ?? key
@@ -3341,20 +3634,11 @@ async function resolveFunction(def, state, key, base) {
3341
3634
  }
3342
3635
  fn = candidate;
3343
3636
  }
3344
- const hasParams = (def.parameters ?? def.arguments ?? []).length > 0;
3345
- let isComputed = false;
3346
- if (!hasParams) {
3347
- isComputed = isString2(def.body) ? bodyReturnsValue(def.body) : fn.length <= 1 && bodyReturnsValue(fn.toString());
3348
- }
3349
- if (isComputed) {
3637
+ if (role === "computed") {
3350
3638
  return computed(() => fn(state));
3351
3639
  }
3352
3640
  return fn;
3353
3641
  }
3354
- function resolveParamNames(def) {
3355
- const names = def.parameters ? paramNames(def.parameters) : def.arguments ?? [];
3356
- return names.length > 0 && names[0] === "state" ? names : ["state", ...names];
3357
- }
3358
3642
  var RESERVED_KEYS = new Set([
3359
3643
  "$schema",
3360
3644
  "$id",
@@ -3569,6 +3853,9 @@ function bindHandler(el, key, val, state) {
3569
3853
  }
3570
3854
  return true;
3571
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
+ }
3572
3859
  if (hasStructuredBody(val)) {
3573
3860
  const { body } = val;
3574
3861
  el.addEventListener(type, (e) => {
@@ -3577,8 +3864,8 @@ function bindHandler(el, key, val, state) {
3577
3864
  return true;
3578
3865
  }
3579
3866
  if (isFunctionDef(val) && isString2(val.body)) {
3580
- const params = resolveParamNames(val);
3581
- const fn = new Function(...params, val.body);
3867
+ const params = handlerParamNames(val);
3868
+ const fn = compileBody(params, val.body, `the ${type} handler`);
3582
3869
  el.addEventListener(type, (e) => fn(scope, e));
3583
3870
  return true;
3584
3871
  }
@@ -4444,6 +4731,7 @@ async function resolveClassJson(def, state, key, base) {
4444
4731
  } catch {
4445
4732
  return resolveViaDevProxy(def, state, key, base);
4446
4733
  }
4734
+ await prepareBodies(classDef);
4447
4735
  if (classDef.$implementation) {
4448
4736
  const schemaStr = JSON.stringify(classDef);
4449
4737
  if (schemaStr.includes('"#/$context/')) {
@@ -4477,6 +4765,7 @@ function classFromSchema(classDef) {
4477
4765
  const rawCtor = classDef.$defs?.constructor;
4478
4766
  const ctor = isCtorDef(rawCtor) ? rawCtor : undefined;
4479
4767
  const methods = classDef.$defs?.methods ?? {};
4768
+ const className = classDef.title ?? "a class";
4480
4769
 
4481
4770
  class DynClass {
4482
4771
  constructor(config = {}) {
@@ -4496,7 +4785,7 @@ function classFromSchema(classDef) {
4496
4785
  if (ctor?.body) {
4497
4786
  const bodyStr = Array.isArray(ctor.body) ? ctor.body.join(`
4498
4787
  `) : ctor.body;
4499
- const init = new Function("config", bodyStr);
4788
+ const init = compileBody(["config"], bodyStr, `the constructor of ${className}`);
4500
4789
  init.call(this, config);
4501
4790
  }
4502
4791
  }
@@ -4515,20 +4804,20 @@ function classFromSchema(classDef) {
4515
4804
  if (typedMethod.role === "accessor") {
4516
4805
  const descriptor = {};
4517
4806
  if (typedMethod.getter) {
4518
- descriptor.get = new Function(typedMethod.getter.body ?? "");
4807
+ descriptor.get = compileBody([], typedMethod.getter.body ?? "", `the getter ${name} of ${className}`);
4519
4808
  }
4520
4809
  if (typedMethod.setter) {
4521
4810
  const sp = (typedMethod.setter.parameters ?? []).map((p) => p.$ref?.split("/").pop() ?? "v");
4522
- descriptor.set = new Function(...sp, typedMethod.setter.body ?? "");
4811
+ descriptor.set = compileBody(sp, typedMethod.setter.body ?? "", `the setter ${name} of ${className}`);
4523
4812
  }
4524
4813
  Object.defineProperty(DynClass.prototype, name, {
4525
4814
  ...descriptor,
4526
4815
  configurable: true
4527
4816
  });
4528
4817
  } else if (typedMethod.scope === "static") {
4529
- DynClass[name] = new Function(...params, bodyStr);
4818
+ DynClass[name] = compileBody(params, bodyStr, `${name} of ${className}`);
4530
4819
  } else {
4531
- DynClass.prototype[name] = new Function(...params, bodyStr);
4820
+ DynClass.prototype[name] = compileBody(params, bodyStr, `${name} of ${className}`);
4532
4821
  }
4533
4822
  }
4534
4823
  Object.defineProperty(DynClass, "name", {
@@ -5307,6 +5596,7 @@ export {
5307
5596
  SUPPORTED_SCHEMA_VERSION,
5308
5597
  _resetPrivatePropWarnings,
5309
5598
  applyStyle,
5599
+ bodyNeedsEraser,
5310
5600
  booleanAttrValue,
5311
5601
  buildScope,
5312
5602
  buildStyleRules,
@@ -5332,6 +5622,7 @@ export {
5332
5622
  observeScope,
5333
5623
  preloadDocument,
5334
5624
  preloadModule,
5625
+ prepareBodies,
5335
5626
  pureSchemeOf,
5336
5627
  reapplyStyle,
5337
5628
  redefineElement,
@@ -5346,6 +5637,8 @@ export {
5346
5637
  resolveTagName,
5347
5638
  runScoped,
5348
5639
  schemeSelectors,
5640
+ setBodyEraser,
5641
+ setBodyEraserLoader,
5349
5642
  setCanvasAssetResolver,
5350
5643
  setCanvasDelinkAnchors,
5351
5644
  setCanvasDelinkCommands,
@@ -5367,5 +5660,5 @@ export {
5367
5660
  transposeCanvasUnits
5368
5661
  };
5369
5662
 
5370
- //# debugId=16998FE20281F1D164756E2164756E21
5663
+ //# debugId=E573B4C42927B2CC64756E2164756E21
5371
5664
  //# sourceMappingURL=runtime.js.map