jtlt 0.13.0 → 0.15.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 (46) hide show
  1. package/.c8rc.json +1 -0
  2. package/CHANGES.md +18 -0
  3. package/README.md +99 -85
  4. package/demo/calltemplate-params-demo.js +13 -22
  5. package/demo/codemirror.esm.js +50 -15
  6. package/demo/vendor/jhtml/src/SAJJ/SAJJ.ObjectArrayDelegator.js +17 -17
  7. package/demo/vendor/jhtml/src/SAJJ/SAJJ.js +60 -61
  8. package/demo/vendor/jhtml/src/jhtml-browser.js +1 -0
  9. package/demo/vendor/jhtml/src/jhtml-node.js +1 -0
  10. package/demo/vendor/jhtml/src/jhtml.js +18 -15
  11. package/dist/AbstractJoiningTransformer.d.ts +5 -5
  12. package/dist/AbstractJoiningTransformer.d.ts.map +1 -1
  13. package/dist/JSONPathTransformer.d.ts +13 -12
  14. package/dist/JSONPathTransformer.d.ts.map +1 -1
  15. package/dist/JSONPathTransformerContext.d.ts +60 -20
  16. package/dist/JSONPathTransformerContext.d.ts.map +1 -1
  17. package/dist/XPathTransformer.d.ts +9 -2
  18. package/dist/XPathTransformer.d.ts.map +1 -1
  19. package/dist/XPathTransformerContext.d.ts +56 -9
  20. package/dist/XPathTransformerContext.d.ts.map +1 -1
  21. package/dist/context-extensions.d.ts +24 -0
  22. package/dist/extendContext.d.ts +10 -0
  23. package/dist/extendContext.d.ts.map +1 -0
  24. package/dist/index.d.ts +121 -13
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/indexedDB.d.ts +107 -0
  27. package/dist/indexedDB.d.ts.map +1 -0
  28. package/dist/maybeAsync.d.ts +15 -0
  29. package/dist/maybeAsync.d.ts.map +1 -0
  30. package/docs/API.expanded.md +70 -20
  31. package/docs/API.md +34 -4
  32. package/docs/TO-DO.md +17 -3
  33. package/eslint.config.js +11 -3
  34. package/package.json +22 -15
  35. package/pnpm-workspace.yaml +9 -0
  36. package/src/AbstractJoiningTransformer.js +3 -3
  37. package/src/JSONPathTransformer.js +48 -8
  38. package/src/JSONPathTransformerContext.js +175 -25
  39. package/src/XPathTransformer.js +33 -1
  40. package/src/XPathTransformerContext.js +145 -16
  41. package/src/context-extensions.d.ts +24 -0
  42. package/src/extendContext.js +21 -0
  43. package/src/index.js +102 -11
  44. package/src/indexedDB.js +530 -0
  45. package/src/maybeAsync.js +44 -0
  46. package/tsconfig.json +2 -2
@@ -0,0 +1,107 @@
1
+ export type OpenDBFunction = typeof import("idb").openDB;
2
+ export type IndexedDBResultType = 'value' | 'key' | 'primaryKey' | 'all';
3
+ export type QueryOptions = {
4
+ index?: string;
5
+ range?: {
6
+ lower?: IDBValidKey;
7
+ upper?: IDBValidKey;
8
+ lowerOpen?: boolean;
9
+ upperOpen?: boolean;
10
+ };
11
+ query?: NonNullable<object | string | number>;
12
+ direction?: 'next' | 'nextunique' | 'prev' | 'prevunique';
13
+ count?: number;
14
+ resultType?: IndexedDBResultType;
15
+ };
16
+ export type IdbCursorLike = {
17
+ key: IDBValidKey;
18
+ primaryKey: IDBValidKey;
19
+ value: unknown;
20
+ continue: () => Promise<IdbCursorLike | null>;
21
+ };
22
+ export type IdbRecord = {
23
+ key: IDBValidKey;
24
+ primaryKey: IDBValidKey;
25
+ value: unknown;
26
+ };
27
+ export type IdbQueryTarget = {
28
+ openCursor: (range?: IDBKeyRange | null, direction?: IDBCursorDirection) => Promise<IdbCursorLike | null>;
29
+ openKeyCursor: (range?: IDBKeyRange | null, direction?: IDBCursorDirection) => Promise<IdbCursorLike | null>;
30
+ getAll: (range?: IDBKeyRange | null, count?: number) => Promise<unknown[]>;
31
+ getAllKeys: (range?: IDBKeyRange | null, count?: number) => Promise<IDBValidKey[]>;
32
+ getAllRecords?: ((options?: {
33
+ query?: IDBKeyRange | null;
34
+ count?: number;
35
+ direction?: IDBCursorDirection;
36
+ }) => Promise<IdbRecord[]>);
37
+ };
38
+ export declare function queryIndexedDB(dbName: string, storeName: string, options: QueryOptions & {
39
+ resultType: 'all';
40
+ }): Promise<IdbRecord[]>;
41
+ export declare function queryIndexedDB(dbName: string, storeName: string, options: QueryOptions & {
42
+ resultType: 'key' | 'primaryKey';
43
+ }): Promise<IDBValidKey[]>;
44
+ export declare function queryIndexedDB(dbName: string, storeName: string, options?: QueryOptions): Promise<unknown[]>;
45
+ export type ParsedIndexedDBExpression = {
46
+ dbName: string;
47
+ storeName: string;
48
+ options: QueryOptions | undefined;
49
+ /**
50
+ * - A JSONPath segment applied to the fetched
51
+ * records (e.g. `.*.name` or `[0].name`), with surrounding whitespace
52
+ * trimmed. Used by the JSONPath engine only; the XPath engine exposes
53
+ * `indexedDB()` as a registered XPath function instead (see
54
+ * {@link evaluateXPathWithIndexedDB}).
55
+ */
56
+ trailing: string;
57
+ };
58
+ /**
59
+ * @typedef {object} ParsedIndexedDBExpression
60
+ * @property {string} dbName
61
+ * @property {string} storeName
62
+ * @property {QueryOptions|undefined} options
63
+ * @property {string} trailing - A JSONPath segment applied to the fetched
64
+ * records (e.g. `.*.name` or `[0].name`), with surrounding whitespace
65
+ * trimmed. Used by the JSONPath engine only; the XPath engine exposes
66
+ * `indexedDB()` as a registered XPath function instead (see
67
+ * {@link evaluateXPathWithIndexedDB}).
68
+ */
69
+ /**
70
+ * Detect and parse an `indexedDB(...)` call at the start of a selector string.
71
+ * @param {string} expr
72
+ * @returns {ParsedIndexedDBExpression|null} `null` when `expr` is not an
73
+ * `indexedDB(...)` expression.
74
+ */
75
+ export declare function parseIndexedDBExpression(expr: string): ParsedIndexedDBExpression | null;
76
+ /**
77
+ * Fetch the records for a parsed `indexedDB(...)` expression and, when a
78
+ * trailing JSONPath segment is present (e.g. `.*.name`), evaluate it against
79
+ * the fetched records. Shared by the JSONPath and XPath engines.
80
+ * @param {ParsedIndexedDBExpression} parsed
81
+ * @param {{preventEval?: boolean}} [options]
82
+ * @returns {Promise<unknown>}
83
+ */
84
+ export declare function resolveIndexedDBQuery(parsed: ParsedIndexedDBExpression, { preventEval }?: {
85
+ preventEval?: boolean;
86
+ }): Promise<unknown>;
87
+ /**
88
+ * Namespace URI backing the predefined `jtlt` prefix under which the XPath
89
+ * `indexedDB()` function is registered.
90
+ */
91
+ export declare const JTLT_XPATH_NAMESPACE = "urn:jtlt";
92
+ /**
93
+ * @param {unknown} expr
94
+ * @returns {boolean} Whether `expr` calls the `indexedDB()` XPath function
95
+ */
96
+ export declare function xpathExpressionUsesIndexedDB(expr: unknown): boolean;
97
+ /**
98
+ * Evaluate an XPath 3.1 expression that may call `jtlt:indexedDB(...)`,
99
+ * to a string. Because fontoxpath is synchronous, this first evaluates the
100
+ * expression in "collect" mode to discover every `indexedDB()` call, awaits
101
+ * those queries, then evaluates again with the records available.
102
+ * @param {string} selectStr
103
+ * @param {Node} contextNode
104
+ * @returns {Promise<string>}
105
+ */
106
+ export declare function evaluateXPathWithIndexedDB(selectStr: string, contextNode: Node): Promise<string>;
107
+ //# sourceMappingURL=indexedDB.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexedDB.d.ts","sourceRoot":"","sources":["../src/indexedDB.js"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Iterate over an array, staying fully synchronous while `body` returns
3
+ * synchronously and only chaining Promises once `body` returns a thenable.
4
+ *
5
+ * This lets a nested asynchronous operation (e.g. an
6
+ * `await this.indexedDB(...)` fetch inside a template) suspend the loop,
7
+ * without forcing the common synchronous case through microtasks or
8
+ * breaking DOM-bound consumers.
9
+ * @template T
10
+ * @param {T[]} array
11
+ * @param {(item: T, index: number, arr: T[]) => any} body
12
+ * @returns {Promise<void>|void}
13
+ */
14
+ export declare function maybeAsyncLoop<T>(array: T[], body: (item: T, index: number, arr: T[]) => any): Promise<void> | void;
15
+ //# sourceMappingURL=maybeAsync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"maybeAsync.d.ts","sourceRoot":"","sources":["../src/maybeAsync.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CALjB,CAAC,EAKkB,KAAK,EAJ1B,CAAC,EAIyB,EAAE,IAAI,EAHhC,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,GAGN,GAF9B,OAAO,CAAC,IAAI,CAAC,GAAC,IAAI,CAgC9B"}
@@ -4,14 +4,18 @@ This document describes the public API surface of JTLT (JavaScript Template Lang
4
4
 
5
5
  > Early alpha: APIs may evolve; experimental sections (XPath) can change without major version bumps.
6
6
 
7
- ## Core façade: `JTLT`
7
+ ## Entry points: `jtlt()` and `JTLT`
8
8
 
9
- High-level entry point for running a transform over JSON data using JSONPath.
9
+ ### `jtlt(config)` — recommended
10
+
11
+ Runs a transform and returns a `Promise` resolving to the result. It takes
12
+ every option below **except `success`**, and does not accept `autostart`
13
+ meaningfully (it always runs).
10
14
 
11
15
  ```js
12
- import JTLT from 'jtlt';
16
+ import {jtlt} from 'jtlt';
13
17
 
14
- const out = JTLT.create({
18
+ const out = await jtlt({
15
19
  data: {title: 'Hello'},
16
20
  outputType: 'string',
17
21
  templates: [
@@ -22,31 +26,56 @@ const out = JTLT.create({
22
26
  this.string('</h1>');
23
27
  }
24
28
  }
25
- ],
29
+ ]
30
+ });
31
+ ```
32
+
33
+ ### `JTLT` class — lower level
34
+
35
+ `JTLT.create(config)` (or `new JTLT(config)`) returns a `JTLT` instance. The
36
+ result is delivered through the **required `success` callback**, which is
37
+ also the return value of `.transform(mode?)`:
38
+
39
+ ```js
40
+ import JTLT from 'jtlt';
41
+
42
+ const inst = JTLT.create({
43
+ data: {title: 'Hello'},
44
+ outputType: 'string',
45
+ templates: [/* … */],
26
46
  success: (res) => res
27
- }).transform('');
47
+ });
48
+ const out = inst.transform('');
28
49
  ```
29
50
 
30
- ### `new JTLT(config)` or `JTLT.create(config)` options
51
+ By default the constructor runs the transform immediately; pass
52
+ `autostart: false` to defer it to your own `.transform()` call.
53
+
54
+ ### Options
55
+
56
+ Common to both entry points unless noted.
31
57
 
32
58
  | Option | Type | Description |
33
59
  | ------ | ---- | ----------- |
34
- | `data` | any | Root JSON/JS object. Required unless `ajaxData` provided. |
60
+ | `data` | object \| primitive | Root JSON/JS value (or DOM `Document`/`Element` for the XPath engine). Required unless `ajaxData` provided. |
35
61
  | `ajaxData` | string | URL to fetch JSON (async start). |
36
- | `templates` | Array<TemplateObject> | Template declarations; see below. |
62
+ | `templates` | Array&lt;TemplateObject&gt; | Template declarations; see below. |
37
63
  | `template` | Function \| TemplateObject | Single root template convenience. |
38
64
  | `query` | Function | Root template convenience (wrapped as `path: '$'`). |
39
65
  | `forQuery` | [select, cb] | One-off query (like FLWOR `for`). Auto-wrapped as root template. |
40
- | `success` | Function(result) | Required callback; also receives transform return. |
66
+ | `success` | Function(result) | **`JTLT` only** — required callback; also the return of `.transform()`. Not accepted by `jtlt()`. |
41
67
  | `mode` | string | Starting mode for template matching. |
42
- | `outputType` | 'string' \| 'dom' \| 'json' | Chooses joiner. Default 'string'. |
43
- | `joiningTransformer` | Joiner instance | Custom joiner; skip auto creation. |
68
+ | `outputType` | 'string' \| 'dom' \| 'json' | Chooses joiner. Default `'string'`. |
69
+ | `engineType` | 'jsonpath' \| 'xpath' | Engine to use. Default `'jsonpath'`. |
70
+ | `xpathVersion` | 1 \| 2 \| 3.1 | XPath engine only. `1` native, `2` xpath2.js, `3.1` fontoxpath. Default `1`. |
71
+ | `sync` | boolean | Off by default (async templates are awaited). When `true`, a template that returns a Promise throws; disables `indexedDB()`. |
72
+ | `joiningTransformer` | Joiner | Custom joiner; skip auto creation. Needs `append` and `get` (see `JoiningTransformerContract`). |
44
73
  | `joiningConfig` | object | Passed to joiner (e.g., `{xmlElements:true}`). |
45
74
  | `unwrapSingleResult` | boolean | For JSON joiner: unwrap single-item root array. |
46
75
  | `errorOnEqualPriority` | boolean | Throw when multiple templates share priority on a node. |
47
- | `specificityPriorityResolver` | fn(path)=>number | Custom resolver for relative priorities. Defaults to XSLT-like JSONPath resolver. |
48
- | `engine` | fn(config)=>any | Override transform engine (defaults to JSONPathTransformer). |
49
- | `autostart` | boolean | If `false`, don’t auto-call `transform()` in constructor. |
76
+ | `specificityPriorityResolver` | fn(path)=&gt;number | Custom resolver for relative priorities. Defaults to XSLT-like JSONPath resolver. |
77
+ | `engine` | fn(config)=&gt;result | Override transform engine (defaults to JSONPathTransformer). |
78
+ | `autostart` | boolean | **`JTLT` only** — if `false`, don't auto-call `transform()` in constructor. |
50
79
  | `preventEval` | boolean | Disable parenthetical eval portions of JSONPath (security). |
51
80
 
52
81
  ## Template objects
@@ -123,9 +152,10 @@ const out = engine.transform('');
123
152
  ```
124
153
 
125
154
  Limitations:
126
- - Versions 2 and 3 (`xpath2.js` and `fontoxpath`) may lack some XPath functions;
127
- stick to basic location paths and simple predicates.
128
- - Namespace resolution not yet exposed (future `namespaceResolver` option).
155
+ - Versions 2 and 3.1 (`xpath2.js` and `fontoxpath`) may lack some XPath
156
+ functions; stick to basic location paths and simple predicates.
157
+ - A user `namespaceResolver` option is not yet exposed (the XPath 3.1
158
+ `jtlt:` prefix used by `indexedDB()` is wired in internally).
129
159
 
130
160
  ## Contexts
131
161
 
@@ -144,6 +174,13 @@ Methods (subset):
144
174
  - `variable(name, select)` – stores value/array from JSONPath.
145
175
  - `callTemplate(name, withParam?)`
146
176
  - `key(name, match, use)` / `getKey(name, value)`
177
+ - `indexedDB(dbName, storeName, options?)` – returns a `Promise` of matching
178
+ IndexedDB records. `options` mirror `jsonpath-plus`'s `resultType`
179
+ (`'value'` default, `'key'`, `'primaryKey'`, `'all'`) plus `index`,
180
+ `range`, `query`, `direction`, `count`. A template using it must be
181
+ `async` (or return the Promise). Throws under `sync: true`. A selector of
182
+ the form `indexedDB('db', 'store').*.name` in `valueOf()` is intercepted
183
+ and resolved the same way.
147
184
  - Joiner passthrough: `string()`, `text()`, `element()`, `object()`, `array()`, `number()`, `boolean()`, etc.
148
185
  \- Cloning helpers:
149
186
  - `copy(propertySets?)` — Shallow clone of current context value (object/array). Nested references are preserved. Optional `propertySets` (array of registered names) merge into the top-level clone when object-like.
@@ -163,6 +200,14 @@ Parallels JSONPath context with XPath evaluation:
163
200
  current context node; absolute paths (`/`, `//`) target the document root.
164
201
  - `variable(name, select)` – always stores node arrays for XPath.
165
202
  - `key(name, match, use)` – index by attribute value; `getKey` returns matching Element or context sentinel (`this`).
203
+ - `indexedDB(dbName, storeName, options?)` – same direct helper as the
204
+ JSONPath context. Additionally, `jtlt:indexedDB('db', 'store')` is
205
+ registered as an XPath 3.1 function (predefined `jtlt` prefix,
206
+ `urn:jtlt`), so `valueOf()` selectors such as
207
+ `string-join(jtlt:indexedDB('db', 'store') ! ?name, ',')` work; options
208
+ are passed as an XPath map. Since fontoxpath is synchronous, the
209
+ expression is evaluated in a collect pass, the queries are awaited, then
210
+ it is re-evaluated with the records. Throws under `sync: true`.
166
211
  - Default template rules: root traverses `.`, element traverses `*`, text nodes emit `nodeValue`, scalars emit `valueOf('.')`.
167
212
  \- Cloning helpers:
168
213
  - `copy()` — Shallow clone of the current context node (`cloneNode(false)`) appended to the joiner.
@@ -456,9 +501,14 @@ See `README.md` for FLWOR-style and join patterns.
456
501
  ## Roadmap (selected)
457
502
 
458
503
  - Namespace support for XPath.
459
- - Copy helpers (deep/shallow) parity with XSLT.
460
- - Streaming / async query execution.
461
504
  - Schema-aware template targeting.
505
+ - Broader async data sources (async templates and IndexedDB access via
506
+ `this.indexedDB(...)` already land).
507
+ - Closing remaining `xsl:copy` / `xsl:copy-of` parity gaps: a sequence
508
+ constructor for `copy()` (shallow-copy the node, then build its content in
509
+ the same call), `use-attribute-sets` on the XPath `copy()` (the JSONPath
510
+ one already takes `propertySets`), and `copy-namespaces` /
511
+ `inherit-namespaces` control over which namespace nodes are carried.
462
512
 
463
513
  ## Versioning
464
514
 
package/docs/API.md CHANGED
@@ -3,11 +3,15 @@
3
3
  For the full reference (engines, contexts, joiners, sorting, keys, variables,
4
4
  property sets, defaults, roadmap), see `docs/API.expanded.md`.
5
5
 
6
- ## Facade: JTLT
6
+ ## Entry points: `jtlt()` and `JTLT`
7
+
8
+ **`jtlt(config)`** is the recommended entry point. It runs the transform and
9
+ returns a `Promise` that resolves to the result — no `success` callback:
7
10
 
8
11
  ```js
9
- import JTLT from 'jtlt';
10
- JTLT.create({
12
+ import {jtlt} from 'jtlt';
13
+
14
+ const out = await jtlt({
11
15
  data: {title: 'Hi'},
12
16
  templates: [
13
17
  {path: '$.title', template (v) {
@@ -15,11 +19,32 @@ JTLT.create({
15
19
  this.string('</h1>');
16
20
  }}
17
21
  ],
22
+ outputType: 'string'
23
+ });
24
+ ```
25
+
26
+ Async templates (e.g. `await this.indexedDB(...)`) are awaited automatically;
27
+ pass `sync: true` to forbid them (a template that returns a Promise then
28
+ throws).
29
+
30
+ The lower-level **`JTLT`** class (`JTLT.create(config)` / `new JTLT(config)`)
31
+ is for when you need the instance itself, `autostart: false`, or to call
32
+ `.transform(mode)` yourself. It delivers the result through a **required
33
+ `success` callback** (also returned from `.transform()`):
34
+
35
+ ```js
36
+ import JTLT from 'jtlt';
37
+
38
+ const out = JTLT.create({
39
+ data: {title: 'Hi'},
40
+ templates: [/* … */],
18
41
  outputType: 'string',
19
- success: (out) => out
42
+ success: (result) => result
20
43
  }).transform('');
21
44
  ```
22
45
 
46
+ `jtlt()` accepts every `JTLT` option except `success`.
47
+
23
48
  ## Engines
24
49
 
25
50
  - JSONPathTransformer: on JSON with JSONPath selectors; resolves
@@ -33,6 +58,11 @@ JTLT.create({
33
58
  `variable(name, select)`, `key(name, match, use)`, `getKey(name, value)`.
34
59
  JSONPath variables store values; XPath variables store arrays of nodes.
35
60
 
61
+ Async: a template may be `async` and `await` inside it. `this.indexedDB(db,
62
+ store, options?)` resolves to matching IndexedDB records (see
63
+ `docs/API.expanded.md`). Unavailable when JTLT is configured with
64
+ `sync: true`.
65
+
36
66
  Cloning:
37
67
  `copy()` (shallow clone current JSON value or DOM node) and
38
68
  `copyOf(select?)` (deep clone; optional selector overrides target; scalar
package/docs/TO-DO.md CHANGED
@@ -76,7 +76,7 @@
76
76
  functions (or in XQuery), add equivalents to XSLT's
77
77
  `document()` and `unparsed-text()` for allowing non-JSON file
78
78
  retrieval (as well as variables/parameters) and also methods for
79
- iterating or retrieving IndexedDB, `localStorage`, and cookies
79
+ iterating or retrieving `localStorage` and cookies
80
80
  (names, keys and values).
81
81
 
82
82
  6. Add `outputType` which uses a DOM joiner but allows specialized
@@ -86,9 +86,23 @@
86
86
 
87
87
  7. See code for other possible to-dos
88
88
 
89
- 8. Promise-based templates; jtlt as templating engine (including zodexy-aware path expressions like @blob() for jsonpath-plus with `copyOf`/`valueOf` `document(...url...)` or our own indexeddb path db/store/key equivalent); work on **schema-driven** search forms too
89
+ 8. jtlt as templating engine (including zodexy-aware path expressions like @blob() for jsonpath-plus with `copyOf`/`valueOf` `document(...url...)`; work on **schema-driven** search forms too
90
90
 
91
- 9. Consider implementing the following elements from <https://www.w3.org/TR/xslt-30/>
91
+ 9. Note that while we could allow `template` to act as a sync function with sync calls to `this.valueOf` even when indexedDB was used within that latter call, the problem is that with the easy solution, it would require adding content to output after subsequent synchronous calls within the function did so.
92
+
93
+ ```js
94
+ const args = {
95
+ template () {
96
+ this.valueOf({select: "indexedDB('db','store').*.name"}); // resolves later
97
+ this.string('!'); // appends NOW
98
+ }
99
+ };
100
+ // -> "!Alice,Bob,Charlie" (not "Alice,Bob,Charlie!")
101
+ ```
102
+
103
+ We'd otherwise have to refactor a great deal to maintain a buffer.
104
+
105
+ 10. Consider implementing the following elements from <https://www.w3.org/TR/xslt-30/>
92
106
  which are not yet implemented.
93
107
 
94
108
  xsl:accept
package/eslint.config.js CHANGED
@@ -9,6 +9,14 @@ export default [
9
9
  'demo/codemirror.esm.js'
10
10
  ]
11
11
  },
12
+ {
13
+ languageOptions: {
14
+ ecmaVersion: 'latest',
15
+ globals: {
16
+ IDBKeyRange: 'readonly'
17
+ }
18
+ }
19
+ },
12
20
  ...ashNazg(['sauron']),
13
21
  {
14
22
  rules: {
@@ -19,9 +27,6 @@ export default [
19
27
  'unicorn/prefer-private-class-fields': 0,
20
28
  'unicorn/no-undeclared-class-members': 0,
21
29
 
22
- // AI was frequently making egregious mistakes here, so make fixable
23
- 'jsdoc/check-alignment': 'error',
24
-
25
30
  // We frequently use callbacks for nested interactions
26
31
  'promise/prefer-await-to-callbacks': 0,
27
32
 
@@ -42,6 +47,9 @@ export default [
42
47
  {
43
48
  files: ['test/**/*.js', 'test.js'],
44
49
  rules: {
50
+ // Reapply rule to testing file as it demonstrates the API
51
+ 'jsdoc/reject-any-type': ['error'],
52
+
45
53
  'unicorn/prefer-https': 0, // Old namespaces
46
54
  // Too many are JSON properties called "children"
47
55
  'unicorn/better-dom-traversing': 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jtlt",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "type": "module",
5
5
  "author": "Brett Zamir",
6
6
  "contributors": [],
@@ -8,12 +8,17 @@
8
8
  "types": "./dist/index.d.ts",
9
9
  "main": "src/index-node.js",
10
10
  "exports": {
11
- "types": "./dist/index.d.ts",
12
- "browser": {
13
- "import": "./src/index-browser.js"
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "browser": {
14
+ "import": "./src/index-browser.js"
15
+ },
16
+ "node": {
17
+ "import": "./src/index-node.js"
18
+ }
14
19
  },
15
- "node": {
16
- "import": "./src/index-node.js"
20
+ "./context-extensions": {
21
+ "types": "./dist/context-extensions.d.ts"
17
22
  }
18
23
  },
19
24
  "engines": {
@@ -29,11 +34,12 @@
29
34
  "@codemirror/lang-json": "^6.0.2",
30
35
  "@codemirror/lang-xml": "^6.1.0",
31
36
  "@codemirror/language": "^6.12.4",
32
- "@codemirror/state": "^6.7.1",
37
+ "@codemirror/state": "^6.7.4",
33
38
  "@lezer/highlight": "^1.2.3",
34
39
  "codemirror": "^6.0.2",
35
40
  "fontoxpath": "^3.34.0",
36
- "jamilih": "0.69.0",
41
+ "idb": "^8.0.3",
42
+ "jamilih": "0.69.1",
37
43
  "jhtml": "0.7.3",
38
44
  "jsdom": "30.0.1",
39
45
  "jsonpath-plus": "10.4.0",
@@ -50,14 +56,15 @@
50
56
  "@types/chai": "^5.2.3",
51
57
  "@types/jsdom": "^30.0.0",
52
58
  "@types/mocha": "^10.0.10",
53
- "baseline-browser-mapping": "^2.11.19",
59
+ "baseline-browser-mapping": "^2.11.21",
54
60
  "c8": "^12.0.0",
55
61
  "chai": "^6.2.2",
56
- "eslint": "^10.9.1",
57
- "eslint-config-ash-nazg": "^42.6.0",
58
- "mocha": "^11.8.0",
59
- "open": "^11.0.1",
60
- "rollup": "^4.63.0",
62
+ "eslint": "^10.10.0",
63
+ "eslint-config-ash-nazg": "^43.1.7",
64
+ "indexeddbshim": "^19.0.1",
65
+ "mocha": "^12.0.0",
66
+ "open": "^11.0.2",
67
+ "rollup": "^4.63.1",
61
68
  "typescript": "^7.0.2"
62
69
  },
63
70
  "bugs": "https://github.com/brettz9/jtlt/issues",
@@ -72,7 +79,7 @@
72
79
  "start": "open http://localhost:8021/demo/ && static -p 8021",
73
80
  "attw": "attw --pack . --profile esm-only",
74
81
  "tsc": "tsc",
75
- "build": "tsc -p tsconfig-prod.json && npm run rollup",
82
+ "build": "tsc -p tsconfig-prod.json && cp src/context-extensions.d.ts dist/context-extensions.d.ts && npm run rollup",
76
83
  "rollup": "rollup -c",
77
84
  "copy-simple-get-json": "cp -r node_modules/simple-get-json/dist/index-es.js demo/vendor/simple-get-json/dist/index-es.js",
78
85
  "copy-jhtml": "cp -Rf node_modules/jhtml/src demo/vendor/jhtml",
@@ -1,4 +1,6 @@
1
1
  allowBuilds:
2
+ better-sqlite3: true
3
+ canvas: true
2
4
  is-hidden-file: true
3
5
  libxmljs: true
4
6
  unrs-resolver: true
@@ -30,3 +32,10 @@ minimumReleaseAgeExclude:
30
32
  - '@rollup/rollup-win32-x64-msvc@4.63.0'
31
33
  - rollup@4.63.0
32
34
  - jhtml@0.7.2 || 0.7.3
35
+ - eslint-config-ash-nazg@43.1.0 || 43.1.6
36
+ - eslint-plugin-escompat@3.12.0
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
39
+ - open@11.0.2
40
+ - powershell-utils@0.2.1
41
+ - websql-configurable@4.0.0
@@ -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 {any[]} args - Positional arguments
262
- * @returns {any} Function return value
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 {any} val - Configuration property value
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 {any} value - Value
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 {any} value - Current context value
30
- * @returns {any}
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 {any} value - Value
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 {any} value - Value
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 {( ...args: any[]) => any} value - Function at current context
74
- * @returns {any}
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,48 @@ 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
- ).template.call(jte, undefined, {mode});
174
+ // `this` carries runtime `extensions`; a consumer's `ContextExtensions`
175
+ // augmentation would otherwise reject the bare context here.
176
+ ).template.call(/** @type {any} */ (jte), undefined, {mode});
177
+
178
+ if (ret !== null && typeof ret !== 'undefined' &&
179
+ typeof ret.then === 'function') {
180
+ if (this._config.sync) {
181
+ throw new Error(
182
+ 'A template returned a Promise but JTLT is configured with ' +
183
+ '`sync: true`.'
184
+ );
185
+ }
186
+ return /** @type {any} */ (
187
+ // eslint-disable-next-line @stylistic/max-len -- Long
188
+ // eslint-disable-next-line promise/prefer-await-to-then -- intentional dynamic sync/async
189
+ ret.then((/** @type {any} */ resolvedRet) => {
190
+ if (typeof resolvedRet !== 'undefined') {
191
+ const joiner = jte._getJoiningTransformer();
192
+ if (typeof resolvedRet === 'string' ||
193
+ (typeof resolvedRet === 'object' && resolvedRet !== null &&
194
+ 'nodeType' in resolvedRet)) {
195
+ joiner.append(/** @type {string|Node} */ (resolvedRet));
196
+ } else {
197
+ /** @type {import('./JSONJoiningTransformer.js').default} */ (
198
+ joiner
199
+ ).append(resolvedRet);
200
+ }
201
+ }
202
+ return /** @type {import('./index.js').ResultType<T>} */ (
203
+ jte.getOutput()
204
+ );
205
+ })
206
+ );
207
+ }
208
+
169
209
  if (typeof ret !== 'undefined') {
170
210
  // Will vary by jte._config.outputType
171
211
  // After the undefined check, ret is ResultType<T>