@jarenjs/json 0.9.2
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/ARCHITECTURE.md +175 -0
- package/LICENSE +21 -0
- package/README.md +471 -0
- package/dist/types/basic.d.ts +32 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/jslt/dispatch.d.ts +11 -0
- package/dist/types/jslt/errors.d.ts +18 -0
- package/dist/types/jslt/index.d.ts +53 -0
- package/dist/types/jslt/stylesheet.d.ts +8 -0
- package/dist/types/jtlt/desugar.d.ts +19 -0
- package/dist/types/jtlt/errors.d.ts +18 -0
- package/dist/types/jtlt/index.d.ts +57 -0
- package/dist/types/jtlt/template.d.ts +8 -0
- package/dist/types/jtlt/writer.d.ts +6 -0
- package/dist/types/path.d.ts +235 -0
- package/dist/types/pointer.d.ts +114 -0
- package/dist/types/query/compile.d.ts +21 -0
- package/dist/types/query/errors.d.ts +18 -0
- package/dist/types/query/index.d.ts +70 -0
- package/dist/types/query/normalize.d.ts +68 -0
- package/dist/types/query/operators.d.ts +424 -0
- package/dist/types/query/runtime.d.ts +93 -0
- package/dist/types/segments.d.ts +62 -0
- package/dist/types/xquery/index.d.ts +19 -0
- package/dist/types/xquery/parse.d.ts +20 -0
- package/docs/JSLT-FORMAT.md +861 -0
- package/docs/JSLT-PRELUDE.md +159 -0
- package/docs/JTLT-FORMAT.md +659 -0
- package/docs/QUERY-FORMAT.md +1221 -0
- package/docs/XQUERY-FRONTEND.md +321 -0
- package/package.json +81 -0
- package/schemas/jaren-jslt.draft-07.schema.json +776 -0
- package/schemas/jaren-jslt.schema.json +776 -0
- package/schemas/jaren-query.draft-07.schema.json +613 -0
- package/schemas/jaren-query.schema.json +375 -0
- package/src/basic.js +300 -0
- package/src/index.js +4 -0
- package/src/jslt/dispatch.js +934 -0
- package/src/jslt/errors.js +34 -0
- package/src/jslt/index.js +121 -0
- package/src/jslt/stylesheet.js +234 -0
- package/src/jtlt/desugar.js +231 -0
- package/src/jtlt/errors.js +34 -0
- package/src/jtlt/index.js +155 -0
- package/src/jtlt/template.js +130 -0
- package/src/jtlt/writer.js +110 -0
- package/src/path.js +977 -0
- package/src/pointer.js +453 -0
- package/src/query/compile.js +817 -0
- package/src/query/errors.js +33 -0
- package/src/query/index.js +150 -0
- package/src/query/normalize.js +1047 -0
- package/src/query/operators.js +1253 -0
- package/src/query/runtime.js +233 -0
- package/src/segments.js +627 -0
- package/src/xquery/index.js +35 -0
- package/src/xquery/parse.js +1647 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
//#region Jaren JSON Query sequence runtime
|
|
2
|
+
// The tagged sequence representation of the query engine (QUERY-FORMAT.md
|
|
3
|
+
// section 2.1). Items are JSON values, and JSON arrays ARE items, so a
|
|
4
|
+
// sequence needs a representation that can never be confused with a value:
|
|
5
|
+
//
|
|
6
|
+
// - the empty sequence is the exported `EMPTY` singleton symbol;
|
|
7
|
+
// - a singleton sequence is the raw item itself (no wrapper, no
|
|
8
|
+
// allocation - "singleton = item", spec section 2.1 rule 5);
|
|
9
|
+
// - a sequence of two or more items is a `Seq` instance.
|
|
10
|
+
//
|
|
11
|
+
// Seqs are always flat: a Seq never contains another Seq, EMPTY, or fewer
|
|
12
|
+
// than two items. All constructors go through `seqOf`/`appendItem`, which
|
|
13
|
+
// maintain the invariant; `assertSeqInvariant` checks it in tests.
|
|
14
|
+
|
|
15
|
+
import { JsonQueryRuntimeError } from './errors.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The empty sequence `()` (same singleton-sentinel pattern as
|
|
19
|
+
* `JSONPATH_NOTHING`).
|
|
20
|
+
*/
|
|
21
|
+
export const EMPTY = Symbol('JsonQuery.Empty');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A sequence of two or more items. Never constructed directly by
|
|
25
|
+
* operator code - use `seqOf` so the flatness invariant holds.
|
|
26
|
+
*/
|
|
27
|
+
export class Seq {
|
|
28
|
+
constructor(items) {
|
|
29
|
+
this.items = items;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Build a sequence value from a flat accumulator array of items.
|
|
35
|
+
* Returns EMPTY for zero items, the raw item for one, a Seq otherwise.
|
|
36
|
+
* The array is adopted, not copied; callers hand over ownership.
|
|
37
|
+
* @param {any[]} items - flat array of items (no Seq, no EMPTY inside)
|
|
38
|
+
* @returns {any} EMPTY, a single item, or a Seq
|
|
39
|
+
*/
|
|
40
|
+
export function seqOf(items) {
|
|
41
|
+
const len = items.length;
|
|
42
|
+
if (len === 0)
|
|
43
|
+
return EMPTY;
|
|
44
|
+
if (len === 1)
|
|
45
|
+
return items[0];
|
|
46
|
+
return new Seq(items);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Append a sequence value's items to a plain accumulator array
|
|
51
|
+
* (the shared flattening step of array constructors and `$seq`).
|
|
52
|
+
* @param {any[]} list - accumulator array of items
|
|
53
|
+
* @param {any} v - a sequence value (EMPTY, item, or Seq)
|
|
54
|
+
*/
|
|
55
|
+
export function appendItem(list, v) {
|
|
56
|
+
if (v === EMPTY)
|
|
57
|
+
return;
|
|
58
|
+
if (v instanceof Seq) {
|
|
59
|
+
const items = v.items;
|
|
60
|
+
for (let i = 0; i < items.length; i++)
|
|
61
|
+
list.push(items[i]);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
list.push(v);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Invoke `fn(item)` for each item of a sequence value, in order.
|
|
69
|
+
* @param {any} v - a sequence value (EMPTY, item, or Seq)
|
|
70
|
+
* @param {(item: any) => void} fn
|
|
71
|
+
*/
|
|
72
|
+
export function forEachItem(v, fn) {
|
|
73
|
+
if (v === EMPTY)
|
|
74
|
+
return;
|
|
75
|
+
if (v instanceof Seq) {
|
|
76
|
+
const items = v.items;
|
|
77
|
+
for (let i = 0; i < items.length; i++)
|
|
78
|
+
fn(items[i]);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
fn(v);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Number of items in a sequence value.
|
|
86
|
+
* @param {any} v - a sequence value (EMPTY, item, or Seq)
|
|
87
|
+
* @returns {number}
|
|
88
|
+
*/
|
|
89
|
+
export function itemCount(v) {
|
|
90
|
+
if (v === EMPTY)
|
|
91
|
+
return 0;
|
|
92
|
+
if (v instanceof Seq)
|
|
93
|
+
return v.items.length;
|
|
94
|
+
return 1;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* First item of a sequence value, or EMPTY for the empty sequence.
|
|
99
|
+
* @param {any} v - a sequence value (EMPTY, item, or Seq)
|
|
100
|
+
* @returns {any}
|
|
101
|
+
*/
|
|
102
|
+
export function firstItem(v) {
|
|
103
|
+
if (v instanceof Seq)
|
|
104
|
+
return v.items[0];
|
|
105
|
+
return v;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Effective boolean value of a sequence value per the EBV table of
|
|
110
|
+
* QUERY-FORMAT.md section 2.2 (D3: singleton array/object is true).
|
|
111
|
+
* @param {any} v - a sequence value (EMPTY, item, or Seq)
|
|
112
|
+
* @param {string} docPath - RFC 6901 pointer for the JQ2003 error
|
|
113
|
+
* @returns {boolean}
|
|
114
|
+
* @throws {JsonQueryRuntimeError} JQ2003 on a sequence of two or more items
|
|
115
|
+
*/
|
|
116
|
+
export function ebv(v, docPath) {
|
|
117
|
+
if (v === EMPTY)
|
|
118
|
+
return false;
|
|
119
|
+
switch (typeof v) {
|
|
120
|
+
case 'boolean':
|
|
121
|
+
return v;
|
|
122
|
+
case 'number':
|
|
123
|
+
return v === v && v !== 0; // false for 0, -0, NaN
|
|
124
|
+
case 'string':
|
|
125
|
+
return v.length !== 0;
|
|
126
|
+
default:
|
|
127
|
+
if (v === null)
|
|
128
|
+
return false;
|
|
129
|
+
if (v instanceof Seq)
|
|
130
|
+
throw new JsonQueryRuntimeError('JQ2003',
|
|
131
|
+
'the effective boolean value of a sequence of two or more items is undefined', docPath);
|
|
132
|
+
return true; // array or object (D3)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Describe a sequence value for a runtime error message (non-normative,
|
|
138
|
+
* human-readable).
|
|
139
|
+
* @param {any} v - a sequence value (EMPTY, item, or Seq)
|
|
140
|
+
* @returns {string}
|
|
141
|
+
*/
|
|
142
|
+
export function describeItem(v) {
|
|
143
|
+
if (v instanceof Seq)
|
|
144
|
+
return `a sequence of ${v.items.length} items`;
|
|
145
|
+
if (v === EMPTY)
|
|
146
|
+
return 'the empty sequence';
|
|
147
|
+
if (v === null)
|
|
148
|
+
return 'null';
|
|
149
|
+
if (Array.isArray(v))
|
|
150
|
+
return 'an array';
|
|
151
|
+
const t = typeof v;
|
|
152
|
+
return t === 'object' ? 'an object' : `a ${t}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Deterministic serialization of one JSON item, for `$groupby` keys
|
|
157
|
+
* (QUERY-FORMAT.md section 6.5) — **engine-internal**, not an interchange
|
|
158
|
+
* format (related to the roadmap's canonical-JSON item). It exists solely
|
|
159
|
+
* so that deep-equal items (D2) map to the same string:
|
|
160
|
+
*
|
|
161
|
+
* - object members serialize sorted by key (code-unit order), so key
|
|
162
|
+
* order never matters;
|
|
163
|
+
* - `-0` normalizes to `0` (D2: `-0` equals `0`);
|
|
164
|
+
* - strings serialize via `JSON.stringify` (its escape discipline means
|
|
165
|
+
* no raw control character ever appears in the output, so a control
|
|
166
|
+
* character is safe as a composite-key separator);
|
|
167
|
+
* - numbers serialize bare via `String(n)` — `NaN` and `±Infinity`
|
|
168
|
+
* (reachable through `$div`) serialize as `NaN`/`Infinity`, which
|
|
169
|
+
* cannot collide with quoted strings. Note this makes `NaN` group
|
|
170
|
+
* with `NaN`, the XQuery grouping rule, even though `NaN` never
|
|
171
|
+
* equals itself under `$eq`.
|
|
172
|
+
*
|
|
173
|
+
* @param {any} value - a JSON item (not EMPTY, not a Seq)
|
|
174
|
+
* @returns {string} a deterministic serialization for grouping
|
|
175
|
+
*/
|
|
176
|
+
export function stableKeyString(value) {
|
|
177
|
+
switch (typeof value) {
|
|
178
|
+
case 'string':
|
|
179
|
+
return JSON.stringify(value);
|
|
180
|
+
case 'number':
|
|
181
|
+
return value === 0 ? '0' : String(value); // normalizes -0
|
|
182
|
+
case 'boolean':
|
|
183
|
+
return value ? 'true' : 'false';
|
|
184
|
+
default:
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
if (value === null)
|
|
188
|
+
return 'null';
|
|
189
|
+
if (Array.isArray(value)) {
|
|
190
|
+
let s = '[';
|
|
191
|
+
for (let i = 0; i < value.length; i++) {
|
|
192
|
+
if (i > 0)
|
|
193
|
+
s += ',';
|
|
194
|
+
s += stableKeyString(value[i]);
|
|
195
|
+
}
|
|
196
|
+
return s + ']';
|
|
197
|
+
}
|
|
198
|
+
const keys = Object.keys(value).sort();
|
|
199
|
+
let s = '{';
|
|
200
|
+
for (let i = 0; i < keys.length; i++) {
|
|
201
|
+
if (i > 0)
|
|
202
|
+
s += ',';
|
|
203
|
+
s += JSON.stringify(keys[i]) + ':' + stableKeyString(value[keys[i]]);
|
|
204
|
+
}
|
|
205
|
+
return s + '}';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Debug-only invariant check: asserts a sequence value is well-formed
|
|
210
|
+
* (a Seq holds 2+ items and contains no nested Seq or EMPTY). Used by
|
|
211
|
+
* tests; never called on hot paths.
|
|
212
|
+
* @param {any} v - a sequence value to check
|
|
213
|
+
* @returns {any} v itself when well-formed
|
|
214
|
+
* @throws {Error} when the flatness invariant is violated
|
|
215
|
+
*/
|
|
216
|
+
export function assertSeqInvariant(v) {
|
|
217
|
+
if (v instanceof Seq) {
|
|
218
|
+
if (v.items.length < 2)
|
|
219
|
+
throw new Error(`Seq invariant violated: ${v.items.length} item(s) in a Seq`);
|
|
220
|
+
for (let i = 0; i < v.items.length; i++) {
|
|
221
|
+
const item = v.items[i];
|
|
222
|
+
if (item instanceof Seq)
|
|
223
|
+
throw new Error(`Seq invariant violated: nested Seq at index ${i}`);
|
|
224
|
+
if (item === EMPTY)
|
|
225
|
+
throw new Error(`Seq invariant violated: EMPTY inside a Seq at index ${i}`);
|
|
226
|
+
if (item === undefined)
|
|
227
|
+
throw new Error(`Seq invariant violated: undefined inside a Seq at index ${i}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return v;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
//#endregion
|