jtlt 0.13.0 → 0.14.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/CHANGES.md +10 -0
- package/README.md +99 -85
- package/demo/calltemplate-params-demo.js +13 -22
- package/demo/vendor/jhtml/src/SAJJ/SAJJ.ObjectArrayDelegator.js +17 -17
- package/demo/vendor/jhtml/src/SAJJ/SAJJ.js +60 -61
- package/demo/vendor/jhtml/src/jhtml-browser.js +1 -0
- package/demo/vendor/jhtml/src/jhtml-node.js +1 -0
- package/demo/vendor/jhtml/src/jhtml.js +18 -15
- package/dist/AbstractJoiningTransformer.d.ts +5 -5
- package/dist/AbstractJoiningTransformer.d.ts.map +1 -1
- package/dist/JSONPathTransformer.d.ts +13 -12
- package/dist/JSONPathTransformer.d.ts.map +1 -1
- package/dist/JSONPathTransformerContext.d.ts +50 -20
- package/dist/JSONPathTransformerContext.d.ts.map +1 -1
- package/dist/XPathTransformer.d.ts +9 -2
- package/dist/XPathTransformer.d.ts.map +1 -1
- package/dist/XPathTransformerContext.d.ts +46 -9
- package/dist/XPathTransformerContext.d.ts.map +1 -1
- package/dist/index.d.ts +104 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/indexedDB.d.ts +107 -0
- package/dist/indexedDB.d.ts.map +1 -0
- package/dist/maybeAsync.d.ts +15 -0
- package/dist/maybeAsync.d.ts.map +1 -0
- package/docs/API.expanded.md +70 -20
- package/docs/API.md +34 -4
- package/docs/TO-DO.md +2 -2
- package/eslint.config.js +11 -3
- package/package.json +7 -5
- package/pnpm-workspace.yaml +9 -0
- package/src/AbstractJoiningTransformer.js +3 -3
- package/src/JSONPathTransformer.js +45 -7
- package/src/JSONPathTransformerContext.js +157 -22
- package/src/XPathTransformer.js +33 -1
- package/src/XPathTransformerContext.js +136 -16
- package/src/index.js +85 -8
- package/src/indexedDB.js +530 -0
- package/src/maybeAsync.js +44 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jtlt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": "Brett Zamir",
|
|
6
6
|
"contributors": [],
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"@lezer/highlight": "^1.2.3",
|
|
34
34
|
"codemirror": "^6.0.2",
|
|
35
35
|
"fontoxpath": "^3.34.0",
|
|
36
|
+
"idb": "^8.0.3",
|
|
36
37
|
"jamilih": "0.69.0",
|
|
37
38
|
"jhtml": "0.7.3",
|
|
38
39
|
"jsdom": "30.0.1",
|
|
@@ -50,14 +51,15 @@
|
|
|
50
51
|
"@types/chai": "^5.2.3",
|
|
51
52
|
"@types/jsdom": "^30.0.0",
|
|
52
53
|
"@types/mocha": "^10.0.10",
|
|
53
|
-
"baseline-browser-mapping": "^2.11.
|
|
54
|
+
"baseline-browser-mapping": "^2.11.20",
|
|
54
55
|
"c8": "^12.0.0",
|
|
55
56
|
"chai": "^6.2.2",
|
|
56
57
|
"eslint": "^10.9.1",
|
|
57
|
-
"eslint-config-ash-nazg": "^
|
|
58
|
+
"eslint-config-ash-nazg": "^43.1.0",
|
|
59
|
+
"indexeddbshim": "^17.4.3",
|
|
58
60
|
"mocha": "^11.8.0",
|
|
59
|
-
"open": "^11.0.
|
|
60
|
-
"rollup": "^4.63.
|
|
61
|
+
"open": "^11.0.2",
|
|
62
|
+
"rollup": "^4.63.1",
|
|
61
63
|
"typescript": "^7.0.2"
|
|
62
64
|
},
|
|
63
65
|
"bugs": "https://github.com/brettz9/jtlt/issues",
|
package/pnpm-workspace.yaml
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
allowBuilds:
|
|
2
|
+
better-sqlite3: true
|
|
3
|
+
canvas: true
|
|
2
4
|
is-hidden-file: true
|
|
3
5
|
libxmljs: true
|
|
6
|
+
sqlite3: true
|
|
4
7
|
unrs-resolver: true
|
|
5
8
|
minimumReleaseAgeExclude:
|
|
6
9
|
- '@rollup/rollup-android-arm-eabi@4.63.0'
|
|
@@ -30,3 +33,9 @@ minimumReleaseAgeExclude:
|
|
|
30
33
|
- '@rollup/rollup-win32-x64-msvc@4.63.0'
|
|
31
34
|
- rollup@4.63.0
|
|
32
35
|
- jhtml@0.7.2 || 0.7.3
|
|
36
|
+
- eslint-config-ash-nazg@43.1.0
|
|
37
|
+
- eslint-plugin-escompat@3.12.0
|
|
38
|
+
- eslint-plugin-jsdoc@64.3.0
|
|
39
|
+
- indexeddbshim@17.4.1 || 17.4.2 || 17.4.3
|
|
40
|
+
- open@11.0.2
|
|
41
|
+
- powershell-utils@0.2.1
|
|
@@ -258,8 +258,8 @@ class AbstractJoiningTransformer {
|
|
|
258
258
|
/**
|
|
259
259
|
* Invoke a registered stylesheet function with positional arguments.
|
|
260
260
|
* @param {string} name - Function name (with namespace)
|
|
261
|
-
* @param {
|
|
262
|
-
* @returns {
|
|
261
|
+
* @param {unknown[]} args - Positional arguments
|
|
262
|
+
* @returns {unknown} Function return value
|
|
263
263
|
*/
|
|
264
264
|
invokeFunctionByArity (name, args = []) {
|
|
265
265
|
const arity = args.length;
|
|
@@ -436,7 +436,7 @@ class AbstractJoiningTransformer {
|
|
|
436
436
|
|
|
437
437
|
/**
|
|
438
438
|
* @param {string} prop - Configuration property name
|
|
439
|
-
* @param {
|
|
439
|
+
* @param {unknown} val - Configuration property value
|
|
440
440
|
* @param {(this: AbstractJoiningTransformer<T>) => void} [cb]
|
|
441
441
|
* Optional callback invoked with this instance
|
|
442
442
|
* @returns {void}
|
|
@@ -16,7 +16,7 @@ class JSONPathTransformer {
|
|
|
16
16
|
/**
|
|
17
17
|
* @template {"json"|"string"|"dom"} U
|
|
18
18
|
* @this {JSONPathTransformerContext<U>}
|
|
19
|
-
* @param {
|
|
19
|
+
* @param {unknown} value - Value
|
|
20
20
|
* @param {{mode?: string}} cfg - Configuration
|
|
21
21
|
* @returns {void}
|
|
22
22
|
*/
|
|
@@ -26,8 +26,8 @@ class JSONPathTransformer {
|
|
|
26
26
|
},
|
|
27
27
|
transformPropertyNames: {
|
|
28
28
|
/**
|
|
29
|
-
* @param {
|
|
30
|
-
* @returns {
|
|
29
|
+
* @param {unknown} value - Current context value
|
|
30
|
+
* @returns {unknown}
|
|
31
31
|
*/
|
|
32
32
|
template (value) {
|
|
33
33
|
// Emit property names for the current object context
|
|
@@ -40,7 +40,7 @@ class JSONPathTransformer {
|
|
|
40
40
|
transformObjects: {
|
|
41
41
|
/**
|
|
42
42
|
* @this {JSONPathTransformerContext}
|
|
43
|
-
* @param {
|
|
43
|
+
* @param {unknown} value - Value
|
|
44
44
|
* @param {{mode?: string}} cfg - Configuration
|
|
45
45
|
* @returns {void}
|
|
46
46
|
*/
|
|
@@ -51,7 +51,7 @@ class JSONPathTransformer {
|
|
|
51
51
|
transformArrays: {
|
|
52
52
|
/**
|
|
53
53
|
* @this {JSONPathTransformerContext}
|
|
54
|
-
* @param {
|
|
54
|
+
* @param {unknown} value - Value
|
|
55
55
|
* @param {{mode?: string}} cfg - Configuration
|
|
56
56
|
* @returns {void}
|
|
57
57
|
*/
|
|
@@ -70,8 +70,9 @@ class JSONPathTransformer {
|
|
|
70
70
|
},
|
|
71
71
|
transformFunctions: {
|
|
72
72
|
/**
|
|
73
|
-
* @param {(
|
|
74
|
-
*
|
|
73
|
+
* @param {(...args: unknown[]) => unknown} value - Function at current
|
|
74
|
+
* context
|
|
75
|
+
* @returns {unknown}
|
|
75
76
|
*/
|
|
76
77
|
template (value) {
|
|
77
78
|
// Call the function and return its result
|
|
@@ -163,9 +164,46 @@ class JSONPathTransformer {
|
|
|
163
164
|
}
|
|
164
165
|
// Set up parameter context for valueOf() access in root template
|
|
165
166
|
jte._params = {0: jte._contextObj};
|
|
167
|
+
/**
|
|
168
|
+
* The template may return a value synchronously or a Promise (e.g. from
|
|
169
|
+
* `await this.indexedDB(...)`), which is awaited unless `config.sync`.
|
|
170
|
+
* @type {any}
|
|
171
|
+
*/
|
|
166
172
|
const ret = /** @type {import('./index.js').JSONPathTemplateObject<T>} */ (
|
|
167
173
|
templateObj
|
|
168
174
|
).template.call(jte, undefined, {mode});
|
|
175
|
+
|
|
176
|
+
if (ret !== null && typeof ret !== 'undefined' &&
|
|
177
|
+
typeof ret.then === 'function') {
|
|
178
|
+
if (this._config.sync) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
'A template returned a Promise but JTLT is configured with ' +
|
|
181
|
+
'`sync: true`.'
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
return /** @type {any} */ (
|
|
185
|
+
// eslint-disable-next-line @stylistic/max-len -- Long
|
|
186
|
+
// eslint-disable-next-line promise/prefer-await-to-then -- intentional dynamic sync/async
|
|
187
|
+
ret.then((/** @type {any} */ resolvedRet) => {
|
|
188
|
+
if (typeof resolvedRet !== 'undefined') {
|
|
189
|
+
const joiner = jte._getJoiningTransformer();
|
|
190
|
+
if (typeof resolvedRet === 'string' ||
|
|
191
|
+
(typeof resolvedRet === 'object' && resolvedRet !== null &&
|
|
192
|
+
'nodeType' in resolvedRet)) {
|
|
193
|
+
joiner.append(/** @type {string|Node} */ (resolvedRet));
|
|
194
|
+
} else {
|
|
195
|
+
/** @type {import('./JSONJoiningTransformer.js').default} */ (
|
|
196
|
+
joiner
|
|
197
|
+
).append(resolvedRet);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return /** @type {import('./index.js').ResultType<T>} */ (
|
|
201
|
+
jte.getOutput()
|
|
202
|
+
);
|
|
203
|
+
})
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
169
207
|
if (typeof ret !== 'undefined') {
|
|
170
208
|
// Will vary by jte._config.outputType
|
|
171
209
|
// After the undefined check, ret is ResultType<T>
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import {JSONPath as jsonpath} from 'jsonpath-plus';
|
|
2
2
|
import JSONPathTransformer from './JSONPathTransformer.js';
|
|
3
|
+
import {maybeAsyncLoop} from './maybeAsync.js';
|
|
4
|
+
import {
|
|
5
|
+
parseIndexedDBExpression, queryIndexedDB, resolveIndexedDBQuery
|
|
6
|
+
} from './indexedDB.js';
|
|
3
7
|
|
|
4
8
|
/**
|
|
5
9
|
* @param {string} string
|
|
@@ -52,7 +56,7 @@ const escapeRegexReplacement = (string) => {
|
|
|
52
56
|
* ctx: JSONPathTransformerContext
|
|
53
57
|
* ) => number} SortComparator
|
|
54
58
|
* @typedef {string | SortObject | SortComparator |
|
|
55
|
-
* Array<string|SortObject>} SortSpec
|
|
59
|
+
* Array<string|SortObject> | null} SortSpec
|
|
56
60
|
*/
|
|
57
61
|
|
|
58
62
|
/**
|
|
@@ -88,6 +92,8 @@ const escapeRegexReplacement = (string) => {
|
|
|
88
92
|
* @property {JoiningTransformerMap[T]} joiningTransformer - Joining transformer
|
|
89
93
|
* @property {boolean} [preventEval] - Whether to prevent eval in
|
|
90
94
|
* JSONPath
|
|
95
|
+
* @property {boolean} [sync] - When true, throw if a template returns a
|
|
96
|
+
* Promise instead of awaiting it (disables `indexedDB()`)
|
|
91
97
|
* @property {(path: string) => number} [specificityPriorityResolver]
|
|
92
98
|
* Priority resolver function
|
|
93
99
|
* @property {import('./index.js').JSONPathTemplateObject<T>[]|
|
|
@@ -195,7 +201,7 @@ class JSONPathTransformerContext {
|
|
|
195
201
|
|
|
196
202
|
/**
|
|
197
203
|
* Gets the current output.
|
|
198
|
-
* @returns {
|
|
204
|
+
* @returns {unknown} The output from the joining transformer
|
|
199
205
|
*/
|
|
200
206
|
getOutput () {
|
|
201
207
|
return this._getJoiningTransformer().get();
|
|
@@ -221,7 +227,7 @@ class JSONPathTransformerContext {
|
|
|
221
227
|
}
|
|
222
228
|
|
|
223
229
|
/**
|
|
224
|
-
* @param {
|
|
230
|
+
* @param {unknown} v - Value to set
|
|
225
231
|
* @returns {this}
|
|
226
232
|
*/
|
|
227
233
|
set (v) {
|
|
@@ -403,7 +409,7 @@ class JSONPathTransformerContext {
|
|
|
403
409
|
const prevCurrPath = that._currPath;
|
|
404
410
|
|
|
405
411
|
// Process in (sorted) order
|
|
406
|
-
|
|
412
|
+
const loopResult = maybeAsyncLoop(matches, (o) => {
|
|
407
413
|
const {value, parent, parentProperty, path} = o;
|
|
408
414
|
const _oldPath = that._currPath;
|
|
409
415
|
that._currPath += path.replace(/^\$/v, '');
|
|
@@ -454,17 +460,19 @@ class JSONPathTransformerContext {
|
|
|
454
460
|
}
|
|
455
461
|
if (onNoMatch === 'deep-skip') {
|
|
456
462
|
// Skip this node and its descendants entirely
|
|
457
|
-
|
|
463
|
+
return;
|
|
458
464
|
}
|
|
459
465
|
if (onNoMatch === 'shallow-copy') {
|
|
460
466
|
// Output the value as-is without processing children
|
|
461
467
|
joiner.append(value);
|
|
462
|
-
|
|
468
|
+
that._currPath = _oldPath;
|
|
469
|
+
return;
|
|
463
470
|
}
|
|
464
471
|
if (onNoMatch === 'deep-copy') {
|
|
465
472
|
// Output the value and all descendants as-is
|
|
466
473
|
joiner.append(JSON.stringify(value));
|
|
467
|
-
|
|
474
|
+
that._currPath = _oldPath;
|
|
475
|
+
return;
|
|
468
476
|
}
|
|
469
477
|
if (onNoMatch === 'text-only-copy') {
|
|
470
478
|
// Output only text content (primitives)
|
|
@@ -472,7 +480,8 @@ class JSONPathTransformerContext {
|
|
|
472
480
|
typeof value === 'boolean') {
|
|
473
481
|
joiner.append(String(value));
|
|
474
482
|
}
|
|
475
|
-
|
|
483
|
+
that._currPath = _oldPath;
|
|
484
|
+
return;
|
|
476
485
|
}
|
|
477
486
|
// 'apply-templates', 'shallow-skip', or other:
|
|
478
487
|
// use default template rules
|
|
@@ -573,6 +582,11 @@ class JSONPathTransformerContext {
|
|
|
573
582
|
const prevTemplateParams = that._params;
|
|
574
583
|
that._params = {0: value};
|
|
575
584
|
|
|
585
|
+
/**
|
|
586
|
+
* The template may return synchronously or return a Promise (e.g. from
|
|
587
|
+
* `await this.indexedDB(...)`), which is awaited unless `config.sync`.
|
|
588
|
+
* @type {any}
|
|
589
|
+
*/
|
|
576
590
|
const ret =
|
|
577
591
|
/** @type {import('./index.js').JSONPathTemplateObject<T>} */ (
|
|
578
592
|
templateObj
|
|
@@ -582,6 +596,42 @@ class JSONPathTransformerContext {
|
|
|
582
596
|
|
|
583
597
|
// Restore previous parameter context
|
|
584
598
|
that._params = prevTemplateParams;
|
|
599
|
+
if (ret !== null && typeof ret !== 'undefined' &&
|
|
600
|
+
typeof ret.then === 'function') {
|
|
601
|
+
if (that._config.sync) {
|
|
602
|
+
throw new Error(
|
|
603
|
+
'A template returned a Promise but JTLT is configured with ' +
|
|
604
|
+
'`sync: true`.'
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
// The loop body deliberately mixes value and no-value returns so
|
|
608
|
+
// `maybeAsyncLoop` can stay synchronous unless a template awaits.
|
|
609
|
+
// eslint-disable-next-line @stylistic/max-len -- Long
|
|
610
|
+
// eslint-disable-next-line promise/prefer-await-to-then, consistent-return -- intentional dynamic sync/async
|
|
611
|
+
return ret.then((/** @type {any} */ resolvedRet) => {
|
|
612
|
+
that._params = prevTemplateParams;
|
|
613
|
+
if (typeof resolvedRet !== 'undefined') {
|
|
614
|
+
const joiner = that._getJoiningTransformer();
|
|
615
|
+
/* c8 ignore start -- _openTagState only on
|
|
616
|
+
StringJoiningTransformer; string-output defensive check */
|
|
617
|
+
// @ts-expect-error -- _openTagState: StringJoiningTransformer only
|
|
618
|
+
if (joiner._openTagState) {
|
|
619
|
+
joiner.append('>');
|
|
620
|
+
// @ts-expect-error -- _openTagState: StringJoiningTransformer
|
|
621
|
+
joiner._openTagState = false;
|
|
622
|
+
}
|
|
623
|
+
/* c8 ignore stop */
|
|
624
|
+
joiner.append(resolvedRet);
|
|
625
|
+
}
|
|
626
|
+
that._parent = parent;
|
|
627
|
+
// Matches reached here always carry a parentProperty; the sync
|
|
628
|
+
// path covers the root-node fallback.
|
|
629
|
+
/* c8 ignore next */
|
|
630
|
+
that._parentProperty = (parentProperty ?? that._parentProperty);
|
|
631
|
+
that._currPath = _oldPath;
|
|
632
|
+
return undefined;
|
|
633
|
+
});
|
|
634
|
+
}
|
|
585
635
|
if (typeof ret !== 'undefined') {
|
|
586
636
|
// After the undefined check, ret is ResultType<T>
|
|
587
637
|
const joiner = that._getJoiningTransformer();
|
|
@@ -604,12 +654,27 @@ class JSONPathTransformerContext {
|
|
|
604
654
|
that._parent = parent;
|
|
605
655
|
that._parentProperty = (parentProperty ?? that._parentProperty);
|
|
606
656
|
that._currPath = _oldPath;
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
if (typeof loopResult?.then === 'function') {
|
|
660
|
+
// Returns a Promise<this> when a nested template ran asynchronously.
|
|
661
|
+
return /** @type {any} */ (
|
|
662
|
+
// eslint-disable-next-line @stylistic/max-len -- Long
|
|
663
|
+
// eslint-disable-next-line promise/prefer-await-to-then -- intentional dynamic sync/async
|
|
664
|
+
loopResult.then(() => {
|
|
665
|
+
this._contextObj = prevContext;
|
|
666
|
+
this._parent = prevParent;
|
|
667
|
+
this._parentProperty = prevParentProp;
|
|
668
|
+
this._currPath = prevCurrPath;
|
|
669
|
+
return this;
|
|
670
|
+
})
|
|
671
|
+
);
|
|
607
672
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
673
|
+
|
|
674
|
+
this._contextObj = prevContext;
|
|
675
|
+
this._parent = prevParent;
|
|
676
|
+
this._parentProperty = prevParentProp;
|
|
677
|
+
this._currPath = prevCurrPath;
|
|
613
678
|
return this;
|
|
614
679
|
}
|
|
615
680
|
|
|
@@ -819,9 +884,12 @@ class JSONPathTransformerContext {
|
|
|
819
884
|
* expression matches
|
|
820
885
|
* @param {string} [options.groupEndingWith] - Ends group when expression
|
|
821
886
|
* matches
|
|
822
|
-
* @param {
|
|
887
|
+
* @param {SortSpec<V>} [options.sort] - Sort specification (same as forEach)
|
|
823
888
|
* @param {(
|
|
824
|
-
* this: JSONPathTransformerContext<T>,
|
|
889
|
+
* this: JSONPathTransformerContext<T>,
|
|
890
|
+
* key: unknown,
|
|
891
|
+
* items: unknown[],
|
|
892
|
+
* ctx: JSONPathTransformerContext<T>
|
|
825
893
|
* ) => void} cb - Callback receives (groupingKey, groupItems, context)
|
|
826
894
|
* @returns {this}
|
|
827
895
|
*/
|
|
@@ -1135,7 +1203,7 @@ class JSONPathTransformerContext {
|
|
|
1135
1203
|
|
|
1136
1204
|
/**
|
|
1137
1205
|
* Returns the current group (for use within forEachGroup callback).
|
|
1138
|
-
* @returns {
|
|
1206
|
+
* @returns {unknown[]|undefined}
|
|
1139
1207
|
*/
|
|
1140
1208
|
currentGroup () {
|
|
1141
1209
|
return /** @type {any} */ (this)._currentGroup;
|
|
@@ -1143,12 +1211,48 @@ class JSONPathTransformerContext {
|
|
|
1143
1211
|
|
|
1144
1212
|
/**
|
|
1145
1213
|
* Returns the current grouping key (for use within forEachGroup callback).
|
|
1146
|
-
* @returns {
|
|
1214
|
+
* @returns {unknown}
|
|
1147
1215
|
*/
|
|
1148
1216
|
currentGroupingKey () {
|
|
1149
1217
|
return /** @type {any} */ (this)._currentGroupingKey;
|
|
1150
1218
|
}
|
|
1151
1219
|
|
|
1220
|
+
/**
|
|
1221
|
+
* Directly query IndexedDB from within a template, e.g.
|
|
1222
|
+
* `await this.indexedDB('myDB', 'myStore', {index: 'byAge'})`.
|
|
1223
|
+
*
|
|
1224
|
+
* Since IndexedDB access is asynchronous, this is unavailable when JTLT is
|
|
1225
|
+
* configured with `sync: true`.
|
|
1226
|
+
* @param {string} dbName - Database name
|
|
1227
|
+
* @param {string} storeName - Object store name
|
|
1228
|
+
* @param {import('./indexedDB.js').QueryOptions} [options] - Query options
|
|
1229
|
+
* @returns {Promise<unknown[]>} The matching records
|
|
1230
|
+
*/
|
|
1231
|
+
indexedDB (dbName, storeName, options) {
|
|
1232
|
+
if (this._config.sync) {
|
|
1233
|
+
throw new Error(
|
|
1234
|
+
'The `indexedDB()` API is unavailable when JTLT is configured with ' +
|
|
1235
|
+
'`sync: true`.'
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1238
|
+
return queryIndexedDB(dbName, storeName, options);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/**
|
|
1242
|
+
* Await a parsed `indexedDB(...)` expression and append its (stringified)
|
|
1243
|
+
* value to the output. Used by {@link valueOf}. Callers reject `config.sync`.
|
|
1244
|
+
* @param {import('./indexedDB.js').ParsedIndexedDBExpression} parsed
|
|
1245
|
+
* @param {any} results - The joining transformer
|
|
1246
|
+
* @returns {Promise<this>}
|
|
1247
|
+
*/
|
|
1248
|
+
async _appendIndexedDBValue (parsed, results) {
|
|
1249
|
+
const value = await resolveIndexedDBQuery(parsed, {
|
|
1250
|
+
preventEval: this._config.preventEval
|
|
1251
|
+
});
|
|
1252
|
+
results.text(String(value));
|
|
1253
|
+
return this;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1152
1256
|
/**
|
|
1153
1257
|
* @param {string|object} [select] - JSONPath selector
|
|
1154
1258
|
* @returns {this}
|
|
@@ -1159,6 +1263,26 @@ class JSONPathTransformerContext {
|
|
|
1159
1263
|
const results = this._getJoiningTransformer();
|
|
1160
1264
|
let result;
|
|
1161
1265
|
|
|
1266
|
+
const selectString = select && typeof select === 'object'
|
|
1267
|
+
? /** @type {{select?: string}} */ (select).select
|
|
1268
|
+
: select;
|
|
1269
|
+
|
|
1270
|
+
// Intercept `indexedDB('db', 'store')<trailing JSONPath>` expressions and
|
|
1271
|
+
// resolve them asynchronously before appending. Returns a Promise so
|
|
1272
|
+
// templates can `await this.valueOf(...)`.
|
|
1273
|
+
if (typeof selectString === 'string') {
|
|
1274
|
+
const parsed = parseIndexedDBExpression(selectString);
|
|
1275
|
+
if (parsed) {
|
|
1276
|
+
if (this._config.sync) {
|
|
1277
|
+
throw new Error(
|
|
1278
|
+
'The `indexedDB()` function is unavailable when JTLT is ' +
|
|
1279
|
+
'configured with `sync: true`.'
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
return /** @type {any} */ (this._appendIndexedDBValue(parsed, results));
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1162
1286
|
if (select && typeof select === 'object' &&
|
|
1163
1287
|
/** @type {{select?: string}} */ (select).select === '.') {
|
|
1164
1288
|
result = this._contextObj;
|
|
@@ -2079,8 +2203,8 @@ class JSONPathTransformerContext {
|
|
|
2079
2203
|
/**
|
|
2080
2204
|
* Invoke a registered stylesheet function with positional arguments.
|
|
2081
2205
|
* @param {string} name - Function name (with namespace)
|
|
2082
|
-
* @param {
|
|
2083
|
-
* @returns {
|
|
2206
|
+
* @param {unknown[]} args - Positional arguments
|
|
2207
|
+
* @returns {unknown} Function return value
|
|
2084
2208
|
*/
|
|
2085
2209
|
invokeFunctionByArity (name, args = []) {
|
|
2086
2210
|
return this._getJoiningTransformer().invokeFunctionByArity(name, args);
|
|
@@ -2233,12 +2357,23 @@ class JSONPathTransformerContext {
|
|
|
2233
2357
|
|
|
2234
2358
|
/**
|
|
2235
2359
|
* @param {string} name - Key name
|
|
2236
|
-
* @param {
|
|
2237
|
-
* @returns {
|
|
2360
|
+
* @param {unknown} value - Value to match
|
|
2361
|
+
* @returns {unknown}
|
|
2238
2362
|
*/
|
|
2239
2363
|
getKey (name, value) {
|
|
2240
2364
|
const key = this.keys[name];
|
|
2241
|
-
|
|
2365
|
+
// Keys are document-global (like `xsl:key`): resolve `match` against the
|
|
2366
|
+
// root, not the current context (which may be a `forEach`/`applyTemplates`
|
|
2367
|
+
// item).
|
|
2368
|
+
const matches = /** @type {any[]} */ (
|
|
2369
|
+
(/** @type {any} */ (jsonpath))({
|
|
2370
|
+
path: JSONPathTransformer.makeJSONPathAbsolute(key.match),
|
|
2371
|
+
json: this._origObj,
|
|
2372
|
+
preventEval: this._config.preventEval,
|
|
2373
|
+
wrap: true,
|
|
2374
|
+
returnType: 'value'
|
|
2375
|
+
})
|
|
2376
|
+
);
|
|
2242
2377
|
for (const match of matches) { // For objects or arrays
|
|
2243
2378
|
if (match && typeof match === 'object' &&
|
|
2244
2379
|
match[key.use] === value) {
|
package/src/XPathTransformer.js
CHANGED
|
@@ -7,6 +7,8 @@ import XPathTransformerContext from './XPathTransformerContext.js';
|
|
|
7
7
|
* @property {import('./index.js').
|
|
8
8
|
* XPathTemplateArray<T>} templates Template objects
|
|
9
9
|
* @property {number} [xpathVersion] XPath version (1|2|3.1)
|
|
10
|
+
* @property {boolean} [sync] When true, throw if a template returns a Promise
|
|
11
|
+
* instead of awaiting it (disables `indexedDB()` and other async features)
|
|
10
12
|
*/
|
|
11
13
|
|
|
12
14
|
/**
|
|
@@ -21,7 +23,7 @@ class XPathTransformer {
|
|
|
21
23
|
static DefaultTemplateRules = {
|
|
22
24
|
transformRoot: {
|
|
23
25
|
/**
|
|
24
|
-
* @param {
|
|
26
|
+
* @param {unknown} node Node
|
|
25
27
|
* @param {{mode:string}} cfg Config
|
|
26
28
|
* @returns {void}
|
|
27
29
|
*/
|
|
@@ -106,7 +108,37 @@ class XPathTransformer {
|
|
|
106
108
|
}
|
|
107
109
|
// Set up parameter context for valueOf() access in root template
|
|
108
110
|
xte._params = {0: xte._contextNode};
|
|
111
|
+
/**
|
|
112
|
+
* The template may return synchronously or return a Promise (e.g. from
|
|
113
|
+
* `await this.indexedDB(...)`), which is awaited unless `config.sync`.
|
|
114
|
+
* @type {any}
|
|
115
|
+
*/
|
|
109
116
|
const ret = templateObj.template.call(xte, undefined, {mode});
|
|
117
|
+
|
|
118
|
+
if (ret !== null && typeof ret !== 'undefined' &&
|
|
119
|
+
typeof ret.then === 'function') {
|
|
120
|
+
if (this._config.sync) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
'A template returned a Promise but JTLT is configured with ' +
|
|
123
|
+
'`sync: true`.'
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
return /** @type {any} */ (
|
|
127
|
+
// eslint-disable-next-line @stylistic/max-len -- Long
|
|
128
|
+
// eslint-disable-next-line promise/prefer-await-to-then -- intentional dynamic sync/async
|
|
129
|
+
ret.then((/** @type {any} */ resolvedRet) => {
|
|
130
|
+
if (typeof resolvedRet !== 'undefined') {
|
|
131
|
+
/** @type {any} */ (xte)._getJoiningTransformer().append(
|
|
132
|
+
resolvedRet
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return /** @type {import('./index.js').ResultType<T>} */ (
|
|
136
|
+
xte.getOutput()
|
|
137
|
+
);
|
|
138
|
+
})
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
110
142
|
if (typeof ret !== 'undefined') {
|
|
111
143
|
/** @type {any} */ (xte)._getJoiningTransformer().append(ret);
|
|
112
144
|
}
|