@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 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 p = fetch(source).then(async (res) => {
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 = resolveParamNames(def);
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 = resolveParamNames(def);
3407
- fn = new Function(...params, def.body);
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
- const hasParams = (def.parameters ?? def.arguments ?? []).length > 0;
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 = resolveParamNames(val);
3671
- const fn = new Function(...params, val.body);
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 = new Function("config", bodyStr);
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 = new Function(typedMethod.getter.body ?? "");
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 = new Function(...sp, typedMethod.setter.body ?? "");
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] = new Function(...params, bodyStr);
4818
+ DynClass[name] = compileBody(params, bodyStr, `${name} of ${className}`);
4620
4819
  } else {
4621
- DynClass.prototype[name] = new Function(...params, bodyStr);
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=0833DD60FD5CF0CE64756E2164756E21
5663
+ //# debugId=E573B4C42927B2CC64756E2164756E21
5461
5664
  //# sourceMappingURL=runtime.js.map