@jxsuite/runtime 0.19.0 → 0.21.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/src/runtime.js CHANGED
@@ -25,7 +25,7 @@ import { reactive, ref, computed, effect, isRef, onEffectCleanup } from "@vue/re
25
25
  *
26
26
  * @param {string | Record<string, any>} source - Path to .json file, URL, or raw document object
27
27
  * @param {HTMLElement} [target] Default is `document.body`
28
- * @param {any} [options]
28
+ * @param {JxRenderOptions} [options]
29
29
  * @returns {Promise<Record<string, any>>} Resolves with the live component scope (state reactive
30
30
  * proxy)
31
31
  */
@@ -88,6 +88,13 @@ export function setSkipServerFunctions(/** @type {boolean} */ v) {
88
88
  _serverFnConfig.skip = v;
89
89
  }
90
90
 
91
+ /** @type {{ skip: boolean }} */
92
+ const _contentConfig = { skip: false };
93
+ /** Set to true to suppress ContentCollection/ContentEntry resolution (used by Studio edit mode). */
94
+ export function setSkipContentResolution(/** @type {boolean} */ v) {
95
+ _contentConfig.skip = v;
96
+ }
97
+
91
98
  /**
92
99
  * Build the reactive scope (state) from the document using the five-shape detection algorithm.
93
100
  *
@@ -232,11 +239,12 @@ export { hasSchemaKeywords };
232
239
  *
233
240
  * @param {string} str
234
241
  * @param {Record<string, any>} state
235
- * @returns {any}
242
+ * @returns {string}
236
243
  */
237
244
  function evaluateTemplate(str, state) {
238
- const fn = new Function("state", "$map", `return \`${str}\``);
239
- return fn(state, state?.$map);
245
+ const $map = state?.$map;
246
+ const fn = new Function("state", "$map", "item", "index", `return \`${str}\``);
247
+ return fn(state, $map, $map?.item, $map?.index);
240
248
  }
241
249
 
242
250
  // ─── Step 2b: Function resolution (Shape 4) ─────────────────────────────────
@@ -254,7 +262,7 @@ const _moduleCache = new Map();
254
262
  * @param {Record<string, any>} state - Reactive scope proxy
255
263
  * @param {string} key - Def key name
256
264
  * @param {string} [base] - Base URL for resolving $src imports
257
- * @returns {Promise<any>}
265
+ * @returns {Promise<unknown>}
258
266
  */
259
267
  async function resolveFunction(def, state, key, base) {
260
268
  if (def.body && def.$src) {
@@ -385,7 +393,7 @@ export const RESERVED_KEYS = new Set([
385
393
  *
386
394
  * @param {Record<string, any>} def
387
395
  * @param {Record<string, any>} state - Reactive scope proxy (or child scope via Object.create)
388
- * @param {any} [options]
396
+ * @param {JxRenderOptions} [options]
389
397
  * @returns {HTMLElement | Text}
390
398
  */
391
399
  export function renderNode(def, state, options) {
@@ -448,7 +456,7 @@ export function renderNode(def, state, options) {
448
456
  /**
449
457
  * Check if a value is a template string (contains ${}).
450
458
  *
451
- * @param {any} val
459
+ * @param {unknown} val
452
460
  * @returns {boolean}
453
461
  */
454
462
  function isTemplateString(val) {
@@ -492,19 +500,22 @@ function applyProperties(el, def, state) {
492
500
  }
493
501
 
494
502
  /**
495
- * @param {any} el
503
+ * @param {HTMLElement} el
496
504
  * @param {string} key
497
- * @param {any} val
505
+ * @param {unknown} val
498
506
  * @param {Record<string, any>} state
499
507
  */
500
508
  function bindProperty(el, key, val, state) {
509
+ const target = /** @type {Record<string, unknown>} */ (/** @type {unknown} */ (el));
501
510
  if (isRefObj(val)) {
502
511
  if (key === "id") {
503
- el[key] = resolveRef(val.$ref, state);
512
+ target[key] = /** @type {string} */ (
513
+ resolveRef(/** @type {{ $ref: string }} */ (val).$ref, state)
514
+ );
504
515
  return;
505
516
  }
506
517
  effect(() => {
507
- el[key] = resolveRef(val.$ref, state);
518
+ target[key] = resolveRef(/** @type {{ $ref: string }} */ (val).$ref, state);
508
519
  });
509
520
  return;
510
521
  }
@@ -512,12 +523,12 @@ function bindProperty(el, key, val, state) {
512
523
  // Universal ${} reactivity — template strings in element properties
513
524
  if (isTemplateString(val)) {
514
525
  effect(() => {
515
- el[key] = evaluateTemplate(val, state);
526
+ target[key] = evaluateTemplate(/** @type {string} */ (val), state);
516
527
  });
517
528
  return;
518
529
  }
519
530
 
520
- el[key] = val;
531
+ target[key] = val;
521
532
  }
522
533
 
523
534
  /**
@@ -548,9 +559,10 @@ export function applyStyle(el, styleDef, mediaQueries = {}, state = {}) {
548
559
  else el.style.setProperty(prop, val);
549
560
  } else if (isTemplateString(val))
550
561
  effect(() => {
551
- /** @type {any} */ (el.style)[prop] = evaluateTemplate(val, state);
562
+ /** @type {Record<string, string>} */ (/** @type {unknown} */ (el.style))[prop] =
563
+ evaluateTemplate(val, state);
552
564
  });
553
- else /** @type {any} */ (el.style)[prop] = val;
565
+ else /** @type {Record<string, string>} */ (/** @type {unknown} */ (el.style))[prop] = val;
554
566
  }
555
567
 
556
568
  const hasNested = Object.keys(nested).length > 0;
@@ -647,7 +659,7 @@ function applyAttributes(el, attrs, state) {
647
659
  /**
648
660
  * @param {Record<string, any>} def
649
661
  * @param {Record<string, any>} state
650
- * @param {any} [options]
662
+ * @param {JxRenderOptions} [options]
651
663
  * @returns {HTMLElement}
652
664
  */
653
665
  function renderMappedArray(def, state, options) {
@@ -672,11 +684,13 @@ function renderMappedArray(def, state, options) {
672
684
  if (!Array.isArray(items)) return;
673
685
  if (filterRef) {
674
686
  const fn = resolveRef(filterRef.$ref, state);
675
- if (typeof fn === "function") items = items.filter(fn);
687
+ if (typeof fn === "function")
688
+ items = items.filter(/** @type {(v: unknown) => boolean} */ (fn));
676
689
  }
677
690
  if (sortRef) {
678
691
  const fn = resolveRef(sortRef.$ref, state);
679
- if (typeof fn === "function") items = [...items].sort(fn);
692
+ if (typeof fn === "function")
693
+ items = [...items].sort(/** @type {(a: unknown, b: unknown) => number} */ (fn));
680
694
  }
681
695
 
682
696
  items.forEach((item, index) => {
@@ -699,7 +713,7 @@ function renderMappedArray(def, state, options) {
699
713
  /**
700
714
  * @param {Record<string, any>} def
701
715
  * @param {Record<string, any>} state
702
- * @param {any} [options]
716
+ * @param {JxRenderOptions} [options]
703
717
  * @returns {HTMLElement}
704
718
  */
705
719
  function renderSwitch(def, state, options) {
@@ -715,7 +729,7 @@ function renderSwitch(def, state, options) {
715
729
 
716
730
  effect(() => {
717
731
  container.innerHTML = "";
718
- const key = resolveRef(def.$switch.$ref, state);
732
+ const key = /** @type {string} */ (resolveRef(def.$switch.$ref, state));
719
733
  const caseDef = def.cases?.[key];
720
734
  if (!caseDef) return;
721
735
 
@@ -732,7 +746,7 @@ function renderSwitch(def, state, options) {
732
746
  const childOpts = options ? { ...options, _path: [...path, "cases", key] } : undefined;
733
747
  container.appendChild(renderNode(doc, childScope, childOpts));
734
748
  })
735
- .catch((/** @type {any} */ e) =>
749
+ .catch((/** @type {unknown} */ e) =>
736
750
  console.error("Jx $switch: failed to load external case", caseDef.$ref, e),
737
751
  );
738
752
  return;
@@ -757,7 +771,7 @@ function renderSwitch(def, state, options) {
757
771
  * @param {Record<string, any>} state - Reactive scope proxy
758
772
  * @param {string} key - Def key (for diagnostics)
759
773
  * @param {string} [base] - Base URL for resolving $src imports
760
- * @returns {Promise<any>}
774
+ * @returns {Promise<unknown>}
761
775
  */
762
776
  export async function resolvePrototype(def, state, key, base) {
763
777
  // ── External class via $src ─────────────────────────────────────────────────
@@ -767,10 +781,10 @@ export async function resolvePrototype(def, state, key, base) {
767
781
 
768
782
  switch (def.$prototype) {
769
783
  case "Request": {
770
- /** @type {import("@vue/reactivity").Ref<any>} */
784
+ /** @type {import("@vue/reactivity").Ref<unknown>} */
771
785
  const s = ref(null);
772
786
  const debounceMs = def.debounce ?? 0;
773
- /** @type {any} */
787
+ /** @type {ReturnType<typeof setTimeout> | null} */
774
788
  let debounceTimer = null;
775
789
 
776
790
  if (!def.manual) {
@@ -786,7 +800,7 @@ export async function resolvePrototype(def, state, key, base) {
786
800
  const controller = new AbortController();
787
801
  onEffectCleanup(() => {
788
802
  controller.abort();
789
- clearTimeout(debounceTimer);
803
+ if (debounceTimer !== null) clearTimeout(debounceTimer);
790
804
  });
791
805
 
792
806
  const doFetch = () =>
@@ -802,8 +816,8 @@ export async function resolvePrototype(def, state, key, base) {
802
816
  .then((d) => {
803
817
  s.value = d;
804
818
  })
805
- .catch((/** @type {any} */ e) => {
806
- if (e.name !== "AbortError") s.value = { error: String(e) };
819
+ .catch((/** @type {unknown} */ e) => {
820
+ if (/** @type {Error} */ (e).name !== "AbortError") s.value = { error: String(e) };
807
821
  });
808
822
 
809
823
  if (debounceMs > 0) {
@@ -902,22 +916,22 @@ export async function resolvePrototype(def, state, key, base) {
902
916
  } = def;
903
917
  const req = indexedDB.open(database, version);
904
918
  req.onupgradeneeded = (e) => {
905
- /** @type {any} */
906
- const db = /** @type {any} */ (e.target)?.result;
919
+ /** @type {IDBDatabase} */
920
+ const db = /** @type {IDBOpenDBRequest} */ (e.target).result;
907
921
  if (!db.objectStoreNames.contains(store)) {
908
922
  const os = db.createObjectStore(store, { keyPath, autoIncrement });
909
923
  for (const i of indexes) os.createIndex(i.name, i.keyPath, { unique: i.unique ?? false });
910
924
  }
911
925
  };
912
926
  req.onsuccess = (e) => {
913
- /** @type {any} */
914
- const db = /** @type {any} */ (e.target)?.result;
927
+ /** @type {IDBDatabase} */
928
+ const db = /** @type {IDBOpenDBRequest} */ (e.target).result;
915
929
  idbState.value = {
916
930
  database,
917
931
  store,
918
932
  version,
919
933
  isReady: true,
920
- getStore: (/** @type {string} */ mode = "readwrite") =>
934
+ getStore: (/** @type {IDBTransactionMode} */ mode = "readwrite") =>
921
935
  Promise.resolve(db.transaction(store, mode).objectStore(store)),
922
936
  };
923
937
  };
@@ -945,6 +959,42 @@ export async function resolvePrototype(def, state, key, base) {
945
959
  case "ReadableStream":
946
960
  return null;
947
961
 
962
+ case "ContentCollection":
963
+ case "ContentEntry": {
964
+ if (_contentConfig.skip) {
965
+ return ref(def.$prototype === "ContentCollection" ? [{ id: "", data: {} }] : null);
966
+ }
967
+ const s = ref(def.$prototype === "ContentCollection" ? [] : null);
968
+ effect(() => {
969
+ let id = def.id;
970
+ if (id && typeof id === "object" && id.$ref?.startsWith("#/$params/")) {
971
+ const paramName = id.$ref.replace("#/$params/", "");
972
+ id = state?.$params?.[paramName];
973
+ } else if (typeof id === "string" && id.includes("${")) {
974
+ id = evaluateTemplate(id, state);
975
+ }
976
+ if (def.$prototype === "ContentEntry" && !id) return;
977
+ fetch("/__studio/content", {
978
+ method: "POST",
979
+ headers: { "Content-Type": "application/json" },
980
+ body: JSON.stringify({
981
+ $prototype: def.$prototype,
982
+ contentType: def.contentType,
983
+ id,
984
+ filter: def.filter,
985
+ sort: def.sort,
986
+ limit: def.limit,
987
+ }),
988
+ })
989
+ .then((r) => (r.ok ? r.json() : null))
990
+ .then((d) => {
991
+ s.value = d;
992
+ })
993
+ .catch(() => {});
994
+ });
995
+ return s;
996
+ }
997
+
948
998
  default:
949
999
  console.warn(
950
1000
  `Jx: unknown $prototype "${def.$prototype}" for "${key}". Did you mean to add '$src'?`,
@@ -976,7 +1026,7 @@ const EXTERNAL_RESERVED = new Set([
976
1026
  * @param {Record<string, any>} state
977
1027
  * @param {string} key
978
1028
  * @param {string} [base]
979
- * @returns {Promise<any>}
1029
+ * @returns {Promise<unknown>}
980
1030
  */
981
1031
  async function resolveExternalPrototype(def, state, key, base) {
982
1032
  const src = def.$src;
@@ -1000,7 +1050,7 @@ async function resolveExternalPrototype(def, state, key, base) {
1000
1050
  * @param {string} src - JS module URL to import
1001
1051
  * @param {string} exportName - Export name to look up
1002
1052
  * @param {string} [base] - Base URL for resolution
1003
- * @returns {Promise<any>}
1053
+ * @returns {Promise<unknown>}
1004
1054
  */
1005
1055
  async function importAndInstantiate(def, src, exportName, base) {
1006
1056
  let mod;
@@ -1049,7 +1099,7 @@ async function importAndInstantiate(def, src, exportName, base) {
1049
1099
  /** @type {import("@vue/reactivity").Ref<any>} */
1050
1100
  const s = ref(value);
1051
1101
  if (typeof instance.subscribe === "function") {
1052
- instance.subscribe((/** @type {any} */ newVal) => {
1102
+ instance.subscribe((/** @type {unknown} */ newVal) => {
1053
1103
  s.value = newVal;
1054
1104
  });
1055
1105
  }
@@ -1064,7 +1114,7 @@ async function importAndInstantiate(def, src, exportName, base) {
1064
1114
  * @param {Record<string, any>} state
1065
1115
  * @param {string} key
1066
1116
  * @param {string} [base]
1067
- * @returns {Promise<any>}
1117
+ * @returns {Promise<unknown>}
1068
1118
  */
1069
1119
  async function resolveClassJson(def, state, key, base) {
1070
1120
  const src = def.$src;
@@ -1116,7 +1166,7 @@ async function resolveClassJson(def, state, key, base) {
1116
1166
  /** @type {import("@vue/reactivity").Ref<any>} */
1117
1167
  const s = ref(value);
1118
1168
  if (typeof instance.subscribe === "function") {
1119
- instance.subscribe((/** @type {any} */ newVal) => {
1169
+ instance.subscribe((/** @type {unknown} */ newVal) => {
1120
1170
  s.value = newVal;
1121
1171
  });
1122
1172
  }
@@ -1128,7 +1178,7 @@ async function resolveClassJson(def, state, key, base) {
1128
1178
  * fields to _-prefixed public fields.
1129
1179
  *
1130
1180
  * @param {Record<string, any>} classDef
1131
- * @returns {any}
1181
+ * @returns {DynamicClass}
1132
1182
  */
1133
1183
  function classFromSchema(classDef) {
1134
1184
  const fields = classDef.$defs?.fields ?? {};
@@ -1138,16 +1188,34 @@ function classFromSchema(classDef) {
1138
1188
  class DynClass {
1139
1189
  constructor(/** @type {Record<string, any>} */ config = {}) {
1140
1190
  for (const [key, field] of Object.entries(fields)) {
1141
- /** @type {any} */
1142
- const typedField = field;
1191
+ /**
1192
+ * @type {{
1193
+ * identifier?: string;
1194
+ * access?: string;
1195
+ * initializer?: unknown;
1196
+ * default?: unknown;
1197
+ * }}
1198
+ */
1199
+ const typedField =
1200
+ /**
1201
+ * @type {{
1202
+ * identifier?: string;
1203
+ * access?: string;
1204
+ * initializer?: unknown;
1205
+ * default?: unknown;
1206
+ * }}
1207
+ */ (field);
1143
1208
  const id = typedField.identifier ?? key;
1144
1209
  const propName = typedField.access === "private" ? `_${id}` : id;
1145
- if (config[id] !== undefined) /** @type {any} */ (this)[propName] = config[id];
1210
+ if (config[id] !== undefined)
1211
+ /** @type {Record<string, unknown>} */ (this)[propName] = config[id];
1146
1212
  else if (typedField.initializer !== undefined)
1147
- /** @type {any} */ (this)[propName] = typedField.initializer;
1213
+ /** @type {Record<string, unknown>} */ (this)[propName] = typedField.initializer;
1148
1214
  else if (typedField.default !== undefined)
1149
- /** @type {any} */ (this)[propName] = structuredClone(typedField.default);
1150
- else /** @type {any} */ (this)[propName] = null;
1215
+ /** @type {Record<string, unknown>} */ (this)[propName] = structuredClone(
1216
+ typedField.default,
1217
+ );
1218
+ else /** @type {Record<string, unknown>} */ (this)[propName] = null;
1151
1219
  }
1152
1220
  if (ctor?.body) {
1153
1221
  const bodyStr = Array.isArray(ctor.body) ? ctor.body.join("\n") : ctor.body;
@@ -1157,13 +1225,35 @@ function classFromSchema(classDef) {
1157
1225
  }
1158
1226
 
1159
1227
  for (const [key, method] of Object.entries(methods)) {
1160
- /** @type {any} */
1161
- const typedMethod = method;
1228
+ /**
1229
+ * @type {{
1230
+ * identifier?: string;
1231
+ * parameters?: Record<string, unknown>[];
1232
+ * body?: string | string[];
1233
+ * role?: string;
1234
+ * scope?: string;
1235
+ * getter?: { body: string };
1236
+ * setter?: { body: string; parameters?: Record<string, unknown>[] };
1237
+ * }}
1238
+ */
1239
+ const typedMethod = /**
1240
+ * @type {{
1241
+ * identifier?: string;
1242
+ * parameters?: Record<string, unknown>[];
1243
+ * body?: string | string[];
1244
+ * role?: string;
1245
+ * scope?: string;
1246
+ * getter?: { body: string };
1247
+ * setter?: { body: string; parameters?: Record<string, unknown>[] };
1248
+ * }}
1249
+ */ (method);
1162
1250
  const name = typedMethod.identifier ?? key;
1163
- const params = (typedMethod.parameters ?? []).map((/** @type {any} */ p) => {
1164
- if (p.$ref) return p.$ref.split("/").pop();
1165
- return p.identifier ?? p.name ?? "arg";
1166
- });
1251
+ const params = /** @type {string[]} */ (
1252
+ (typedMethod.parameters ?? []).map((/** @type {Record<string, unknown>} */ p) => {
1253
+ if (p.$ref) return /** @type {string} */ (/** @type {string} */ (p.$ref).split("/").pop());
1254
+ return /** @type {string} */ (p.identifier ?? p.name ?? "arg");
1255
+ })
1256
+ );
1167
1257
  const bodyStr = Array.isArray(typedMethod.body)
1168
1258
  ? typedMethod.body.join("\n")
1169
1259
  : (typedMethod.body ?? "");
@@ -1172,23 +1262,27 @@ function classFromSchema(classDef) {
1172
1262
  /** @type {PropertyDescriptor} */
1173
1263
  const descriptor = {};
1174
1264
  if (typedMethod.getter)
1175
- descriptor.get = /** @type {any} */ (new Function(typedMethod.getter.body));
1265
+ descriptor.get = /** @type {() => unknown} */ (new Function(typedMethod.getter.body));
1176
1266
  if (typedMethod.setter) {
1177
1267
  const sp = (typedMethod.setter.parameters ?? []).map(
1178
- (/** @type {any} */ p) => p.$ref?.split("/").pop() ?? "v",
1268
+ (/** @type {Record<string, unknown>} */ p) =>
1269
+ /** @type {string} */ (p.$ref)?.split("/").pop() ?? "v",
1270
+ );
1271
+ descriptor.set = /** @type {(v: unknown) => void} */ (
1272
+ new Function(...sp, typedMethod.setter.body)
1179
1273
  );
1180
- descriptor.set = /** @type {any} */ (new Function(...sp, typedMethod.setter.body));
1181
1274
  }
1182
1275
  Object.defineProperty(DynClass.prototype, name, { ...descriptor, configurable: true });
1183
1276
  } else if (typedMethod.scope === "static") {
1184
- /** @type {any} */ (DynClass)[name] = new Function(...params, bodyStr);
1277
+ /** @type {DynamicClass} */ (DynClass)[name] = new Function(...params, bodyStr);
1185
1278
  } else {
1186
- /** @type {any} */ (DynClass.prototype)[name] = new Function(...params, bodyStr);
1279
+ /** @type {DynamicClass} */ (DynClass).prototype[name] = new Function(...params, bodyStr);
1187
1280
  }
1188
1281
  }
1189
1282
 
1190
1283
  Object.defineProperty(DynClass, "name", { value: classDef.title, configurable: true });
1191
- return DynClass;
1284
+ const dynCtor = /** @type {DynamicClass} */ (/** @type {unknown} */ (DynClass));
1285
+ return dynCtor;
1192
1286
  }
1193
1287
 
1194
1288
  /**
@@ -1200,7 +1294,7 @@ function classFromSchema(classDef) {
1200
1294
  * @param {Record<string, any>} state
1201
1295
  * @param {string} key
1202
1296
  * @param {string} [base]
1203
- * @returns {Promise<any>}
1297
+ * @returns {Promise<unknown>}
1204
1298
  */
1205
1299
  async function resolveViaDevProxy(def, state, key, base) {
1206
1300
  /** @type {Record<string, any>} */
@@ -1209,7 +1303,9 @@ async function resolveViaDevProxy(def, state, key, base) {
1209
1303
  if (!EXTERNAL_RESERVED.has(k)) config[k] = v;
1210
1304
  }
1211
1305
 
1212
- const hasTemplates = Object.values(config).some((/** @type {any} */ v) => isTemplateString(v));
1306
+ const hasTemplates = Object.values(config).some((/** @type {unknown} */ v) =>
1307
+ isTemplateString(v),
1308
+ );
1213
1309
 
1214
1310
  /** @param {Record<string, any>} resolvedConfig */
1215
1311
  const doResolve = (resolvedConfig) =>
@@ -1239,17 +1335,17 @@ async function resolveViaDevProxy(def, state, key, base) {
1239
1335
  resolvedConfig[k] = isTemplateString(v) ? evaluateTemplate(v, state) : v;
1240
1336
  }
1241
1337
  doResolve(resolvedConfig)
1242
- .then((/** @type {any} */ value) => {
1338
+ .then((/** @type {unknown} */ value) => {
1243
1339
  s.value = value;
1244
1340
  })
1245
- .catch((/** @type {any} */ e) => console.error("Jx dev proxy:", e));
1341
+ .catch((/** @type {unknown} */ e) => console.error("Jx dev proxy:", e));
1246
1342
  });
1247
1343
  } else {
1248
1344
  doResolve(config)
1249
- .then((/** @type {any} */ value) => {
1345
+ .then((/** @type {unknown} */ value) => {
1250
1346
  s.value = value;
1251
1347
  })
1252
- .catch((/** @type {any} */ e) => console.error("Jx dev proxy:", e));
1348
+ .catch((/** @type {unknown} */ e) => console.error("Jx dev proxy:", e));
1253
1349
  }
1254
1350
  return s;
1255
1351
  }
@@ -1264,7 +1360,7 @@ async function resolveViaDevProxy(def, state, key, base) {
1264
1360
  * @param {Record<string, any>} state
1265
1361
  * @param {string} key
1266
1362
  * @param {string} [base]
1267
- * @returns {Promise<any>}
1363
+ * @returns {Promise<unknown>}
1268
1364
  */
1269
1365
  async function resolveServerFunction(def, state, key, base) {
1270
1366
  const src = def.$src;
@@ -1298,12 +1394,12 @@ async function resolveServerFunction(def, state, key, base) {
1298
1394
  throw new Error(`Jx: "${exportName}" from "${src}" is not a function`);
1299
1395
 
1300
1396
  const rawArgs = def.arguments ?? {};
1301
- const hasReactiveArg = Object.values(rawArgs).some((/** @type {any} */ v) => isRefObj(v));
1397
+ const hasReactiveArg = Object.values(rawArgs).some((/** @type {unknown} */ v) => isRefObj(v));
1302
1398
  const resolveArgs = () => {
1303
1399
  /** @type {Record<string, any>} */
1304
1400
  const args = {};
1305
1401
  for (const [k, v] of Object.entries(rawArgs)) {
1306
- args[k] = isRefObj(v) ? resolveRef(/** @type {any} */ (v).$ref, state) : v;
1402
+ args[k] = isRefObj(v) ? resolveRef(/** @type {{ $ref: string }} */ (v).$ref, state) : v;
1307
1403
  }
1308
1404
  return args;
1309
1405
  };
@@ -1316,7 +1412,7 @@ async function resolveServerFunction(def, state, key, base) {
1316
1412
  const args = resolveArgs();
1317
1413
  onEffectCleanup(() => {});
1318
1414
  fn(args)
1319
- .then((/** @type {any} */ result) => {
1415
+ .then((/** @type {unknown} */ result) => {
1320
1416
  s.value = result;
1321
1417
  })
1322
1418
  .catch(() => {});
@@ -1336,17 +1432,17 @@ async function resolveServerFunction(def, state, key, base) {
1336
1432
  * @param {Record<string, any>} state
1337
1433
  * @param {string} key
1338
1434
  * @param {string} [base]
1339
- * @returns {Promise<any>}
1435
+ * @returns {Promise<unknown>}
1340
1436
  */
1341
1437
  async function resolveServerFunctionViaProxy(def, state, key, base) {
1342
1438
  const rawArgs = def.arguments ?? {};
1343
- const hasReactiveArg = Object.values(rawArgs).some((/** @type {any} */ v) => isRefObj(v));
1439
+ const hasReactiveArg = Object.values(rawArgs).some((/** @type {unknown} */ v) => isRefObj(v));
1344
1440
 
1345
1441
  const resolveArgs = () => {
1346
1442
  /** @type {Record<string, any>} */
1347
1443
  const args = {};
1348
1444
  for (const [k, v] of Object.entries(rawArgs)) {
1349
- args[k] = isRefObj(v) ? resolveRef(/** @type {any} */ (v).$ref, state) : v;
1445
+ args[k] = isRefObj(v) ? resolveRef(/** @type {{ $ref: string }} */ (v).$ref, state) : v;
1350
1446
  }
1351
1447
  return args;
1352
1448
  };
@@ -1375,17 +1471,17 @@ async function resolveServerFunctionViaProxy(def, state, key, base) {
1375
1471
  const args = resolveArgs();
1376
1472
  onEffectCleanup(() => {});
1377
1473
  doResolve(args)
1378
- .then((/** @type {any} */ result) => {
1474
+ .then((/** @type {unknown} */ result) => {
1379
1475
  s.value = result;
1380
1476
  })
1381
- .catch((/** @type {any} */ e) => console.error("Jx server proxy:", e));
1477
+ .catch((/** @type {unknown} */ e) => console.error("Jx server proxy:", e));
1382
1478
  });
1383
1479
  } else {
1384
1480
  doResolve(resolveArgs())
1385
- .then((/** @type {any} */ result) => {
1481
+ .then((/** @type {unknown} */ result) => {
1386
1482
  s.value = result;
1387
1483
  })
1388
- .catch((/** @type {any} */ e) => console.error("Jx server proxy:", e));
1484
+ .catch((/** @type {unknown} */ e) => console.error("Jx server proxy:", e));
1389
1485
  }
1390
1486
  return s;
1391
1487
  }
@@ -1398,7 +1494,7 @@ async function resolveServerFunctionViaProxy(def, state, key, base) {
1398
1494
  *
1399
1495
  * @param {string} ref
1400
1496
  * @param {Record<string, any>} state - Reactive scope proxy (or child scope)
1401
- * @returns {any}
1497
+ * @returns {unknown}
1402
1498
  */
1403
1499
  export function resolveRef(ref, state) {
1404
1500
  if (typeof ref !== "string") return ref;
@@ -1426,7 +1522,7 @@ export function resolveRef(ref, state) {
1426
1522
  /**
1427
1523
  * Check if v is a Vue ref (including computed).
1428
1524
  *
1429
- * @param {any} v
1525
+ * @param {unknown} v
1430
1526
  * @returns {boolean}
1431
1527
  */
1432
1528
  export function isSignal(v) {
@@ -1434,11 +1530,15 @@ export function isSignal(v) {
1434
1530
  }
1435
1531
 
1436
1532
  /**
1437
- * @param {any} v
1533
+ * @param {unknown} v
1438
1534
  * @returns {boolean}
1439
1535
  */
1440
1536
  function isRefObj(v) {
1441
- return v !== null && typeof v === "object" && typeof v.$ref === "string";
1537
+ return (
1538
+ v !== null &&
1539
+ typeof v === "object" &&
1540
+ typeof (/** @type {Record<string, unknown>} */ (v).$ref) === "string"
1541
+ );
1442
1542
  }
1443
1543
 
1444
1544
  /**
@@ -1450,12 +1550,12 @@ function isNestedSelector(k) {
1450
1550
  }
1451
1551
 
1452
1552
  /**
1453
- * @param {any} obj
1553
+ * @param {unknown} obj
1454
1554
  * @param {string} path
1455
- * @returns {any}
1555
+ * @returns {unknown}
1456
1556
  */
1457
1557
  function getPath(obj, path) {
1458
- return path.split(/[./]/).reduce((o, k) => o?.[k], obj);
1558
+ return path.split(/[./]/).reduce((o, k) => /** @type {Record<string, unknown>} */ (o)?.[k], obj);
1459
1559
  }
1460
1560
 
1461
1561
  /**
@@ -1484,7 +1584,7 @@ export function camelToKebab(s) {
1484
1584
  /**
1485
1585
  * Convert a style rules object to a CSS text string (skipping nested selectors).
1486
1586
  *
1487
- * @param {Record<string, any>} rules
1587
+ * @param {Record<string, unknown> | object} rules
1488
1588
  * @returns {string}
1489
1589
  */
1490
1590
  export function toCSSText(rules) {
@@ -1630,8 +1730,8 @@ export async function defineElement(source, base) {
1630
1730
 
1631
1731
  // Merge $props set as JS properties by parent before connection
1632
1732
  for (const key of Object.keys(def.state ?? {})) {
1633
- if (key in this && /** @type {any} */ (this)[key] !== undefined) {
1634
- state[key] = /** @type {any} */ (this)[key];
1733
+ if (key in this && /** @type {Record<string, unknown>} */ (this)[key] !== undefined) {
1734
+ state[key] = /** @type {Record<string, unknown>} */ (this)[key];
1635
1735
  }
1636
1736
  }
1637
1737
  // Set up property getters/setters that forward into reactive state
@@ -1639,7 +1739,7 @@ export async function defineElement(source, base) {
1639
1739
  if (!(key in HTMLElement.prototype)) {
1640
1740
  Object.defineProperty(this, key, {
1641
1741
  get: () => state[key],
1642
- set: (/** @type {any} */ v) => {
1742
+ set: (/** @type {unknown} */ v) => {
1643
1743
  state[key] = v;
1644
1744
  },
1645
1745
  configurable: true,
@@ -1647,7 +1747,7 @@ export async function defineElement(source, base) {
1647
1747
  }
1648
1748
  }
1649
1749
 
1650
- /** @type {any} */ (this)._state = state;
1750
+ /** @type {Record<string, unknown>} */ (this)._state = state;
1651
1751
 
1652
1752
  // Capture light DOM children (for slot distribution) before rendering
1653
1753
  const slottedChildren = Array.from(this.childNodes);
@@ -1677,16 +1777,16 @@ export async function defineElement(source, base) {
1677
1777
  }
1678
1778
 
1679
1779
  disconnectedCallback() {
1680
- /** @type {any} */
1681
- const self = this;
1780
+ /** @type {{ _state?: Record<string, any> }} */
1781
+ const self = /** @type {{ _state?: Record<string, any> }} */ (/** @type {unknown} */ (this));
1682
1782
  if (typeof self._state?.onUnmount === "function") {
1683
1783
  self._state.onUnmount(self._state);
1684
1784
  }
1685
1785
  }
1686
1786
 
1687
1787
  adoptedCallback() {
1688
- /** @type {any} */
1689
- const self = this;
1788
+ /** @type {{ _state?: Record<string, any> }} */
1789
+ const self = /** @type {{ _state?: Record<string, any> }} */ (/** @type {unknown} */ (this));
1690
1790
  if (typeof self._state?.onAdopted === "function") {
1691
1791
  self._state.onAdopted(self._state);
1692
1792
  }
@@ -1697,8 +1797,8 @@ export async function defineElement(source, base) {
1697
1797
  /** @type {string | null} */ oldVal,
1698
1798
  /** @type {string | null} */ newVal,
1699
1799
  ) {
1700
- /** @type {any} */
1701
- const self = this;
1800
+ /** @type {{ _state?: Record<string, any> }} */
1801
+ const self = /** @type {{ _state?: Record<string, any> }} */ (/** @type {unknown} */ (this));
1702
1802
  if (!self._state || oldVal === newVal) return;
1703
1803
  const camelKey = name.replace(
1704
1804
  /-([a-z])/g,
@@ -1720,31 +1820,30 @@ export async function defineElement(source, base) {
1720
1820
  *
1721
1821
  * @param {Record<string, any>} def
1722
1822
  * @param {Record<string, any>} state
1723
- * @param {any} [options]
1724
- * @param {any[]} [path]
1823
+ * @param {JxRenderOptions} [options]
1824
+ * @param {JxPath} [path]
1725
1825
  * @returns {HTMLElement}
1726
1826
  */
1727
1827
  function renderCustomElementWithProps(def, state, options, path) {
1728
- /** @type {any} */
1729
1828
  const el = document.createElement(def.tagName);
1730
1829
 
1731
- if (options?.onNodeCreated) options.onNodeCreated(el, path, def);
1830
+ if (options?.onNodeCreated) options.onNodeCreated(el, path ?? [], def);
1732
1831
 
1733
1832
  // Set JS properties from $props (before connection)
1734
1833
  for (const [key, val] of Object.entries(def.$props ?? {})) {
1735
1834
  if (isRefObj(val)) {
1736
1835
  const resolved = resolveRef(val.$ref, state);
1737
- el[key] = resolved;
1836
+ /** @type {Record<string, unknown>} */ (el)[key] = resolved;
1738
1837
  // Reactive forwarding: re-set the property when the source changes
1739
1838
  effect(() => {
1740
- el[key] = resolveRef(val.$ref, state);
1839
+ /** @type {Record<string, unknown>} */ (el)[key] = resolveRef(val.$ref, state);
1741
1840
  });
1742
1841
  } else if (isTemplateString(val)) {
1743
1842
  effect(() => {
1744
- el[key] = evaluateTemplate(val, state);
1843
+ /** @type {Record<string, unknown>} */ (el)[key] = evaluateTemplate(val, state);
1745
1844
  });
1746
1845
  } else {
1747
- el[key] = val;
1846
+ /** @type {Record<string, unknown>} */ (el)[key] = val;
1748
1847
  }
1749
1848
  }
1750
1849