@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.
Files changed (57) hide show
  1. package/ARCHITECTURE.md +175 -0
  2. package/LICENSE +21 -0
  3. package/README.md +471 -0
  4. package/dist/types/basic.d.ts +32 -0
  5. package/dist/types/index.d.ts +4 -0
  6. package/dist/types/jslt/dispatch.d.ts +11 -0
  7. package/dist/types/jslt/errors.d.ts +18 -0
  8. package/dist/types/jslt/index.d.ts +53 -0
  9. package/dist/types/jslt/stylesheet.d.ts +8 -0
  10. package/dist/types/jtlt/desugar.d.ts +19 -0
  11. package/dist/types/jtlt/errors.d.ts +18 -0
  12. package/dist/types/jtlt/index.d.ts +57 -0
  13. package/dist/types/jtlt/template.d.ts +8 -0
  14. package/dist/types/jtlt/writer.d.ts +6 -0
  15. package/dist/types/path.d.ts +235 -0
  16. package/dist/types/pointer.d.ts +114 -0
  17. package/dist/types/query/compile.d.ts +21 -0
  18. package/dist/types/query/errors.d.ts +18 -0
  19. package/dist/types/query/index.d.ts +70 -0
  20. package/dist/types/query/normalize.d.ts +68 -0
  21. package/dist/types/query/operators.d.ts +424 -0
  22. package/dist/types/query/runtime.d.ts +93 -0
  23. package/dist/types/segments.d.ts +62 -0
  24. package/dist/types/xquery/index.d.ts +19 -0
  25. package/dist/types/xquery/parse.d.ts +20 -0
  26. package/docs/JSLT-FORMAT.md +861 -0
  27. package/docs/JSLT-PRELUDE.md +159 -0
  28. package/docs/JTLT-FORMAT.md +659 -0
  29. package/docs/QUERY-FORMAT.md +1221 -0
  30. package/docs/XQUERY-FRONTEND.md +321 -0
  31. package/package.json +81 -0
  32. package/schemas/jaren-jslt.draft-07.schema.json +776 -0
  33. package/schemas/jaren-jslt.schema.json +776 -0
  34. package/schemas/jaren-query.draft-07.schema.json +613 -0
  35. package/schemas/jaren-query.schema.json +375 -0
  36. package/src/basic.js +300 -0
  37. package/src/index.js +4 -0
  38. package/src/jslt/dispatch.js +934 -0
  39. package/src/jslt/errors.js +34 -0
  40. package/src/jslt/index.js +121 -0
  41. package/src/jslt/stylesheet.js +234 -0
  42. package/src/jtlt/desugar.js +231 -0
  43. package/src/jtlt/errors.js +34 -0
  44. package/src/jtlt/index.js +155 -0
  45. package/src/jtlt/template.js +130 -0
  46. package/src/jtlt/writer.js +110 -0
  47. package/src/path.js +977 -0
  48. package/src/pointer.js +453 -0
  49. package/src/query/compile.js +817 -0
  50. package/src/query/errors.js +33 -0
  51. package/src/query/index.js +150 -0
  52. package/src/query/normalize.js +1047 -0
  53. package/src/query/operators.js +1253 -0
  54. package/src/query/runtime.js +233 -0
  55. package/src/segments.js +627 -0
  56. package/src/xquery/index.js +35 -0
  57. 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