@jxsuite/runtime 0.23.2 → 0.25.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 +56 -31
- package/dist/runtime.js.map +5 -5
- package/package.json +8 -6
- package/src/{expression.js → expression.ts} +110 -112
- package/src/{runtime.js → runtime.ts} +297 -275
- package/src/types.ts +14 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.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.
|
|
18
|
-
"./expression": "./src/expression.
|
|
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.
|
|
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.25.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
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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:
|
|
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:
|
|
174
|
+
return { obj, key: lastKey as string };
|
|
168
175
|
}
|
|
169
176
|
return { obj: state, key: ref };
|
|
170
177
|
}
|
|
171
178
|
|
|
172
|
-
/**
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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(
|
|
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
|
-
(
|
|
279
|
-
|
|
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((
|
|
286
|
-
return evaluateExpression(value, state, event, {
|
|
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((
|
|
291
|
-
return evaluateExpression(value, state, event, {
|
|
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
|
-
/**
|
|
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
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
|
|
464
|
-
|
|
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
|
}
|