@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,659 @@
1
+ # The Jaren JTLT Format
2
+
3
+ **Version 0.1 — Specification**
4
+
5
+ Module: `@jarenjs/json/jtlt`. This document is the language contract for the
6
+ Jaren JTLT text-template layer, the way [JSLT-FORMAT](./JSLT-FORMAT.md) is
7
+ the contract for the dispatch engine it compiles to, and
8
+ [QUERY-FORMAT](./QUERY-FORMAT.md) the contract for the expression language
9
+ both share.
10
+
11
+ > **Naming note (non-normative).** "JTLT" here names the Jaren template
12
+ > layer for text — *JSON template language for text*, the
13
+ > XSLT-`method="text"` / T4 derivative of this stack. Final naming/branding
14
+ > is an open question and is deliberately not settled here, mirroring the
15
+ > query and JSLT naming notes.
16
+
17
+ ---
18
+
19
+ ## 1. Introduction
20
+
21
+ ### 1.1 What this language is
22
+
23
+ A JTLT **template** is a JSON document holding an ordered list of template
24
+ rules — the same rule shape as a JSLT stylesheet — whose bodies are
25
+ **segment lists** (literal text, interpolated query expressions, `$apply`
26
+ splices) and whose transformation result is a **string**. Where JSLT
27
+ answers "reshape this JSON into that JSON", JTLT answers "render this JSON
28
+ as that text": Markdown, XML, source code, configuration files — anything
29
+ with the shape of a character stream.
30
+
31
+ JTLT is a **front-end, not a second engine** — the same relationship the
32
+ XQuery module has to the query engine. A consumer compiles the template
33
+ into an ordinary JSLT 0.1 stylesheet and serializes the dispatched result;
34
+ the reference implementation exposes that stylesheet as
35
+ `render.stylesheet` (§11). Matching, conflict resolution, modes, dispatch,
36
+ recursion limits, externals, and the type-test hook are therefore
37
+ **inherited from JSLT by construction**, not restated: this document
38
+ specifies only what JTLT adds —
39
+
40
+ - the **template document** (§2): the `$jtlt` envelope and its `output`
41
+ member;
42
+ - the **segment vocabulary** (§3): how rule bodies denote text;
43
+ - the **built-in template rules** (§4): what unmatched values render as;
44
+ - **serialization** (§5): the output methods and their escaping contract;
45
+ - the **error surface** (§9): `TL`-prefixed codes with `docPath` pointers
46
+ into the *template* document.
47
+
48
+ The layer lives at `packages/json/src/jtlt/` — a module boundary inside
49
+ `@jarenjs/json`, like `jslt/` and `xquery/`. It adds **zero operators** to
50
+ the query vocabulary and **zero members** to the JSLT vocabulary; the JSLT
51
+ engine compiles JTLT's output without knowing JTLT exists.
52
+
53
+ ### 1.2 Conformance and normative language
54
+
55
+ The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHALL**, **SHALL
56
+ NOT**, **SHOULD**, **SHOULD NOT**, **RECOMMENDED**, **MAY**, and
57
+ **OPTIONAL** in this document are to be interpreted as described in
58
+ RFC 2119.
59
+
60
+ - A **producer** emits template documents and MUST emit documents valid per
61
+ this specification.
62
+ - A **consumer** (template compiler + renderer) MUST accept every valid
63
+ template, MUST reject invalid ones with the compile errors of §9, MUST
64
+ raise the runtime errors of §9 under the conditions specified there, and
65
+ MUST produce the exact output strings this document and its fixtures
66
+ define.
67
+
68
+ Everything JSLT-FORMAT.md specifies for stylesheets applies to the
69
+ compiled form of a template except where this document says otherwise; in
70
+ particular, everything QUERY-FORMAT.md specifies applies inside expression
71
+ segments verbatim.
72
+
73
+ ### 1.3 Terminology
74
+
75
+ - **Template** — the top-level JSON document handed to the compiler (§2).
76
+ - **Rule** — one template rule object (§2.2).
77
+ - **Segment list** — a rule's body: a JSON array of segments (§3).
78
+ - **Segment** — one body element: literal text, an expression, a nested
79
+ segment list, or one of the three segment operators (§3.1).
80
+ - **Interpolation** — serializing an expression segment's result into the
81
+ output text (§3.2).
82
+ - **Output method** — the template's serialization mode, `"text"` or
83
+ `"xml"` (§5).
84
+ - **Built-in template rules** — the rendering of values no user rule
85
+ matches (§4).
86
+
87
+ ### 1.4 What JTLT inherits from JSLT
88
+
89
+ The delegation table, stated once. Each row is normative by reference:
90
+
91
+ | concern | contract |
92
+ |---|---|
93
+ | rule `match` (path, schema, both) | JSLT-FORMAT §3 |
94
+ | conflict resolution (`priority`, document order) | JSLT-FORMAT §4, plus the reserved band of §2.2 |
95
+ | dispatch, recursion, `maxDepth` guard | JSLT-FORMAT §5.1, §5.4 |
96
+ | `$apply` value forms, semantics, location propagation, mode defaults | JSLT-FORMAT §6 |
97
+ | modes | JSLT-FORMAT §7 |
98
+ | parameters, reserved `root`/`path` externals | JSLT-FORMAT §8 |
99
+ | the `compileTypeTest` hook | JSLT-FORMAT §9 |
100
+ | expression evaluation inside segments | QUERY-FORMAT, all of it |
101
+
102
+ Two JSLT concepts do **not** carry over:
103
+
104
+ 1. **`unmatched` dispositions.** JTLT's built-in template rules (§4) match
105
+ every value, so no value is ever "unmatched" in the JSLT sense; the
106
+ `share`/`fresh`/`error` vocabulary and the `modes` envelope member have
107
+ no meaning here and are not part of the template envelope (§2.1).
108
+ 2. **Sharing semantics.** The result of a rendering is a fresh string;
109
+ JSLT's normative `===` sharing (JSLT-FORMAT §5.3) is about JSON
110
+ outputs and does not apply.
111
+
112
+ ---
113
+
114
+ ## 2. The template document
115
+
116
+ ### 2.1 Top level
117
+
118
+ A template is either:
119
+
120
+ 1. a JSON **array of rules** (the shorthand form) — implies version
121
+ `"0.1"` and the `"text"` output method; or
122
+ 2. the **envelope object**:
123
+
124
+ ```json
125
+ { "$jtlt": "0.1",
126
+ "output": "xml",
127
+ "rules": [] }
128
+ ```
129
+
130
+ `$jtlt` and `rules` are REQUIRED (`rules` MUST be an array of rules);
131
+ `output` is OPTIONAL. Unknown envelope members are compile error
132
+ TL0001. A `$jtlt` value other than the string `"0.1"` — including
133
+ non-string values — is compile error TL0006.
134
+
135
+ A template that is neither an array nor an object of the envelope shape is
136
+ TL0001.
137
+
138
+ - `"output"`: one of `"text"` (the default) or `"xml"` — the serialization
139
+ method of §5. Any other value is TL0001, and the error message SHOULD
140
+ name the supported methods. `"json"` and `"toml"` are deliberately not
141
+ methods — see §5.1.
142
+
143
+ There is no `unmatched` member and no `modes` member (§1.4); a producer
144
+ MUST NOT emit them and a consumer MUST reject them as unknown members.
145
+
146
+ ### 2.2 Rules
147
+
148
+ A **rule** is an object with the members
149
+
150
+ ```json
151
+ { "match": "$.store.book[*]", "mode": "toc", "priority": 2, "body": ["- ", "$.title", "\n"] }
152
+ ```
153
+
154
+ - `body` (REQUIRED) — a **segment list**: a JSON array of segments (§3).
155
+ A `body` that is not an array is TL0002 — this is the one place JTLT is
156
+ *narrower* than JSLT, where a body is any query expression.
157
+ - `match` (OPTIONAL) — what the rule fires on, per JSLT-FORMAT §3
158
+ verbatim: a JSONPath string, or an object with `path` and/or `schema`
159
+ members. A rule with no `match` matches every value. The template
160
+ compiler checks only that `match` is a string or an object (TL0002);
161
+ everything deeper is validated by the JSLT layer and surfaces as TL0005
162
+ (§9).
163
+ - `mode` (OPTIONAL) — a string naming the rule's mode (JSLT-FORMAT §7);
164
+ default is the unnamed mode `""`. A non-string `mode` is TL0002.
165
+ - `priority` (OPTIONAL) — a finite JSON number for explicit conflict
166
+ resolution (JSLT-FORMAT §4). A non-number or non-finite `priority` is
167
+ TL0002. Priorities **at or below `-1e307` are RESERVED** for the
168
+ built-in template rules (§4) and are compile error TL0003.
169
+
170
+ A rule that is not an object, or that lacks `body`, is TL0002. Unknown
171
+ rule members are TL0002 — the vocabulary is **closed**, the same culture
172
+ as the query and JSLT formats.
173
+
174
+ ---
175
+
176
+ ## 3. Segments
177
+
178
+ ### 3.1 The segment forms
179
+
180
+ A segment list is a JSON array; each element is exactly one of the
181
+ following. The list is **closed**: anything else is compile error TL0004,
182
+ pointing at the offending element.
183
+
184
+ | segment | meaning |
185
+ |---|---|
186
+ | string not starting with `$` | **literal text**, emitted raw (§5) |
187
+ | string starting with `$$` | **literal text** with one leading `$` removed: `"$$price"` emits `$price` — the query format's own escape, applied to text |
188
+ | any other string starting with `$` | an **expression segment**: a query expression per QUERY-FORMAT §4 (absolute path, variable-rooted path, or invalid — invalid forms are compile errors surfacing as TL0005), interpolated per §3.2 |
189
+ | array | a **nested segment list**, rendered in place; nesting is purely organizational and has no semantic effect |
190
+ | `{ "$apply": … }` (single-key) | an **apply splice** (§3.4) |
191
+ | `{ "$raw": expr }` (single-key) | **unescaped interpolation** (§3.3) |
192
+ | `{ "$json": expr }` (single-key) | **JSON embedding** (§3.3) |
193
+ | any other object with at least one `$`-prefixed key | an **expression segment**: an operator phrase per QUERY-FORMAT (FLWOR, `$if`, `$concat`, …), interpolated per §3.2 |
194
+ | object with no `$`-prefixed key | TL0004 — a map constructor cannot be serialized; this is always a mistake |
195
+ | number, boolean, `null` | TL0004 — write literal text as a string, so `42` and `"42"` cannot be silently conflated |
196
+
197
+ `$raw`, `$json`, and the segment-position treatment of `$apply` are
198
+ **segment-level forms**: they are recognized only as complete body-list
199
+ elements (at any nesting depth of segment lists). Inside an expression
200
+ segment, `$raw` and `$json` are unknown operators (a compile error via
201
+ TL0005), and `$apply` is the ordinary JSLT operator whose sequence result
202
+ becomes payload values — almost never what a template author wants; see
203
+ §3.4.
204
+
205
+ ### 3.2 Interpolation
206
+
207
+ An expression segment is evaluated per QUERY-FORMAT with the dispatched
208
+ value as `$`, then serialized:
209
+
210
+ 1. Each item of the result sequence is converted to its **text value**:
211
+
212
+ | item | text value |
213
+ |---|---|
214
+ | string | the string itself |
215
+ | number | the shortest round-trip decimal form (ECMAScript `Number::toString`) |
216
+ | `true` / `false` | `"true"` / `"false"` |
217
+ | `null` | the empty string |
218
+ | object or array | runtime error TL2001 |
219
+
220
+ 2. The text values are joined with a **single space** (U+0020) — the XSLT
221
+ `xsl:value-of` separator default.
222
+ 3. The empty sequence renders as the empty string.
223
+ 4. Under the `"xml"` output method the joined text is escaped (§5.3);
224
+ under `"text"` it is emitted raw.
225
+
226
+ The `null` row is a deliberate deviation from the `$string` cast
227
+ (QUERY-FORMAT §8.10), which spells `null` as `"null"`: interpolation is
228
+ *text serialization* — an absent-ish value renders as nothing — while
229
+ `$string` is a *data cast*. An author who wants the spelling writes
230
+ `{ "$string": expr }` as the segment.
231
+
232
+ The TL2001 row is deliberate too: there is no default text value of a
233
+ container. Dispatch into it with `$apply`, or embed it with `$json`. The
234
+ error names the segment's `docPath`.
235
+
236
+ ### 3.3 `$raw` and `$json`
237
+
238
+ - `{ "$raw": expr }` interpolates exactly per §3.2 steps 1–3 but is
239
+ **never escaped** — the XSLT `disable-output-escaping` analogue for
240
+ data-carried markup. Under the `"text"` method, `$raw` and a plain
241
+ expression segment are indistinguishable.
242
+ - `{ "$json": expr }` serializes each result item as **JSON text**
243
+ (ECMAScript `JSON.stringify`; containers are permitted and expected),
244
+ space-joins multiple items, and escapes the result per the output
245
+ method like any interpolation. The empty sequence renders as the empty
246
+ string.
247
+
248
+ ### 3.4 `$apply` splices
249
+
250
+ As a segment, `{ "$apply": … }` takes every value form and semantics of
251
+ JSLT-FORMAT §6 — selector-only, `[selector, mode]`, location propagation,
252
+ the rule's own mode as the default target — and **splices the rendered
253
+ output** of the dispatched values into the surrounding text, in order.
254
+ Nothing separates consecutive dispatch outputs; rules own their own
255
+ whitespace.
256
+
257
+ Because JTLT rule bodies are segment lists — not object constructors —
258
+ the `[]` idiom that JSLT-FORMAT §6.3 warns about does not arise: a bare
259
+ `{ "$apply": … }` element and a nested `[{ "$apply": … }]` element render
260
+ identically (§3.1's nesting rule).
261
+
262
+ `$apply` remains an ordinary operator *inside* expression segments (it is
263
+ injected by the JSLT layer into every body compile), but there its result
264
+ items are interpolated as payload values — containers raise TL2001 —
265
+ rather than spliced as rendered text. Consumers MUST NOT alter that
266
+ inherited behavior; authors SHOULD keep `$apply` at segment level.
267
+
268
+ ---
269
+
270
+ ## 4. The built-in template rules
271
+
272
+ Dispatching a value **no user rule matches** renders it with the built-in
273
+ template rules — the XSLT built-ins, restated for JSON:
274
+
275
+ - a **container** (object or array) applies templates to **every child in
276
+ document order**, in the current mode, and splices the results —
277
+ as if by `[{ "$apply": "$[*]" }]`;
278
+ - an **atom** (string, number, boolean, `null`) is **interpolated** per
279
+ §3.2, including method escaping — as if by `["$"]`.
280
+
281
+ Consequences, all normative:
282
+
283
+ 1. The empty template renders any input as the concatenated text values
284
+ of its atoms, in document order (Appendix A.1).
285
+ 2. `{ "$apply": "$.title" }` against an unmatched string is a *value-of
286
+ with rule-override capability*: it renders the string today, and a
287
+ later rule matching `$.title` takes over that rendering without the
288
+ call site changing (Appendix A.5).
289
+ 3. Every value matches *some* rule, so JSLT's `unmatched` dispositions
290
+ never trigger (§1.4).
291
+
292
+ The built-in rules sit **below every user rule**: they lose to any user
293
+ rule of any priority. A consumer implementing them as ordinary appended
294
+ rules MUST place them at a priority at or below the reserved band of
295
+ §2.2, one per mode in use. A **matchless user rule** (JSLT default
296
+ priority `-1`) therefore replaces the built-in behavior for its mode —
297
+ the override mechanism, exactly as in XSLT.
298
+
299
+ Recursion through the built-in container rule and through `$apply` is
300
+ bounded by the inherited `maxDepth` guard (JSLT-FORMAT §5.4); exceeding
301
+ it surfaces as TL2003 wrapping JT2001.
302
+
303
+ > **Location note.** The built-in container rule's children are selected
304
+ > by a `$`-rooted path, so they carry locations and can match path rules
305
+ > below (JSLT-FORMAT §6.4). A value dispatched **location-less** — e.g.
306
+ > by `{ "$apply": "$" }`, whose bare-`$` selector is not a located path —
307
+ > can match only schema and matchless rules; the built-ins still render
308
+ > it.
309
+
310
+ ---
311
+
312
+ ## 5. Output methods and serialization
313
+
314
+ ### 5.1 The `output` member
315
+
316
+ `"output"` selects how the rendered stream becomes the final string. 0.1
317
+ defines two methods; the member is designed for growth (a future method
318
+ is a new envelope value, not a new document shape).
319
+
320
+ **Non-normative rationale — why not `"json"` and `"toml"`.** JTLT's model
321
+ is a *text stream*: rules contribute character runs in document order.
322
+ JSON and TOML are *whole-document* serializations — they need the
323
+ complete value tree before the first byte is right, and their natural
324
+ producer is a JSLT transform followed by a serializer, not a template.
325
+ Keeping the method list stream-shaped keeps this contract honest.
326
+
327
+ ### 5.2 The `"text"` method
328
+
329
+ Every emitted text run — literal, interpolation, `$raw`, `$json`, the
330
+ built-in atom rule — is written **verbatim**. There is no escaping, no
331
+ trimming, no newline normalization: whitespace in literal segments is the
332
+ author's, preserved exactly.
333
+
334
+ ### 5.3 The `"xml"` method
335
+
336
+ Literal text segments and `$raw` interpolations are written verbatim —
337
+ literal template text *is* the markup, the XSLT literal-result-element /
338
+ T4 text-block contract. Every other emission — expression segments,
339
+ `$json` segments, and the built-in atom rule — is **escaped**: each
340
+ occurrence of the five characters below is replaced by its reference.
341
+
342
+ | character | replacement |
343
+ |---|---|
344
+ | `&` | `&` |
345
+ | `<` | `&lt;` |
346
+ | `>` | `&gt;` |
347
+ | `"` | `&quot;` |
348
+ | `'` | `&#39;` |
349
+
350
+ All five are always escaped, so one rule serves element content and
351
+ single- or double-quoted attribute values alike.
352
+
353
+ **Non-normative.** The method escapes *data*; it does not police
354
+ *documents*. Well-formedness — balanced tags, one root element, legal
355
+ name characters, no literal `&` in literal text — is the author's
356
+ responsibility, exactly as in T4. `"xml"` is equally suitable for HTML
357
+ output; the escape set is safe HTML.
358
+
359
+ ---
360
+
361
+ ## 6. The compiled stylesheet (non-normative)
362
+
363
+ Only the rendered string is normative. The reference implementation
364
+ reaches it by **desugaring**: literal segments become `$const` pairs,
365
+ expression segments become tagged constructors carrying their template
366
+ `docPath`, `$apply` passes through verbatim, and the built-in rules of §4
367
+ are appended as matchless rules at priority `-1e308` — one per mode
368
+ named anywhere in the template. The result is a valid JSLT 0.1 stylesheet
369
+ with **user rule indexes preserved**, exposed as `render.stylesheet`
370
+ (§11) so authors can inspect exactly what dispatches on their behalf.
371
+
372
+ Consumers MAY compile templates any other way — a fused single-walk
373
+ renderer is an explicitly anticipated future — provided every observable
374
+ of this document (output strings, error codes, template `docPath`s,
375
+ `render.stylesheet` validity) is preserved.
376
+
377
+ ---
378
+
379
+ ## 7. Externals and parameters
380
+
381
+ JSLT-FORMAT §8 verbatim: free variables in expression segments are the
382
+ template's parameters, bound at render time
383
+ (`render(data, { rate: 1.21 })`); `root` and `path` are reserved,
384
+ engine-bound names; the compiled renderer exposes `render.externals` with
385
+ the user parameters in first-appearance order. Appendix A.6 exercises all
386
+ three.
387
+
388
+ ---
389
+
390
+ ## 8. The type-test hook
391
+
392
+ JSLT-FORMAT §9 verbatim: `options.compileTypeTest` serves `schema` match
393
+ conditions and the `$valid`/`$assert`/`$as` operators inside expression
394
+ segments. A template using schema matches compiled without a hook fails
395
+ exactly as in JSLT (JT0006), surfacing as TL0005.
396
+
397
+ ---
398
+
399
+ ## 9. Errors
400
+
401
+ ### 9.1 Error objects
402
+
403
+ Consumers MUST raise compile-time errors as `JtltCompileError` and
404
+ runtime errors as `JtltRuntimeError`, with the same shape as the query
405
+ and JSLT errors:
406
+
407
+ - `code` — a stable identifier from the registry below;
408
+ - `message` — human-readable, non-normative;
409
+ - `docPath` — an RFC 6901 JSON Pointer into the **template document**
410
+ (e.g. `/rules/1/body/2`);
411
+ - `cause` — the wrapped underlying error, where the registry says so.
412
+
413
+ In the bare-array shorthand (§2.1) the document has no `rules` member;
414
+ `docPath` pointers then start at the rule index (`/1/body/2`).
415
+
416
+ **The remap requirement.** Errors raised by the underlying JSLT/query
417
+ layers point into the *compiled stylesheet*; a consumer MUST translate
418
+ `docPath` back into the template document wherever the pointer targets a
419
+ construct the template author wrote (a rule member, a segment, a position
420
+ inside an expression segment). Pointers into consumer-generated
421
+ constructs (the built-in rules of §4) carry `docPath` `""` — the whole
422
+ document. Message text MAY still quote compiled-stylesheet paths; the
423
+ `docPath` member is the contract.
424
+
425
+ ### 9.2 Registry
426
+
427
+ | code | condition |
428
+ |---|---|
429
+ | TL0001 | template shape: not array/object, missing/invalid `rules`, unknown envelope member, unknown `output` method |
430
+ | TL0002 | rule shape: not an object, missing or non-array `body`, unknown member, `match`/`mode`/`priority` of the wrong type |
431
+ | TL0003 | `priority` in the reserved band (at or below `-1e307`) |
432
+ | TL0004 | not a segment: `null`/number/boolean element, or an object with no `$`-prefixed key |
433
+ | TL0005 | the compiled stylesheet was rejected — wraps `JsltCompileError` (which may itself wrap query or hook errors); `docPath` remapped per §9.1 |
434
+ | TL0006 | unknown `$jtlt` version |
435
+ | TL2001 | interpolating an object or array (§3.2); `docPath` names the segment |
436
+ | TL2002 | malformed segment stream — an engine-contract violation, never author error; a consumer bug if ever observed |
437
+ | TL2003 | rendering raised a `JsltRuntimeError` — depth guard, body runtime errors, unbound parameters; wraps it, `docPath` remapped per §9.1 |
438
+
439
+ TL0xxx are compile errors (`JtltCompileError`), TL2xxx runtime errors
440
+ (`JtltRuntimeError`) — the same numbering convention as JQ/JT.
441
+
442
+ ---
443
+
444
+ ## 10. Correspondence with XSLT and T4 (non-normative)
445
+
446
+ | XSLT / T4 | JTLT 0.1 |
447
+ |---|---|
448
+ | `<xsl:output method="text"/>` / T4 template | `"output": "text"` (the default) |
449
+ | `<xsl:output method="xml"/>` | `"output": "xml"` (§5.3) |
450
+ | literal result text / T4 text block | literal string segment |
451
+ | `<xsl:value-of select="…"/>` / T4 `<#= … #>` | expression segment (§3.2) |
452
+ | `value-of`'s default `separator=" "` | the single-space sequence join (§3.2) |
453
+ | `<xsl:apply-templates select="…" mode="…"/>` | `{ "$apply": [selector, mode] }` splice (§3.4) |
454
+ | built-in template rules (text output) | §4 — containers recurse, atoms emit text |
455
+ | `disable-output-escaping="yes"` | `{ "$raw": expr }` |
456
+ | — (no analogue) | `{ "$json": expr }` |
457
+ | template rule / `match` / `mode` / `priority` | inherited from JSLT verbatim (§1.4) |
458
+
459
+ Deliberate deviations:
460
+
461
+ 1. **Unmatched atoms render under the built-ins** exactly as XSLT's text
462
+ built-ins emit text nodes — including the classic surprise that an
463
+ over-broad `$apply` leaks stray text into the output. JTLT keeps the
464
+ behavior because it is what makes `$apply` a value-of (§4); the
465
+ remedy, as in XSLT, is a more precise selector or an overriding rule.
466
+ 2. **`null` renders as nothing** (§3.2) — JSON's `null` is closer to an
467
+ absent text node than to the four-letter word.
468
+ 3. **No indentation engine in 0.1.** T4's structured-whitespace helpers
469
+ and `xsl:output/@indent` have no analogue yet; literal whitespace is
470
+ preserved verbatim and is the whole story. An indentation story is
471
+ roadmap.
472
+ 4. **One document, no imports, no named templates** — inherited from
473
+ JSLT 0.1's composition stance.
474
+
475
+ ---
476
+
477
+ ## 11. API note (non-normative)
478
+
479
+ ```
480
+ compileJtltStylesheet(doc, options) -> render
481
+ render(data, externals?) // string out
482
+ render.externals // user parameter names (§7)
483
+ render.output // the resolved output method
484
+ render.doc // deeply frozen copy of the template
485
+ render.stylesheet // the frozen compiled JSLT stylesheet (§6)
486
+ renderText(template, data, externals?, options?) // one-shot, WeakMap-cached
487
+ ```
488
+
489
+ Module: `@jarenjs/json/jtlt`. `options` carries `compileTypeTest` (§8) and
490
+ `maxDepth` (JSLT-FORMAT §5.4), both for `compileJtltStylesheet` and as the
491
+ optional fourth argument of `renderText`. The one-shot function is the
492
+ counterpart of `queryJson`/`transformJson`, caching compiled templates by
493
+ document identity and compile-option values in a WeakMap.
494
+
495
+ ---
496
+
497
+ ## Appendix A. Worked examples (normative fixtures)
498
+
499
+ Every example is complete and destined to run verbatim as engine tests.
500
+ Unless noted, the output method is the default `"text"` and the mode is
501
+ the unnamed mode. Output strings are shown with escaped newlines.
502
+
503
+ ### A.1 The empty template renders the input's text
504
+
505
+ ```json
506
+ []
507
+ ```
508
+
509
+ Input:
510
+
511
+ ```json
512
+ { "greeting": "hello", "count": 2, "flag": true, "gap": null }
513
+ ```
514
+
515
+ Output: `"hello2true"` — the built-in rules walk the tree in document
516
+ order, atoms emit their text values, `null` emits nothing (§3.2, §4).
517
+ Contrast JSLT, whose empty stylesheet is the identity *JSON* transform.
518
+
519
+ ### A.2 A Markdown list
520
+
521
+ ```json
522
+ [ { "match": "$", "body": ["# Books\n", { "$apply": "$.store.book[*]" }] },
523
+ { "match": "$.store.book[*]", "body": ["- ", "$.title", " (", "$.price", ")\n"] } ]
524
+ ```
525
+
526
+ Input:
527
+
528
+ ```json
529
+ { "store": { "book": [ { "title": "A", "price": 8.95 },
530
+ { "title": "B", "price": 12.99 } ] } }
531
+ ```
532
+
533
+ Output: `"# Books\n- A (8.95)\n- B (12.99)\n"`. The root rule owns the
534
+ frame, the `$apply` splices one rendered line per book, each rule owns its
535
+ own newline (§3.4).
536
+
537
+ ### A.3 XML — escaped interpolation, raw literals, `$raw`
538
+
539
+ ```json
540
+ { "$jtlt": "0.1",
541
+ "output": "xml",
542
+ "rules": [
543
+ { "match": "$",
544
+ "body": ["<note title=\"", "$.title", "\">", { "$raw": "$.markup" }, "</note>"] }
545
+ ] }
546
+ ```
547
+
548
+ Input:
549
+
550
+ ```json
551
+ { "title": "Q&A", "markup": "<b>hi</b>" }
552
+ ```
553
+
554
+ Output: `"<note title=\"Q&amp;A\"><b>hi</b></note>"`. The interpolated
555
+ title is escaped — the same escape set serves the attribute value — while
556
+ the literal markup and the `$raw` splice pass through verbatim (§5.3).
557
+
558
+ ### A.4 Two modes: table of contents + body text
559
+
560
+ ```json
561
+ { "$jtlt": "0.1",
562
+ "rules": [
563
+ { "match": "$",
564
+ "body": ["TOC\n", { "$apply": ["$.sections[*]", "toc"] }, "\n",
565
+ { "$apply": "$.sections[*]" }] },
566
+ { "mode": "toc", "match": "$.sections[*]", "body": ["- ", "$.heading", "\n"] },
567
+ { "match": "$.sections[*]",
568
+ "body": ["== ", "$.heading", " ==\n", "$.text", "\n"] }
569
+ ] }
570
+ ```
571
+
572
+ Input:
573
+
574
+ ```json
575
+ { "sections": [
576
+ { "heading": "Introduction", "text": "Start here." },
577
+ { "heading": "Usage", "text": "Then this." } ] }
578
+ ```
579
+
580
+ Output:
581
+
582
+ ```
583
+ "TOC\n- Introduction\n- Usage\n\n== Introduction ==\nStart here.\n== Usage ==\nThen this.\n"
584
+ ```
585
+
586
+ The same sections render twice — once per mode, each mode with its own
587
+ rule chain, exactly JSLT-FORMAT §7 (and its Appendix A.4, in text).
588
+
589
+ ### A.5 `$apply` is a value-of with rule override
590
+
591
+ ```json
592
+ [ { "match": "$", "body": ["Title: ", { "$apply": "$.title" }, "\n"] } ]
593
+ ```
594
+
595
+ Input `{ "title": "Moby Dick" }` renders `"Title: Moby Dick\n"` — no rule
596
+ matches the string, so the built-in atom rule interpolates it (§4).
597
+ Appending a rule:
598
+
599
+ ```json
600
+ [ { "match": "$", "body": ["Title: ", { "$apply": "$.title" }, "\n"] },
601
+ { "match": "$.title", "body": ["«", "$", "»"] } ]
602
+ ```
603
+
604
+ renders `"Title: «Moby Dick»\n"` — the override takes the rendering
605
+ without the call site changing.
606
+
607
+ ### A.6 Parameters and the reserved externals
608
+
609
+ ```json
610
+ [ { "match": "$..price",
611
+ "body": ["$path", " = ", { "$mul": ["$", "$rate"] }, " ", "$root.currency", "\n"] } ]
612
+ ```
613
+
614
+ Called as `render(input, { rate: 1.21 })`; `render.externals` is
615
+ `["rate"]` — `root` and `path` are engine-bound and excluded (§7).
616
+
617
+ Input:
618
+
619
+ ```json
620
+ { "currency": "EUR", "items": [ { "sku": "a1", "price": 10 } ] }
621
+ ```
622
+
623
+ Output: `"$['items'][0]['price'] = 12.1 EUR\n"`. The other atoms
624
+ (`"EUR"`, `"a1"`) sit outside the matched location's rendering only
625
+ because the matched rule's output replaces the price *within the
626
+ built-in walk* — the walk still visits `currency` and `sku`, so the full
627
+ output begins with `EUR` and contains `a1`: precisely,
628
+ `"EURa1$['items'][0]['price'] = 12.1 EUR\n"`. This is deviation 1 of §10
629
+ in action; match the root to own the frame (A.2) when stray text is
630
+ unwelcome.
631
+
632
+ ### A.7 Embedding JSON with `$json`
633
+
634
+ ```json
635
+ [ { "match": "$", "body": ["const data = ", { "$json": "$" }, ";"] } ]
636
+ ```
637
+
638
+ Input `{ "a": [1, 2] }` renders `"const data = {\"a\":[1,2]};"` — the one
639
+ sanctioned way to put a container into the stream (§3.3).
640
+
641
+ ---
642
+
643
+ ## Appendix B. LLM structured output (non-normative)
644
+
645
+ No schema artifact is published for templates in version 0.1 — this is
646
+ the honest gap between JTLT and its siblings, and closing it is roadmap.
647
+ The intended derivation follows the JSLT artifact's discipline
648
+ (JSLT-FORMAT Appendix B): reuse the committed query artifact's definition
649
+ map for expression segments, define the segment alternation of §3.1 over
650
+ it (strings, nested lists, `$raw`/`$json`/`$apply` phrases), reuse the
651
+ JSLT artifact's rule scaffolding with `body` narrowed to the segment-list
652
+ array, and derive a draft-07 twin mechanically. Until that artifact
653
+ exists, generated templates should be validated by compiling them:
654
+ `compileJtltStylesheet` is the authority, and every rejection carries a
655
+ `docPath` into the template for a repair loop.
656
+
657
+ Templates remain ordinary JSON throughout a toolchain — function-call
658
+ arguments, retrieved rule sets, reviewed diffs, audit-log entries, and
659
+ replayable renderers without a text parser.