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.
Files changed (38) hide show
  1. package/CHANGES.md +10 -0
  2. package/README.md +99 -85
  3. package/demo/calltemplate-params-demo.js +13 -22
  4. package/demo/vendor/jhtml/src/SAJJ/SAJJ.ObjectArrayDelegator.js +17 -17
  5. package/demo/vendor/jhtml/src/SAJJ/SAJJ.js +60 -61
  6. package/demo/vendor/jhtml/src/jhtml-browser.js +1 -0
  7. package/demo/vendor/jhtml/src/jhtml-node.js +1 -0
  8. package/demo/vendor/jhtml/src/jhtml.js +18 -15
  9. package/dist/AbstractJoiningTransformer.d.ts +5 -5
  10. package/dist/AbstractJoiningTransformer.d.ts.map +1 -1
  11. package/dist/JSONPathTransformer.d.ts +13 -12
  12. package/dist/JSONPathTransformer.d.ts.map +1 -1
  13. package/dist/JSONPathTransformerContext.d.ts +50 -20
  14. package/dist/JSONPathTransformerContext.d.ts.map +1 -1
  15. package/dist/XPathTransformer.d.ts +9 -2
  16. package/dist/XPathTransformer.d.ts.map +1 -1
  17. package/dist/XPathTransformerContext.d.ts +46 -9
  18. package/dist/XPathTransformerContext.d.ts.map +1 -1
  19. package/dist/index.d.ts +104 -10
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/indexedDB.d.ts +107 -0
  22. package/dist/indexedDB.d.ts.map +1 -0
  23. package/dist/maybeAsync.d.ts +15 -0
  24. package/dist/maybeAsync.d.ts.map +1 -0
  25. package/docs/API.expanded.md +70 -20
  26. package/docs/API.md +34 -4
  27. package/docs/TO-DO.md +2 -2
  28. package/eslint.config.js +11 -3
  29. package/package.json +7 -5
  30. package/pnpm-workspace.yaml +9 -0
  31. package/src/AbstractJoiningTransformer.js +3 -3
  32. package/src/JSONPathTransformer.js +45 -7
  33. package/src/JSONPathTransformerContext.js +157 -22
  34. package/src/XPathTransformer.js +33 -1
  35. package/src/XPathTransformerContext.js +136 -16
  36. package/src/index.js +85 -8
  37. package/src/indexedDB.js +530 -0
  38. package/src/maybeAsync.js +44 -0
package/CHANGES.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # jtlt CHANGES
2
2
 
3
+ ## 0.14.0
4
+
5
+ - feat: indexedDB JSONPath and XPath support
6
+ - feat: async templates are awaited by default; new off-by-default `sync`
7
+ option throws instead (replaces `async`/`syncOnly`)
8
+ - fix: `getKey()` resolves its `match` against the document root, so it now
9
+ works inside `forEach()`/`applyTemplates()` callbacks
10
+ - fix(types): more precise typing (any -> unknown)
11
+ - docs: lead with the `jtlt()` function; correct examples
12
+
3
13
  ## 0.13.0
4
14
 
5
15
  - fix(types): more precise typing
package/README.md CHANGED
@@ -8,8 +8,6 @@ As with XSLT, allows for declarative, linear declaration of
8
8
  (recursive) templates and can be transformed into different
9
9
  formats (e.g., strings, JSON, or DOM objects).
10
10
 
11
- ***Beta state!!!***
12
-
13
11
  See the [Demo](https://brettz9.github.io/jtlt/demo/).
14
12
 
15
13
  ## Credits
@@ -29,9 +27,56 @@ See the [test file](./test/browser/index.html).
29
27
 
30
28
  ## Basic usage
31
29
 
32
- ### Node
30
+ The quickest way to run a transform is the **`jtlt()`** function. Give it a
31
+ config object; it runs the transform and returns a `Promise` that resolves to
32
+ the result:
33
+
34
+ ```js
35
+ import {jtlt} from 'jtlt';
36
+
37
+ const data = {title: 'Hello', items: ['a', 'b']};
38
+
39
+ const templates = [
40
+ {path: '$', template () {
41
+ this.applyTemplates('$.title');
42
+ this.applyTemplates('$.items[*]');
43
+ }},
44
+ {path: '$.title', template (v) {
45
+ this.element('h1', {}, [], () => this.text(v));
46
+ }},
47
+ {path: '$.items[*]', template (v) {
48
+ this.element('li', {}, [], () => this.text(v));
49
+ }}
50
+ ];
51
+
52
+ const out = await jtlt({data, templates, outputType: 'string'});
53
+ // -> <h1>Hello</h1><li>a</li><li>b</li>
54
+ ```
55
+
56
+ Templates may be `async` (for example to `await this.indexedDB(...)`);
57
+ `jtlt()` awaits them automatically. Pass `sync: true` to forbid asynchronous
58
+ templates (a template that then returns a Promise throws).
59
+
60
+ The same call works in Node and the browser (in the browser you must also
61
+ load the dependencies — see the [test file](./test/browser/index.html)). For
62
+ XML/HTML sources, add `engineType: 'xpath'` — see
63
+ [Quick start (XML source with XPath)](#quick-start-xml-source-with-xpath).
64
+
65
+ ### `jtlt()` vs `JTLT.create()`
66
+
67
+ `jtlt()` is a thin, Promise-returning wrapper around the lower-level
68
+ `JTLT` class. Prefer `jtlt()`. Reach for `JTLT.create()` /
69
+ `new JTLT()` only when you need the instance itself, `autostart: false`, or
70
+ to drive `.transform(mode)` yourself.
33
71
 
34
- ### Browser
72
+ | | `jtlt(config)` | `JTLT.create(config)` |
73
+ | --- | --- | --- |
74
+ | Returns | a `Promise` of the result | a `JTLT` instance |
75
+ | Result delivery | the resolved value | a required `success` callback (also returned by `.transform()`) |
76
+ | Async templates | awaited automatically | awaited automatically; `.transform()` returns a Promise |
77
+
78
+ Anywhere below that shows `JTLT.create({…}).transform(mode)` can instead be
79
+ written `await jtlt({…, mode})`.
35
80
 
36
81
  ## API
37
82
 
@@ -39,16 +84,20 @@ See the [docs](docs/API.md). A high‑level overview is below.
39
84
 
40
85
  ## API overview
41
86
 
42
- JTLT has two layers:
87
+ Run a transform with the **`jtlt(config)`** function (Promise-returning,
88
+ recommended) or the lower-level **`JTLT`** class (`JTLT.create(config)` /
89
+ `new JTLT(config)`, which delivers the result through a required `success`
90
+ callback). Under the hood JTLT has two layers:
43
91
 
44
92
  - Engine (template application):
45
93
  - JSONPathTransformer: Applies templates to JSON by matching JSONPath selectors (and optional modes), resolving priority, and invoking the winning template. Falls back to built‑in default rules when no user template matches.
46
94
  - JSONPathTransformerContext: The execution context passed to templates. It mirrors the joiner API (e.g., string(), object(), array()) so templates can emit results. It also provides helpers like applyTemplates(), callTemplate(), valueOf(), variable(), and forEach().
47
95
  - XPathTransformer (experimental): Applies templates to
48
96
  XML/HTML DOM by matching XPath selectors (and optional modes).
49
- Supports three evaluation modes: version 1 (native
50
- XPathEvaluator), version 2 (via xpath2.js), and version 3 (via fontoxpath).
51
- Falls back to built‑in default rules when no template matches.
97
+ Supports three evaluation modes: version `1` (native
98
+ XPathEvaluator), version `2` (via xpath2.js), and version `3.1` (via
99
+ fontoxpath). Falls back to built‑in default rules when no template
100
+ matches.
52
101
  - XPathTransformerContext (experimental): Execution context for
53
102
  XPath. Offers get(), forEach(), valueOf(), variable(), key()
54
103
  and the same joiner helpers as the JSONPath context.
@@ -69,16 +118,16 @@ JTLT has two layers:
69
118
 
70
119
  ### Common joiner methods
71
120
 
72
- - append(value): Central sink. Based on context, concatenates to string, pushes to array, or assigns to an object property.
73
- - get(): Return the accumulated result.
74
- - object(obj?, cb?, usePropertySets?, propSets?): Enter object context; optionally seed from an object or build via cb.
75
- - array(arr?, cb?): Enter array context; optionally seed from an array or build via cb.
76
- - string(str, cb?): Emit a string value (no HTML escaping). In String joiner, optional cb lets you compose nested fragments before emitting.
77
- - number(num), boolean(bool), null(), undefined() (JS mode only), nonfiniteNumber(NaN|Infinity), function(fn) (JS mode only): Emit primitives/functions.
78
- - element(name, attrs?, children?, cb?): Build elements (String and DOM joiners). In String joiner, uses Jamilih under the hood to serialize; in DOM joiner, creates Elements.
79
- - attribute(name, value, avoidEscape?): Add attributes to the most recently opened element (String joiner) or to the current Element (DOM joiner).
80
- - text(txt): Emit text content. In String joiner, escapes & and <, and closes an open start tag if needed.
81
- - plainText(str): Raw, no‑escape append that bypasses context routing in the String joiner (always writes to top‑level buffer). In DOM/JSON joiners, it maps to text()/string() respectively.
121
+ - `append(value)`: Central sink. Based on context, concatenates to string, pushes to array, or assigns to an object property.
122
+ - `get()`: Return the accumulated result.
123
+ - `object(obj?, cb?, usePropertySets?, propSets?)`: Enter object context; optionally seed from an object or build via cb.
124
+ - `array(arr?, cb?)`: Enter array context; optionally seed from an array or build via cb.
125
+ - `string(str, cb?)`: Emit a string value (no HTML escaping). In String joiner, optional cb lets you compose nested fragments before emitting.
126
+ - `number(num), boolean(bool), null(), undefined()` (JS mode only), `nonfiniteNumber(NaN|Infinity), function(fn)` (JS mode only): Emit primitives/functions.
127
+ - `element(name, attrs?, children?, cb?)`: Build elements (String and DOM joiners). In String joiner, uses Jamilih under the hood to serialize; in DOM joiner, creates Elements.
128
+ - `attribute(name, value, avoidEscape?)`: Add attributes to the most recently opened element (String joiner) or to the current Element (DOM joiner).
129
+ - `text(txt)`: Emit text content. In String joiner, escapes & and <, and closes an open start tag if needed.
130
+ - `plainText(str)`: Raw, no‑escape append that bypasses context routing in the String joiner (always writes to top‑level buffer). In DOM/JSON joiners, it maps to text()/string() respectively.
82
131
 
83
132
  ### string() vs text() vs plainText() (String joiner)
84
133
 
@@ -95,56 +144,32 @@ Provide joiningConfig when constructing JTLT:
95
144
  - joiningConfig.xmlElements: Switch element() to XML serialization mode in the String joiner.
96
145
  - joiningConfig.preEscapedAttributes: Skip escaping attribute values in the String joiner.
97
146
 
98
- ## Quick start (JSON source)
99
-
100
- ```js
101
- import {jtlt} from 'jtlt';
102
-
103
- const data = {title: 'Hello', items: ['a', 'b']};
104
-
105
- const templates = [
106
- {path: '$', template () {
107
- this.applyTemplates();
108
- }},
109
- {path: '$.title', template (v) {
110
- this.string('<h1>', () => this.text(v));
111
- this.string('</h1>');
112
- }},
113
- {path: '$.items[*]', template (v) {
114
- this.element('li', {}, [], () => this.text(v));
115
- }}
116
- ];
117
-
118
- const out = await jtlt({data, templates, outputType: 'string'});
119
-
120
- console.log(out);
121
- ```
122
-
123
- Notes:
147
+ ## Notes on the basic example
124
148
 
125
149
  - Modes let you organize multiple passes or output targets.
126
- - You can also call templates by name via this.callTemplate('name').
127
- - For DOM output, use outputType: 'dom'. For JSON output, use 'json'.
150
+ - You can also call templates by name via `this.callTemplate('name')`.
151
+ - For DOM output, use `outputType: 'dom'`. For JSON output, use `'json'`
152
+ (the default is `'string'`).
128
153
 
129
- ### Quick start (XML source with XPath)
154
+ ## Quick start (XML source with XPath)
130
155
 
131
156
  You can run templates against XML/HTML using XPath instead of JSONPath.
132
157
 
133
158
  - `data` should be a Document or Element (e.g., from `DOMParser` with
134
159
  `text/xml`).
135
- - `xpathVersion`: `1` uses native XPath (browser like). `2` uses
136
- `xpath2.js` for XPath 2.0‑style evaluation. and `3.1` uses `fontoxpath`
137
- for XPath 3.1.
160
+ - `xpathVersion`: `1` uses native XPath (browserlike). `2` uses
161
+ `xpath2.js` for XPath 2.0‑style evaluation. `3.1` uses `fontoxpath` for
162
+ XPath 3.1. Default is `1`.
138
163
  - In version 2, some functions may be missing; prefer simple path
139
164
  expressions. Use version 1 for standard XPath 1.0 function support.
140
165
 
141
- Example (string output) using the JTLT facade with XPath:
166
+ Example (string output) with `jtlt()` and the XPath engine:
142
167
 
143
168
  ```js
144
169
  import {JSDOM} from 'jsdom';
145
170
  import {jtlt} from 'jtlt';
146
171
 
147
- const {window} = new JSDOM('<!doctype><html><body></body></html>');
172
+ const {window} = new JSDOM('<!doctype html><html><body></body></html>');
148
173
  const parser = new window.DOMParser();
149
174
  const doc = parser.parseFromString(
150
175
  '<root><item>a</item><item>b</item></root>', 'text/xml'
@@ -160,8 +185,7 @@ const templates = [
160
185
  {
161
186
  path: '//item',
162
187
  template (n) {
163
- this.string('<li>', () => this.text(n.textContent));
164
- this.string('</li>');
188
+ this.element('li', {}, [], () => this.text(n.textContent));
165
189
  }
166
190
  }
167
191
  ];
@@ -171,8 +195,7 @@ const out = await jtlt({
171
195
  templates,
172
196
  outputType: 'string',
173
197
  engineType: 'xpath',
174
- xpathVersion: 1, // or 2
175
- success: (res) => res
198
+ xpathVersion: 1 // or 2, or 3.1
176
199
  });
177
200
  // -> <li>a</li><li>b</li>
178
201
  ```
@@ -182,15 +205,14 @@ const out = await jtlt({
182
205
  If you just want to run a single, non-recursive query (similar to an XQuery "for … where … return …"), you can skip defining templates and use `forQuery` to seed a root function that iterates a JSONPath and emits results.
183
206
 
184
207
  - `forQuery` takes the same arguments you’d pass to `this.forEach(select, cb)`: an absolute JSONPath selector and a callback invoked for each match.
185
- - You can set variables via `this.variable(name, select)` and use plain JavaScript `if` for conditions (there is no dedicated `this.if`).
208
+ - The callback runs once per match with `this` bound to that match, so use plain JavaScript `if` for conditions (there is no dedicated `this.if`).
186
209
 
187
- Example: collect item names whose price meets a threshold, using a variable sourced from the root.
210
+ Example: collect item names whose price is at least 10.
188
211
 
189
212
  ```js
190
- import JTLT from 'jtlt';
213
+ import {jtlt} from 'jtlt';
191
214
 
192
215
  const data = {
193
- threshold: 10,
194
216
  items: [
195
217
  {name: 'A', price: 8},
196
218
  {name: 'B', price: 12},
@@ -198,37 +220,29 @@ const data = {
198
220
  ]
199
221
  };
200
222
 
201
- const jtlt = JTLT.create({
223
+ const result = await jtlt({
202
224
  data,
203
225
  outputType: 'json', // Top-level result will be a JSON array
204
226
  // forQuery mirrors: this.forEach(select, cb)
205
227
  forQuery: [
206
228
  '$.items[*]',
207
229
  function (item) {
208
- // Set a reusable variable from the root context
209
- this.variable('threshold', '$.threshold');
210
- const {threshold} = this.vars;
211
-
212
230
  // Use normal JS conditionals (no this.if helper)
213
- if (item.price >= threshold) {
231
+ if (item.price >= 10) {
214
232
  // In JSON output mode, appending a string pushes into
215
233
  // the top-level array
216
234
  this.string(item.name);
217
235
  }
218
236
  }
219
- ],
220
- // success receives the final result; return it for convenience
221
- success: (out) => out
237
+ ]
222
238
  });
223
-
224
- const result = jtlt.transform();
225
239
  // result => ['B', 'C']
226
240
  ```
227
241
 
228
242
  Tips:
229
243
 
230
244
  - For string output, set `outputType: 'string'` and emit with `this.text()`/`this.string()` in the callback.
231
- - `this.variable(name, select)` evaluates the JSONPath against the current context (root for `forQuery`), storing it in `this.vars[name]`.
245
+ - `forQuery`'s callback context is the matched item, not the root to use a value from the root (e.g. a `threshold`), use a root template instead: `this.variable('threshold', '$.threshold')` then `this.forEach('$.items[*]', cb)` (see the FLWOR example below).
232
246
  - If you need multiple passes or richer logic, switch to named templates and modes.
233
247
 
234
248
  ## FLWOR-style (XQuery) example
@@ -238,7 +252,7 @@ You can express the essentials of a FLWOR expression (For, Let, Where, Order by,
238
252
  Scenario: list book titles whose price is at/above a threshold, ordered by price descending and then title ascending.
239
253
 
240
254
  ```js
241
- import JTLT from 'jtlt';
255
+ import {jtlt} from 'jtlt';
242
256
 
243
257
  const data = {
244
258
  threshold: 10,
@@ -274,8 +288,7 @@ const templates = [
274
288
  }}
275
289
  ];
276
290
 
277
- const out = JTLT.create({data, templates, outputType: 'string'}).
278
- transform('html');
291
+ const out = await jtlt({data, templates, outputType: 'string', mode: 'html'});
279
292
 
280
293
  // -> <ul><li>Brave New</li><li>Cobalt</li><li>Delta</li></ul>
281
294
  console.log(out);
@@ -297,7 +310,7 @@ You can model a join across two arrays (e.g., orders ↔ customers) using two `f
297
310
  Example: render an HTML list of orders annotated with customer names.
298
311
 
299
312
  ```js
300
- import JTLT from 'jtlt';
313
+ import {jtlt} from 'jtlt';
301
314
 
302
315
  const data = {
303
316
  customers: [
@@ -333,10 +346,11 @@ const templates = [
333
346
  }}
334
347
  ];
335
348
 
336
- const out = JTLT.create({
337
- data, templates, outputType: 'string'
338
- }).transform('html');
339
- // -> <ul><li>Bob Keyboard</li><li>Alice — Mouse</li></ul>
349
+ const out = await jtlt({
350
+ data, templates, outputType: 'string', mode: 'html'
351
+ });
352
+ // sorted by date ascending:
353
+ // -> <ul><li>Alice — Mouse</li><li>Bob — Keyboard</li></ul>
340
354
  console.log(out);
341
355
  ```
342
356
 
@@ -352,7 +366,7 @@ Notes:
352
366
  Define an index once, then perform O(1) lookups from another sequence when rendering. If no match is found, `getKey()` returns the current context (`this`) as a sentinel; check for that to skip safely.
353
367
 
354
368
  ```js
355
- import JTLT from 'jtlt';
369
+ import {jtlt} from 'jtlt';
356
370
 
357
371
  const data = {
358
372
  customers: [
@@ -383,9 +397,9 @@ const templates = [
383
397
  }}
384
398
  ];
385
399
 
386
- const out = JTLT.create({
387
- data, templates, outputType: 'string'
388
- }).transform('html');
400
+ const out = await jtlt({
401
+ data, templates, outputType: 'string', mode: 'html'
402
+ });
389
403
  // -> <ul><li>Bob: Keyboard</li></ul>
390
404
  console.log(out);
391
405
  ```
@@ -416,7 +430,7 @@ Differences / current limitations:
416
430
  allow a particuluar subset of JavaScript.
417
431
  - Stylesheet composition/precedence: no `xsl:import`/`xsl:include` equivalents; only basic priority and modes.
418
432
  - Schema awareness: no type-aware processing (a major XSLT/XQuery feature).
419
- - Multi-output (`xsl:result-document`): not built-in; pick one output type per transform.
433
+ - One output type per transform, though `document()` / `resultDocument()` can emit several documents of that type within a run.
420
434
 
421
435
  ## Differences between an exact equivalence with XSLT
422
436
 
@@ -1,17 +1,17 @@
1
1
  /**
2
2
  * Demo: Using valueOf() to access parameters in callTemplate
3
3
  *
4
- * This demonstrates the new feature where parameters passed via callTemplate
4
+ * This demonstrates the feature where parameters passed via callTemplate
5
5
  * can be accessed within the template using valueOf({select: '$paramName'})
6
6
  * instead of having to receive them as function parameters.
7
7
  */
8
8
 
9
9
  /* eslint-disable no-console -- Demo file */
10
10
 
11
- import JTLT from '../src/index-node.js';
11
+ import {jtlt} from '../src/index-node.js';
12
12
 
13
13
  console.log('=== Demo 1: Named parameters ===');
14
- JTLT.create({
14
+ console.log(await jtlt({
15
15
  data: {
16
16
  users: [
17
17
  {name: 'Alice', role: 'Admin'},
@@ -50,15 +50,12 @@ JTLT.create({
50
50
  this.string(')\n');
51
51
  }
52
52
  }
53
- ],
54
- success (result) {
55
- console.log(result);
56
- console.log('\n');
57
- }
58
- });
53
+ ]
54
+ }));
55
+ console.log('\n');
59
56
 
60
57
  console.log('=== Demo 2: Indexed parameters (no names) ===');
61
- JTLT.create({
58
+ console.log(await jtlt({
62
59
  data: {value: 'Test'},
63
60
  outputType: 'string',
64
61
  templates: [
@@ -88,15 +85,12 @@ JTLT.create({
88
85
  this.string('\n');
89
86
  }
90
87
  }
91
- ],
92
- success (result) {
93
- console.log(result);
94
- console.log('\n');
95
- }
96
- });
88
+ ]
89
+ }));
90
+ console.log('\n');
97
91
 
98
92
  console.log('=== Demo 3: Nested callTemplate ===');
99
- JTLT.create({
93
+ console.log(await jtlt({
100
94
  data: {company: 'ACME Corp'},
101
95
  outputType: 'string',
102
96
  templates: [
@@ -135,8 +129,5 @@ JTLT.create({
135
129
  this.string('\n');
136
130
  }
137
131
  }
138
- ],
139
- success (result) {
140
- console.log(result);
141
- }
142
- });
132
+ ]
133
+ }));
@@ -7,13 +7,12 @@ import SAJJ from './SAJJ.js';
7
7
  /* eslint-enable jsdoc/reject-any-type -- Arbitrary */
8
8
 
9
9
  /**
10
- * @abstract
11
- * @class
12
- * @todo Might add an add() method which defines how to combine result values
13
- * (so as to allow for other means besides string concatenation)
14
- */
10
+ * @abstract
11
+ * @class
12
+ * @todo Might add an add() method which defines how to combine result values
13
+ * (so as to allow for other means besides string concatenation)
14
+ */
15
15
  class ObjectArrayDelegator extends SAJJ {
16
- /* eslint-disable jsdoc/require-returns-check -- Abstract */
17
16
  /**
18
17
  * @returns {AnyDelegated}
19
18
  */
@@ -76,7 +75,6 @@ class ObjectArrayDelegator extends SAJJ {
76
75
  ) {
77
76
  throw new Error('Abstract');
78
77
  }
79
- /* eslint-enable jsdoc/require-returns-check -- Abstract */
80
78
 
81
79
  // It is probably not necessary to override the defaults for the following
82
80
  // two methods and perhaps not any of the others either
@@ -109,17 +107,19 @@ class ObjectArrayDelegator extends SAJJ {
109
107
  }
110
108
  } else {
111
109
  for (const key in value) {
112
- if (Object.hasOwn(value, key)) {
113
- this.currentKey = key;
114
- this.currentObject = value[key];
115
- keyVals.push(
116
- this.keyValueHandler(
117
- value[key], key, value, parentKey,
118
- parentObjectArrayBool, false, i
119
- )
120
- );
121
- i++;
110
+ if (!Object.hasOwn(value, key)) {
111
+ continue;
122
112
  }
113
+
114
+ this.currentKey = key;
115
+ this.currentObject = value[key];
116
+ keyVals.push(
117
+ this.keyValueHandler(
118
+ value[key], key, value, parentKey,
119
+ parentObjectArrayBool, false, i
120
+ )
121
+ );
122
+ i++;
123
123
  }
124
124
  }
125
125
  }
@@ -99,9 +99,9 @@
99
99
  */
100
100
 
101
101
  /**
102
- * @typedef {"undefined"|"null"|"boolean"|"symbol"|
103
- * "number"|"nonfiniteNumber"|"bigint"|
104
- * "string"|"function"|"array"|"object"|"ignore"} SAJJType
102
+ * @typedef {"undefined"|"null"|"boolean"|"symbol"
103
+ * |"number"|"nonfiniteNumber"|"bigint"
104
+ * |"string"|"function"|"array"|"object"|"ignore"} SAJJType
105
105
  */
106
106
 
107
107
  // PRIVATE STATIC UTILITIES
@@ -119,7 +119,7 @@ function _copyObject (obj, deep) {
119
119
  const copyObj = {};
120
120
  // eslint-disable-next-line guard-for-in -- Deliberate iterating of prototype
121
121
  for (const prop in obj) {
122
- copyObj[prop] = deep && obj[prop] && typeof obj[prop] === 'object'
122
+ copyObj[prop] = deep && obj[prop] !== null && typeof obj[prop] === 'object'
123
123
  ? _copyObject(/** @type {NestedObject} */ (obj[prop]))
124
124
  : obj[prop];
125
125
  }
@@ -150,6 +150,17 @@ function _copyObject (obj, deep) {
150
150
  class SAJJ {
151
151
  ret = '';
152
152
 
153
+ /** @type {SAJJOptions|undefined} */
154
+ options;
155
+
156
+ /**
157
+ * @param {SAJJOptions} [options] See setDefaultOptions() function body for
158
+ * some possibilities
159
+ */
160
+ constructor (options) {
161
+ this.setDefaultOptions(options);
162
+ }
163
+
153
164
  /* eslint-disable jsdoc/require-returns-check -- Abstract */
154
165
  /**
155
166
  * Could override for logging; meant for allowing dropping of
@@ -398,18 +409,6 @@ class SAJJ {
398
409
  }
399
410
  /* eslint-enable jsdoc/require-returns-check -- Abstract */
400
411
 
401
- /**
402
- * @param {SAJJOptions} options See setDefaultOptions() function body for
403
- * some possibilities
404
- */
405
- constructor (options) {
406
- /** @type {SAJJOptions} */
407
- // eslint-disable-next-line no-unused-expressions -- TS
408
- this.options;
409
-
410
- this.setDefaultOptions(options);
411
- }
412
-
413
412
  // OPTIONS
414
413
  /**
415
414
  * @param {SAJJOptions} [options]
@@ -447,11 +446,11 @@ class SAJJ {
447
446
  }
448
447
 
449
448
  /**
450
- * Rather than use the strategy design pattern, we'll override our prototype
451
- * selectively.
452
- * @param {SAJJOptions} options
453
- * @returns {void}
454
- */
449
+ * Rather than use the strategy design pattern, we'll override our prototype
450
+ * selectively.
451
+ * @param {SAJJOptions} options
452
+ * @returns {void}
453
+ */
455
454
  alterDefaultHandlers (options) {
456
455
  if (this.distinguishKeysValues) {
457
456
  this.keyValueHandler = this.keyValueDistinguishedHandler;
@@ -464,17 +463,17 @@ class SAJJ {
464
463
  // PUBLIC METHODS TO INITIATE PARSING
465
464
 
466
465
  /**
467
- * For strings, one may wish to use Clarinet (<https://github.com/dscape/clarinet>) to
468
- * avoid extra overhead or parsing twice.
469
- * @param {string} str The JSON string to be walked (after complete conversion
470
- * to an object)
471
- * @param {object|object[]} [parentObject] The parent object or array
472
- * containing the string
473
- * @param {string} [parentKey] The parent object or array's key
474
- * @param {boolean} [parentObjectArrayBool] Whether the parent object is an
475
- * array (not another object)
476
- * @returns {AnyValue}
477
- */
466
+ * For strings, one may wish to use Clarinet (<https://github.com/dscape/clarinet>) to
467
+ * avoid extra overhead or parsing twice.
468
+ * @param {string} str The JSON string to be walked (after complete conversion
469
+ * to an object)
470
+ * @param {object|object[]} [parentObject] The parent object or array
471
+ * containing the string
472
+ * @param {string} [parentKey] The parent object or array's key
473
+ * @param {boolean} [parentObjectArrayBool] Whether the parent object is an
474
+ * array (not another object)
475
+ * @returns {AnyValue}
476
+ */
478
477
  walkJSONString (str, parentObject, parentKey, parentObjectArrayBool) {
479
478
  return this.walkJSONObject(
480
479
  JSON.parse(str), parentObject, parentKey, parentObjectArrayBool
@@ -482,26 +481,26 @@ class SAJJ {
482
481
  }
483
482
 
484
483
  /**
485
- *
486
- * @param {import('../jhtml.js').JSONObject} obj The JSON object to walk
487
- * @param {object|object[]} [parentObject] The parent object or array
488
- * containing the string
489
- * @param {string} [parentKey] The parent object or array's key
490
- * @param {boolean} [parentObjectArrayBool] Whether the parent object is an
491
- * array (not another object)
492
- * @property {string|AnyValue} ret The intermediate return value (if any) from
493
- * beginHandler and delegateHandlersByType delegation
494
- * @returns {string} The final return value including beginHandler and
495
- * delegateHandlersByType delegation plus any endHandler additions;
496
- * one may build one's own intermediate values, but "ret" should be
497
- * set to return the value
498
- */
484
+ *
485
+ * @param {import('../jhtml.js').JSONObject} obj The JSON object to walk
486
+ * @param {object|object[]} [parentObject] The parent object or array
487
+ * containing the string
488
+ * @param {string} [parentKey] The parent object or array's key
489
+ * @param {boolean} [parentObjectArrayBool] Whether the parent object is an
490
+ * array (not another object)
491
+ * @property {string|AnyValue} ret The intermediate return value (if any) from
492
+ * beginHandler and delegateHandlersByType delegation
493
+ * @returns {string} The final return value including beginHandler and
494
+ * delegateHandlersByType delegation plus any endHandler additions;
495
+ * one may build one's own intermediate values, but "ret" should be
496
+ * set to return the value
497
+ */
499
498
  walkJSONObject (obj, parentObject, parentKey, parentObjectArrayBool) {
500
499
  this.root = obj;
501
- const parObj = parentObject || this.options.parentObject,
502
- parKey = parentKey || this.options.parentKey,
500
+ const parObj = parentObject || this.options?.parentObject,
501
+ parKey = parentKey || this.options?.parentKey,
503
502
  parObjArrBool = parentObjectArrayBool ||
504
- this.options.parentObjectArrayBool ||
503
+ this.options?.parentObjectArrayBool ||
505
504
  (parObj && this.isArrayType(parObj));
506
505
  this.ret = this.beginHandler(obj, parObj, parKey, parObjArrBool);
507
506
  this.ret += this.delegateHandlersByType(obj, parObj, parKey, parObjArrBool);
@@ -661,23 +660,23 @@ class SAJJ {
661
660
  }
662
661
 
663
662
  /**
664
- * Could override to always return false if one wished to merge
665
- * arrayHandler/objectHandler or, if in JSMode, to merge detectObjectType
666
- * and this isArrayType method. To merge arrayKeyValueHandler and
667
- * objectKeyValueHandler, see keyValueHandler.
668
- * @param {AnyValue} obj
669
- * @returns {boolean}
670
- */
663
+ * Could override to always return false if one wished to merge
664
+ * arrayHandler/objectHandler or, if in JSMode, to merge detectObjectType
665
+ * and this isArrayType method. To merge arrayKeyValueHandler and
666
+ * objectKeyValueHandler, see keyValueHandler.
667
+ * @param {AnyValue} obj
668
+ * @returns {boolean}
669
+ */
671
670
  isArrayType (obj) {
672
671
  return Object.prototype.toString.call(obj) === '[object Array]';
673
672
  }
674
673
 
675
674
  /**
676
- * Allow overriding to detect `Date`, `RegExp`, or other types (which will in
677
- * turn route to corresponding names).
678
- * @param {AnyValue} obj
679
- * @returns {"object"}
680
- */
675
+ * Allow overriding to detect `Date`, `RegExp`, or other types (which will in
676
+ * turn route to corresponding names).
677
+ * @param {AnyValue} obj
678
+ * @returns {"object"}
679
+ */
681
680
  detectObjectType (
682
681
  // eslint-disable-next-line no-unused-vars -- Signature
683
682
  obj
@@ -1,5 +1,6 @@
1
1
  import {setWindow} from './jhtml.js';
2
2
 
3
+ // eslint-disable-next-line unicorn/no-top-level-side-effects -- Static
3
4
  setWindow(/** @type {Window & typeof globalThis} */ (globalThis));
4
5
 
5
6
  export * from './jhtml.js';