@jxsuite/runtime 0.23.2 → 0.24.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jxsuite/runtime",
3
- "version": "0.23.2",
3
+ "version": "0.24.0",
4
4
  "description": "JSON-native reactive web component runtime",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -14,19 +14,21 @@
14
14
  ],
15
15
  "type": "module",
16
16
  "exports": {
17
- ".": "./src/runtime.js",
18
- "./expression": "./src/expression.js"
17
+ ".": "./src/runtime.ts",
18
+ "./expression": "./src/expression.ts",
19
+ "./types": "./src/types.ts"
19
20
  },
20
21
  "publishConfig": {
21
22
  "provenance": true
22
23
  },
23
24
  "scripts": {
24
- "build": "bun build ./src/runtime.js --outdir dist --target browser --sourcemap=linked",
25
- "test": "bun test",
26
- "test:coverage": "bun test --coverage",
25
+ "build": "bun build ./src/runtime.ts --outdir dist --target browser --sourcemap=linked",
26
+ "test": "bun test --isolate",
27
+ "test:coverage": "bun test --isolate --coverage",
27
28
  "upgrade": "bunx npm-check-updates -u && bun install"
28
29
  },
29
30
  "dependencies": {
31
+ "@jxsuite/schema": "^0.24.0",
30
32
  "@vue/reactivity": "^3.5.35"
31
33
  },
32
34
  "devDependencies": {
@@ -1,3 +1,4 @@
1
+ /// <reference lib="dom" />
1
2
  /**
2
3
  * Jx Declarative Expression Engine (spec §19)
3
4
  *
@@ -6,7 +7,17 @@
6
7
  * @module expression
7
8
  */
8
9
 
9
- /** @typedef {{ operator: string; target: any; value?: any; initial?: any }} ExpressionNode */
10
+ export interface ExpressionNode {
11
+ operator: string;
12
+ target: unknown;
13
+ value?: unknown;
14
+ initial?: unknown;
15
+ }
16
+
17
+ interface CompileOpts {
18
+ statePrefix?: string;
19
+ eventParam?: string;
20
+ }
10
21
 
11
22
  const MUTATING_OPS = new Set([
12
23
  "=",
@@ -51,54 +62,53 @@ export const BLESSED_OPERATORS = new Set([
51
62
  * @param {string} op
52
63
  * @returns {boolean}
53
64
  */
54
- export function isMutating(op) {
65
+ export function isMutating(op: string) {
55
66
  return MUTATING_OPS.has(op);
56
67
  }
57
68
 
58
69
  // ─── Runtime Evaluation ──────────────────────────────────────────────────────
59
70
 
60
- /**
61
- * Resolve an operand to its runtime value.
62
- *
63
- * @param {any} operand
64
- * @param {Record<string, any>} state
65
- * @param {Event | null} event
66
- * @param {{ acc?: any; item?: any; index?: number }} [iterCtx]
67
- * @returns {any}
68
- */
69
- function resolveOperand(operand, state, event, iterCtx) {
71
+ interface IterCtx {
72
+ acc?: unknown;
73
+ item?: unknown;
74
+ index?: number;
75
+ }
76
+
77
+ /** Resolve an operand to its runtime value. */
78
+ function resolveOperand(
79
+ operand: unknown,
80
+ state: Record<string, any>,
81
+ event: Event | null,
82
+ iterCtx?: IterCtx,
83
+ ): unknown {
70
84
  if (operand === null || operand === undefined) return operand;
71
85
 
72
86
  // Nested expression node
73
87
  if (typeof operand === "object" && !Array.isArray(operand) && "operator" in operand) {
74
- return evaluateExpression(operand, state, event, iterCtx);
88
+ return evaluateExpression(operand as ExpressionNode, state, event, iterCtx);
75
89
  }
76
90
 
77
91
  // $ref pointer
78
92
  if (typeof operand === "object" && !Array.isArray(operand) && "$ref" in operand) {
79
- return resolveExprRef(operand.$ref, state, event, iterCtx);
93
+ return resolveExprRef((operand as { $ref: string }).$ref, state, event, iterCtx);
80
94
  }
81
95
 
82
96
  // Array of operands (e.g., splice args)
83
97
  if (Array.isArray(operand)) {
84
- return operand.map((o) => resolveOperand(o, state, event, iterCtx));
98
+ return operand.map((o: unknown) => resolveOperand(o, state, event, iterCtx));
85
99
  }
86
100
 
87
101
  // Literal
88
102
  return operand;
89
103
  }
90
104
 
91
- /**
92
- * Resolve a $ref string within expression context. Handles event#/, $reduce/acc, $map/, #/state/,
93
- * and other schemes.
94
- *
95
- * @param {string} ref
96
- * @param {Record<string, any>} state
97
- * @param {Event | null} event
98
- * @param {{ acc?: any; item?: any; index?: number }} [iterCtx]
99
- * @returns {any}
100
- */
101
- function resolveExprRef(ref, state, event, iterCtx) {
105
+ /** Resolve a $ref string within expression context. */
106
+ function resolveExprRef(
107
+ ref: string,
108
+ state: Record<string, any>,
109
+ event: Event | null,
110
+ iterCtx?: IterCtx,
111
+ ) {
102
112
  if (ref === "$reduce/acc") {
103
113
  return iterCtx?.acc;
104
114
  }
@@ -131,16 +141,13 @@ function resolveExprRef(ref, state, event, iterCtx) {
131
141
  return state[ref] ?? null;
132
142
  }
133
143
 
134
- /**
135
- * Resolve a $ref to a writable location — returns { obj, key } for assignment.
136
- *
137
- * @param {string} ref
138
- * @param {Record<string, any>} state
139
- * @param {Event | null} event
140
- * @param {{ acc?: any; item?: any; index?: number }} [iterCtx]
141
- * @returns {{ obj: any; key: string }}
142
- */
143
- function resolveWritableRef(ref, state, event, iterCtx) {
144
+ /** Resolve a $ref to a writable location — returns { obj, key } for assignment. */
145
+ function resolveWritableRef(
146
+ ref: string,
147
+ state: Record<string, any>,
148
+ event: Event | null,
149
+ iterCtx?: IterCtx,
150
+ ) {
144
151
  if (ref.startsWith("$map/")) {
145
152
  const parts = ref.split("/");
146
153
  const key = parts[1];
@@ -152,7 +159,7 @@ function resolveWritableRef(ref, state, event, iterCtx) {
152
159
  const pathParts = parts.slice(2);
153
160
  const lastKey = pathParts.pop();
154
161
  const obj = pathParts.length > 0 ? getPath(base, pathParts.join("/")) : base;
155
- return { obj, key: /** @type {string} */ (lastKey) };
162
+ return { obj, key: lastKey as string };
156
163
  }
157
164
  return { obj: state.$map ?? state, key: key === "item" ? "$map/item" : "$map/index" };
158
165
  }
@@ -164,21 +171,18 @@ function resolveWritableRef(ref, state, event, iterCtx) {
164
171
  const lastKey = parts.pop();
165
172
  let obj = state;
166
173
  for (const p of parts) obj = obj[p];
167
- return { obj, key: /** @type {string} */ (lastKey) };
174
+ return { obj, key: lastKey as string };
168
175
  }
169
176
  return { obj: state, key: ref };
170
177
  }
171
178
 
172
- /**
173
- * Evaluate an expression node at runtime.
174
- *
175
- * @param {ExpressionNode} node
176
- * @param {Record<string, any>} state
177
- * @param {Event | null} event
178
- * @param {{ acc?: any; item?: any; index?: number }} [iterCtx]
179
- * @returns {any}
180
- */
181
- export function evaluateExpression(node, state, event, iterCtx) {
179
+ /** Evaluate an expression node at runtime. */
180
+ export function evaluateExpression(
181
+ node: ExpressionNode,
182
+ state: Record<string, any>,
183
+ event: Event | null,
184
+ iterCtx?: IterCtx,
185
+ ): unknown {
182
186
  const { operator, target, value, initial } = node;
183
187
 
184
188
  if (!BLESSED_OPERATORS.has(operator)) {
@@ -187,15 +191,15 @@ export function evaluateExpression(node, state, event, iterCtx) {
187
191
 
188
192
  // ─── Unary ───
189
193
  if (UNARY_OPS.has(operator) && !("value" in node)) {
190
- const operand = resolveOperand(target, state, event, iterCtx);
194
+ const operand: unknown = resolveOperand(target, state, event, iterCtx);
191
195
  if (operator === "!") return !operand;
192
- if (operator === "-") return -operand;
196
+ if (operator === "-") return -(operand as number);
193
197
  }
194
198
 
195
199
  // ─── Binary (pure) ───
196
200
  if (BINARY_OPS.has(operator)) {
197
- const left = resolveOperand(target, state, event, iterCtx);
198
- const right = resolveOperand(value, state, event, iterCtx);
201
+ const left = resolveOperand(target, state, event, iterCtx) as number & string;
202
+ const right = resolveOperand(value, state, event, iterCtx) as number & string;
199
203
  switch (operator) {
200
204
  case "+":
201
205
  return left + right;
@@ -228,8 +232,13 @@ export function evaluateExpression(node, state, event, iterCtx) {
228
232
 
229
233
  // ─── Assignment ───
230
234
  if (ASSIGNMENT_OPS.has(operator)) {
231
- const rhs = resolveOperand(value, state, event, iterCtx);
232
- const { obj, key } = resolveWritableRef(target.$ref, state, event, iterCtx);
235
+ const rhs = resolveOperand(value, state, event, iterCtx) as number;
236
+ const { obj, key } = resolveWritableRef(
237
+ (target as { $ref: string }).$ref,
238
+ state,
239
+ event,
240
+ iterCtx,
241
+ );
233
242
  switch (operator) {
234
243
  case "=":
235
244
  obj[key] = rhs;
@@ -252,7 +261,7 @@ export function evaluateExpression(node, state, event, iterCtx) {
252
261
 
253
262
  // ─── Array methods ───
254
263
  if (ARRAY_METHOD_OPS.has(operator)) {
255
- const arr = resolveOperand(target, state, event, iterCtx);
264
+ const arr: unknown[] = resolveOperand(target, state, event, iterCtx) as unknown[];
256
265
  switch (operator) {
257
266
  case "push":
258
267
  return arr.push(resolveOperand(value, state, event, iterCtx));
@@ -263,65 +272,64 @@ export function evaluateExpression(node, state, event, iterCtx) {
263
272
  case "shift":
264
273
  return arr.shift();
265
274
  case "splice": {
266
- const args = resolveOperand(value, state, event, iterCtx);
267
- return arr.splice(...args);
275
+ const args: unknown[] = resolveOperand(value, state, event, iterCtx) as unknown[];
276
+ return (arr.splice as (...a: unknown[]) => unknown[])(...args);
268
277
  }
269
278
  }
270
279
  }
271
280
 
272
281
  // ─── Aggregates (pure) ───
273
282
  if (AGGREGATE_OPS.has(operator)) {
274
- const arr = resolveOperand(target, state, event, iterCtx);
283
+ const arr: unknown[] = resolveOperand(target, state, event, iterCtx) as unknown[];
275
284
  if (operator === "reduce") {
276
- const seed = resolveOperand(initial, state, event, iterCtx);
277
- return arr.reduce(
278
- (/** @type {any} */ acc, /** @type {any} */ item, /** @type {number} */ index) => {
279
- return evaluateExpression(value, state, event, { acc, item, index });
280
- },
281
- seed,
282
- );
285
+ const seed: unknown = resolveOperand(initial, state, event, iterCtx);
286
+ return arr.reduce((acc: unknown, item: unknown, index: number) => {
287
+ return evaluateExpression(value as ExpressionNode, state, event, { acc, item, index });
288
+ }, seed);
283
289
  }
284
290
  if (operator === "map") {
285
- return arr.map((/** @type {any} */ item, /** @type {number} */ index) => {
286
- return evaluateExpression(value, state, event, { ...iterCtx, item, index });
291
+ return arr.map((item: unknown, index: number) => {
292
+ return evaluateExpression(value as ExpressionNode, state, event, {
293
+ ...iterCtx,
294
+ item,
295
+ index,
296
+ });
287
297
  });
288
298
  }
289
299
  if (operator === "filter") {
290
- return arr.filter((/** @type {any} */ item, /** @type {number} */ index) => {
291
- return evaluateExpression(value, state, event, { ...iterCtx, item, index });
300
+ return arr.filter((item: unknown, index: number) => {
301
+ return evaluateExpression(value as ExpressionNode, state, event, {
302
+ ...iterCtx,
303
+ item,
304
+ index,
305
+ });
292
306
  });
293
307
  }
294
308
  }
309
+
310
+ return undefined;
295
311
  }
296
312
 
297
313
  // ─── Compiler: Expression → JS Source ────────────────────────────────────────
298
314
 
299
- /** @typedef {{ statePrefix?: string; eventParam?: string }} CompileOpts */
300
-
301
- /**
302
- * Compile an operand to a JS source string.
303
- *
304
- * @param {any} operand
305
- * @param {CompileOpts} opts
306
- * @returns {string}
307
- */
308
- function compileOperand(operand, opts) {
315
+ /** Compile an operand to a JS source string. */
316
+ function compileOperand(operand: unknown, opts: CompileOpts): string {
309
317
  if (operand === null) return "null";
310
318
  if (operand === undefined) return "undefined";
311
319
 
312
320
  // Nested expression node
313
321
  if (typeof operand === "object" && !Array.isArray(operand) && "operator" in operand) {
314
- return compileExpression(operand, opts);
322
+ return compileExpression(operand as ExpressionNode, opts);
315
323
  }
316
324
 
317
325
  // $ref pointer
318
326
  if (typeof operand === "object" && !Array.isArray(operand) && "$ref" in operand) {
319
- return compileRef(operand.$ref, opts);
327
+ return compileRef((operand as { $ref: string }).$ref, opts);
320
328
  }
321
329
 
322
330
  // Array of operands
323
331
  if (Array.isArray(operand)) {
324
- return operand.map((o) => compileOperand(o, opts)).join(", ");
332
+ return operand.map((o: unknown) => compileOperand(o, opts)).join(", ");
325
333
  }
326
334
 
327
335
  // Literal
@@ -335,7 +343,7 @@ function compileOperand(operand, opts) {
335
343
  * @param {CompileOpts} opts
336
344
  * @returns {string}
337
345
  */
338
- function compileRef(ref, opts) {
346
+ function compileRef(ref: string, opts: CompileOpts) {
339
347
  const s = opts.statePrefix ?? "state";
340
348
  const e = opts.eventParam ?? "event";
341
349
 
@@ -371,16 +379,10 @@ function compileRef(ref, opts) {
371
379
  return `${s}.${ref}`;
372
380
  }
373
381
 
374
- /**
375
- * Compile a writable $ref target to its JS equivalent (for LHS of assignment).
376
- *
377
- * @param {any} target
378
- * @param {CompileOpts} opts
379
- * @returns {string}
380
- */
381
- function compileTarget(target, opts) {
382
- if (typeof target === "object" && "$ref" in target) {
383
- return compileRef(target.$ref, opts);
382
+ /** Compile a writable $ref target to its JS equivalent (for LHS of assignment). */
383
+ function compileTarget(target: unknown, opts: CompileOpts): string {
384
+ if (typeof target === "object" && target !== null && "$ref" in target) {
385
+ return compileRef((target as { $ref: string }).$ref, opts);
384
386
  }
385
387
  return compileOperand(target, opts);
386
388
  }
@@ -393,33 +395,33 @@ function compileTarget(target, opts) {
393
395
  * @param {CompileOpts} [opts]
394
396
  * @returns {string}
395
397
  */
396
- export function compileExpression(node, opts = {}) {
398
+ export function compileExpression(node: ExpressionNode, opts: CompileOpts = {}): string {
397
399
  const { operator, target, value, initial } = node;
398
400
 
399
401
  // ─── Unary ───
400
402
  if (UNARY_OPS.has(operator) && !("value" in node)) {
401
- const operand = compileOperand(target, opts);
403
+ const operand: string = compileOperand(target, opts);
402
404
  if (operator === "!") return `!(${operand})`;
403
405
  if (operator === "-") return `-(${operand})`;
404
406
  }
405
407
 
406
408
  // ─── Binary (pure) ───
407
409
  if (BINARY_OPS.has(operator)) {
408
- const left = compileOperand(target, opts);
409
- const right = compileOperand(value, opts);
410
+ const left: string = compileOperand(target, opts);
411
+ const right: string = compileOperand(value, opts);
410
412
  return `(${left} ${operator} ${right})`;
411
413
  }
412
414
 
413
415
  // ─── Assignment ───
414
416
  if (ASSIGNMENT_OPS.has(operator)) {
415
- const lhs = compileTarget(target, opts);
416
- const rhs = compileOperand(value, opts);
417
+ const lhs: string = compileTarget(target, opts);
418
+ const rhs: string = compileOperand(value, opts);
417
419
  return `${lhs} ${operator} ${rhs}`;
418
420
  }
419
421
 
420
422
  // ─── Array methods ───
421
423
  if (ARRAY_METHOD_OPS.has(operator)) {
422
- const arr = compileTarget(target, opts);
424
+ const arr: string = compileTarget(target, opts);
423
425
  switch (operator) {
424
426
  case "push":
425
427
  return `${arr}.push(${compileOperand(value, opts)})`;
@@ -430,8 +432,8 @@ export function compileExpression(node, opts = {}) {
430
432
  case "shift":
431
433
  return `${arr}.shift()`;
432
434
  case "splice": {
433
- const args = Array.isArray(value)
434
- ? value.map((o) => compileOperand(o, opts)).join(", ")
435
+ const args: string = Array.isArray(value)
436
+ ? value.map((o: unknown) => compileOperand(o, opts)).join(", ")
435
437
  : compileOperand(value, opts);
436
438
  return `${arr}.splice(${args})`;
437
439
  }
@@ -440,10 +442,10 @@ export function compileExpression(node, opts = {}) {
440
442
 
441
443
  // ─── Aggregates (pure) ───
442
444
  if (AGGREGATE_OPS.has(operator)) {
443
- const arr = compileOperand(target, opts);
444
- const itemExpr = compileExpression(value, opts);
445
+ const arr: string = compileOperand(target, opts);
446
+ const itemExpr: string = compileExpression(value as ExpressionNode, opts);
445
447
  if (operator === "reduce") {
446
- const seed = compileOperand(initial, opts);
448
+ const seed: string = compileOperand(initial, opts);
447
449
  return `${arr}.reduce((_acc, _item, _index) => ${itemExpr}, ${seed})`;
448
450
  }
449
451
  if (operator === "map") {
@@ -459,11 +461,7 @@ export function compileExpression(node, opts = {}) {
459
461
 
460
462
  // ─── Utilities ───────────────────────────────────────────────────────────────
461
463
 
462
- /**
463
- * @param {any} obj
464
- * @param {string} path
465
- * @returns {any}
466
- */
467
- function getPath(obj, path) {
468
- return path.split(/[./]/).reduce((o, k) => /** @type {any} */ (o)?.[k], obj);
464
+ /** Resolve a dotted/slashed path on an object. */
465
+ function getPath(obj: unknown, path: string): unknown {
466
+ return path.split(/[./]/).reduce((o, k) => (o as Record<string, unknown>)?.[k], obj);
469
467
  }