@endevops/effect-codec-xml 0.0.1 → 0.1.0-beta.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 (50) hide show
  1. package/README.md +81 -62
  2. package/dist/codec.d.ts +17 -9
  3. package/dist/codec.d.ts.map +1 -1
  4. package/dist/codec.js +29 -18
  5. package/dist/codec.js.map +1 -1
  6. package/dist/conventions.d.ts +4 -4
  7. package/dist/conventions.js +7 -7
  8. package/dist/conventions.js.map +1 -1
  9. package/dist/entities/entity-decoder.d.ts +33 -33
  10. package/dist/entities/entity-decoder.d.ts.map +1 -1
  11. package/dist/entities/entity-decoder.js +63 -64
  12. package/dist/entities/entity-decoder.js.map +1 -1
  13. package/dist/errors.d.ts +2 -2
  14. package/dist/errors.js +2 -2
  15. package/dist/errors.js.map +1 -1
  16. package/dist/namespaces.js +40 -13
  17. package/dist/namespaces.js.map +1 -1
  18. package/dist/naming.d.ts +6 -6
  19. package/dist/naming.d.ts.map +1 -1
  20. package/dist/naming.js +3 -3
  21. package/dist/naming.js.map +1 -1
  22. package/dist/parse.d.ts +5 -5
  23. package/dist/parse.js +14 -14
  24. package/dist/parse.js.map +1 -1
  25. package/dist/plain-value.js +242 -0
  26. package/dist/plain-value.js.map +1 -0
  27. package/dist/render.d.ts +1 -1
  28. package/dist/render.d.ts.map +1 -1
  29. package/dist/render.js +28 -28
  30. package/dist/render.js.map +1 -1
  31. package/dist/xml-error.d.ts +9 -9
  32. package/dist/xml-error.js +18 -18
  33. package/dist/xml-error.js.map +1 -1
  34. package/dist/xml-value.d.ts +7 -7
  35. package/dist/xml-value.d.ts.map +1 -1
  36. package/dist/xml-value.js +6 -7
  37. package/dist/xml-value.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/codec.ts +98 -71
  40. package/src/conventions.ts +7 -7
  41. package/src/entities/entity-decoder.ts +98 -99
  42. package/src/errors.ts +3 -3
  43. package/src/index.ts +3 -3
  44. package/src/namespaces.ts +62 -36
  45. package/src/naming.ts +34 -35
  46. package/src/parse.ts +26 -26
  47. package/src/plain-value.ts +312 -0
  48. package/src/render.ts +44 -44
  49. package/src/xml-error.ts +18 -18
  50. package/src/xml-value.ts +10 -11
package/README.md CHANGED
@@ -1,15 +1,15 @@
1
- # @endevops/effect-xml-codec
1
+ # @endevops/effect-codec-xml
2
2
 
3
3
  A round-trip Effect Schema codec for XML. `toCodecXml(schema)` returns a
4
4
  `Schema` whose `Encoded` is XML text, so `Schema.encodeSync` writes a document
5
- and `Schema.decodeSync` reads one back, the way `Schema.toCodecJson` works for
6
- JSON. There is no second call to a renderer or a parser at the call site.
5
+ and `Schema.decodeSync` reads one back, in the same way `Schema.toCodecJson` does
6
+ for JSON. There is no second call to a renderer or a parser at the call site.
7
7
  `renderXml` and `parseXml` are the text layer underneath, and remain available
8
8
  on their own.
9
9
 
10
10
  ```typescript
11
11
  import { Schema } from 'effect';
12
- import { toCodecXml } from '@endevops/effect-xml-codec';
12
+ import { toCodecXml } from '@endevops/effect-codec-xml';
13
13
 
14
14
  const Book = Schema.Struct({ '@id': Schema.String, title: Schema.String, pages: Schema.Number, tag: Schema.Array(Schema.String) });
15
15
 
@@ -27,7 +27,7 @@ Schema.decodeSync(codec)('<book id="1"><title>Dune</title><pages>412</pages><tag
27
27
 
28
28
  Effect 4 ships `Schema.toEncoderXml`, and it is one-way: a value goes in, an XML
29
29
  string comes out, and there is no way back. It also has no notion of an
30
- attribute — a field named `@id` becomes an element called `<@id>` with its name
30
+ attribute. A field named `@id` becomes an element called `<@id>` with its name
31
31
  rewritten.
32
32
 
33
33
  This package pairs Effect's own XML-value derivation, `Schema.toCodecStringTree`,
@@ -37,8 +37,8 @@ the text layer and the `@`/`#text` conventions.
37
37
 
38
38
  ## The mapping
39
39
 
40
- An `XmlValue` is the plainest value that can hold a document — strings, arrays
41
- and records. What gives it XML meaning is two key conventions:
40
+ An `XmlValue` is the plainest value that can hold a document: strings, arrays
41
+ and records. Two key conventions give it XML meaning:
42
42
 
43
43
  | In the value | In the document |
44
44
  | ----------------------- | ------------------------------------- |
@@ -62,6 +62,24 @@ decision rather than the parser's:
62
62
  `Schema.Struct({ '@id': Schema.String, '#text': Schema.String })` matches
63
63
  `<a id="1">hello</a>`.
64
64
 
65
+ A field that wants a plain value does not have to describe the attributes
66
+ themselves. Where the schema wants a scalar, a `#text` node is read on its own
67
+ and the other attributes are discarded, so
68
+ `Schema.Struct({ title: Schema.String })` also matches
69
+ `<title lang="en">Dune</title>` and yields `'Dune'`. An element that also
70
+ carries a child element there is refused instead, as an effect error: a plain
71
+ value has nowhere to put a child, and dropping one silently would lose a field
72
+ the document actually carried.
73
+
74
+ The same holds wherever a scalar sits: under a union, or under a repeated field,
75
+ which derives as a union of an array and `undefined`. Each branch is folded in
76
+ turn, so `Schema.Struct({ id: Schema.Literals(['E', 'S']) })` matches
77
+ `<id schemeID="UNCL5305">E</id>` inside either. The reverse also works: an
78
+ element the schema reads as a struct with an `xmlValue` field, reduced by the
79
+ parser to bare character data because it carries no attributes, has that string
80
+ put back under the value's key, so a nested price struct still reads
81
+ `<price>1.5</price>` instead of failing on the bare string.
82
+
65
83
  ## Namespaces
66
84
 
67
85
  A schema describes a value in local names, so a namespace is an annotation on
@@ -101,7 +119,7 @@ The annotation is attached with `Schema.annotate`:
101
119
 
102
120
  ```typescript
103
121
  import { Schema } from 'effect';
104
- import { toCodecXml } from '@endevops/effect-xml-codec';
122
+ import { toCodecXml } from '@endevops/effect-codec-xml';
105
123
 
106
124
  const SOAP = 'http://schemas.xmlsoap.org/soap/envelope/';
107
125
  const AUTH = 'urn:auth';
@@ -163,9 +181,9 @@ Two limits:
163
181
  | `XmlError` | The failure `EntityDecoder` and `validate` report. |
164
182
 
165
183
  `toCodecXml` returns a `Schema`, so encoding and decoding are `Schema.encodeSync`
166
- and `Schema.decodeSync` (or the `Effect` forms), and every other Schema operation
167
- — `Schema.toFormatter`, `Schema.toJsonSchemaDocument`, the guards — applies to it
168
- unchanged. A failure in either direction arrives as a `SchemaIssue.Issue`: a
184
+ and `Schema.decodeSync` (or the `Effect` forms). Every other Schema operation,
185
+ including `Schema.toFormatter`, `Schema.toJsonSchemaDocument` and the guards,
186
+ applies to it unchanged. A failure in either direction arrives as a
169
187
  document that will not parse or a value that will not write is reported with its
170
188
  underlying XML message, alongside the schema mismatches Effect already reports.
171
189
  The root element is named from the `rootName` option, then the schema's
@@ -176,21 +194,21 @@ The root element is named from the `rootName` option, then the schema's
176
194
  This package absorbed `@endevops/common-xml`, so the two primitives the text
177
195
  layer reads documents with ship here rather than as a sibling dependency.
178
196
 
179
- `EntityDecoder` expands the reference syntax XML inherits from HTML — `&name;`,
180
- `&#NNN;`, `&#xHH;` — with expansion limits, registration hooks, and a
197
+ `EntityDecoder` expands the reference syntax XML inherits from HTML: `&name;`,
198
+ `&#NNN;`, `&#xHH;`. It applies expansion limits, registration hooks, and a
181
199
  numeric-reference policy. `parseXml` uses it for character data and falls back
182
200
  to the raw text for a bare `&`. It reports failures as `XmlError`, a tagged
183
- error whose `reason` union is what `Effect.catchReason` narrows on.
201
+ error whose `reason` union is the one `Effect.catchReason` narrows on.
184
202
 
185
- The name validators are plain synchronous predicates — `isName`, `isNcName`,
186
- `isQName`, `isNmToken`, `isNmTokens` — because a regex test cannot fail and the
203
+ The name validators are plain synchronous predicates (`isName`, `isNcName`,
204
+ `isQName`, `isNmToken`, `isNmTokens`), because a regex test cannot fail and the
187
205
  renderer and parser call them per name in their hot loops. `sanitize` rewrites
188
206
  an illegal name into the nearest legal one, and `validate` reports why a name
189
207
  failed through an `Effect`.
190
208
 
191
209
  ```typescript
192
210
  import { Effect } from 'effect';
193
- import { EntityDecoder, isQName, sanitize, validate } from '@endevops/effect-xml-codec';
211
+ import { EntityDecoder, isQName, sanitize, validate } from '@endevops/effect-codec-xml';
194
212
 
195
213
  isQName('svg:circle'); // true
196
214
  sanitize('not a name', 'ncName'); // 'not_a_name'
@@ -208,10 +226,10 @@ did not come across: nothing in this package reaches them.
208
226
  **The derivation is Effect's.** `toCodecXml` derives
209
227
  `Schema.toCodecStringTree`, the same derivation `Schema.toEncoderXml` uses, and
210
228
  runs it through this package's `renderXml` and `parseXml` on the two text
211
- directions. That is what makes every schema feature Effect supports — structs,
212
- arrays, unions, records, recursion, refinements, branded types, transformations —
213
- work here without this package re-implementing the walk over a schema AST, and it
214
- is what the round-trip specs are exercising.
229
+ directions. Every schema feature Effect supports then works here without this
230
+ package re-implementing the walk over a schema AST: structs,
231
+ arrays, unions, records, recursion, refinements, branded types, transformations.
232
+ The round-trip specs exercise that.
215
233
 
216
234
  **The shape is `toCodecJson`'s.** The value `toCodecXml` returns is a `Schema`:
217
235
  `Type` is the source schema's `Type`, `Encoded` is XML text, the service
@@ -232,8 +250,8 @@ normalization would otherwise eat it: a literal newline in an attribute comes
232
250
  back as a space unless it is written `&#10;`.
233
251
 
234
252
  **Comments and processing instructions are markup, not data.** The parser skips
235
- them, which is what `XMLBuilder` does by default. CDATA becomes character data,
236
- since that is what it is.
253
+ them, which is the default `XMLBuilder` behaviour. CDATA becomes character data,
254
+ because that is what it is.
237
255
 
238
256
  ## Performance
239
257
 
@@ -256,40 +274,41 @@ Four findings shaped the code, and all are measured rather than assumed:
256
274
 
257
275
  - **Escaping was the whole cost of a large document.** The entity encoder that
258
276
  used to live here escaped by applying five sequential global replacements, one
259
- per character, so a document with a single `&` in twenty thousand characters
260
- was scanned five times over to change one byte — 58µs for that one document.
261
- Escaping is now a single pattern scan to find the first character that needs
262
- replacing, then one pass to build the result, which is 7.6x faster for clean
263
- text and 28x faster for text with a character in it. `src/render.spec.ts` pins
264
- the fast path with explicit expectations.
277
+ per character. A document with a single `&` in twenty thousand characters was
278
+ therefore scanned five times over to change one byte, at 58µs for that one
279
+ document. Escaping is now a single pattern scan to find the first character
280
+ that needs replacing, then one pass to build the result. That is 7.6x faster
281
+ for clean text and 28x faster for text with a character in it.
282
+ `src/render.spec.ts` pins the fast path with explicit expectations.
265
283
  - **Resolving a name is a regex test, not an `Effect`.** The validators used to
266
284
  return `Effect`s for pure questions, which left the renderer and the parser
267
285
  running an effect per distinct element and attribute name in every document.
268
286
  Running a runtime to read a boolean cost roughly 1µs per name, and a small
269
- document has about a dozen names, so name resolution was most of what a
287
+ document has about a dozen names. Name resolution was therefore most of what a
270
288
  serialize and a parse did. The merged package now exposes plain synchronous
271
289
  predicates (`isQName` and the rest, `sanitize`) with no `Effect` wrapper, and
272
290
  the renderer's namer and the parser's name cache call them directly.
273
291
  `resolveName` is the one exception: it is effectful because `'error'` mode
274
- rejects an illegal name, and a rejection is a failure rather than a value, so
275
- it reports through the error channel like every other fallible step of a parse
276
- or a render. That is the bulk of the gain on a small document; the 500-row
277
- document improves by a few percent because it asks the same handful of names,
278
- and the per-row work is what dominates there.
292
+ rejects an illegal name, and a rejection is a failure rather than a value. It
293
+ reports through the error channel like every other fallible step of a parse
294
+ or a render. That accounts for most of the gain on a small document. The
295
+ 500-row document improves by a few percent because it asks the same handful of
296
+ names, and the per-row work dominates there.
279
297
  - **The codec adds no layer of its own.** `toCodecXml` derives
280
298
  `Schema.toCodecStringTree` and runs the tree through `renderXml`, so a value is
281
299
  encoded by Effect's parser and then rendered, with nothing wrapped around
282
- either. Dropping the extra `Schema.toCodecArrayFromSingle` layer the codec used
283
- to carry is part of why the table above is higher than the numbers this README
284
- quoted before: the single-element-array leniency is the caller's to compose now,
285
- and the plain codec does not pay for it on every array.
300
+ either. The codec also no longer carries an extra
301
+ `Schema.toCodecArrayFromSingle` layer. Dropping it is part of why the table
302
+ above is higher than the numbers this README quoted before: the
303
+ single-element-array leniency is the caller's to compose now, and the plain
304
+ codec does not pay for it on every array.
286
305
  - **A small document is dominated by something this package does not own.** Of
287
306
  the ~9µs it takes to serialize one, roughly 2.5µs is Effect's
288
307
  `toCodecStringTree` derivation, which walks the schema on every call, and the
289
308
  rest is this package's renderer. The renderer and parser underneath run at
290
- roughly 2.5µs and 2.5µs on a flat document. A caller serializing the same
291
- shape on every request should build the codec once and reuse it, which is what
292
- the API is shaped for.
309
+ roughly 2.5µs and 2.5µs on a flat document. A caller that serializes the same
310
+ shape on every request should build the codec once and reuse it, which is the
311
+ shape the API encourages.
293
312
 
294
313
  The other things the benchmarks changed: one pass over a record's keys instead of
295
314
  one per role a key can play, name resolution memoized per document rather than
@@ -311,14 +330,14 @@ fallback all see it.
311
330
  ### Against the libraries on npm
312
331
 
313
332
  `packages/benchmarks/bench/comparison.bench.ts` measures the same object through
314
- this codec and
315
- through three npm libraries — `fast-xml-builder`, `fast-xml-parser`, and
316
- `@nodable/flexible-xml-parser`. The equivalence is established rather than
317
- assumed: with `attributeNamePrefix: '@'`, **`fast-xml-builder` produces
318
- byte-identical output to this codec**, and the benchmark asserts it, so a change
319
- that breaks it fails the suite instead of quietly comparing different work.
333
+ this codec and through three npm libraries: `fast-xml-builder`, `fast-xml-parser`,
334
+ and `@nodable/flexible-xml-parser`. The benchmark establishes the equivalence
335
+ rather than assuming it. With `attributeNamePrefix: '@'`,
336
+ **`fast-xml-builder` produces byte-identical output to this codec**, and the
337
+ benchmark asserts it, so a change that breaks it fails the suite instead of
338
+ quietly comparing different work.
320
339
 
321
- **Encoding** — one object to the same bytes:
340
+ **Encoding**: one object to the same bytes:
322
341
 
323
342
  | Document | This codec | `fast-xml-builder` |
324
343
  | ------------------- | ---------- | ------------------ |
@@ -326,7 +345,7 @@ that breaks it fails the suite instead of quietly comparing different work.
326
345
  | 500 rows | 1,698/s | 1,269/s (0.75x) |
327
346
  | one large text node | 584,358/s | 254,355/s (0.44x) |
328
347
 
329
- **Decoding** — one document to the same value:
348
+ **Decoding**: one document to the same value:
330
349
 
331
350
  | Document | This codec | `@nodable/flexible-xml-parser` | `fast-xml-parser` |
332
351
  | ------------------- | ---------- | ------------------------------ | ----------------- |
@@ -334,9 +353,9 @@ that breaks it fails the suite instead of quietly comparing different work.
334
353
  | 500 rows | 1,975/s | 70/s (0.04x) | 486/s (0.25x) |
335
354
  | one large text node | 20,715/s | 5,694/s (0.27x) | 8,209/s (0.40x) |
336
355
 
337
- **A full round trip**, which is the number an application actually pays. Neither
338
- a builder nor a parser can do both halves, so the last two rows are each
339
- ecosystem doing the same work with two libraries and hand-joining them:
356
+ **A full round trip**, the number an application actually pays. Neither a
357
+ builder nor a parser can do both halves, so the last two rows are each ecosystem
358
+ doing the same work with two libraries and hand-joining them:
340
359
 
341
360
  | Path | Throughput |
342
361
  | ----------------------------------------------------------- | ---------------- |
@@ -346,14 +365,14 @@ ecosystem doing the same work with two libraries and hand-joining them:
346
365
 
347
366
  **This codec reaches the parser and renderer directly.** It encodes and decodes
348
367
  through `Schema.encodeSync` and `Schema.decodeSync`, with no runtime per call,
349
- which is why it is the fastest row on both sides. The npm parsers do less work —
350
- they do not validate the result against a schema — so the comparison is
368
+ so it is the fastest row on both sides. The npm parsers do less work, because
369
+ they do not validate the result against a schema, so the comparison is
351
370
  directional rather than like-for-like.
352
371
 
353
- Four things to be straight about when reading those tables:
372
+ Four caveats apply when reading those tables:
354
373
 
355
374
  - **These are one machine's numbers, from one run.** The relative error is under
356
- 2% on every row, but the machine's load moved between runs, so treat the
375
+ 2% on every row, but the machine's load moved between runs. Treat the
357
376
  ratios as the durable part and the absolute figures as a range.
358
377
  - **This codec's decode does strictly more work.** It parses _and_ validates the
359
378
  result against the schema, coercing `"30"` to `30` and failing on a mismatch.
@@ -361,11 +380,11 @@ Four things to be straight about when reading those tables:
361
380
  2,984/s and the schema pass brings it to 1,975/s, so roughly a third of the
362
381
  decode time is validation the comparison rows do not pay.
363
382
  - **The parsers do work this codec does not.** They coerce tag values through a
364
- value-parser pipeline — entity decoding, whitespace normalizing, boolean and
365
- number parsing — where a schema already decided the type. Both sides have work
366
- the other lacks, and neither is idle.
383
+ value-parser pipeline: entity decoding, whitespace normalizing, boolean and
384
+ number parsing, where a schema already decided the type. Both sides have work
385
+ the other lacks, and both are busy.
367
386
  - **Neither ecosystem is doing the whole job on its own.** A builder has no
368
- reader and a parser has no writer, so the round-trip table is the honest
387
+ reader and a parser has no writer, so the round-trip table is the fair
369
388
  comparison and the single-direction tables are the diagnostic ones.
370
389
 
371
390
  ## Limitations
@@ -396,7 +415,7 @@ quietly.
396
415
  children and read back as a single run, so `<p>a<b/>c</p>` reads as
397
416
  `{ b: '', '#text': 'ac' }`. Reading and re-rendering is stable from there on.
398
417
  - **Whitespace at the edges of text is trimmed** unless `preserveWhitespace` is
399
- set. That is what makes a pretty-printed document read as the same value as an
418
+ set. That lets a pretty-printed document read as the same value as an
400
419
  unindented one. Whitespace _inside_ a run is never touched.
401
420
  - **`Schema.BigInt` is write-only.** Effect's `StringTree` derivation lowers one
402
421
  to its decimal text and has no way to raise it again.
package/dist/codec.d.ts CHANGED
@@ -5,8 +5,8 @@ import { Schema } from "effect";
5
5
  /**
6
6
  * @description Options for {@link toCodecXml}.\
7
7
  * The render options name and shape the document; the parse options decide how strictly it is read back.\
8
- * `rootName` is the one the codec resolves for itself when the caller leaves it out, taking it from the schema's `identifier` or `title` annotation and falling
9
- * back to `'root'`.
8
+ * `rootName` is the one the codec resolves for itself when the caller leaves it out. The codec takes it from the schema's `identifier` or `title` annotation and
9
+ * falls back to `'root'`.
10
10
  */
11
11
  export type XmlCodecOptions = XmlRenderOptions & XmlParseOptions;
12
12
  /**
@@ -19,15 +19,18 @@ export interface toCodecXml<S extends Schema.Constraint> extends Schema.decodeTo
19
19
  /**
20
20
  * @description Derives the XML codec for a schema: a `Schema` whose `Encoded` is an XML document, so `Schema.encodeSync(codec)` writes text and
21
21
  * `Schema.decodeSync(codec)` reads it back. The derivation is Effect's `Schema.toCodecStringTree`; the text layer is this package's {@link renderXml}
22
- * and {@link parseXml}.
22
+ * and {@link parseXml}. Call it data-first, `toCodecXml(schema, options)`, or data-last, `toCodecXml(options)(schema)`, so it drops into `pipe` beside
23
+ * the rest of the Effect combinators. The two forms are one function: the first argument decides the style, a schema is read as data-first and an
24
+ * options object as data-last. `pipe(schema, toCodecXml)` carries no options and works because a schema on its own is data-first.
23
25
  *
24
26
  * @example
25
27
  * ```typescript
26
- * import { Schema } from 'effect';
27
- * import { toCodecXml } from '@endevops/effect-xml-codec';
28
+ * import { Schema, pipe } from 'effect';
29
+ * import { toCodecXml } from '@endevops/effect-codec-xml';
28
30
  *
29
31
  * const Book = Schema.Struct({ '@id': Schema.String, title: Schema.String, pages: Schema.Number });
30
32
  * const codec = toCodecXml(Book, { rootName: 'book' });
33
+ * const piped = pipe(Book, toCodecXml({ rootName: 'book' }));
31
34
  *
32
35
  * const value = { '@id': '1', title: 'Dune', pages: 412 };
33
36
  *
@@ -38,11 +41,16 @@ export interface toCodecXml<S extends Schema.Constraint> extends Schema.decodeTo
38
41
  * // => { '@id': '1', title: 'Dune', pages: 412 }
39
42
  * ```;
40
43
  *
41
- * @param schema - The schema describing the value.
42
- * @param options - Render and parse options. `rootName` defaults to the schema's `identifier` or `title` annotation, then to `'root'`.
44
+ * @param schema - The schema describing the value, in the data-first form.
45
+ * @param options - Render and parse options. `rootName` defaults to the schema's `identifier` or `title` annotation, then to `'root'`. In the
46
+ * data-last form this is the only argument, and the schema arrives from `pipe`.
43
47
  *
44
- * @returns The codec, with the source schema's `Type` and the same service requirements.
48
+ * @returns The codec, with the source schema's `Type` and the same service requirements. In the data-last form, a function from the schema to the
49
+ * codec.
45
50
  */
46
- export declare const toCodecXml: <S extends Schema.Constraint>(schema: S, options?: XmlCodecOptions) => toCodecXml<S>;
51
+ export declare const toCodecXml: {
52
+ <S extends Schema.Constraint>(schema: S, options?: XmlCodecOptions): toCodecXml<S>;
53
+ (options?: XmlCodecOptions): <S extends Schema.Constraint>(schema: S) => toCodecXml<S>;
54
+ };
47
55
  //#endregion
48
56
  //# sourceMappingURL=codec.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"codec.d.ts","names":[],"sources":["../src/codec.ts"],"mappings":";;;;;;;;;;YAyCY,kBAAkB,mBAAmB;;;;;iBAMhC,WAAW,UAAU,OAAO,oBAAoB,OAAO,SAAS,OAAO,kBAAkB,IAAI,OAAO;WAC1G,SAAS,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA8BlB,aAAc,UAAU,OAAO,YAAU,QAAU,GAAC,UAAW,oBAAuB,WAAW"}
1
+ {"version":3,"file":"codec.d.ts","names":[],"sources":["../src/codec.ts"],"mappings":";;;;;;;;;;YA2CY,kBAAkB,mBAAmB;;;;;iBAMhC,WAAW,UAAU,OAAO,oBAAoB,OAAO,SAAS,OAAO,kBAAkB,IAAI,OAAO;WAC1G,SAAS,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAsDlB;GACV,UAAU,OAAO,YAAY,QAAQ,GAAG,UAAU,kBAAkB,WAAW;GAC/E,UAAU,mBAAmB,UAAU,OAAO,YAAY,QAAQ,MAAM,WAAW"}
package/dist/codec.js CHANGED
@@ -1,21 +1,34 @@
1
1
  import "./conventions.js";
2
2
  import { decodeNames, encodeNames, namespacePlan } from "./namespaces.js";
3
3
  import { parseXml } from "./parse.js";
4
+ import { normalizePlainValue } from "./plain-value.js";
4
5
  import { renderXml } from "./render.js";
5
- import { Effect, Predicate, Schema, SchemaAST, SchemaIssue, SchemaTransformation } from "effect";
6
+ import { Effect, Function, Predicate, Schema, SchemaAST, SchemaIssue, SchemaTransformation } from "effect";
6
7
  //#region src/codec.ts
8
+ const isActivePlan = (plan) => plan.byKey.size > 0 || plan.nameByKey.size > 0 || plan.attributeKeys.size > 0 || plan.valueByElement.size > 0 || plan.root !== void 0 || plan.rootName !== void 0;
9
+ const resolveRenderOptions = (schema, plan, options) => {
10
+ const rootName = options.rootName ?? plan.rootName ?? SchemaAST.resolveIdentifier(schema.ast) ?? SchemaAST.resolveTitle(schema.ast) ?? "root";
11
+ const wireRootName = plan.root !== void 0 && plan.root.prefix !== "" && !rootName.includes(":") ? `${plan.root.prefix}:${rootName}` : rootName;
12
+ return {
13
+ ...options,
14
+ rootName: wireRootName
15
+ };
16
+ };
7
17
  /**
8
18
  * @description Derives the XML codec for a schema: a `Schema` whose `Encoded` is an XML document, so `Schema.encodeSync(codec)` writes text and
9
19
  * `Schema.decodeSync(codec)` reads it back. The derivation is Effect's `Schema.toCodecStringTree`; the text layer is this package's {@link renderXml}
10
- * and {@link parseXml}.
20
+ * and {@link parseXml}. Call it data-first, `toCodecXml(schema, options)`, or data-last, `toCodecXml(options)(schema)`, so it drops into `pipe` beside
21
+ * the rest of the Effect combinators. The two forms are one function: the first argument decides the style, a schema is read as data-first and an
22
+ * options object as data-last. `pipe(schema, toCodecXml)` carries no options and works because a schema on its own is data-first.
11
23
  *
12
24
  * @example
13
25
  * ```typescript
14
- * import { Schema } from 'effect';
15
- * import { toCodecXml } from '@endevops/effect-xml-codec';
26
+ * import { Schema, pipe } from 'effect';
27
+ * import { toCodecXml } from '@endevops/effect-codec-xml';
16
28
  *
17
29
  * const Book = Schema.Struct({ '@id': Schema.String, title: Schema.String, pages: Schema.Number });
18
30
  * const codec = toCodecXml(Book, { rootName: 'book' });
31
+ * const piped = pipe(Book, toCodecXml({ rootName: 'book' }));
19
32
  *
20
33
  * const value = { '@id': '1', title: 'Dune', pages: 412 };
21
34
  *
@@ -26,29 +39,27 @@ import { Effect, Predicate, Schema, SchemaAST, SchemaIssue, SchemaTransformation
26
39
  * // => { '@id': '1', title: 'Dune', pages: 412 }
27
40
  * ```;
28
41
  *
29
- * @param schema - The schema describing the value.
30
- * @param options - Render and parse options. `rootName` defaults to the schema's `identifier` or `title` annotation, then to `'root'`.
42
+ * @param schema - The schema describing the value, in the data-first form.
43
+ * @param options - Render and parse options. `rootName` defaults to the schema's `identifier` or `title` annotation, then to `'root'`. In the
44
+ * data-last form this is the only argument, and the schema arrives from `pipe`.
31
45
  *
32
- * @returns The codec, with the source schema's `Type` and the same service requirements.
46
+ * @returns The codec, with the source schema's `Type` and the same service requirements. In the data-last form, a function from the schema to the
47
+ * codec.
33
48
  */
34
- const toCodecXml = (schema, options = {}) => {
49
+ const toCodecXml = Function.dual((args) => Schema.isSchema(args[0]), (schema, options = {}) => {
35
50
  const planned = namespacePlan(schema);
36
51
  if (Predicate.hasProperty(planned, "error")) throw new Error(`Invalid XML namespace annotation:\n\t- ${planned.error}.`);
37
52
  const plan = planned.plan;
38
- const active = plan.byKey.size > 0 || plan.nameByKey.size > 0 || plan.attributeKeys.size > 0 || plan.valueByElement.size > 0 || plan.root !== void 0 || plan.rootName !== void 0;
39
- const rootName = options.rootName ?? plan.rootName ?? SchemaAST.resolveIdentifier(schema.ast) ?? SchemaAST.resolveTitle(schema.ast) ?? "root";
40
- const wireRootName = plan.root !== void 0 && plan.root.prefix !== "" && !rootName.includes(":") ? `${plan.root.prefix}:${rootName}` : rootName;
41
- const renderOptions = {
42
- ...options,
43
- rootName: wireRootName
44
- };
45
- return Schema.String.pipe(Schema.decodeTo(Schema.toCodecStringTree(schema), SchemaTransformation.transformEffect({
53
+ const active = isActivePlan(plan);
54
+ const renderOptions = resolveRenderOptions(schema, plan, options);
55
+ const stringTree = Schema.toCodecStringTree(schema);
56
+ return Schema.String.pipe(Schema.decodeTo(stringTree, SchemaTransformation.transformEffect({
46
57
  decode: (text, parseOptions) => {
47
58
  if (!Predicate.isString(text)) return Effect.fail(new SchemaIssue.InvalidValue({ message: `Expected a string, but received ${typeof text}.` }, text, parseOptions));
48
59
  return parseXml(text, options).pipe(Effect.map((value) => active ? decodeNames(value, plan, {}, "") : value), Effect.tapError((error) => Effect.logError(`XML parse error: ${error.message}`).pipe(Effect.annotateLogs({
49
60
  cause: error,
50
61
  message: "XML parse error"
51
- }))), Effect.mapError((error) => new SchemaIssue.InvalidValue({ message: error.message }, text, parseOptions)));
62
+ }))), Effect.mapError((error) => new SchemaIssue.InvalidValue({ message: error.message }, text, parseOptions)), Effect.flatMap((value) => Effect.fromResult(normalizePlainValue(value, stringTree.ast, "")).pipe(Effect.mapError((message) => new SchemaIssue.InvalidValue({ message }, value, parseOptions)))));
52
63
  },
53
64
  encode: (value, parseOptions) => {
54
65
  if (active && Array.isArray(value) && (plan.root !== void 0 || plan.rootName !== void 0)) return Effect.fail(new SchemaIssue.InvalidValue({ message: "An array at the root of a namespaced schema cannot carry the root namespace or name." }, value, parseOptions));
@@ -56,7 +67,7 @@ const toCodecXml = (schema, options = {}) => {
56
67
  return renderXml(wire, renderOptions).pipe(Effect.mapError((error) => new SchemaIssue.InvalidValue({ message: error.message }, value, parseOptions)));
57
68
  }
58
69
  })));
59
- };
70
+ });
60
71
  //#endregion
61
72
  export { toCodecXml };
62
73
 
package/dist/codec.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"codec.js","names":[],"sources":["../src/codec.ts"],"sourcesContent":["// The codec: a schema, and the XML text that carries it.\n//\n// `toCodecXml` is this package's counterpart to `Schema.toCodecJson`. It\n// returns a `Schema` whose `Type` is the source schema's `Type` and whose\n// `Encoded` is XML text, so a value is written with `Schema.encodeSync(codec)`\n// and read back with `Schema.decodeSync(codec)` — one call each, the way the\n// JSON codec works. There is no value tree at the call site and no second call\n// to a renderer or a parser.\n//\n// The derivation underneath is Effect's own `Schema.toCodecStringTree`, so\n// every schema feature Effect supports composes here without this package\n// re-implementing the walk over a schema AST. On the way out the codec runs the\n// value tree through `renderXml`; on the way back in it runs the document\n// through `parseXml`. Both are the same text layer this package exports on\n// their own, and both failures — an illegal name, a document that is not\n// well-formed — arrive as the `SchemaIssue.Issue` a schema is expected to\n// report, with the underlying message preserved.\n//\n// The conventions stay in the keys: a key starting with `@` is an attribute,\n// `#text` is character data, and every other key is a child element. The root\n// element is named from the schema's `identifier` or `title` annotation when it\n// has one, and from the `rootName` option otherwise; it defaults to `'root'`,\n// the same name Effect's own XML encoder uses.\n\nimport { Effect, Predicate, Schema, SchemaAST, SchemaIssue, SchemaTransformation } from 'effect';\n\nimport type { XmlParseOptions } from './parse.ts';\nimport type { XmlRenderOptions } from './render.ts';\nimport type { XmlValue } from './xml-value.ts';\n\nimport { DEFAULT_ROOT_NAME } from './conventions.ts';\nimport { decodeNames, encodeNames, namespacePlan, ROOT_ELEMENT } from './namespaces.ts';\nimport { parseXml } from './parse.ts';\nimport { renderXml } from './render.ts';\n\n/**\n * @description Options for {@link toCodecXml}.\\\n * The render options name and shape the document; the parse options decide how strictly it is read back.\\\n * `rootName` is the one the codec resolves for itself when the caller leaves it out, taking it from the schema's `identifier` or `title` annotation and falling\n * back to `'root'`.\n */\nexport type XmlCodecOptions = XmlRenderOptions & XmlParseOptions;\n\n/**\n * @description The XML codec for a schema, as a `Schema`. `Type` is the schema's own `Type` and `Encoded` is XML text, so it encodes a value to a document and\n * decodes a document to a value in one step each. The service requirements of the source schema are preserved.\n */\nexport interface toCodecXml<S extends Schema.Constraint> extends Schema.decodeTo<Schema.toCodecStringTree<S>, Schema.String> {\n readonly Rebuild: toCodecXml<S>;\n}\n\n/**\n * @description Derives the XML codec for a schema: a `Schema` whose `Encoded` is an XML document, so `Schema.encodeSync(codec)` writes text and\n * `Schema.decodeSync(codec)` reads it back. The derivation is Effect's `Schema.toCodecStringTree`; the text layer is this package's {@link renderXml}\n * and {@link parseXml}.\n *\n * @example\n * ```typescript\n * import { Schema } from 'effect';\n * import { toCodecXml } from '@endevops/effect-xml-codec';\n *\n * const Book = Schema.Struct({ '@id': Schema.String, title: Schema.String, pages: Schema.Number });\n * const codec = toCodecXml(Book, { rootName: 'book' });\n *\n * const value = { '@id': '1', title: 'Dune', pages: 412 };\n *\n * Schema.encodeSync(codec)(value);\n * // => '<book id=\"1\"><title>Dune</title><pages>412</pages></book>'\n *\n * Schema.decodeSync(codec)('<book id=\"1\"><title>Dune</title><pages>412</pages></book>');\n * // => { '@id': '1', title: 'Dune', pages: 412 }\n * ```;\n *\n * @param schema - The schema describing the value.\n * @param options - Render and parse options. `rootName` defaults to the schema's `identifier` or `title` annotation, then to `'root'`.\n *\n * @returns The codec, with the source schema's `Type` and the same service requirements.\n */\nexport const toCodecXml = <S extends Schema.Constraint>(schema: S, options: XmlCodecOptions = {}): toCodecXml<S> => {\n const planned = namespacePlan(schema);\n if (Predicate.hasProperty(planned, 'error')) {\n throw new Error(`Invalid XML namespace annotation:\\n\\t- ${planned.error}.`);\n }\n const plan = planned.plan;\n const active =\n plan.byKey.size > 0 ||\n plan.nameByKey.size > 0 ||\n plan.attributeKeys.size > 0 ||\n plan.valueByElement.size > 0 ||\n plan.root !== undefined ||\n plan.rootName !== undefined;\n\n const rootName =\n options.rootName ?? plan.rootName ?? SchemaAST.resolveIdentifier(schema.ast) ?? SchemaAST.resolveTitle(schema.ast) ?? DEFAULT_ROOT_NAME;\n const wireRootName = plan.root !== undefined && plan.root.prefix !== '' && !rootName.includes(':') ? `${plan.root.prefix}:${rootName}` : rootName;\n\n const renderOptions: XmlRenderOptions = { ...options, rootName: wireRootName };\n\n return Schema.String.pipe(\n Schema.decodeTo(\n Schema.toCodecStringTree(schema),\n SchemaTransformation.transformEffect({\n decode: (text, parseOptions) => {\n if (!Predicate.isString(text)) {\n return Effect.fail(new SchemaIssue.InvalidValue({ message: `Expected a string, but received ${typeof text}.` }, text, parseOptions));\n }\n\n return parseXml(text, options).pipe(\n Effect.map(value => (active ? decodeNames(value, plan, {}, ROOT_ELEMENT) : value)),\n Effect.tapError(error =>\n Effect.logError(`XML parse error: ${error.message}`).pipe(Effect.annotateLogs({ cause: error, message: 'XML parse error' }))\n ),\n Effect.mapError(error => new SchemaIssue.InvalidValue({ message: error.message }, text, parseOptions))\n );\n },\n\n encode: (value, parseOptions) => {\n if (active && Array.isArray(value) && (plan.root !== undefined || plan.rootName !== undefined)) {\n return Effect.fail(\n new SchemaIssue.InvalidValue(\n { message: 'An array at the root of a namespaced schema cannot carry the root namespace or name.' },\n value,\n parseOptions\n )\n );\n }\n\n const wire = active ? encodeNames(value as XmlValue, plan, plan.root, {}, ROOT_ELEMENT) : (value as XmlValue);\n return renderXml(wire, renderOptions).pipe(\n Effect.mapError(error => new SchemaIssue.InvalidValue({ message: error.message }, value, parseOptions))\n );\n },\n })\n )\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8EA,MAAa,cAA2C,QAAW,UAA2B,CAAC,MAAqB;CAClH,MAAM,UAAU,cAAc,MAAM;CACpC,IAAI,UAAU,YAAY,SAAS,OAAO,GACxC,MAAM,IAAI,MAAM,0CAA0C,QAAQ,MAAM,EAAE;CAE5E,MAAM,OAAO,QAAQ;CACrB,MAAM,SACJ,KAAK,MAAM,OAAO,KAClB,KAAK,UAAU,OAAO,KACtB,KAAK,cAAc,OAAO,KAC1B,KAAK,eAAe,OAAO,KAC3B,KAAK,SAAS,KAAA,KACd,KAAK,aAAa,KAAA;CAEpB,MAAM,WACJ,QAAQ,YAAY,KAAK,YAAY,UAAU,kBAAkB,OAAO,GAAG,KAAK,UAAU,aAAa,OAAO,GAAG,KAAA;CACnH,MAAM,eAAe,KAAK,SAAS,KAAA,KAAa,KAAK,KAAK,WAAW,MAAM,CAAC,SAAS,SAAS,GAAG,IAAI,GAAG,KAAK,KAAK,OAAO,GAAG,aAAa;CAEzI,MAAM,gBAAkC;EAAE,GAAG;EAAS,UAAU;CAAa;CAE7E,OAAO,OAAO,OAAO,KACnB,OAAO,SACL,OAAO,kBAAkB,MAAM,GAC/B,qBAAqB,gBAAgB;EACnC,SAAS,MAAM,iBAAiB;GAC9B,IAAI,CAAC,UAAU,SAAS,IAAI,GAC1B,OAAO,OAAO,KAAK,IAAI,YAAY,aAAa,EAAE,SAAS,mCAAmC,OAAO,KAAK,GAAG,GAAG,MAAM,YAAY,CAAC;GAGrI,OAAO,SAAS,MAAM,OAAO,CAAC,CAAC,KAC7B,OAAO,KAAI,UAAU,SAAS,YAAY,OAAO,MAAM,CAAC,GAAA,EAAe,IAAI,KAAM,GACjF,OAAO,UAAS,UACd,OAAO,SAAS,oBAAoB,MAAM,SAAS,CAAC,CAAC,KAAK,OAAO,aAAa;IAAE,OAAO;IAAO,SAAS;GAAkB,CAAC,CAAC,CAC7H,GACA,OAAO,UAAS,UAAS,IAAI,YAAY,aAAa,EAAE,SAAS,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,CACvG;EACF;EAEA,SAAS,OAAO,iBAAiB;GAC/B,IAAI,UAAU,MAAM,QAAQ,KAAK,MAAM,KAAK,SAAS,KAAA,KAAa,KAAK,aAAa,KAAA,IAClF,OAAO,OAAO,KACZ,IAAI,YAAY,aACd,EAAE,SAAS,uFAAuF,GAClG,OACA,YACF,CACF;GAGF,MAAM,OAAO,SAAS,YAAY,OAAmB,MAAM,KAAK,MAAM,CAAC,GAAA,EAAe,IAAK;GAC3F,OAAO,UAAU,MAAM,aAAa,CAAC,CAAC,KACpC,OAAO,UAAS,UAAS,IAAI,YAAY,aAAa,EAAE,SAAS,MAAM,QAAQ,GAAG,OAAO,YAAY,CAAC,CACxG;EACF;CACF,CAAC,CACH,CACF;AACF"}
1
+ {"version":3,"file":"codec.js","names":[],"sources":["../src/codec.ts"],"sourcesContent":["// The codec: a schema, and the XML text that carries it.\n//\n// `toCodecXml` is this package's counterpart to `Schema.toCodecJson`. It\n// returns a `Schema` whose `Type` is the source schema's `Type` and whose\n// `Encoded` is XML text, so a value is written with `Schema.encodeSync(codec)`\n// and read back with `Schema.decodeSync(codec)`. That is one call each, as with\n// the JSON codec. There is no value tree at the call site and no second call to\n// a renderer or a parser.\n//\n// The derivation underneath is Effect's own `Schema.toCodecStringTree`, so\n// every schema feature Effect supports composes here without this package\n// re-implementing the walk over a schema AST. On encode the codec runs the value\n// tree through `renderXml`; on decode it runs the document through `parseXml`.\n// Both are the same text layer this package exports on its own, and both\n// failures arrive as the `SchemaIssue.Issue` a schema is expected to report,\n// with the underlying message preserved. Those failures are an illegal name and\n// a document that is not well-formed.\n//\n// The conventions stay in the keys: a key starting with `@` is an attribute,\n// `#text` is character data, and every other key is a child element. The root\n// element is named from the schema's `identifier` or `title` annotation when it\n// has one, and from the `rootName` option otherwise; it defaults to `'root'`,\n// the same name Effect's own XML encoder uses.\n\nimport { Effect, Function, Predicate, Schema, SchemaAST, SchemaIssue, SchemaTransformation } from 'effect';\n\nimport type { NamespacePlan } from './namespaces.ts';\nimport type { XmlParseOptions } from './parse.ts';\nimport type { XmlRenderOptions } from './render.ts';\nimport type { XmlValue } from './xml-value.ts';\n\nimport { DEFAULT_ROOT_NAME } from './conventions.ts';\nimport { decodeNames, encodeNames, namespacePlan, ROOT_ELEMENT } from './namespaces.ts';\nimport { parseXml } from './parse.ts';\nimport { normalizePlainValue } from './plain-value.ts';\nimport { renderXml } from './render.ts';\n\n/**\n * @description Options for {@link toCodecXml}.\\\n * The render options name and shape the document; the parse options decide how strictly it is read back.\\\n * `rootName` is the one the codec resolves for itself when the caller leaves it out. The codec takes it from the schema's `identifier` or `title` annotation and\n * falls back to `'root'`.\n */\nexport type XmlCodecOptions = XmlRenderOptions & XmlParseOptions;\n\n/**\n * @description The XML codec for a schema, as a `Schema`. `Type` is the schema's own `Type` and `Encoded` is XML text, so it encodes a value to a document and\n * decodes a document to a value in one step each. The service requirements of the source schema are preserved.\n */\nexport interface toCodecXml<S extends Schema.Constraint> extends Schema.decodeTo<Schema.toCodecStringTree<S>, Schema.String> {\n readonly Rebuild: toCodecXml<S>;\n}\n\n// Whether the plan places any node at all: a rename, an attribute, a value, or a root namespace or name. A plan with none of these leaves the value tree\n// alone on both sides, so the codec skips the name walk.\nconst isActivePlan = (plan: NamespacePlan): boolean =>\n plan.byKey.size > 0 ||\n plan.nameByKey.size > 0 ||\n plan.attributeKeys.size > 0 ||\n plan.valueByElement.size > 0 ||\n plan.root !== undefined ||\n plan.rootName !== undefined;\n\n// The render options with the root name resolved and any root prefix applied. The caller's `rootName` wins, then the plan's, then the schema's `identifier`\n// or `title` annotation, then `'root'`. A root namespace with a prefix qualifies the name unless the caller already wrote one.\nconst resolveRenderOptions = <S extends Schema.Constraint>(schema: S, plan: NamespacePlan, options: XmlCodecOptions): XmlRenderOptions => {\n const rootName =\n options.rootName ?? plan.rootName ?? SchemaAST.resolveIdentifier(schema.ast) ?? SchemaAST.resolveTitle(schema.ast) ?? DEFAULT_ROOT_NAME;\n const wireRootName = plan.root !== undefined && plan.root.prefix !== '' && !rootName.includes(':') ? `${plan.root.prefix}:${rootName}` : rootName;\n return { ...options, rootName: wireRootName };\n};\n\n/**\n * @description Derives the XML codec for a schema: a `Schema` whose `Encoded` is an XML document, so `Schema.encodeSync(codec)` writes text and\n * `Schema.decodeSync(codec)` reads it back. The derivation is Effect's `Schema.toCodecStringTree`; the text layer is this package's {@link renderXml}\n * and {@link parseXml}. Call it data-first, `toCodecXml(schema, options)`, or data-last, `toCodecXml(options)(schema)`, so it drops into `pipe` beside\n * the rest of the Effect combinators. The two forms are one function: the first argument decides the style, a schema is read as data-first and an\n * options object as data-last. `pipe(schema, toCodecXml)` carries no options and works because a schema on its own is data-first.\n *\n * @example\n * ```typescript\n * import { Schema, pipe } from 'effect';\n * import { toCodecXml } from '@endevops/effect-codec-xml';\n *\n * const Book = Schema.Struct({ '@id': Schema.String, title: Schema.String, pages: Schema.Number });\n * const codec = toCodecXml(Book, { rootName: 'book' });\n * const piped = pipe(Book, toCodecXml({ rootName: 'book' }));\n *\n * const value = { '@id': '1', title: 'Dune', pages: 412 };\n *\n * Schema.encodeSync(codec)(value);\n * // => '<book id=\"1\"><title>Dune</title><pages>412</pages></book>'\n *\n * Schema.decodeSync(codec)('<book id=\"1\"><title>Dune</title><pages>412</pages></book>');\n * // => { '@id': '1', title: 'Dune', pages: 412 }\n * ```;\n *\n * @param schema - The schema describing the value, in the data-first form.\n * @param options - Render and parse options. `rootName` defaults to the schema's `identifier` or `title` annotation, then to `'root'`. In the\n * data-last form this is the only argument, and the schema arrives from `pipe`.\n *\n * @returns The codec, with the source schema's `Type` and the same service requirements. In the data-last form, a function from the schema to the\n * codec.\n */\nexport const toCodecXml: {\n <S extends Schema.Constraint>(schema: S, options?: XmlCodecOptions): toCodecXml<S>;\n (options?: XmlCodecOptions): <S extends Schema.Constraint>(schema: S) => toCodecXml<S>;\n} = Function.dual(\n args => Schema.isSchema(args[0]),\n <S extends Schema.Constraint>(schema: S, options: XmlCodecOptions = {}): toCodecXml<S> => {\n const planned = namespacePlan(schema);\n if (Predicate.hasProperty(planned, 'error')) {\n throw new Error(`Invalid XML namespace annotation:\\n\\t- ${planned.error}.`);\n }\n const plan = planned.plan;\n const active = isActivePlan(plan);\n const renderOptions = resolveRenderOptions(schema, plan, options);\n const stringTree = Schema.toCodecStringTree(schema);\n\n return Schema.String.pipe(\n Schema.decodeTo(\n stringTree,\n SchemaTransformation.transformEffect({\n decode: (text, parseOptions) => {\n if (!Predicate.isString(text)) {\n return Effect.fail(new SchemaIssue.InvalidValue({ message: `Expected a string, but received ${typeof text}.` }, text, parseOptions));\n }\n\n return parseXml(text, options).pipe(\n Effect.map(value => (active ? decodeNames(value, plan, {}, ROOT_ELEMENT) : value)),\n Effect.tapError(error =>\n Effect.logError(`XML parse error: ${error.message}`).pipe(Effect.annotateLogs({ cause: error, message: 'XML parse error' }))\n ),\n Effect.mapError(error => new SchemaIssue.InvalidValue({ message: error.message }, text, parseOptions)),\n Effect.flatMap(value =>\n Effect.fromResult(normalizePlainValue(value, stringTree.ast, '')).pipe(\n Effect.mapError(message => new SchemaIssue.InvalidValue({ message }, value, parseOptions))\n )\n )\n );\n },\n\n encode: (value, parseOptions) => {\n if (active && Array.isArray(value) && (plan.root !== undefined || plan.rootName !== undefined)) {\n return Effect.fail(\n new SchemaIssue.InvalidValue(\n { message: 'An array at the root of a namespaced schema cannot carry the root namespace or name.' },\n value,\n parseOptions\n )\n );\n }\n\n const wire = active ? encodeNames(value as XmlValue, plan, plan.root, {}, ROOT_ELEMENT) : (value as XmlValue);\n return renderXml(wire, renderOptions).pipe(\n Effect.mapError(error => new SchemaIssue.InvalidValue({ message: error.message }, value, parseOptions))\n );\n },\n })\n )\n );\n }\n);\n"],"mappings":";;;;;;;AAuDA,MAAM,gBAAgB,SACpB,KAAK,MAAM,OAAO,KAClB,KAAK,UAAU,OAAO,KACtB,KAAK,cAAc,OAAO,KAC1B,KAAK,eAAe,OAAO,KAC3B,KAAK,SAAS,KAAA,KACd,KAAK,aAAa,KAAA;AAIpB,MAAM,wBAAqD,QAAW,MAAqB,YAA+C;CACxI,MAAM,WACJ,QAAQ,YAAY,KAAK,YAAY,UAAU,kBAAkB,OAAO,GAAG,KAAK,UAAU,aAAa,OAAO,GAAG,KAAA;CACnH,MAAM,eAAe,KAAK,SAAS,KAAA,KAAa,KAAK,KAAK,WAAW,MAAM,CAAC,SAAS,SAAS,GAAG,IAAI,GAAG,KAAK,KAAK,OAAO,GAAG,aAAa;CACzI,OAAO;EAAE,GAAG;EAAS,UAAU;CAAa;AAC9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,MAAa,aAGT,SAAS,MACX,SAAQ,OAAO,SAAS,KAAK,EAAE,IACD,QAAW,UAA2B,CAAC,MAAqB;CACxF,MAAM,UAAU,cAAc,MAAM;CACpC,IAAI,UAAU,YAAY,SAAS,OAAO,GACxC,MAAM,IAAI,MAAM,0CAA0C,QAAQ,MAAM,EAAE;CAE5E,MAAM,OAAO,QAAQ;CACrB,MAAM,SAAS,aAAa,IAAI;CAChC,MAAM,gBAAgB,qBAAqB,QAAQ,MAAM,OAAO;CAChE,MAAM,aAAa,OAAO,kBAAkB,MAAM;CAElD,OAAO,OAAO,OAAO,KACnB,OAAO,SACL,YACA,qBAAqB,gBAAgB;EACnC,SAAS,MAAM,iBAAiB;GAC9B,IAAI,CAAC,UAAU,SAAS,IAAI,GAC1B,OAAO,OAAO,KAAK,IAAI,YAAY,aAAa,EAAE,SAAS,mCAAmC,OAAO,KAAK,GAAG,GAAG,MAAM,YAAY,CAAC;GAGrI,OAAO,SAAS,MAAM,OAAO,CAAC,CAAC,KAC7B,OAAO,KAAI,UAAU,SAAS,YAAY,OAAO,MAAM,CAAC,GAAA,EAAe,IAAI,KAAM,GACjF,OAAO,UAAS,UACd,OAAO,SAAS,oBAAoB,MAAM,SAAS,CAAC,CAAC,KAAK,OAAO,aAAa;IAAE,OAAO;IAAO,SAAS;GAAkB,CAAC,CAAC,CAC7H,GACA,OAAO,UAAS,UAAS,IAAI,YAAY,aAAa,EAAE,SAAS,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,GACrG,OAAO,SAAQ,UACb,OAAO,WAAW,oBAAoB,OAAO,WAAW,KAAK,EAAE,CAAC,CAAC,CAAC,KAChE,OAAO,UAAS,YAAW,IAAI,YAAY,aAAa,EAAE,QAAQ,GAAG,OAAO,YAAY,CAAC,CAC3F,CACF,CACF;EACF;EAEA,SAAS,OAAO,iBAAiB;GAC/B,IAAI,UAAU,MAAM,QAAQ,KAAK,MAAM,KAAK,SAAS,KAAA,KAAa,KAAK,aAAa,KAAA,IAClF,OAAO,OAAO,KACZ,IAAI,YAAY,aACd,EAAE,SAAS,uFAAuF,GAClG,OACA,YACF,CACF;GAGF,MAAM,OAAO,SAAS,YAAY,OAAmB,MAAM,KAAK,MAAM,CAAC,GAAA,EAAe,IAAK;GAC3F,OAAO,UAAU,MAAM,aAAa,CAAC,CAAC,KACpC,OAAO,UAAS,UAAS,IAAI,YAAY,aAAa,EAAE,SAAS,MAAM,QAAQ,GAAG,OAAO,YAAY,CAAC,CACxG;EACF;CACF,CAAC,CACH,CACF;AACF,CACF"}
@@ -73,10 +73,10 @@ export declare const isTextKey: (key: string) => boolean;
73
73
  */
74
74
  export declare const isReservedKey: (key: string) => boolean;
75
75
  /**
76
- * @description Resolves a name to something legal in an XML document. The renderer and the parser both resolve a name per element and per attribute, and an
77
- * illegal name under `'error'` mode is a rejection rather than a value, so this returns an `Effect` with the `XmlParseError` in its error channel
78
- * rather than throwing it. Callers `yield*` it and the failure composes with `catchTag` and the rest; the two internal call sites in `parse.ts` and
79
- * `render.ts` use {@link resolveNameSync} directly, because their walks are synchronous hot paths.
76
+ * @description Resolves a name to something legal in an XML document. The renderer and the parser both resolve a name per element and per attribute. An illegal
77
+ * name under `'error'` mode is a rejection rather than a value, so this returns an `Effect` with the `XmlParseError` in its error channel instead of
78
+ * throwing it. Callers `yield*` it and the failure composes with `catchTag` and the rest. The two internal call sites in `parse.ts` and `render.ts`
79
+ * use {@link resolveNameSync} directly, because their walks are synchronous hot paths.
80
80
  *
81
81
  * @param name - The candidate element or attribute name.
82
82
  * @param options - Repair mode and XML version.
@@ -51,9 +51,9 @@ const isTextKey = (key) => key === TEXT_KEY;
51
51
  */
52
52
  const isReservedKey = (key) => isTextKey(key);
53
53
  /**
54
- * @description Resolves a name to something legal in an XML document, synchronously. The renderer and the parser both resolve a name per element and per attribute
55
- * — the codec's hot path — so the walk calls this directly and keeps the work in plain JavaScript. `'error'` mode reports an illegal name by throwing
56
- * an {@link XmlParseError}; the callers that need it in a typed channel use {@link resolveName}, which wraps this.
54
+ * @description Resolves a name to something legal in an XML document, synchronously. The renderer and the parser both resolve a name per element and per
55
+ * attribute, and that is the codec's hot path, so the walk calls this directly and keeps the work in plain JavaScript. `'error'` mode reports an
56
+ * illegal name by throwing an {@link XmlParseError}; the callers that need it in a typed channel use {@link resolveName}, which wraps this.
57
57
  *
58
58
  * @param name - The candidate element or attribute name.
59
59
  * @param options - Repair mode and XML version.
@@ -96,10 +96,10 @@ const resolveNameResult = (name, options) => {
96
96
  }
97
97
  };
98
98
  /**
99
- * @description Resolves a name to something legal in an XML document. The renderer and the parser both resolve a name per element and per attribute, and an
100
- * illegal name under `'error'` mode is a rejection rather than a value, so this returns an `Effect` with the `XmlParseError` in its error channel
101
- * rather than throwing it. Callers `yield*` it and the failure composes with `catchTag` and the rest; the two internal call sites in `parse.ts` and
102
- * `render.ts` use {@link resolveNameSync} directly, because their walks are synchronous hot paths.
99
+ * @description Resolves a name to something legal in an XML document. The renderer and the parser both resolve a name per element and per attribute. An illegal
100
+ * name under `'error'` mode is a rejection rather than a value, so this returns an `Effect` with the `XmlParseError` in its error channel instead of
101
+ * throwing it. Callers `yield*` it and the failure composes with `catchTag` and the rest. The two internal call sites in `parse.ts` and `render.ts`
102
+ * use {@link resolveNameSync} directly, because their walks are synchronous hot paths.
103
103
  *
104
104
  * @param name - The candidate element or attribute name.
105
105
  * @param options - Repair mode and XML version.
@@ -1 +1 @@
1
- {"version":3,"file":"conventions.js","names":[],"sources":["../src/conventions.ts"],"sourcesContent":["import { Effect, Predicate, Result } from 'effect';\n\nimport type { XmlVersion } from './naming.ts';\n\nimport { XmlParseError } from './errors.ts';\nimport { isQName, sanitize, validate } from './naming.ts';\n\n/**\n * @description The key prefix that marks a field as an XML attribute. `@xmlns` is written as `xmlns=\"…\"`.\n */\nexport const ATTRIBUTE_PREFIX = '@';\n\n/**\n * @description The reserved key holding an element's character data, alongside its attributes and child elements.\n */\nexport const TEXT_KEY = '#text';\n\n/**\n * @description The element name used when nothing else names the root. Matches the default Effect uses in `Schema.toEncoderXml`.\n */\nexport const DEFAULT_ROOT_NAME = 'root';\n\n/**\n * @description The element name used for array members that have no natural name of their own. Matches the default in `Schema.toEncoderXml`.\n */\nexport const DEFAULT_ITEM_NAME = 'item';\n\n/**\n * @description What to do with a name that is not a legal XML name.\n *\n * - `repair` rewrites it into the nearest legal name. The default, because a serializer that silently produces a different tag name is worse than one\n * that produces a legal one.\n * - `error` fails the render. Use it when a rewritten name would silently change the meaning of the document.\n * - `ignore` writes the name as given, producing a document that is not well-formed. Only useful when a downstream step rewrites names anyway.\n */\nexport type NameMode = 'error' | 'ignore' | 'repair';\n\n/**\n * @description Options for {@link resolveName}.\n */\nexport interface ResolveNameOptions {\n /**\n * @description What to do with an illegal name. Defaults to `'repair'`.\n */\n readonly mode?: NameMode | undefined;\n\n /**\n * @description XML version to validate against. Defaults to `'1.0'`.\n */\n readonly xmlVersion?: XmlVersion | undefined;\n}\n\n/**\n * @description Whether a record key names an attribute rather than a child element.\n *\n * @param key - The key to classify.\n *\n * @returns Whether the key carries the {@link ATTRIBUTE_PREFIX}.\n */\nexport const isAttributeKey = (key: string): boolean => key.charCodeAt(0) === 64 && key.length > 1;\n\n/**\n * @description The attribute name a record key stands for: `@xmlns` becomes `xmlns`.\n *\n * @param key - A key that {@link isAttributeKey} accepted.\n *\n * @returns The name with the prefix removed.\n */\nexport const attributeName = (key: string): string => key.slice(1);\n\n/**\n * @description Whether a record key holds character data rather than a child element or an attribute.\n *\n * @param key - The key to classify.\n *\n * @returns Whether the key is the reserved {@link TEXT_KEY}.\n */\nexport const isTextKey = (key: string): boolean => key === TEXT_KEY;\n\n/**\n * @description Whether a key is one this package reserves. Only {@link TEXT_KEY} is reserved today; every other key is read as a child element name.\n *\n * @param key - The key to classify.\n *\n * @returns Whether the key is reserved.\n */\nexport const isReservedKey = (key: string): boolean => isTextKey(key);\n\n/**\n * @description Resolves a name to something legal in an XML document, synchronously. The renderer and the parser both resolve a name per element and per attribute\n * — the codec's hot path — so the walk calls this directly and keeps the work in plain JavaScript. `'error'` mode reports an illegal name by throwing\n * an {@link XmlParseError}; the callers that need it in a typed channel use {@link resolveName}, which wraps this.\n *\n * @param name - The candidate element or attribute name.\n * @param options - Repair mode and XML version.\n *\n * @returns The name to write, unchanged when it was already legal.\n *\n * @throws {XmlParseError} When the name is illegal and the mode is `'error'`.\n */\nexport const resolveNameSync = (name: string, { mode = 'repair', xmlVersion = '1.0' }: ResolveNameOptions = {}): string => {\n if (isQName(name, { xmlVersion })) return name;\n if (mode === 'ignore') return name;\n if (mode === 'repair') return sanitize(name, 'name', { replacement: '_' });\n\n // `validate` only fails for an unknown production, which `'qName'` is not, so\n // this runs a plain result and the failure is the reason the name is refused.\n const result = Effect.runSync(validate(name, 'qName', { xmlVersion }));\n const reason = !result.valid ? result.reason : 'is not a legal XML name';\n throw new XmlParseError({ message: `Invalid XML name ${JSON.stringify(name)}: ${reason}`, position: -1, input: name });\n};\n\n/**\n * @description {@link resolveNameSync} with the thrown failure folded into a {@link Result}, so an effectful caller can carry it in a typed channel without a\n * try/catch of its own.\n *\n * @param name - The candidate element or attribute name.\n * @param options - Repair mode and XML version.\n *\n * @returns The resolved name, or the failure to report.\n */\nconst resolveNameResult = (name: string, options: ResolveNameOptions): Result.Result<string, XmlParseError> => {\n try {\n return Result.succeed(resolveNameSync(name, options));\n } catch (cause) {\n if (cause instanceof XmlParseError) {\n return Result.fail(cause);\n }\n return Result.fail(new XmlParseError({ message: Predicate.isError(cause) ? cause.message : String(cause), position: -1, input: name }));\n }\n};\n\n/**\n * @description Resolves a name to something legal in an XML document. The renderer and the parser both resolve a name per element and per attribute, and an\n * illegal name under `'error'` mode is a rejection rather than a value, so this returns an `Effect` with the `XmlParseError` in its error channel\n * rather than throwing it. Callers `yield*` it and the failure composes with `catchTag` and the rest; the two internal call sites in `parse.ts` and\n * `render.ts` use {@link resolveNameSync} directly, because their walks are synchronous hot paths.\n *\n * @param name - The candidate element or attribute name.\n * @param options - Repair mode and XML version.\n *\n * @returns An effect producing the name to write, unchanged when it was already legal.\n */\nexport const resolveName = (name: string, options: ResolveNameOptions = {}): Effect.Effect<string, XmlParseError> =>\n Effect.suspend(() => Effect.fromResult(resolveNameResult(name, options)));\n"],"mappings":";;;;;;;AAUA,MAAa,mBAAmB;;;;AAKhC,MAAa,WAAW;;;;AAKxB,MAAa,oBAAoB;;;;AAKjC,MAAa,oBAAoB;;;;;;;;AAkCjC,MAAa,kBAAkB,QAAyB,IAAI,WAAW,CAAC,MAAM,MAAM,IAAI,SAAS;;;;;;;;AASjG,MAAa,iBAAiB,QAAwB,IAAI,MAAM,CAAC;;;;;;;;AASjE,MAAa,aAAa,QAAyB,QAAQ;;;;;;;;AAS3D,MAAa,iBAAiB,QAAyB,UAAU,GAAG;;;;;;;;;;;;;AAcpE,MAAa,mBAAmB,MAAc,EAAE,OAAO,UAAU,aAAa,UAA8B,CAAC,MAAc;CACzH,IAAI,QAAQ,MAAM,EAAE,WAAW,CAAC,GAAG,OAAO;CAC1C,IAAI,SAAS,UAAU,OAAO;CAC9B,IAAI,SAAS,UAAU,OAAO,SAAS,MAAM,QAAQ,EAAE,aAAa,IAAI,CAAC;CAIzE,MAAM,SAAS,OAAO,QAAQ,SAAS,MAAM,SAAS,EAAE,WAAW,CAAC,CAAC;CACrE,MAAM,SAAS,CAAC,OAAO,QAAQ,OAAO,SAAS;CAC/C,MAAM,IAAI,cAAc;EAAE,SAAS,oBAAoB,KAAK,UAAU,IAAI,EAAE,IAAI;EAAU,UAAU;EAAI,OAAO;CAAK,CAAC;AACvH;;;;;;;;;;AAWA,MAAM,qBAAqB,MAAc,YAAsE;CAC7G,IAAI;EACF,OAAO,OAAO,QAAQ,gBAAgB,MAAM,OAAO,CAAC;CACtD,SAAS,OAAO;EACd,IAAI,iBAAiB,eACnB,OAAO,OAAO,KAAK,KAAK;EAE1B,OAAO,OAAO,KAAK,IAAI,cAAc;GAAE,SAAS,UAAU,QAAQ,KAAK,IAAI,MAAM,UAAU,OAAO,KAAK;GAAG,UAAU;GAAI,OAAO;EAAK,CAAC,CAAC;CACxI;AACF;;;;;;;;;;;;AAaA,MAAa,eAAe,MAAc,UAA8B,CAAC,MACvE,OAAO,cAAc,OAAO,WAAW,kBAAkB,MAAM,OAAO,CAAC,CAAC"}
1
+ {"version":3,"file":"conventions.js","names":[],"sources":["../src/conventions.ts"],"sourcesContent":["import { Effect, Predicate, Result } from 'effect';\n\nimport type { XmlVersion } from './naming.ts';\n\nimport { XmlParseError } from './errors.ts';\nimport { isQName, sanitize, validate } from './naming.ts';\n\n/**\n * @description The key prefix that marks a field as an XML attribute. `@xmlns` is written as `xmlns=\"…\"`.\n */\nexport const ATTRIBUTE_PREFIX = '@';\n\n/**\n * @description The reserved key holding an element's character data, alongside its attributes and child elements.\n */\nexport const TEXT_KEY = '#text';\n\n/**\n * @description The element name used when nothing else names the root. Matches the default Effect uses in `Schema.toEncoderXml`.\n */\nexport const DEFAULT_ROOT_NAME = 'root';\n\n/**\n * @description The element name used for array members that have no natural name of their own. Matches the default in `Schema.toEncoderXml`.\n */\nexport const DEFAULT_ITEM_NAME = 'item';\n\n/**\n * @description What to do with a name that is not a legal XML name.\n *\n * - `repair` rewrites it into the nearest legal name. The default, because a serializer that silently produces a different tag name is worse than one\n * that produces a legal one.\n * - `error` fails the render. Use it when a rewritten name would silently change the meaning of the document.\n * - `ignore` writes the name as given, producing a document that is not well-formed. Only useful when a downstream step rewrites names anyway.\n */\nexport type NameMode = 'error' | 'ignore' | 'repair';\n\n/**\n * @description Options for {@link resolveName}.\n */\nexport interface ResolveNameOptions {\n /**\n * @description What to do with an illegal name. Defaults to `'repair'`.\n */\n readonly mode?: NameMode | undefined;\n\n /**\n * @description XML version to validate against. Defaults to `'1.0'`.\n */\n readonly xmlVersion?: XmlVersion | undefined;\n}\n\n/**\n * @description Whether a record key names an attribute rather than a child element.\n *\n * @param key - The key to classify.\n *\n * @returns Whether the key carries the {@link ATTRIBUTE_PREFIX}.\n */\nexport const isAttributeKey = (key: string): boolean => key.charCodeAt(0) === 64 && key.length > 1;\n\n/**\n * @description The attribute name a record key stands for: `@xmlns` becomes `xmlns`.\n *\n * @param key - A key that {@link isAttributeKey} accepted.\n *\n * @returns The name with the prefix removed.\n */\nexport const attributeName = (key: string): string => key.slice(1);\n\n/**\n * @description Whether a record key holds character data rather than a child element or an attribute.\n *\n * @param key - The key to classify.\n *\n * @returns Whether the key is the reserved {@link TEXT_KEY}.\n */\nexport const isTextKey = (key: string): boolean => key === TEXT_KEY;\n\n/**\n * @description Whether a key is one this package reserves. Only {@link TEXT_KEY} is reserved today; every other key is read as a child element name.\n *\n * @param key - The key to classify.\n *\n * @returns Whether the key is reserved.\n */\nexport const isReservedKey = (key: string): boolean => isTextKey(key);\n\n/**\n * @description Resolves a name to something legal in an XML document, synchronously. The renderer and the parser both resolve a name per element and per\n * attribute, and that is the codec's hot path, so the walk calls this directly and keeps the work in plain JavaScript. `'error'` mode reports an\n * illegal name by throwing an {@link XmlParseError}; the callers that need it in a typed channel use {@link resolveName}, which wraps this.\n *\n * @param name - The candidate element or attribute name.\n * @param options - Repair mode and XML version.\n *\n * @returns The name to write, unchanged when it was already legal.\n *\n * @throws {XmlParseError} When the name is illegal and the mode is `'error'`.\n */\nexport const resolveNameSync = (name: string, { mode = 'repair', xmlVersion = '1.0' }: ResolveNameOptions = {}): string => {\n if (isQName(name, { xmlVersion })) return name;\n if (mode === 'ignore') return name;\n if (mode === 'repair') return sanitize(name, 'name', { replacement: '_' });\n\n // `validate` only fails for an unknown production, which `'qName'` is not, so\n // this runs a plain result and the failure is the reason the name is refused.\n const result = Effect.runSync(validate(name, 'qName', { xmlVersion }));\n const reason = !result.valid ? result.reason : 'is not a legal XML name';\n throw new XmlParseError({ message: `Invalid XML name ${JSON.stringify(name)}: ${reason}`, position: -1, input: name });\n};\n\n/**\n * @description {@link resolveNameSync} with the thrown failure folded into a {@link Result}, so an effectful caller can carry it in a typed channel without a\n * try/catch of its own.\n *\n * @param name - The candidate element or attribute name.\n * @param options - Repair mode and XML version.\n *\n * @returns The resolved name, or the failure to report.\n */\nconst resolveNameResult = (name: string, options: ResolveNameOptions): Result.Result<string, XmlParseError> => {\n try {\n return Result.succeed(resolveNameSync(name, options));\n } catch (cause) {\n if (cause instanceof XmlParseError) {\n return Result.fail(cause);\n }\n return Result.fail(new XmlParseError({ message: Predicate.isError(cause) ? cause.message : String(cause), position: -1, input: name }));\n }\n};\n\n/**\n * @description Resolves a name to something legal in an XML document. The renderer and the parser both resolve a name per element and per attribute. An illegal\n * name under `'error'` mode is a rejection rather than a value, so this returns an `Effect` with the `XmlParseError` in its error channel instead of\n * throwing it. Callers `yield*` it and the failure composes with `catchTag` and the rest. The two internal call sites in `parse.ts` and `render.ts`\n * use {@link resolveNameSync} directly, because their walks are synchronous hot paths.\n *\n * @param name - The candidate element or attribute name.\n * @param options - Repair mode and XML version.\n *\n * @returns An effect producing the name to write, unchanged when it was already legal.\n */\nexport const resolveName = (name: string, options: ResolveNameOptions = {}): Effect.Effect<string, XmlParseError> =>\n Effect.suspend(() => Effect.fromResult(resolveNameResult(name, options)));\n"],"mappings":";;;;;;;AAUA,MAAa,mBAAmB;;;;AAKhC,MAAa,WAAW;;;;AAKxB,MAAa,oBAAoB;;;;AAKjC,MAAa,oBAAoB;;;;;;;;AAkCjC,MAAa,kBAAkB,QAAyB,IAAI,WAAW,CAAC,MAAM,MAAM,IAAI,SAAS;;;;;;;;AASjG,MAAa,iBAAiB,QAAwB,IAAI,MAAM,CAAC;;;;;;;;AASjE,MAAa,aAAa,QAAyB,QAAQ;;;;;;;;AAS3D,MAAa,iBAAiB,QAAyB,UAAU,GAAG;;;;;;;;;;;;;AAcpE,MAAa,mBAAmB,MAAc,EAAE,OAAO,UAAU,aAAa,UAA8B,CAAC,MAAc;CACzH,IAAI,QAAQ,MAAM,EAAE,WAAW,CAAC,GAAG,OAAO;CAC1C,IAAI,SAAS,UAAU,OAAO;CAC9B,IAAI,SAAS,UAAU,OAAO,SAAS,MAAM,QAAQ,EAAE,aAAa,IAAI,CAAC;CAIzE,MAAM,SAAS,OAAO,QAAQ,SAAS,MAAM,SAAS,EAAE,WAAW,CAAC,CAAC;CACrE,MAAM,SAAS,CAAC,OAAO,QAAQ,OAAO,SAAS;CAC/C,MAAM,IAAI,cAAc;EAAE,SAAS,oBAAoB,KAAK,UAAU,IAAI,EAAE,IAAI;EAAU,UAAU;EAAI,OAAO;CAAK,CAAC;AACvH;;;;;;;;;;AAWA,MAAM,qBAAqB,MAAc,YAAsE;CAC7G,IAAI;EACF,OAAO,OAAO,QAAQ,gBAAgB,MAAM,OAAO,CAAC;CACtD,SAAS,OAAO;EACd,IAAI,iBAAiB,eACnB,OAAO,OAAO,KAAK,KAAK;EAE1B,OAAO,OAAO,KAAK,IAAI,cAAc;GAAE,SAAS,UAAU,QAAQ,KAAK,IAAI,MAAM,UAAU,OAAO,KAAK;GAAG,UAAU;GAAI,OAAO;EAAK,CAAC,CAAC;CACxI;AACF;;;;;;;;;;;;AAaA,MAAa,eAAe,MAAc,UAA8B,CAAC,MACvE,OAAO,cAAc,OAAO,WAAW,kBAAkB,MAAM,OAAO,CAAC,CAAC"}