jtlt 0.18.0 → 0.19.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": "jtlt",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "type": "module",
5
5
  "author": "Brett Zamir",
6
6
  "contributors": [],
@@ -39,7 +39,7 @@
39
39
  "codemirror": "^6.0.2",
40
40
  "fontoxpath": "^3.34.0",
41
41
  "idb": "^8.0.3",
42
- "jamilih": "0.69.1",
42
+ "jamilih": "0.70.0",
43
43
  "jhtml": "0.7.3",
44
44
  "jsdom": "30.0.1",
45
45
  "jsonpath-plus": "10.4.0",
@@ -51,17 +51,17 @@
51
51
  },
52
52
  "devDependencies": {
53
53
  "@arethetypeswrong/cli": "^0.18.5",
54
- "@node-static/node-static": "^0.9.1",
54
+ "@node-static/node-static": "^0.9.2",
55
55
  "@rollup/plugin-node-resolve": "^16.0.3",
56
56
  "@types/chai": "^5.2.3",
57
57
  "@types/jsdom": "^30.0.0",
58
58
  "@types/mocha": "^10.0.10",
59
- "baseline-browser-mapping": "^2.11.21",
59
+ "baseline-browser-mapping": "^2.11.22",
60
60
  "c8": "^12.0.0",
61
61
  "chai": "^6.2.2",
62
62
  "eslint": "^10.10.0",
63
- "eslint-config-ash-nazg": "^43.1.7",
64
- "indexeddbshim": "^19.0.1",
63
+ "eslint-config-ash-nazg": "^43.1.8",
64
+ "indexeddbshim": "^19.0.2",
65
65
  "mocha": "^12.0.0",
66
66
  "open": "^11.0.2",
67
67
  "rollup": "^4.63.1",
@@ -35,7 +35,10 @@ minimumReleaseAgeExclude:
35
35
  - eslint-config-ash-nazg@43.1.0 || 43.1.6
36
36
  - eslint-plugin-escompat@3.12.0
37
37
  - eslint-plugin-jsdoc@64.3.0 || 64.3.3
38
- - indexeddbshim@17.4.1 || 17.4.2 || 17.4.3 || 17.5.0 || 17.6.0
38
+ - indexeddbshim@17.4.1 || 17.4.2 || 17.4.3 || 17.5.0 || 17.6.0 || 19.0.2
39
39
  - open@11.0.2
40
40
  - powershell-utils@0.2.1
41
41
  - websql-configurable@4.0.0
42
+ - '@node-static/node-static@0.9.2'
43
+ - baseline-browser-mapping@2.11.22
44
+ - jamilih@0.70.0
@@ -300,7 +300,7 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
300
300
  * @overload
301
301
  * @param {Element|string} elName
302
302
  * @param {(this: DOMJoiningTransformer) => void} cb
303
- * @returns {DOMJoiningTransformer}
303
+ * @returns {DOMJoiningTransformer|Promise<DOMJoiningTransformer>}
304
304
  */
305
305
  /**
306
306
  * @overload
@@ -314,9 +314,15 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
314
314
  * childNodes, or callback
315
315
  * @param {(Node|string)[]|((this: DOMJoiningTransformer) => void)
316
316
  * } [childNodes] - Child nodes or callback
317
- * @param {(this: DOMJoiningTransformer) => void} [cb] - Callback
317
+ * @param {(this: DOMJoiningTransformer) => void} [cb] -
318
+ * Callback; may be async (e.g. to `await` a `$indexedDB` fetch), in
319
+ * which case `element()` itself returns a `Promise` instead of `this`.
320
+ * (Typed as returning plain `void`, not `void|Promise<void>` — a union
321
+ * there would lose TypeScript's usual "a void-returning callback
322
+ * parameter accepts any actual return value" leniency, which existing
323
+ * callers rely on for e.g. `() => this.text(...)` concise arrows.)
318
324
  * @param {string[]} [useAttributeSets] - Attribute set names to apply
319
- * @returns {DOMJoiningTransformer}
325
+ * @returns {DOMJoiningTransformer|Promise<DOMJoiningTransformer>}
320
326
  */
321
327
  element (elem, atts, childNodes, cb, useAttributeSets) {
322
328
  // Handle argument overloading like other transformers
@@ -440,12 +446,24 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
440
446
  }
441
447
  }
442
448
 
449
+ const finishRoot = () => {
450
+ this._dom = oldDOM;
451
+ return this;
452
+ };
443
453
  if (cb) {
444
- cb.call(this._context || this);
454
+ // `cb`'s declared return type is plain `void` (see the parameter's
455
+ // JSDoc), preserving TypeScript's void-return leniency for
456
+ // callers — cast here to duck-type the real value.
457
+ const cbResult = /** @type {any} */ (cb.call(this._context || this));
458
+ // `cb` may be an async function (e.g. one that awaits a
459
+ // `$indexedDB` fetch); resume only once it settles, so `_dom` is
460
+ // restored in the right order.
461
+ if (cbResult && typeof cbResult.then === 'function') {
462
+ // eslint-disable-next-line promise/prefer-await-to-then -- Not async
463
+ return cbResult.then(() => finishRoot());
464
+ }
445
465
  }
446
- this._dom = oldDOM;
447
-
448
- return this;
466
+ return finishRoot();
449
467
  }
450
468
 
451
469
  // Non-root elements
@@ -494,12 +512,20 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
494
512
  }
495
513
  }
496
514
 
515
+ const finish = () => {
516
+ this._dom = oldDOM;
517
+ return this;
518
+ };
497
519
  if (cb) {
498
- cb.call(this._context || this);
520
+ // `cb`'s declared return type is plain `void` — cast here to
521
+ // duck-type the real value; see the note above the other branch.
522
+ const cbResult = /** @type {any} */ (cb.call(this._context || this));
523
+ if (cbResult && typeof cbResult.then === 'function') {
524
+ // eslint-disable-next-line promise/prefer-await-to-then -- Not async
525
+ return cbResult.then(() => finish());
526
+ }
499
527
  }
500
- this._dom = oldDOM;
501
-
502
- return this;
528
+ return finish();
503
529
  }
504
530
 
505
531
  /**
@@ -30,6 +30,12 @@ function _makeDatasetAttribute (n0) {
30
30
  * : import('./DOMJoiningTransformer.js').default}
31
31
  * @returns {void}
32
32
  */
33
+ // Kept as plain `void` (not `void|Promise<void>`) rather than a union:
34
+ // TypeScript's "a void-returning callback parameter accepts any actual
35
+ // return value" leniency — relied on throughout the test suite for
36
+ // concise-body arrows like `() => this.text(...)` — only applies to a
37
+ // literal `void` return type, not a union. An async callback (returning
38
+ // `Promise<void>`) is still assignable here for the same reason.
33
39
 
34
40
  /**
35
41
  * Attributes object for element() allowing standard string attributes
@@ -392,9 +398,11 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
392
398
  * @param {ElementAttributes|any[]|SimpleCallback} [atts]
393
399
  * Attrs, children, or cb.
394
400
  * @param {any[]|SimpleCallback} [childNodes] Children or cb.
395
- * @param {SimpleCallback} [cb] Builder callback.
401
+ * @param {SimpleCallback} [cb] Builder callback; may be async (e.g. to
402
+ * `await` a `$indexedDB` fetch), in which case `element()` itself
403
+ * returns a `Promise` instead of `this`.
396
404
  * @param {string[]} [useAttributeSets] - Attribute set names to apply
397
- * @returns {JSONJoiningTransformer}
405
+ * @returns {JSONJoiningTransformer|Promise<JSONJoiningTransformer>}
398
406
  */
399
407
  element (elem, atts, childNodes, cb, useAttributeSets) {
400
408
  this._requireSameChildren('json', 'element');
@@ -485,77 +493,96 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
485
493
  }));
486
494
  }
487
495
 
488
- // Callback-driven building (attribute/text/nested element mutate stack)
489
- if (cb) {
490
- // Push current state onto a stack
491
- this._elementStack.push({attsObj, jmlChildren});
492
- cb.call(this._context || this);
493
- const state = /** @type {ElementInfo} */ (this._elementStack.pop());
494
- ({attsObj} = state);
495
- // Children may have been mutated by nested element()/text();
496
- // already in jmlChildren
497
- }
498
-
499
- // Build Jamilih array
500
- /** @type {any[]} */
501
- const jmlEl = [elementName];
502
- if (Object.keys(attsObj).length) {
503
- jmlEl.push(attsObj, jmlChildren);
504
- } else {
505
- jmlEl.push(jmlChildren);
506
- }
507
-
508
- if (isRoot) {
509
- // todo: indent, cdataSectionElements
510
- const {
511
- omitXmlDeclaration, bareDoctype, doctypePublic, doctypeSystem, method
512
- } = this._outputConfig ?? {};
513
-
514
- const dtd = bareDoctype !== false
515
- ? [{$DOCTYPE: {
516
- name: elementName,
517
- publicId: doctypePublic ?? null, // Public ID (optional)
518
- systemId: doctypeSystem ?? null // System ID (optional)
519
- }}]
520
- : [];
521
-
522
- let xmlDeclaration;
523
- /* c8 ignore start -- third OR condition short-circuits */
524
- if (!omitXmlDeclaration && (
525
- method === 'xml' || method === 'xhtml' || omitXmlDeclaration === false)
526
- ) {
527
- const {version, encoding, standalone} = this._outputConfig ?? {};
528
-
529
- xmlDeclaration = {
530
- version,
531
- encoding,
532
- standalone
533
- };
496
+ // Everything from here on depends on `attsObj`/`jmlChildren` having
497
+ // been fully populated by `cb` (when given) — deferred into a
498
+ // continuation so an async `cb` (e.g. one that awaits a `$indexedDB`
499
+ // fetch) can be awaited first, keeping output correctly ordered.
500
+ const finishElement = () => {
501
+ // Build Jamilih array
502
+ /** @type {any[]} */
503
+ const jmlEl = [elementName];
504
+ if (Object.keys(attsObj).length) {
505
+ jmlEl.push(attsObj, jmlChildren);
506
+ } else {
507
+ jmlEl.push(jmlChildren);
534
508
  }
535
- /* c8 ignore stop */
536
509
 
537
- const doc = {$document: {
538
- ...(xmlDeclaration ? {xmlDeclaration} : {}),
539
- childNodes: [
540
- ...(method === 'xml' || method === 'xhtml' ? dtd : []),
541
- jmlEl
542
- ]
543
- }};
510
+ if (isRoot) {
511
+ // todo: indent, cdataSectionElements
512
+ const {
513
+ omitXmlDeclaration, bareDoctype, doctypePublic, doctypeSystem, method
514
+ } = this._outputConfig ?? {};
515
+
516
+ const dtd = bareDoctype !== false
517
+ ? [{$DOCTYPE: {
518
+ name: elementName,
519
+ publicId: doctypePublic ?? null, // Public ID (optional)
520
+ systemId: doctypeSystem ?? null // System ID (optional)
521
+ }}]
522
+ : [];
523
+
524
+ let xmlDeclaration;
525
+ /* c8 ignore start -- third OR condition short-circuits */
526
+ if (!omitXmlDeclaration && (
527
+ method === 'xml' || method === 'xhtml' ||
528
+ omitXmlDeclaration === false)
529
+ ) {
530
+ const {version, encoding, standalone} = this._outputConfig ?? {};
531
+
532
+ xmlDeclaration = {
533
+ version,
534
+ encoding,
535
+ standalone
536
+ };
537
+ }
538
+ /* c8 ignore stop */
539
+
540
+ const doc = {$document: {
541
+ ...(xmlDeclaration ? {xmlDeclaration} : {}),
542
+ childNodes: [
543
+ ...(method === 'xml' || method === 'xhtml' ? dtd : []),
544
+ jmlEl
545
+ ]
546
+ }};
547
+
548
+ // Removed this._doc; use this._docs only
549
+ if (this._cfg.exposeDocuments) {
550
+ this._docs.push(doc);
551
+ }
552
+ }
544
553
 
545
- // Removed this._doc; use this._docs only
546
- if (this._cfg.exposeDocuments) {
547
- this._docs.push(doc);
554
+ // If inside a parent element, append as its child; otherwise
555
+ // append to root
556
+ if (this._elementStack.length) {
557
+ const top = /** @type {ElementInfo} */ (this._elementStack.at(-1));
558
+ top.jmlChildren.push(jmlEl);
559
+ } else {
560
+ this.append(jmlEl);
548
561
  }
549
- }
562
+ return this;
563
+ };
550
564
 
551
- // If inside a parent element, append as its child; otherwise append to root
552
- if (this._elementStack.length) {
553
- const top = /** @type {ElementInfo} */ (this._elementStack.at(-1));
554
- top.jmlChildren.push(jmlEl);
555
- } else {
556
- this.append(jmlEl);
565
+ // Callback-driven building (attribute/text/nested element mutate stack)
566
+ if (cb) {
567
+ // Push current state onto a stack
568
+ this._elementStack.push({attsObj, jmlChildren});
569
+ // `cb`'s declared return type is plain `void` (see `SimpleCallback`)
570
+ // — cast here to duck-type the real value.
571
+ const cbResult = /** @type {any} */ (cb.call(this._context || this));
572
+ const finishWithStack = () => {
573
+ const state = /** @type {ElementInfo} */ (this._elementStack.pop());
574
+ ({attsObj} = state);
575
+ // Children may have been mutated by nested element()/text();
576
+ // already in jmlChildren
577
+ return finishElement();
578
+ };
579
+ if (cbResult && typeof cbResult.then === 'function') {
580
+ // eslint-disable-next-line promise/prefer-await-to-then -- Not async
581
+ return cbResult.then(() => finishWithStack());
582
+ }
583
+ return finishWithStack();
557
584
  }
558
- return this;
585
+ return finishElement();
559
586
  }
560
587
 
561
588
  /**
@@ -1,4 +1,7 @@
1
1
  import JSONPathTransformerContext from './JSONPathTransformerContext.js';
2
+ import {
3
+ compileJSONTemplate, isJSONTemplateNodeArray
4
+ } from './jsonTemplate.js';
2
5
 
3
6
  /**
4
7
  * Applies named JSONPath-driven templates to JSON data.
@@ -106,18 +109,35 @@ class JSONPathTransformer {
106
109
  throw new TypeError('config.templates is required');
107
110
  }
108
111
  this.templates = config.templates.map(function (template) {
112
+ /** @type {import('./index.js').JSONPathTemplateObject<T>} */
113
+ let normalized;
109
114
  if (Array.isArray(template)) {
110
115
  // Todo: We could allow a third argument (at beginning or
111
116
  // end?) to represent template name
112
- return /** @type {import('./index.js').JSONPathTemplateObject<T>} */ (
113
- {path: template[0], template: template[1]}
114
- );
117
+ normalized = /**
118
+ * @type {import('./index.js').
119
+ JSONPathTemplateObject<T>} */ (
120
+ {path: template[0], template: template[1]}
121
+ );
122
+ } else if (template.match && !template.path) {
123
+ // Normalize 'match' to 'path' for XSLT compatibility
124
+ normalized = {...template, path: template.match};
125
+ } else {
126
+ normalized = template;
115
127
  }
116
- // Normalize 'match' to 'path' for XSLT compatibility
117
- if (template.match && !template.path) {
118
- return {...template, path: template.match};
128
+ // A declarative (jamilih-shaped) node array, as opposed to a
129
+ // `TemplateFunction` — compile it once, up front, so every later
130
+ // dispatch site (root, applyTemplates, callTemplate) only ever sees
131
+ // a plain function.
132
+ if (isJSONTemplateNodeArray(normalized.template)) {
133
+ const format = normalized.format || config.defaultTemplateFormat ||
134
+ 'json';
135
+ normalized = {
136
+ ...normalized,
137
+ template: compileJSONTemplate(normalized.template, {format})
138
+ };
119
139
  }
120
- return template;
140
+ return normalized;
121
141
  });
122
142
  this.templates.forEach((template, i, templates) => {
123
143
  // eslint-disable-next-line @stylistic/max-len -- Long
@@ -164,16 +184,26 @@ class JSONPathTransformer {
164
184
  }
165
185
  // Set up parameter context for valueOf() access in root template
166
186
  jte._params = {0: jte._contextObj};
187
+ // `template` is always a function here: a declarative (jamilih-shaped)
188
+ // Array is compiled to one up front, in the constructor.
189
+ const {template: rootTemplateFn} =
190
+ /**
191
+ * @type {import('./index.js').JSONPathTemplateObject<T> &
192
+ * {template: import('./index.js').
193
+ * TemplateFunction<T, "json", JSONPathTransformerContext<T>>}}
194
+ */ (
195
+ templateObj
196
+ );
167
197
  /**
168
198
  * The template may return a value synchronously or a Promise (e.g. from
169
199
  * `await this.indexedDB(...)`), which is awaited unless `config.sync`.
170
200
  * @type {any}
171
201
  */
172
- const ret = /** @type {import('./index.js').JSONPathTemplateObject<T>} */ (
173
- templateObj
202
+ const ret = (
174
203
  // `this` carries runtime `extensions`; a consumer's `ContextExtensions`
175
204
  // augmentation would otherwise reject the bare context here.
176
- ).template.call(/** @type {any} */ (jte), undefined, {mode});
205
+ rootTemplateFn
206
+ ).call(/** @type {any} */ (jte), undefined, {mode});
177
207
 
178
208
  if (ret !== null && typeof ret !== 'undefined' &&
179
209
  typeof ret.then === 'function') {