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.
- package/.c8rc.json +1 -0
- package/CHANGES.md +18 -0
- package/README.md +99 -85
- package/demo/calltemplate-params-demo.js +13 -22
- package/demo/codemirror.esm.js +50 -15
- package/demo/vendor/jhtml/src/SAJJ/SAJJ.ObjectArrayDelegator.js +17 -17
- package/demo/vendor/jhtml/src/SAJJ/SAJJ.js +60 -61
- package/demo/vendor/jhtml/src/jhtml-browser.js +1 -0
- package/demo/vendor/jhtml/src/jhtml-node.js +1 -0
- package/demo/vendor/jhtml/src/jhtml.js +18 -15
- package/dist/AbstractJoiningTransformer.d.ts +5 -5
- package/dist/AbstractJoiningTransformer.d.ts.map +1 -1
- package/dist/JSONPathTransformer.d.ts +13 -12
- package/dist/JSONPathTransformer.d.ts.map +1 -1
- package/dist/JSONPathTransformerContext.d.ts +60 -20
- package/dist/JSONPathTransformerContext.d.ts.map +1 -1
- package/dist/XPathTransformer.d.ts +9 -2
- package/dist/XPathTransformer.d.ts.map +1 -1
- package/dist/XPathTransformerContext.d.ts +56 -9
- package/dist/XPathTransformerContext.d.ts.map +1 -1
- package/dist/context-extensions.d.ts +24 -0
- package/dist/extendContext.d.ts +10 -0
- package/dist/extendContext.d.ts.map +1 -0
- package/dist/index.d.ts +121 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/indexedDB.d.ts +107 -0
- package/dist/indexedDB.d.ts.map +1 -0
- package/dist/maybeAsync.d.ts +15 -0
- package/dist/maybeAsync.d.ts.map +1 -0
- package/docs/API.expanded.md +70 -20
- package/docs/API.md +34 -4
- package/docs/TO-DO.md +17 -3
- package/eslint.config.js +11 -3
- package/package.json +22 -15
- package/pnpm-workspace.yaml +9 -0
- package/src/AbstractJoiningTransformer.js +3 -3
- package/src/JSONPathTransformer.js +48 -8
- package/src/JSONPathTransformerContext.js +175 -25
- package/src/XPathTransformer.js +33 -1
- package/src/XPathTransformerContext.js +145 -16
- package/src/context-extensions.d.ts +24 -0
- package/src/extendContext.js +21 -0
- package/src/index.js +102 -11
- package/src/indexedDB.js +530 -0
- package/src/maybeAsync.js +44 -0
- 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"}
|
package/docs/API.expanded.md
CHANGED
|
@@ -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
|
-
##
|
|
7
|
+
## Entry points: `jtlt()` and `JTLT`
|
|
8
8
|
|
|
9
|
-
|
|
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
|
|
16
|
+
import {jtlt} from 'jtlt';
|
|
13
17
|
|
|
14
|
-
const out =
|
|
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
|
-
})
|
|
47
|
+
});
|
|
48
|
+
const out = inst.transform('');
|
|
28
49
|
```
|
|
29
50
|
|
|
30
|
-
|
|
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` |
|
|
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
|
|
62
|
+
| `templates` | Array<TemplateObject> | 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) |
|
|
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
|
-
| `
|
|
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)
|
|
48
|
-
| `engine` | fn(config)
|
|
49
|
-
| `autostart` | boolean |
|
|
76
|
+
| `specificityPriorityResolver` | fn(path)=>number | Custom resolver for relative priorities. Defaults to XSLT-like JSONPath resolver. |
|
|
77
|
+
| `engine` | fn(config)=>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
|
|
127
|
-
stick to basic location paths and simple predicates.
|
|
128
|
-
-
|
|
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
|
-
##
|
|
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
|
|
10
|
-
|
|
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: (
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
12
|
-
|
|
13
|
-
"
|
|
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
|
-
"
|
|
16
|
-
"
|
|
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.
|
|
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
|
-
"
|
|
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.
|
|
59
|
+
"baseline-browser-mapping": "^2.11.21",
|
|
54
60
|
"c8": "^12.0.0",
|
|
55
61
|
"chai": "^6.2.2",
|
|
56
|
-
"eslint": "^10.
|
|
57
|
-
"eslint-config-ash-nazg": "^
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
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",
|
package/pnpm-workspace.yaml
CHANGED
|
@@ -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 {
|
|
262
|
-
* @returns {
|
|
261
|
+
* @param {unknown[]} args - Positional arguments
|
|
262
|
+
* @returns {unknown} Function return value
|
|
263
263
|
*/
|
|
264
264
|
invokeFunctionByArity (name, args = []) {
|
|
265
265
|
const arity = args.length;
|
|
@@ -436,7 +436,7 @@ class AbstractJoiningTransformer {
|
|
|
436
436
|
|
|
437
437
|
/**
|
|
438
438
|
* @param {string} prop - Configuration property name
|
|
439
|
-
* @param {
|
|
439
|
+
* @param {unknown} val - Configuration property value
|
|
440
440
|
* @param {(this: AbstractJoiningTransformer<T>) => void} [cb]
|
|
441
441
|
* Optional callback invoked with this instance
|
|
442
442
|
* @returns {void}
|
|
@@ -16,7 +16,7 @@ class JSONPathTransformer {
|
|
|
16
16
|
/**
|
|
17
17
|
* @template {"json"|"string"|"dom"} U
|
|
18
18
|
* @this {JSONPathTransformerContext<U>}
|
|
19
|
-
* @param {
|
|
19
|
+
* @param {unknown} value - Value
|
|
20
20
|
* @param {{mode?: string}} cfg - Configuration
|
|
21
21
|
* @returns {void}
|
|
22
22
|
*/
|
|
@@ -26,8 +26,8 @@ class JSONPathTransformer {
|
|
|
26
26
|
},
|
|
27
27
|
transformPropertyNames: {
|
|
28
28
|
/**
|
|
29
|
-
* @param {
|
|
30
|
-
* @returns {
|
|
29
|
+
* @param {unknown} value - Current context value
|
|
30
|
+
* @returns {unknown}
|
|
31
31
|
*/
|
|
32
32
|
template (value) {
|
|
33
33
|
// Emit property names for the current object context
|
|
@@ -40,7 +40,7 @@ class JSONPathTransformer {
|
|
|
40
40
|
transformObjects: {
|
|
41
41
|
/**
|
|
42
42
|
* @this {JSONPathTransformerContext}
|
|
43
|
-
* @param {
|
|
43
|
+
* @param {unknown} value - Value
|
|
44
44
|
* @param {{mode?: string}} cfg - Configuration
|
|
45
45
|
* @returns {void}
|
|
46
46
|
*/
|
|
@@ -51,7 +51,7 @@ class JSONPathTransformer {
|
|
|
51
51
|
transformArrays: {
|
|
52
52
|
/**
|
|
53
53
|
* @this {JSONPathTransformerContext}
|
|
54
|
-
* @param {
|
|
54
|
+
* @param {unknown} value - Value
|
|
55
55
|
* @param {{mode?: string}} cfg - Configuration
|
|
56
56
|
* @returns {void}
|
|
57
57
|
*/
|
|
@@ -70,8 +70,9 @@ class JSONPathTransformer {
|
|
|
70
70
|
},
|
|
71
71
|
transformFunctions: {
|
|
72
72
|
/**
|
|
73
|
-
* @param {(
|
|
74
|
-
*
|
|
73
|
+
* @param {(...args: unknown[]) => unknown} value - Function at current
|
|
74
|
+
* context
|
|
75
|
+
* @returns {unknown}
|
|
75
76
|
*/
|
|
76
77
|
template (value) {
|
|
77
78
|
// Call the function and return its result
|
|
@@ -163,9 +164,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
|
-
|
|
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>
|