jtlt 0.2.0 → 0.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.
Files changed (50) hide show
  1. package/CHANGES.md +18 -0
  2. package/README.md +16 -87
  3. package/demo/calltemplate-params-demo.js +138 -0
  4. package/demo/index.html +31 -0
  5. package/demo/index.js +30 -0
  6. package/demo/xpath2-placeholder.js +1 -0
  7. package/dist/AbstractJoiningTransformer.d.ts +83 -9
  8. package/dist/AbstractJoiningTransformer.d.ts.map +1 -1
  9. package/dist/DOMJoiningTransformer.d.ts +85 -25
  10. package/dist/DOMJoiningTransformer.d.ts.map +1 -1
  11. package/dist/JSONJoiningTransformer.d.ts +159 -51
  12. package/dist/JSONJoiningTransformer.d.ts.map +1 -1
  13. package/dist/JSONPathTransformer.d.ts +37 -38
  14. package/dist/JSONPathTransformer.d.ts.map +1 -1
  15. package/dist/JSONPathTransformerContext.d.ts +247 -121
  16. package/dist/JSONPathTransformerContext.d.ts.map +1 -1
  17. package/dist/StringJoiningTransformer.d.ts +132 -41
  18. package/dist/StringJoiningTransformer.d.ts.map +1 -1
  19. package/dist/XPathTransformer.d.ts +35 -20
  20. package/dist/XPathTransformer.d.ts.map +1 -1
  21. package/dist/XPathTransformerContext.d.ts +191 -99
  22. package/dist/XPathTransformerContext.d.ts.map +1 -1
  23. package/dist/index-browser.d.ts +4 -0
  24. package/dist/index-browser.d.ts.map +1 -0
  25. package/dist/index-node.d.ts +4 -0
  26. package/dist/index-node.d.ts.map +1 -0
  27. package/dist/index.d.ts +330 -57
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/types.d.ts +204 -0
  30. package/dist/types.d.ts.map +1 -0
  31. package/docs/API.expanded.md +167 -2
  32. package/docs/API.md +91 -1
  33. package/docs/TO-DO.md +144 -0
  34. package/docs/calltemplate-params.md +251 -0
  35. package/eslint.config.js +9 -5
  36. package/package.json +13 -7
  37. package/pnpm-workspace.yaml +1 -0
  38. package/src/AbstractJoiningTransformer.js +54 -15
  39. package/src/DOMJoiningTransformer.js +275 -28
  40. package/src/JSONJoiningTransformer.js +351 -70
  41. package/src/JSONPathTransformer.js +48 -30
  42. package/src/JSONPathTransformerContext.js +308 -104
  43. package/src/StringJoiningTransformer.js +311 -57
  44. package/src/XPathTransformer.js +27 -12
  45. package/src/XPathTransformerContext.js +467 -89
  46. package/src/index-browser.js +5 -0
  47. package/src/index-node.js +7 -0
  48. package/src/index.js +498 -97
  49. package/typings/xpath2-js.d.ts +40 -1
  50. package/src/types/xpath2-js.d.ts +0 -2
package/src/index.js CHANGED
@@ -1,10 +1,4 @@
1
1
  import {getJSON} from 'simple-get-json';
2
- // import JHTML from 'jhtml';
3
- // import jsonpath from 'jsonpath-plus';
4
- // import {jml} from 'jamilih';
5
- import {JSDOM} from 'jsdom';
6
-
7
- // import Stringifier from 'jhtml/SAJJ/SAJJ.Stringifier.js';
8
2
 
9
3
  import DOMJoiningTransformer from './DOMJoiningTransformer.js';
10
4
  import JSONJoiningTransformer from './JSONJoiningTransformer.js';
@@ -13,18 +7,105 @@ import XPathTransformer from './XPathTransformer.js';
13
7
  import StringJoiningTransformer from './StringJoiningTransformer.js';
14
8
  import XSLTStyleJSONPathResolver from './XSLTStyleJSONPathResolver.js';
15
9
 
10
+ /** @type {import('jsdom').DOMWindow | typeof globalThis} */
11
+ let _win;
12
+ /**
13
+ * @param {import('jsdom').DOMWindow | typeof globalThis} win
14
+ */
15
+ export const setWindow = (win) => {
16
+ _win = win;
17
+ };
18
+
19
+ /**
20
+ * Internal options extension adding private runtime state flags.
21
+ * Not part of the public API surface but used for narrowing casts.
22
+ * @typedef {JTLTOptions & {
23
+ * _customJoiningTransformer?: boolean
24
+ * }} InternalJTLTOptions
25
+ */
26
+
27
+ /**
28
+ * A template declaration whose `template` executes with `this` bound
29
+ * to the engine-specific context type `TCtx`.
30
+ * Either `path` must be provided (for pattern matching), or `name` must be
31
+ * provided (for named templates callable via callTemplate), or both.
32
+ * @template T
33
+ * @template U
34
+ * @template TCtx
35
+ * @typedef {object} TemplateObject
36
+ * @property {string} [path] - JSONPath or XPath selector for matching nodes
37
+ * @property {string} [name] - Optional name for calling via callTemplate
38
+ * @property {string} [mode] - Optional mode for template matching
39
+ * @property {number} [priority] - Priority for template selection
40
+ * @property {TemplateFunction<T, U, TCtx>} template - Template function
41
+ */
42
+
43
+ /**
44
+ * A callable template function with an engine-specific `this`.
45
+ * @template T
46
+ * @template U
47
+ * @template TCtx
48
+ * @typedef {(this: TCtx,
49
+ * value: ResultType<U>,
50
+ * cfg?: {mode?: string}
51
+ * ) => ResultType<T>|void} TemplateFunction
52
+ */
53
+
54
+ /**
55
+ * @template T
56
+ * @typedef {TemplateObject<T, "json",
57
+ * import('./JSONPathTransformerContext.js').default<T>
58
+ * >} JSONPathTemplateObject
59
+ */
60
+ /**
61
+ * @template T
62
+ * @typedef {TemplateObject<T, "dom",
63
+ * import('./XPathTransformerContext.js').default
64
+ * >} XPathTemplateObject
65
+ */
66
+ /**
67
+ * @template T
68
+ * @typedef {(XPathTemplateObject<T> | [string, TemplateFunction<T, "dom",
69
+ * import('./XPathTransformerContext.js').default
70
+ * >])[]} XPathTemplateArray
71
+ */
72
+ /**
73
+ * @template T
74
+ * @typedef {JSONPathTemplateObject<T> | [string, TemplateFunction<T, "json",
75
+ * import('./JSONPathTransformerContext.js').default
76
+ * >]} JSONPathTemplateArray
77
+ */
78
+
79
+ /**
80
+ * @typedef {(
81
+ * StringJoiningTransformer|
82
+ * DOMJoiningTransformer|
83
+ * JSONJoiningTransformer
84
+ * )} JoiningTransformer
85
+ */
86
+
87
+ /**
88
+ * @typedef {"json"|"string"|"dom"} joiningTypes
89
+ */
90
+
16
91
  /**
17
- * @typedef {object} JTLTOptions
18
- * @property {Function} success A callback supplied with a single
19
- * argument that is the result of this instance's transform() method.
20
- * @property {any[]} [templates] An array of template objects
21
- * @property {object|Function} [template] A function assumed to be a
22
- * root template or a single, complete template object
23
- * @property {Function} [query] A function assumed to be a root template
24
- * @property {any[]} [forQuery] An array with arguments to be supplied
25
- * to a single call to `forEach` (and which will serve as the root
26
- * template)
27
- * @property {any} [data] A JSON object
92
+ * @template T
93
+ * @typedef {T extends "json" ? unknown : T extends "string" ? string :
94
+ * DocumentFragment|Element} ResultType
95
+ */
96
+
97
+ /**
98
+ * Options common to both engines.
99
+ * @template T
100
+ * @typedef {object} BaseJTLTOptions
101
+ * @property {(
102
+ * result: ResultType<T>
103
+ * ) => void} success A callback supplied
104
+ * with a single argument that is the result of this instance's
105
+ * transform() method. When used in TypeScript, this can be made
106
+ * generic as `success<T>(result: T): void`.
107
+ * @property {null|boolean|number|string|object} [data] A JSON
108
+ * object or DOM document (XPath)
28
109
  * @property {string} [ajaxData] URL of a JSON file to retrieve for
29
110
  * evaluation
30
111
  * @property {boolean} [errorOnEqualPriority] Whether or not to
@@ -36,30 +117,77 @@ import XSLTStyleJSONPathResolver from './XSLTStyleJSONPathResolver.js';
36
117
  * input, but reduces capabilities of JSONPath.
37
118
  * @property {boolean} [unwrapSingleResult] For JSON output, whether to
38
119
  * unwrap single-element root arrays to return just the element
120
+ * @property {boolean} [exposeDocuments] When true, joiners return an array
121
+ * of complete documents: XMLDocument[] for DOM, document wrapper objects[]
122
+ * for JSON, and string[] for string joiners. Each array element corresponds
123
+ * to a root element built during transformation.
39
124
  * @property {string} [mode] The mode in which to begin the transform.
40
- * @property {string} [outputType] Output type: 'string', 'dom', or 'json'
41
- * @property {Function} [engine] Will be based the
125
+ * @property {(opts: JTLTOptions &
126
+ * Required<Pick<JTLTOptions, "joiningTransformer">>
127
+ * ) => ResultType<T>} [engine] Will be based on the
42
128
  * same config as passed to this instance. Defaults to a transforming
43
129
  * function based on JSONPath and with its own set of priorities for
44
130
  * processing templates.
45
- * @property {'jsonpath'|'xpath'} [engineType] Choose built-in engine.
46
- * Defaults to 'jsonpath'. When 'xpath', `xpathVersion` may be used.
47
- * @property {1|2} [xpathVersion] XPath engine version: 1 (native) or 2
48
- * (xpath2.js) when `engineType` is 'xpath'.
49
- * @property {Function} [specificityPriorityResolver]
131
+ * @property {(path: string) => 0 | 0.5 | -0.5} [specificityPriorityResolver]
50
132
  * Callback for getting the priority by specificity
51
- * @property {{get: Function, append: Function, string?: Function,
52
- * object?: Function, array?: Function}} [joiningTransformer] Can
53
- * be a singleton or class instance. Defaults to string joining for output
54
- * transformation.
55
- * @property {object} [joiningConfig] Config to pass on to the joining
56
- * transformer
57
- * @property {any} [parent] Parent object for context
133
+ * @property {JoiningTransformer} [joiningTransformer]
134
+ * A concrete joining transformer instance (or custom subclass) responsible
135
+ * for accumulating output. When omitted, one is created automatically based
136
+ * on `outputType`.
137
+ * @property {import('./AbstractJoiningTransformer.js').
138
+ * JoiningTransformerConfig<T>} [joiningConfig] Config for the joining
139
+ * transformer.
140
+ * @property {object} [parent] Parent object for context
58
141
  * @property {string} [parentProperty] Parent property name for context
59
142
  */
60
143
 
61
- const {window} = new JSDOM();
62
- const {document} = window;
144
+ /**
145
+ * JSONPath engine options with context-aware template typing.
146
+ * @template [T = "json"]
147
+ * @typedef {BaseJTLTOptions<T> & {
148
+ * templates?: JSONPathTemplateArray<T>[],
149
+ * template?: JSONPathTemplateObject<T> | TemplateFunction<T, "json",
150
+ * import('./JSONPathTransformerContext.js').default
151
+ * >,
152
+ * query?: TemplateFunction<T, "json",
153
+ * import('./JSONPathTransformerContext.js').default
154
+ * >,
155
+ * forQuery?: [string, TemplateFunction<T, "json",
156
+ * import('./XPathTransformerContext.js').default
157
+ * >],
158
+ * engineType?: 'jsonpath',
159
+ * outputType?: T
160
+ * }} JSONPathJTLTOptions
161
+ */
162
+
163
+ /**
164
+ * XPath engine options with context-aware template typing.
165
+ * @template T
166
+ * @typedef {BaseJTLTOptions<T> & {
167
+ * templates?: XPathTemplateArray<T>,
168
+ * template?: XPathTemplateObject<T> | TemplateFunction<T, "dom",
169
+ * import('./XPathTransformerContext.js').default
170
+ * >,
171
+ * query?: TemplateFunction<T, "dom",
172
+ * import('./XPathTransformerContext.js').default
173
+ * >,
174
+ * forQuery?: [string, TemplateFunction<T, "dom",
175
+ * import('./JSONPathTransformerContext.js').default
176
+ * >],
177
+ * engineType: 'xpath',
178
+ * xpathVersion?: 1|2,
179
+ * outputType?: 'string'|'dom'|'json'
180
+ * }} XPathJTLTOptions
181
+ */
182
+
183
+ /**
184
+ * @typedef {JSONPathJTLTOptions |
185
+ * JSONPathJTLTOptions<"string"> |
186
+ * JSONPathJTLTOptions<"dom"> |
187
+ * XPathJTLTOptions<"json">|
188
+ * XPathJTLTOptions<"string">|
189
+ * XPathJTLTOptions<"dom">} JTLTOptions
190
+ */
63
191
 
64
192
  /**
65
193
  * High-level façade for running a JTLT transform.
@@ -75,6 +203,30 @@ class JTLT {
75
203
  * config.template, or config.templates, but one must be
76
204
  * present and of valid type. For the source json, one must use
77
205
  * either a valid config.ajaxData or config.data parameter.
206
+ * @overload
207
+ * @param {JSONPathJTLTOptions} config Options for JSONPath engine
208
+ */
209
+ /**
210
+ * @overload
211
+ * @param {JSONPathJTLTOptions<"string">} config Options for JSONPath engine
212
+ */
213
+ /**
214
+ * @overload
215
+ * @param {JSONPathJTLTOptions<"dom">} config Options for JSONPath engine
216
+ */
217
+ /**
218
+ * @overload
219
+ * @param {XPathJTLTOptions<"json">} config Options for XPath engine
220
+ */
221
+ /**
222
+ * @overload
223
+ * @param {XPathJTLTOptions<"string">} config Options for XPath engine
224
+ */
225
+ /**
226
+ * @overload
227
+ * @param {XPathJTLTOptions<"dom">} config Options for XPath engine
228
+ */
229
+ /**
78
230
  * @param {JTLTOptions} config Options
79
231
  * @todo Remove JSONPath dependency in query use of '$'?
80
232
  */
@@ -83,7 +235,10 @@ class JTLT {
83
235
  this.config = config || {};
84
236
 
85
237
  // Track if a custom joiner was provided
86
- /** @type {any} */ (this.config)._customJoiningTransformer =
238
+ /**
239
+ * @type {InternalJTLTOptions}
240
+ */
241
+ (this.config)._customJoiningTransformer =
87
242
  Boolean(this.config.joiningTransformer);
88
243
 
89
244
  this.setDefaults(config);
@@ -94,7 +249,7 @@ class JTLT {
94
249
  getJSON(this.config.ajaxData, (function (cfg) {
95
250
  return function (json) {
96
251
  that.config.data = json;
97
- that._autoStart(/** @type {any} */ (cfg).mode);
252
+ that._autoStart(cfg.mode);
98
253
  };
99
254
  }(config)));
100
255
  return;
@@ -102,7 +257,7 @@ class JTLT {
102
257
  if (this.config.data === undefined) {
103
258
  throw new Error('You must supply either config.ajaxData or config.data');
104
259
  }
105
- this._autoStart(/** @type {any} */ (config).mode);
260
+ this._autoStart(config.mode);
106
261
  }
107
262
 
108
263
  /**
@@ -110,62 +265,78 @@ class JTLT {
110
265
  * StringJoiningTransformer}
111
266
  */
112
267
  _createJoiningTransformer () {
113
- let JT;
114
- switch (this.config.outputType) {
115
- case 'dom':
116
- JT = DOMJoiningTransformer;
117
- break;
118
- case 'json':
119
- JT = JSONJoiningTransformer;
120
- break;
121
- case 'string': default:
122
- JT = StringJoiningTransformer;
123
- break;
124
- }
125
-
126
- /** @type {any} */
127
- let initial;
128
-
129
268
  // Derive a document to use for joiners when running XPath engine
130
269
  /** @type {Document|undefined} */
131
270
  let docForJoiner;
132
- if ((/** @type {any} */ (this.config)).engineType === 'xpath') {
133
- const {data} = /** @type {any} */ (this.config);
271
+ if (this.config.engineType === 'xpath') {
272
+ const {data} = this.config;
134
273
  if (data && typeof data === 'object') {
274
+ const dataNode = /** @type {Document|Element} */ (data);
135
275
  // Document
136
- if (data.nodeType === 9) {
137
- docForJoiner = data;
276
+ if ((/** @type {Document} */ (dataNode)).nodeType === 9) {
277
+ docForJoiner = /** @type {Document} */ (dataNode);
138
278
  // Element or Node with ownerDocument
139
- } else if (data.ownerDocument) {
140
- docForJoiner = data.ownerDocument;
279
+ } else if ((/** @type {Element} */ (dataNode)).ownerDocument) {
280
+ docForJoiner = (/** @type {Element} */ (dataNode)).ownerDocument;
141
281
  }
142
282
  }
143
283
  }
144
- if (JT === StringJoiningTransformer) {
145
- initial = '';
146
- } else if (JT === DOMJoiningTransformer) {
147
- initial = (docForJoiner || document).createDocumentFragment();
148
- } else {
149
- initial = [];
150
- }
151
284
 
152
- // Build config for joining transformer
153
- const joiningConfig = this.config.joiningConfig || {
154
- string: {}, json: {}, dom: {}, jamilih: {},
155
- document: docForJoiner || document
156
- };
285
+ // Build config, supporting both direct config or nested structure
286
+ const baseConfig = this.config.joiningConfig || {};
157
287
 
158
- // Pass unwrapSingleResult to JSON joiner if configured
159
- if (JT === JSONJoiningTransformer &&
160
- /** @type {any} */ (this.config).unwrapSingleResult) {
161
- /** @type {any} */ (joiningConfig).unwrapSingleResult = true;
288
+ switch (this.config.outputType) {
289
+ case 'dom': {
290
+ /**
291
+ * @type {import('./AbstractJoiningTransformer.js').
292
+ * DOMJoiningTransformerConfig}
293
+ */
294
+ const domConfig = /** @type {typeof domConfig} */ ({
295
+ ...baseConfig,
296
+ document: docForJoiner || _win.document
297
+ });
298
+ if (this.config.exposeDocuments) {
299
+ domConfig.exposeDocuments = true;
300
+ }
301
+ const initial = (docForJoiner || _win.document).createDocumentFragment();
302
+ return new DOMJoiningTransformer(initial, domConfig);
303
+ }
304
+ case 'json': {
305
+ /**
306
+ * @type {import('./AbstractJoiningTransformer.js').
307
+ * JSONJoiningTransformerConfig}
308
+ */
309
+ const jsonConfig = /** @type {typeof jsonConfig} */ ({
310
+ ...baseConfig
311
+ });
312
+ // Pass unwrapSingleResult to JSON joiner if configured
313
+ if (this.config.unwrapSingleResult) {
314
+ jsonConfig.unwrapSingleResult = true;
315
+ }
316
+ // Pass exposeDocuments to JSON joiner if configured
317
+ if (this.config.exposeDocuments) {
318
+ jsonConfig.exposeDocuments = true;
319
+ }
320
+ return new JSONJoiningTransformer([], jsonConfig);
321
+ }
322
+ case 'string': default: {
323
+ /**
324
+ * @type {import('./AbstractJoiningTransformer.js').
325
+ * StringJoiningTransformerConfig}
326
+ */
327
+ const stringConfig = /** @type {typeof stringConfig} */ ({
328
+ ...baseConfig
329
+ });
330
+ if (this.config.exposeDocuments) {
331
+ stringConfig.exposeDocuments = true;
332
+ }
333
+ return new StringJoiningTransformer('', stringConfig);
334
+ }
162
335
  }
163
-
164
- return new JT(initial, joiningConfig);
165
336
  }
166
337
 
167
338
  /**
168
- * @param {string} mode
339
+ * @param {string|undefined} mode
169
340
  * @returns {void}
170
341
  */
171
342
  _autoStart (mode) {
@@ -177,7 +348,7 @@ class JTLT {
177
348
  return;
178
349
  }
179
350
 
180
- this.transform(mode);
351
+ this.transform(/** @type {string} */ (mode));
181
352
  }
182
353
 
183
354
  /**
@@ -191,11 +362,25 @@ class JTLT {
191
362
  const query = cfg.forQuery
192
363
  // eslint-disable-next-line @stylistic/operator-linebreak -- TS
193
364
  ? /**
194
- * @this {any}
195
- * @returns {void}
196
- */
365
+ * @this {import('./JSONPathTransformerContext.js').default |
366
+ * import('./XPathTransformerContext.js').default}
367
+ * @returns {void}
368
+ */
197
369
  function () {
198
- this.forEach([].slice.call(cfg.forQuery));
370
+ const [path, fn] =
371
+ /**
372
+ * @type {[string, TemplateFunction<
373
+ * joiningTypes,
374
+ * "dom"|"json",
375
+ * import('./JSONPathTransformerContext.js').default |
376
+ * import('./XPathTransformerContext.js').default
377
+ * >]}
378
+ */ (
379
+ cfg.forQuery
380
+ );
381
+ // eslint-disable-next-line @stylistic/max-len -- Long
382
+ // eslint-disable-next-line unicorn/no-array-method-this-argument -- Not array
383
+ this.forEach(path, fn);
199
384
  }
200
385
  : cfg.query || (
201
386
  typeof cfg.templates === 'function'
@@ -205,23 +390,102 @@ class JTLT {
205
390
  : null
206
391
  );
207
392
  this.config.templates = query
208
- ? [
393
+ // eslint-disable-next-line @stylistic/max-len -- Long
394
+ ? /** @type {JSONPathTemplateObject<joiningTypes>[]|XPathTemplateObject<joiningTypes>[]} */ ([
209
395
  {name: 'root', path: '$', template: query}
210
- ]
211
- : cfg.templates || [cfg.template];
396
+ ])
397
+ // eslint-disable-next-line @stylistic/max-len -- Long
398
+ : /** @type {JSONPathTemplateObject<joiningTypes>[]|XPathTemplateObject<joiningTypes>[]} */ (
399
+ cfg.templates || [cfg.template]
400
+ );
212
401
  this.config.errorOnEqualPriority = cfg.errorOnEqualPriority || false;
213
402
  this.config.engine = this.config.engine ||
214
403
  /**
215
- * @param {JTLTOptions} configParam
216
- * @returns {any}
404
+ * @param {JTLTOptions &
405
+ * Required<Pick<JTLTOptions, "joiningTransformer">>} configParam
406
+ * @returns {ResultType<joiningTypes>}
217
407
  */
218
408
  function (configParam) {
219
- if ((/** @type {any} */ (configParam)).engineType === 'xpath') {
220
- const xt = new XPathTransformer(/** @type {any} */ (configParam));
221
- return xt.transform(/** @type {any} */ (configParam).mode);
409
+ if (configParam.engineType === 'xpath') {
410
+ let xt;
411
+ /* c8 ignore next -- Defensive: outputType set in setDefaults */
412
+ const outputType = cfg.outputType || 'json';
413
+ // eslint-disable-next-line sonarjs/no-all-duplicated-branches -- TS
414
+ if (outputType === 'string') {
415
+ xt = new (/** @type {typeof XPathTransformer<"string">} */ (
416
+ XPathTransformer
417
+ ))(
418
+ /**
419
+ * @type {import('./XPathTransformer.js').
420
+ * XPathTransformerConfig<"string"> &
421
+ * import('./XPathTransformerContext.js').
422
+ * XPathTransformerContextConfig}
423
+ */
424
+ (configParam)
425
+ );
426
+ // eslint-disable-next-line sonarjs/no-duplicated-branches -- TS
427
+ } else if (outputType === 'json') {
428
+ xt = new (/** @type {typeof XPathTransformer<"json">} */ (
429
+ XPathTransformer
430
+ ))(
431
+ /**
432
+ * @type {import('./XPathTransformer.js').
433
+ * XPathTransformerConfig<"json"> &
434
+ * import('./XPathTransformerContext.js').
435
+ * XPathTransformerContextConfig}
436
+ */
437
+ (configParam)
438
+ );
439
+ // eslint-disable-next-line sonarjs/no-duplicated-branches -- TS
440
+ } else {
441
+ xt = new (/** @type {typeof XPathTransformer<"dom">} */ (
442
+ XPathTransformer
443
+ ))(
444
+ /**
445
+ * @type {import('./XPathTransformer.js').
446
+ * XPathTransformerConfig<"dom"> &
447
+ * import('./XPathTransformerContext.js').
448
+ * XPathTransformerContextConfig}
449
+ */
450
+ (configParam)
451
+ );
452
+ }
453
+ return xt.transform(configParam.mode);
454
+ }
455
+
456
+ // Type assertion is safe here because _createJoiningTransformer
457
+ // ensures the joiningTransformer type matches outputType
458
+ const outputType = configParam.outputType || 'json';
459
+
460
+ // Branch based on outputType to help TypeScript narrow the type
461
+ if (outputType === 'string') {
462
+ const jpt = new JSONPathTransformer(
463
+ /**
464
+ * @type {import('./JSONPathTransformerContext.js').
465
+ * JSONPathTransformerContextConfig<"string">}
466
+ */
467
+ (configParam)
468
+ );
469
+ return jpt.transform(configParam.mode);
222
470
  }
223
- const jpt = new JSONPathTransformer(/** @type {any} */ (configParam));
224
- return jpt.transform(/** @type {any} */ (configParam).mode);
471
+ if (outputType === 'dom') {
472
+ const jpt = new JSONPathTransformer(
473
+ /**
474
+ * @type {import('./JSONPathTransformerContext.js').
475
+ * JSONPathTransformerContextConfig<"dom">}
476
+ */
477
+ (configParam)
478
+ );
479
+ return jpt.transform(configParam.mode);
480
+ }
481
+ const jpt = new JSONPathTransformer(
482
+ /**
483
+ * @type {import('./JSONPathTransformerContext.js').
484
+ * JSONPathTransformerContextConfig<"json">}
485
+ */
486
+ (configParam)
487
+ );
488
+ return jpt.transform(configParam.mode);
225
489
  };
226
490
  // Todo: Let's also, unlike XSLT and the following, give options for
227
491
  // higher priority to absolute fixed paths over recursive descent
@@ -237,8 +501,8 @@ class JTLT {
237
501
  }
238
502
 
239
503
  /**
240
- * @param {string} mode The mode of the transformation
241
- * @returns {any} Result of transformation
504
+ * @param {string} [mode] The mode of the transformation
505
+ * @returns {void}
242
506
  * @todo Allow for a success callback in case the jsonpath code is modified
243
507
  * to work asynchronously (as with queries to access remote JSON
244
508
  * stores)
@@ -256,18 +520,155 @@ class JTLT {
256
520
 
257
521
  // Create a fresh joining transformer for each transform to avoid
258
522
  // accumulation, but only if a custom one wasn't provided
259
- if (!(/** @type {any} */ (this.config))._customJoiningTransformer) {
523
+ if (!(
524
+ /**
525
+ * @type {InternalJTLTOptions}
526
+ */
527
+ (this.config)
528
+ )._customJoiningTransformer) {
260
529
  this.config.joiningTransformer = this._createJoiningTransformer();
261
530
  }
262
531
 
263
532
  this.config.mode = mode;
264
- const ret = /** @type {Function} */ (this.config.success)(
265
- (/** @type {any} */ (this.config.engine))(this.config)
533
+ const {engine} = this.config;
534
+ /* c8 ignore next 3 -- Defensive: always configured by setDefaults */
535
+ if (!engine) {
536
+ throw new Error('Engine is not configured');
537
+ }
538
+ const result = engine(
539
+ // eslint-disable-next-line @stylistic/max-len -- Long type
540
+ /** @type {JTLTOptions & Required<Pick<JTLTOptions, "joiningTransformer">>} */ (
541
+ this.config
542
+ )
543
+ );
544
+ // The engine returns ResultType<T>. We cast through never to bypass
545
+ // the impossible intersection type that TypeScript infers for the union.
546
+ const ret = this.config.success(
547
+ /** @type {never} */ (result)
266
548
  );
267
549
  return ret;
268
550
  }
269
551
  }
270
552
 
553
+ /**
554
+ * Create and run a JTLT instance with the appropriate engine typing.
555
+ *
556
+ * Overloads help TypeScript select the correct constructor signature.
557
+ * @overload
558
+ * @param {Omit<JSONPathJTLTOptions<"json">, "success">} cfg
559
+ * @returns {Promise<ResultType<"json">>}
560
+ */
561
+ /**
562
+ * @overload
563
+ * @param {Omit<JSONPathJTLTOptions<"string">, "success">} cfg
564
+ * @returns {Promise<ResultType<"string">>}
565
+ */
566
+ /**
567
+ * @overload
568
+ * @param {Omit<JSONPathJTLTOptions<"dom">, "success">} cfg
569
+ * @returns {Promise<ResultType<"dom">>}
570
+ */
571
+ /**
572
+ * @overload
573
+ * @param {Omit<XPathJTLTOptions<"json">, "success">} cfg
574
+ * @returns {Promise<ResultType<"json">>}
575
+ */
576
+ /**
577
+ * @overload
578
+ * @param {Omit<XPathJTLTOptions<"dom">, "success">} cfg
579
+ * @returns {Promise<ResultType<"dom">>}
580
+ */
581
+ /**
582
+ * @overload
583
+ * @param {Omit<XPathJTLTOptions<"string">, "success">} cfg
584
+ * @returns {Promise<ResultType<"string">>}
585
+ */
586
+ /**
587
+ * @param {Omit<JTLTOptions, "success">} cfg Options
588
+ */
589
+ export function jtlt (cfg) {
590
+ // eslint-disable-next-line promise/avoid-new -- Own API
591
+ return new Promise((resolve) => {
592
+ // Narrow the constructor overload based on engineType
593
+ if (cfg && cfg.engineType === 'xpath') {
594
+ const outputType = cfg.outputType || 'string';
595
+
596
+ if (outputType === 'json') {
597
+ // eslint-disable-next-line no-new -- API
598
+ new JTLT(
599
+ /** @type {XPathJTLTOptions<"json">} */ ({
600
+ ...cfg,
601
+ outputType: 'json',
602
+ success (val) {
603
+ resolve(val);
604
+ }
605
+ })
606
+ );
607
+ } else if (outputType === 'dom') {
608
+ // eslint-disable-next-line no-new -- API
609
+ new JTLT(
610
+ /** @type {XPathJTLTOptions<"dom">} */ ({
611
+ ...cfg,
612
+ outputType: 'dom',
613
+ success (val) {
614
+ resolve(val);
615
+ }
616
+ })
617
+ );
618
+ } else {
619
+ // eslint-disable-next-line no-new -- API
620
+ new JTLT(
621
+ /** @type {XPathJTLTOptions<"string">} */ ({
622
+ ...cfg,
623
+ outputType: 'string',
624
+ success (val) {
625
+ resolve(val);
626
+ }
627
+ })
628
+ );
629
+ }
630
+ return;
631
+ }
632
+
633
+ const outputType = cfg.outputType || 'json';
634
+
635
+ if (outputType === 'string') {
636
+ // eslint-disable-next-line no-new -- API
637
+ new JTLT(
638
+ /** @type {JSONPathJTLTOptions<"string">} */ ({
639
+ ...cfg,
640
+ outputType: 'string',
641
+ success (val) {
642
+ resolve(val);
643
+ }
644
+ })
645
+ );
646
+ } else if (outputType === 'dom') {
647
+ // eslint-disable-next-line no-new -- API
648
+ new JTLT(
649
+ /** @type {JSONPathJTLTOptions<"dom">} */ ({
650
+ ...cfg,
651
+ outputType: 'dom',
652
+ success (val) {
653
+ resolve(val);
654
+ }
655
+ })
656
+ );
657
+ } else {
658
+ // eslint-disable-next-line no-new -- API
659
+ new JTLT(
660
+ /** @type {JSONPathJTLTOptions<"json">} */ ({
661
+ ...cfg,
662
+ outputType: 'json',
663
+ success (val) {
664
+ resolve(val);
665
+ }
666
+ })
667
+ );
668
+ }
669
+ });
670
+ }
671
+
271
672
  export {
272
673
  default as AbstractJoiningTransformer
273
674
  } from './AbstractJoiningTransformer.js';