@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,33 @@
|
|
|
1
|
+
//#region Jaren JSON Query errors
|
|
2
|
+
// Error classes for the Jaren JSON Query engine (QUERY-FORMAT.md section 10).
|
|
3
|
+
// Every error carries a stable `code` from the spec registry and a `docPath`,
|
|
4
|
+
// an RFC 6901 JSON Pointer into the *query document* locating the offending
|
|
5
|
+
// construct.
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Error thrown when a query document is rejected at compile time
|
|
9
|
+
* (`JQ0xxx` codes, QUERY-FORMAT.md section 10.2).
|
|
10
|
+
*/
|
|
11
|
+
export class JsonQueryCompileError extends Error {
|
|
12
|
+
constructor(code, message, docPath) {
|
|
13
|
+
super(`${code}: ${message} at ${docPath}`);
|
|
14
|
+
this.name = 'JsonQueryCompileError';
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.docPath = docPath;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Error thrown when evaluating a compiled query fails
|
|
22
|
+
* (`JQ2xxx` codes, QUERY-FORMAT.md section 10.3).
|
|
23
|
+
*/
|
|
24
|
+
export class JsonQueryRuntimeError extends Error {
|
|
25
|
+
constructor(code, message, docPath) {
|
|
26
|
+
super(`${code}: ${message} at ${docPath}`);
|
|
27
|
+
this.name = 'JsonQueryRuntimeError';
|
|
28
|
+
this.code = code;
|
|
29
|
+
this.docPath = docPath;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
//#region Jaren JSON Query (QUERY-FORMAT.md)
|
|
2
|
+
// Public API of the Jaren JSON Query engine: a declarative query-and-
|
|
3
|
+
// transformation language for JSON with XQuery 3.1 semantics and a
|
|
4
|
+
// JSON-native surface. The query document itself is JSON, with RFC 9535
|
|
5
|
+
// JSONPath strings as its navigation leaves; a bare JSONPath string is
|
|
6
|
+
// the degenerate query. See packages/json/docs/QUERY-FORMAT.md for the
|
|
7
|
+
// language contract.
|
|
8
|
+
//
|
|
9
|
+
// Two-stage compiler, same architecture as path.js: normalize.js turns
|
|
10
|
+
// the query document into a frozen AST (all JQ0xxx checks), compile.js
|
|
11
|
+
// turns the AST into specialized closures. The tagged sequence
|
|
12
|
+
// representation (runtime.js) never escapes this module: results map to
|
|
13
|
+
// plain JSON out (empty sequence -> undefined, singleton -> the item,
|
|
14
|
+
// longer sequence -> array of items).
|
|
15
|
+
|
|
16
|
+
import { normalizeQuery, deepFreezeCopy } from './normalize.js';
|
|
17
|
+
import { compileNode, UNBOUND } from './compile.js';
|
|
18
|
+
import { EMPTY, Seq, ebv } from './runtime.js';
|
|
19
|
+
|
|
20
|
+
export { JsonQueryCompileError, JsonQueryRuntimeError } from './errors.js';
|
|
21
|
+
|
|
22
|
+
const hasOwn = Object.hasOwn;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Compile a Jaren JSON Query document into a reusable query function.
|
|
26
|
+
*
|
|
27
|
+
* The returned function applies the query to a JSON value and returns the
|
|
28
|
+
* result as plain JSON: `undefined` for the empty sequence, the item
|
|
29
|
+
* itself for a singleton result, an array of items for a longer sequence.
|
|
30
|
+
* It also carries helper methods and metadata:
|
|
31
|
+
*
|
|
32
|
+
* - `query(data, externals?)` - the query result as described above
|
|
33
|
+
* - `query.first(data, externals?)` - first item of the result, or `undefined`
|
|
34
|
+
* - `query.exists(data, externals?)` - true when the result is non-empty
|
|
35
|
+
* - `query.ebv(data, externals?)` - the effective boolean value of the
|
|
36
|
+
* result per the EBV table (section 2.2): empty -> false, a singleton
|
|
37
|
+
* per its type (array/object -> true, D3), two or more items ->
|
|
38
|
+
* `JsonQueryRuntimeError` JQ2003. Computed on the internal sequence
|
|
39
|
+
* value, before the plain-JSON mapping - the mapped result is ambiguous
|
|
40
|
+
* there (an array is both a multi-item sequence and one array item).
|
|
41
|
+
* - `query.externals` - names of the external parameters (section 9), in
|
|
42
|
+
* order of first appearance; bind them via the `externals` argument
|
|
43
|
+
* (`{ name: value, ... }`). Evaluating a reference to an unbound
|
|
44
|
+
* external raises `JQ2006`.
|
|
45
|
+
* - `query.doc` - a deeply frozen copy of the query document (the
|
|
46
|
+
* caller's object is never frozen)
|
|
47
|
+
*
|
|
48
|
+
* @param {any} doc - the query document (any JSON value; a bare RFC 9535
|
|
49
|
+
* JSONPath string is the degenerate query)
|
|
50
|
+
* @param {object} [options] - compile options
|
|
51
|
+
* @param {(schemaJson: any, docPath: string) => ((value: any) => boolean)}
|
|
52
|
+
* [options.compileTypeTest] - hook compiling a JSON Schema literal into
|
|
53
|
+
* a boolean item predicate, called once per schema literal at query
|
|
54
|
+
* compile time (QUERY-FORMAT.md section 8.11). `@jarenjs/validate/query`
|
|
55
|
+
* exports `createTypeTestCompiler()` producing one; any conforming
|
|
56
|
+
* implementation works - this package never imports the validator.
|
|
57
|
+
* Without a hook, the schema operators `$valid`/`$assert`/`$as` are
|
|
58
|
+
* compile error JQ0008.
|
|
59
|
+
* @param {object} [options.extensions] - package-internal operator
|
|
60
|
+
* extension point, the operator analogue of `compileTypeTest` (used by
|
|
61
|
+
* the JSLT layer; not a public contract). A plain object of
|
|
62
|
+
* `name -> entry` following the operator registry contract; see
|
|
63
|
+
* normalizeQuery in normalize.js for the full shape. The published
|
|
64
|
+
* format vocabulary is unchanged: without extensions, documents using
|
|
65
|
+
* such operators fail JQ0002.
|
|
66
|
+
* @returns {function} the compiled query function
|
|
67
|
+
* @throws {JsonQueryCompileError} when the document violates the format
|
|
68
|
+
* @example
|
|
69
|
+
* const q = compileJsonQuery({
|
|
70
|
+
* "$let": { "b": "$.store.book[0]" },
|
|
71
|
+
* "$return": { "title": "$b.title", "cheap": { "$lt": ["$b.price", "$max"] } }
|
|
72
|
+
* });
|
|
73
|
+
* q.externals; // ['max']
|
|
74
|
+
* q(data, { max: 10 }); // { title: 'Sayings of the Century', cheap: true }
|
|
75
|
+
*/
|
|
76
|
+
export function compileJsonQuery(doc, options = {}) {
|
|
77
|
+
const { root, frameSize, externals } = normalizeQuery(doc, options);
|
|
78
|
+
const get = compileNode(root);
|
|
79
|
+
const extCount = externals.length;
|
|
80
|
+
|
|
81
|
+
function evaluate(data, ext) {
|
|
82
|
+
const frame = new Array(frameSize);
|
|
83
|
+
frame[0] = data;
|
|
84
|
+
for (let i = 0; i < extCount; i++) {
|
|
85
|
+
const e = externals[i];
|
|
86
|
+
frame[e.slot] = ext != null && hasOwn(ext, e.name) ? ext[e.name] : UNBOUND;
|
|
87
|
+
}
|
|
88
|
+
return get(frame);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const query = (data, ext) => {
|
|
92
|
+
const v = evaluate(data, ext);
|
|
93
|
+
if (v === EMPTY)
|
|
94
|
+
return undefined;
|
|
95
|
+
return v instanceof Seq ? v.items : v;
|
|
96
|
+
};
|
|
97
|
+
query.first = (data, ext) => {
|
|
98
|
+
const v = evaluate(data, ext);
|
|
99
|
+
if (v === EMPTY)
|
|
100
|
+
return undefined;
|
|
101
|
+
return v instanceof Seq ? v.items[0] : v;
|
|
102
|
+
};
|
|
103
|
+
query.exists = (data, ext) => evaluate(data, ext) !== EMPTY;
|
|
104
|
+
query.ebv = (data, ext) => ebv(evaluate(data, ext), '');
|
|
105
|
+
query.externals = Object.freeze(externals.map((e) => e.name));
|
|
106
|
+
query.doc = deepFreezeCopy(doc);
|
|
107
|
+
return query;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const OBJECT_CACHE = new WeakMap();
|
|
111
|
+
const STRING_CACHE = new Map();
|
|
112
|
+
const STRING_CACHE_LIMIT = 512;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Apply a Jaren JSON Query document to a JSON value in one call.
|
|
116
|
+
* Compiled queries are cached: object documents by identity (WeakMap),
|
|
117
|
+
* string documents (the degenerate JSONPath case) by value (FIFO, 512
|
|
118
|
+
* entries - the same pattern as `queryJSONPath`).
|
|
119
|
+
* @param {any} doc - the query document
|
|
120
|
+
* @param {any} data - the JSON value to query
|
|
121
|
+
* @param {object} [externals] - external parameter bindings (`{ name: value }`)
|
|
122
|
+
* @returns {any} the query result (undefined | item | array of items)
|
|
123
|
+
* @throws {JsonQueryCompileError} when the document violates the format
|
|
124
|
+
* @throws {JsonQueryRuntimeError} on any JQ2xxx runtime condition
|
|
125
|
+
*/
|
|
126
|
+
export function queryJson(doc, data, externals) {
|
|
127
|
+
let query;
|
|
128
|
+
if (typeof doc === 'string') {
|
|
129
|
+
query = STRING_CACHE.get(doc);
|
|
130
|
+
if (query === undefined) {
|
|
131
|
+
query = compileJsonQuery(doc);
|
|
132
|
+
if (STRING_CACHE.size >= STRING_CACHE_LIMIT)
|
|
133
|
+
STRING_CACHE.delete(STRING_CACHE.keys().next().value);
|
|
134
|
+
STRING_CACHE.set(doc, query);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else if (typeof doc === 'object' && doc !== null) {
|
|
138
|
+
query = OBJECT_CACHE.get(doc);
|
|
139
|
+
if (query === undefined) {
|
|
140
|
+
query = compileJsonQuery(doc);
|
|
141
|
+
OBJECT_CACHE.set(doc, query);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else { // scalar documents are trivial literals; compiling is cheaper than caching
|
|
145
|
+
query = compileJsonQuery(doc);
|
|
146
|
+
}
|
|
147
|
+
return query(data, externals);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
//#endregion
|