@endevops/effect-codec-xml 0.0.1 → 0.1.0-beta.1

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 (47) hide show
  1. package/README.md +63 -62
  2. package/dist/codec.d.ts +17 -9
  3. package/dist/codec.d.ts.map +1 -1
  4. package/dist/codec.js +25 -16
  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 +2 -2
  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/render.d.ts +1 -1
  26. package/dist/render.d.ts.map +1 -1
  27. package/dist/render.js +28 -28
  28. package/dist/render.js.map +1 -1
  29. package/dist/xml-error.d.ts +9 -9
  30. package/dist/xml-error.js +18 -18
  31. package/dist/xml-error.js.map +1 -1
  32. package/dist/xml-value.d.ts +7 -7
  33. package/dist/xml-value.d.ts.map +1 -1
  34. package/dist/xml-value.js +6 -7
  35. package/dist/xml-value.js.map +1 -1
  36. package/package.json +1 -1
  37. package/src/codec.ts +91 -71
  38. package/src/conventions.ts +7 -7
  39. package/src/entities/entity-decoder.ts +98 -99
  40. package/src/errors.ts +3 -3
  41. package/src/index.ts +3 -3
  42. package/src/namespaces.ts +16 -16
  43. package/src/naming.ts +34 -35
  44. package/src/parse.ts +26 -26
  45. package/src/render.ts +44 -44
  46. package/src/xml-error.ts +18 -18
  47. package/src/xml-value.ts +10 -11
package/dist/xml-error.js CHANGED
@@ -8,35 +8,35 @@ import { Schema } from "effect";
8
8
  *
9
9
  * ## Why one error with a `reason`, and not one error per cause
10
10
  *
11
- * The causes would mean a class each, and a caller handling "any of these" would need `catchTags` with all of them. The causes are not independent
12
- * decisions a caller usually wants to make separately — they are all "this XML input was not acceptable", and the useful split is coarse: a bad
13
- * _configuration_ versus a bad _document_. So there is one error, and `reason` narrows to the specific cause. Recovery is a `catchReason` away:
11
+ * The causes would mean a class each, and a caller handling "any of these" would need `catchTags` with all of them. A caller does not usually want to
12
+ * decide between the causes separately. They are all "this XML input was not acceptable", and the useful split is coarse: a bad _configuration_
13
+ * versus a bad _document_. So there is one error, and `reason` narrows to the specific cause. Recovery is a `catchReason` away:
14
14
  *
15
15
  * @example
16
16
  * ```typescript
17
17
  * import { Effect } from 'effect';
18
- * import { EntityDecoder, XmlError } from '@endevops/effect-xml-codec';
18
+ * import { EntityDecoder, XmlError } from '@endevops/effect-codec-xml';
19
19
  *
20
20
  * const limited = new EntityDecoder({ limit: { maxTotalExpansions: 2 } }).decode('&&&').pipe(
21
21
  * Effect.catchReason('XmlError', 'ExpansionLimitExceeded', reason => Effect.succeed(`gave up after ${reason.actual}`)),
22
22
  * );
23
23
  * ```;
24
24
  *
25
- * The message is kept alongside `reason` and is part of the schema, because the text is load-bearing: the
26
- * `[EntityReplacer]` prefix in particular is documented as something callers match on, so it is reproduced exactly
27
- * rather than reworded.
25
+ * The message is kept alongside `reason` and is part of the schema, because callers depend on the text. The
26
+ * `[EntityReplacer]` prefix in particular is documented as something callers match on, so the codec reproduces it exactly
27
+ * rather than rewording it.
28
28
  */
29
29
  /**
30
30
  * @description The specific cause of an {@link XmlError}, as a tagged union. The `_tag` on each member is the discriminant `Effect.catchReason` matches on, and
31
- * the payload is what a handler needs in order to decide or to report. Every member is a case the decoder or the name validators actually raise —
32
- * there is no catch-all member, so an exhaustive `match` stays exhaustive as causes are added.
31
+ * the payload is what a handler needs in order to decide or to report. Every member is a case the decoder or the name validators actually raise.
32
+ * There is no catch-all member, so an exhaustive `match` stays exhaustive as causes are added.
33
33
  */
34
34
  const XmlErrorReason = Schema.TaggedUnion({
35
35
  /**
36
36
  * @description A required argument was `null` or another non-value where the package requires a real one. Raised by the factories that take caller-supplied
37
- * input — {@link EntityDecoder.make} among them — when they are handed `null` for an options object that has no meaningful default. It is a
38
- * distinct case from the rest because the argument is not _wrong_, it is _absent_, and a caller who wrote `make(null)` meant something the type
39
- * system does not allow: a decoder with every default is `make({})`, and saying so here is more useful than silently producing one.
37
+ * input, {@link EntityDecoder.make} among them, when they are handed `null` for an options object that has no meaningful default. It is a distinct
38
+ * case from the rest because the argument is not _wrong_, it is _absent_, and a caller who wrote `make(null)` meant something the type system does
39
+ * not allow. A decoder with every default is `make({})`, and saying so here is more useful than silently producing one.
40
40
  */
41
41
  MissingOptions: {
42
42
  /**
@@ -45,7 +45,7 @@ const XmlErrorReason = Schema.TaggedUnion({
45
45
  parameter: Schema.String },
46
46
  /**
47
47
  * @description A name was checked against one of the five XML name productions and given a different one. Unreachable from TypeScript, where `Production` is a
48
- * closed union — it is the guard for untyped JavaScript callers and for values that crossed a boundary as `unknown`.
48
+ * closed union. It is the guard for untyped JavaScript callers and for values that crossed a boundary as `unknown`.
49
49
  */
50
50
  InvalidProduction: {
51
51
  production: Schema.String,
@@ -73,7 +73,7 @@ parameter: Schema.String },
73
73
  */
74
74
  EntityRejected: {
75
75
  /**
76
- * @description Which registration was in progress. Both are runtime-injected, which is why they share a tier for limit accounting.
76
+ * @description Which registration was in progress. The runtime injects both, so they share a tier for limit accounting.
77
77
  */
78
78
  context: Schema.Literals(["external", "input"]),
79
79
  /**
@@ -87,7 +87,7 @@ parameter: Schema.String },
87
87
  */
88
88
  ExpansionLimitExceeded: {
89
89
  /**
90
- * @description The count that tripped the limit. Deliberately not reset on failure, so this is the real over-limit total rather than the ceiling.
90
+ * @description The count that tripped the limit. The counter is not reset on failure, so this is the real over-limit total rather than the ceiling.
91
91
  */
92
92
  actual: Schema.Number,
93
93
  /**
@@ -125,14 +125,14 @@ parameter: Schema.String },
125
125
  }
126
126
  });
127
127
  /**
128
- * @description Every failure this package can report, in the `E` channel of the effects that can fail. Carries both a `reason` — the typed, matchable cause — and
128
+ * @description Every failure this package can report, in the `E` channel of the effects that can fail. Carries both a `reason`, the typed and matchable cause, and
129
129
  * a `message`, which is the human-readable form the package has always produced. Both are part of the schema, so an error survives a round-trip
130
130
  * through a serialised boundary without losing either.
131
131
  *
132
132
  * @example
133
133
  * ```typescript
134
134
  * import { Effect } from 'effect';
135
- * import { EntityDecoder } from '@endevops/effect-xml-codec';
135
+ * import { EntityDecoder } from '@endevops/effect-codec-xml';
136
136
  *
137
137
  * const program = Effect.gen(function*() {
138
138
  * const decoder = yield* EntityDecoder.make({});
@@ -147,7 +147,7 @@ var XmlError = class extends Schema.TaggedError()("XmlError", {
147
147
  reason: XmlErrorReason,
148
148
  /**
149
149
  * @description Human-readable description. The `[EntityReplacer]` and `[EntityDecoder]` prefixes are reproduced verbatim from the original throw sites, because
150
- * they are documented as load-bearing for anything matching on them.
150
+ * anything matching on them depends on them.
151
151
  */
152
152
  message: Schema.String
153
153
  }) {};
@@ -1 +1 @@
1
- {"version":3,"file":"xml-error.js","names":[],"sources":["../src/xml-error.ts"],"sourcesContent":["/**\n * @description Every way the entity decoder and the name validators can fail, as one typed error. The package used to throw plain `Error` and `TypeError` from\n * places spread across the entity and naming areas. A caller had to match on message text, and there was nothing to narrow on. All of it is now a\n * single {@link XmlError} in the `E` channel of the effects that can fail, with the specific cause in a `reason` field rather than parsed back out of\n * a string.\n *\n * ## Why one error with a `reason`, and not one error per cause\n *\n * The causes would mean a class each, and a caller handling \"any of these\" would need `catchTags` with all of them. The causes are not independent\n * decisions a caller usually wants to make separately — they are all \"this XML input was not acceptable\", and the useful split is coarse: a bad\n * _configuration_ versus a bad _document_. So there is one error, and `reason` narrows to the specific cause. Recovery is a `catchReason` away:\n *\n * @example\n * ```typescript\n * import { Effect } from 'effect';\n * import { EntityDecoder, XmlError } from '@endevops/effect-xml-codec';\n *\n * const limited = new EntityDecoder({ limit: { maxTotalExpansions: 2 } }).decode('&amp;&amp;&amp;').pipe(\n * Effect.catchReason('XmlError', 'ExpansionLimitExceeded', reason => Effect.succeed(`gave up after ${reason.actual}`)),\n * );\n * ```;\n *\n * The message is kept alongside `reason` and is part of the schema, because the text is load-bearing: the\n * `[EntityReplacer]` prefix in particular is documented as something callers match on, so it is reproduced exactly\n * rather than reworded.\n */\n\nimport { Schema } from 'effect';\n\n/**\n * @description The specific cause of an {@link XmlError}, as a tagged union. The `_tag` on each member is the discriminant `Effect.catchReason` matches on, and\n * the payload is what a handler needs in order to decide or to report. Every member is a case the decoder or the name validators actually raise —\n * there is no catch-all member, so an exhaustive `match` stays exhaustive as causes are added.\n */\nexport const XmlErrorReason = Schema.TaggedUnion({\n /**\n * @description A required argument was `null` or another non-value where the package requires a real one. Raised by the factories that take caller-supplied\n * input — {@link EntityDecoder.make} among them — when they are handed `null` for an options object that has no meaningful default. It is a\n * distinct case from the rest because the argument is not _wrong_, it is _absent_, and a caller who wrote `make(null)` meant something the type\n * system does not allow: a decoder with every default is `make({})`, and saying so here is more useful than silently producing one.\n */\n MissingOptions: {\n /**\n * @description The parameter that was given nothing, named as it appears in the signature.\n */\n parameter: Schema.String,\n },\n\n /**\n * @description A name was checked against one of the five XML name productions and given a different one. Unreachable from TypeScript, where `Production` is a\n * closed union — it is the guard for untyped JavaScript callers and for values that crossed a boundary as `unknown`.\n */\n InvalidProduction: {\n production: Schema.String,\n /**\n * @description The productions that would have been accepted, comma-separated, as they appear in the message.\n */\n expected: Schema.String,\n },\n\n /**\n * @description An entity name cannot be written as a reference, so it is refused at registration.\n */\n InvalidEntityName: {\n /**\n * @description The rejected name, as it was passed in.\n */\n name: Schema.String,\n /**\n * @description The offending character, or `#` for a name that starts with one. A leading `#` is refused by position rather than by the character sweep,\n * because a name starting with `#` is a numeric reference's token and would collide with the numeric pipeline.\n */\n character: Schema.String,\n },\n\n /**\n * @description A registration hook refused an entity and asked for the registration to abort.\n */\n EntityRejected: {\n /**\n * @description Which registration was in progress. Both are runtime-injected, which is why they share a tier for limit accounting.\n */\n context: Schema.Literals(['external', 'input']),\n /**\n * @description The entity name, without `&` or `;`.\n */\n name: Schema.String,\n },\n\n /**\n * @description A document expanded more tracked entity references than {@link EntityDecoderLimitOptions.maxTotalExpansions} allows. A document can define an\n * entity that references another ten times over. Ten deep is a denial of service; a hundred is a fork bomb written in XML.\n */\n ExpansionLimitExceeded: {\n /**\n * @description The count that tripped the limit. Deliberately not reset on failure, so this is the real over-limit total rather than the ceiling.\n */\n actual: Schema.Number,\n /**\n * @description The configured ceiling. The check is `actual > limit`, so a limit of `2` allows two expansions.\n */\n limit: Schema.Number,\n },\n\n /**\n * @description A document grew by more characters through entity expansion than {@link EntityDecoderLimitOptions.maxExpandedLength} allows. Only the surplus\n * counts: a reference whose replacement is no longer than the `&token;` it replaces contributes nothing, so this bounds growth rather than document\n * size.\n */\n ExpandedLengthLimitExceeded: {\n /**\n * @description The accumulated growth that tripped the limit.\n */\n actual: Schema.Number,\n /**\n * @description The configured ceiling.\n */\n limit: Schema.Number,\n },\n\n /**\n * @description A numeric character reference was prohibited by the configured policy.\n */\n ProhibitedCharacterReference: {\n /**\n * @description The raw token without `&` and `;`, e.g. `#38` or `#x26`.\n */\n token: Schema.String,\n /**\n * @description The codepoint the reference resolved to, so a log can name the character that was refused.\n */\n codepoint: Schema.Number,\n },\n});\n\n/**\n * @description The reason a well-formedness or policy failure occurred.\n */\nexport type XmlErrorReason = typeof XmlErrorReason.Type;\n\n/**\n * @description Every failure this package can report, in the `E` channel of the effects that can fail. Carries both a `reason` — the typed, matchable cause — and\n * a `message`, which is the human-readable form the package has always produced. Both are part of the schema, so an error survives a round-trip\n * through a serialised boundary without losing either.\n *\n * @example\n * ```typescript\n * import { Effect } from 'effect';\n * import { EntityDecoder } from '@endevops/effect-xml-codec';\n *\n * const program = Effect.gen(function*() {\n * const decoder = yield* EntityDecoder.make({});\n * return yield* decoder.decode('a &amp; b');\n * });\n * ```;\n */\nexport class XmlError extends Schema.TaggedError<XmlError>()('XmlError', {\n /**\n * @description The specific cause. Narrow on `_tag`, or recover with `Effect.catchReason`.\n */\n reason: XmlErrorReason,\n\n /**\n * @description Human-readable description. The `[EntityReplacer]` and `[EntityDecoder]` prefixes are reproduced verbatim from the original throw sites, because\n * they are documented as load-bearing for anything matching on them.\n */\n message: Schema.String,\n}) {}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,MAAa,iBAAiB,OAAO,YAAY;;;;;;;CAO/C,gBAAgB;;;;AAId,WAAW,OAAO,OACpB;;;;;CAMA,mBAAmB;EACjB,YAAY,OAAO;;;;EAInB,UAAU,OAAO;CACnB;;;;CAKA,mBAAmB;;;;EAIjB,MAAM,OAAO;;;;;EAKb,WAAW,OAAO;CACpB;;;;CAKA,gBAAgB;;;;EAId,SAAS,OAAO,SAAS,CAAC,YAAY,OAAO,CAAC;;;;EAI9C,MAAM,OAAO;CACf;;;;;CAMA,wBAAwB;;;;EAItB,QAAQ,OAAO;;;;EAIf,OAAO,OAAO;CAChB;;;;;;CAOA,6BAA6B;;;;EAI3B,QAAQ,OAAO;;;;EAIf,OAAO,OAAO;CAChB;;;;CAKA,8BAA8B;;;;EAI5B,OAAO,OAAO;;;;EAId,WAAW,OAAO;CACpB;AACF,CAAC;;;;;;;;;;;;;;;;;AAuBD,IAAa,WAAb,cAA8B,OAAO,YAAsB,CAAC,CAAC,YAAY;;;;CAIvE,QAAQ;;;;;CAMR,SAAS,OAAO;AAClB,CAAC,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"xml-error.js","names":[],"sources":["../src/xml-error.ts"],"sourcesContent":["/**\n * @description Every way the entity decoder and the name validators can fail, as one typed error. The package used to throw plain `Error` and `TypeError` from\n * places spread across the entity and naming areas. A caller had to match on message text, and there was nothing to narrow on. All of it is now a\n * single {@link XmlError} in the `E` channel of the effects that can fail, with the specific cause in a `reason` field rather than parsed back out of\n * a string.\n *\n * ## Why one error with a `reason`, and not one error per cause\n *\n * The causes would mean a class each, and a caller handling \"any of these\" would need `catchTags` with all of them. A caller does not usually want to\n * decide between the causes separately. They are all \"this XML input was not acceptable\", and the useful split is coarse: a bad _configuration_\n * versus a bad _document_. So there is one error, and `reason` narrows to the specific cause. Recovery is a `catchReason` away:\n *\n * @example\n * ```typescript\n * import { Effect } from 'effect';\n * import { EntityDecoder, XmlError } from '@endevops/effect-codec-xml';\n *\n * const limited = new EntityDecoder({ limit: { maxTotalExpansions: 2 } }).decode('&amp;&amp;&amp;').pipe(\n * Effect.catchReason('XmlError', 'ExpansionLimitExceeded', reason => Effect.succeed(`gave up after ${reason.actual}`)),\n * );\n * ```;\n *\n * The message is kept alongside `reason` and is part of the schema, because callers depend on the text. The\n * `[EntityReplacer]` prefix in particular is documented as something callers match on, so the codec reproduces it exactly\n * rather than rewording it.\n */\n\nimport { Schema } from 'effect';\n\n/**\n * @description The specific cause of an {@link XmlError}, as a tagged union. The `_tag` on each member is the discriminant `Effect.catchReason` matches on, and\n * the payload is what a handler needs in order to decide or to report. Every member is a case the decoder or the name validators actually raise.\n * There is no catch-all member, so an exhaustive `match` stays exhaustive as causes are added.\n */\nexport const XmlErrorReason = Schema.TaggedUnion({\n /**\n * @description A required argument was `null` or another non-value where the package requires a real one. Raised by the factories that take caller-supplied\n * input, {@link EntityDecoder.make} among them, when they are handed `null` for an options object that has no meaningful default. It is a distinct\n * case from the rest because the argument is not _wrong_, it is _absent_, and a caller who wrote `make(null)` meant something the type system does\n * not allow. A decoder with every default is `make({})`, and saying so here is more useful than silently producing one.\n */\n MissingOptions: {\n /**\n * @description The parameter that was given nothing, named as it appears in the signature.\n */\n parameter: Schema.String,\n },\n\n /**\n * @description A name was checked against one of the five XML name productions and given a different one. Unreachable from TypeScript, where `Production` is a\n * closed union. It is the guard for untyped JavaScript callers and for values that crossed a boundary as `unknown`.\n */\n InvalidProduction: {\n production: Schema.String,\n /**\n * @description The productions that would have been accepted, comma-separated, as they appear in the message.\n */\n expected: Schema.String,\n },\n\n /**\n * @description An entity name cannot be written as a reference, so it is refused at registration.\n */\n InvalidEntityName: {\n /**\n * @description The rejected name, as it was passed in.\n */\n name: Schema.String,\n /**\n * @description The offending character, or `#` for a name that starts with one. A leading `#` is refused by position rather than by the character sweep,\n * because a name starting with `#` is a numeric reference's token and would collide with the numeric pipeline.\n */\n character: Schema.String,\n },\n\n /**\n * @description A registration hook refused an entity and asked for the registration to abort.\n */\n EntityRejected: {\n /**\n * @description Which registration was in progress. The runtime injects both, so they share a tier for limit accounting.\n */\n context: Schema.Literals(['external', 'input']),\n /**\n * @description The entity name, without `&` or `;`.\n */\n name: Schema.String,\n },\n\n /**\n * @description A document expanded more tracked entity references than {@link EntityDecoderLimitOptions.maxTotalExpansions} allows. A document can define an\n * entity that references another ten times over. Ten deep is a denial of service; a hundred is a fork bomb written in XML.\n */\n ExpansionLimitExceeded: {\n /**\n * @description The count that tripped the limit. The counter is not reset on failure, so this is the real over-limit total rather than the ceiling.\n */\n actual: Schema.Number,\n /**\n * @description The configured ceiling. The check is `actual > limit`, so a limit of `2` allows two expansions.\n */\n limit: Schema.Number,\n },\n\n /**\n * @description A document grew by more characters through entity expansion than {@link EntityDecoderLimitOptions.maxExpandedLength} allows. Only the surplus\n * counts: a reference whose replacement is no longer than the `&token;` it replaces contributes nothing, so this bounds growth rather than document\n * size.\n */\n ExpandedLengthLimitExceeded: {\n /**\n * @description The accumulated growth that tripped the limit.\n */\n actual: Schema.Number,\n /**\n * @description The configured ceiling.\n */\n limit: Schema.Number,\n },\n\n /**\n * @description A numeric character reference was prohibited by the configured policy.\n */\n ProhibitedCharacterReference: {\n /**\n * @description The raw token without `&` and `;`, e.g. `#38` or `#x26`.\n */\n token: Schema.String,\n /**\n * @description The codepoint the reference resolved to, so a log can name the character that was refused.\n */\n codepoint: Schema.Number,\n },\n});\n\n/**\n * @description The reason a well-formedness or policy failure occurred.\n */\nexport type XmlErrorReason = typeof XmlErrorReason.Type;\n\n/**\n * @description Every failure this package can report, in the `E` channel of the effects that can fail. Carries both a `reason`, the typed and matchable cause, and\n * a `message`, which is the human-readable form the package has always produced. Both are part of the schema, so an error survives a round-trip\n * through a serialised boundary without losing either.\n *\n * @example\n * ```typescript\n * import { Effect } from 'effect';\n * import { EntityDecoder } from '@endevops/effect-codec-xml';\n *\n * const program = Effect.gen(function*() {\n * const decoder = yield* EntityDecoder.make({});\n * return yield* decoder.decode('a &amp; b');\n * });\n * ```;\n */\nexport class XmlError extends Schema.TaggedError<XmlError>()('XmlError', {\n /**\n * @description The specific cause. Narrow on `_tag`, or recover with `Effect.catchReason`.\n */\n reason: XmlErrorReason,\n\n /**\n * @description Human-readable description. The `[EntityReplacer]` and `[EntityDecoder]` prefixes are reproduced verbatim from the original throw sites, because\n * anything matching on them depends on them.\n */\n message: Schema.String,\n}) {}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,MAAa,iBAAiB,OAAO,YAAY;;;;;;;CAO/C,gBAAgB;;;;AAId,WAAW,OAAO,OACpB;;;;;CAMA,mBAAmB;EACjB,YAAY,OAAO;;;;EAInB,UAAU,OAAO;CACnB;;;;CAKA,mBAAmB;;;;EAIjB,MAAM,OAAO;;;;;EAKb,WAAW,OAAO;CACpB;;;;CAKA,gBAAgB;;;;EAId,SAAS,OAAO,SAAS,CAAC,YAAY,OAAO,CAAC;;;;EAI9C,MAAM,OAAO;CACf;;;;;CAMA,wBAAwB;;;;EAItB,QAAQ,OAAO;;;;EAIf,OAAO,OAAO;CAChB;;;;;;CAOA,6BAA6B;;;;EAI3B,QAAQ,OAAO;;;;EAIf,OAAO,OAAO;CAChB;;;;CAKA,8BAA8B;;;;EAI5B,OAAO,OAAO;;;;EAId,WAAW,OAAO;CACpB;AACF,CAAC;;;;;;;;;;;;;;;;;AAuBD,IAAa,WAAb,cAA8B,OAAO,YAAsB,CAAC,CAAC,YAAY;;;;CAIvE,QAAQ;;;;;CAMR,SAAS,OAAO;AAClB,CAAC,CAAC,CAAC,CAAC"}
@@ -10,10 +10,10 @@ export interface XmlRecord {
10
10
  }
11
11
  /**
12
12
  * @description One value in an XML document: nothing at all, character data, a repeated run of children, or a record of attributes, text and child elements.
13
- * `undefined` is a value of its own rather than an omission, because that is how an absent optional field survives a round trip: a field with no
14
- * value stays distinguishable from a field whose value is the empty string, and the renderer writes neither of them. The shape is deliberately the
15
- * same one `Schema.toCodecStringTree` derives, which is what lets every schema feature Effect supports round-trip through this package without
16
- * re-implementing the derivation.
13
+ * `undefined` is a value of its own rather than an omission, because that is how an absent optional field survives a round trip. A field with no
14
+ * value stays distinguishable from a field whose value is the empty string, and the renderer writes neither of them. The shape is the same one
15
+ * `Schema.toCodecStringTree` derives, and sharing that shape is what lets every schema feature Effect supports round-trip through this package
16
+ * without re-implementing the derivation.
17
17
  */
18
18
  export type XmlValue = string | undefined | ReadonlyArray<XmlValue> | XmlRecord;
19
19
  /**
@@ -25,13 +25,13 @@ export type XmlValue = string | undefined | ReadonlyArray<XmlValue> | XmlRecord;
25
25
  */
26
26
  export declare const isXmlValue: (input: unknown) => input is XmlValue;
27
27
  /**
28
- * @description A schema for {@link XmlValue}, so a value can be validated on its own — when it arrives from a store or a queue rather than from {@link parseXml},
29
- * and the schema it belongs to is not in hand.
28
+ * @description A schema for {@link XmlValue}, so a value can be validated on its own. This helps when it arrives from a store or a queue rather than from
29
+ * {@link parseXml}, and the schema it belongs to is not in hand.
30
30
  *
31
31
  * @example
32
32
  * ```typescript
33
33
  * import { Schema } from 'effect';
34
- * import { XmlValue } from '@endevops/effect-xml-codec';
34
+ * import { XmlValue } from '@endevops/effect-codec-xml';
35
35
  *
36
36
  * Schema.decodeUnknownSync(XmlValue)({ book: { '@id': '1', title: 'Dune' } }); // => { book: { '@id': '1', title: 'Dune' } }
37
37
  * Schema.decodeUnknownSync(XmlValue)({ book: { title: 42 } }); // => throws XmlValue
@@ -1 +1 @@
1
- {"version":3,"file":"xml-value.d.ts","names":[],"sources":["../src/xml-value.ts"],"mappings":";;;;;;;iBAOiB;YACL,eAAe;;;;;;;;;YAUf,gCAAgC,cAAc,YAAY;;;;;;;;qBAezD,aAAU,mBAAqB,SAAS;;;;;;;;;;;;;;qBAuExC,UAAU,OAAO,MAAM"}
1
+ {"version":3,"file":"xml-value.d.ts","names":[],"sources":["../src/xml-value.ts"],"mappings":";;;;;;;iBAOiB;YACL,eAAe;;;;;;;;;YAUf,gCAAgC,cAAc,YAAY;;;;;;;;qBAezD,aAAU,mBAAqB,SAAS;;;;;;;;;;;;;;qBAsExC,UAAU,OAAO,MAAM"}
package/dist/xml-value.js CHANGED
@@ -14,10 +14,9 @@ const MAX_GUARD_DEPTH = 512;
14
14
  */
15
15
  const isXmlValue = (input) => check(input, 0);
16
16
  /**
17
- * @description One level of {@link isXmlValue}, with the depth it was reached at. The depth is the whole defence against a value built to be hostile: a
18
- * self-referential object would otherwise recurse until the stack gave out, and a value nested thousands deep would take it with it. Both are
19
- * rejected here instead, which is why this is a real recursion with a bound rather than a loop — the model is a tree, and a tree is walked by walking
20
- * it.
17
+ * @description One level of {@link isXmlValue}, with the depth it was reached at. The depth is the whole defence against a value built to be hostile. A
18
+ * self-referential object would otherwise recurse until the stack gave out, and a value nested thousands deep would take it with it. This rejects
19
+ * both, so it is a bounded recursion rather than a loop. The model is a tree, and walking a tree means walking it.
21
20
  *
22
21
  * @param input - The candidate value.
23
22
  * @param depth - How many levels down this value sits.
@@ -57,13 +56,13 @@ const everyFieldIs = (input, depth) => {
57
56
  return true;
58
57
  };
59
58
  /**
60
- * @description A schema for {@link XmlValue}, so a value can be validated on its own — when it arrives from a store or a queue rather than from {@link parseXml},
61
- * and the schema it belongs to is not in hand.
59
+ * @description A schema for {@link XmlValue}, so a value can be validated on its own. This helps when it arrives from a store or a queue rather than from
60
+ * {@link parseXml}, and the schema it belongs to is not in hand.
62
61
  *
63
62
  * @example
64
63
  * ```typescript
65
64
  * import { Schema } from 'effect';
66
- * import { XmlValue } from '@endevops/effect-xml-codec';
65
+ * import { XmlValue } from '@endevops/effect-codec-xml';
67
66
  *
68
67
  * Schema.decodeUnknownSync(XmlValue)({ book: { '@id': '1', title: 'Dune' } }); // => { book: { '@id': '1', title: 'Dune' } }
69
68
  * Schema.decodeUnknownSync(XmlValue)({ book: { title: 42 } }); // => throws XmlValue
@@ -1 +1 @@
1
- {"version":3,"file":"xml-value.js","names":[],"sources":["../src/xml-value.ts"],"sourcesContent":["import { Predicate, Schema } from 'effect';\n\n/**\n * @description A record of child elements, attributes and character data. A key starting with `@` is an attribute, the reserved `#text` key is character data, and\n * every other key is a child element name. An `undefined` value means the field is absent, which is how an absent optional field stays\n * distinguishable from an empty one.\n */\nexport interface XmlRecord {\n readonly [name: string]: XmlValue | undefined;\n}\n\n/**\n * @description One value in an XML document: nothing at all, character data, a repeated run of children, or a record of attributes, text and child elements.\n * `undefined` is a value of its own rather than an omission, because that is how an absent optional field survives a round trip: a field with no\n * value stays distinguishable from a field whose value is the empty string, and the renderer writes neither of them. The shape is deliberately the\n * same one `Schema.toCodecStringTree` derives, which is what lets every schema feature Effect supports round-trip through this package without\n * re-implementing the derivation.\n */\nexport type XmlValue = string | undefined | ReadonlyArray<XmlValue> | XmlRecord;\n\n/**\n * @description How deep {@link isXmlValue} will walk before giving up. A document nested deeper than this is treated as invalid rather than allowed to exhaust the\n * stack.\n */\nconst MAX_GUARD_DEPTH = 512;\n\n/**\n * @description Whether an arbitrary value is a well-formed {@link XmlValue}.\n *\n * @param input - The candidate value.\n *\n * @returns Whether the value is absent, character data, an array of `XmlValue`, or a plain record of them.\n */\nexport const isXmlValue = (input: unknown): input is XmlValue => check(input, 0);\n\n/**\n * @description One level of {@link isXmlValue}, with the depth it was reached at. The depth is the whole defence against a value built to be hostile: a\n * self-referential object would otherwise recurse until the stack gave out, and a value nested thousands deep would take it with it. Both are\n * rejected here instead, which is why this is a real recursion with a bound rather than a loop — the model is a tree, and a tree is walked by walking\n * it.\n *\n * @param input - The candidate value.\n * @param depth - How many levels down this value sits.\n *\n * @returns Whether this level is legal, and the rest of the value with it.\n */\nconst check = (input: unknown, depth: number): boolean => {\n // `undefined` is a value in its own right: it is how an absent optional field survives a round trip, so a\n // record is allowed to hold one and a document is allowed to be missing a field.\n if (Predicate.isUndefined(input) || Predicate.isString(input)) return true;\n if (Predicate.isNull(input) || !Predicate.isObjectOrArray(input)) return false;\n\n if (depth > MAX_GUARD_DEPTH) return false;\n\n // Arrays and records are the only two things left, and each is a walk of its\n // own rather than another branch here: an array of them or a record of them.\n return Array.isArray(input) ? everyMemberIs(input, depth) : everyFieldIs(input, depth);\n};\n\n/**\n * @description Whether an array holds nothing but legal values one level down.\n *\n * @param members - The array's members.\n * @param depth - The depth the array itself sits at.\n *\n * @returns Whether every member is a legal `XmlValue`.\n */\nconst everyMemberIs = (members: ReadonlyArray<unknown>, depth: number): boolean => {\n for (const member of members) {\n if (!check(member, depth + 1)) return false;\n }\n return true;\n};\n\n/**\n * @description Whether an object is a plain record of legal values one level down. Plainness is checked here rather than by the caller because a `Date` or a `Map`\n * has values a record cannot hold, and walking them would be walking something the model has no way to represent.\n *\n * @param input - The object to walk.\n * @param depth - The depth the object itself sits at.\n *\n * @returns Whether the object is a plain record whose every field is a legal `XmlValue`.\n */\nconst everyFieldIs = (input: object, depth: number): boolean => {\n if (!Predicate.isReadonlyObject(input)) return false;\n for (const key of Object.keys(input)) {\n if (!check(input[key], depth + 1)) return false;\n }\n return true;\n};\n\n/**\n * @description A schema for {@link XmlValue}, so a value can be validated on its own — when it arrives from a store or a queue rather than from {@link parseXml},\n * and the schema it belongs to is not in hand.\n *\n * @example\n * ```typescript\n * import { Schema } from 'effect';\n * import { XmlValue } from '@endevops/effect-xml-codec';\n *\n * Schema.decodeUnknownSync(XmlValue)({ book: { '@id': '1', title: 'Dune' } }); // => { book: { '@id': '1', title: 'Dune' } }\n * Schema.decodeUnknownSync(XmlValue)({ book: { title: 42 } }); // => throws XmlValue\n * ```;\n */\nexport const XmlValue: Schema.Codec<XmlValue> = Schema.declare<XmlValue>(isXmlValue, {\n identifier: 'XmlValue',\n expected: 'an XML value: character data, an array of them, or a record of them',\n});\n"],"mappings":";;;;;;AAwBA,MAAM,kBAAkB;;;;;;;;AASxB,MAAa,cAAc,UAAsC,MAAM,OAAO,CAAC;;;;;;;;;;;;AAa/E,MAAM,SAAS,OAAgB,UAA2B;CAGxD,IAAI,UAAU,YAAY,KAAK,KAAK,UAAU,SAAS,KAAK,GAAG,OAAO;CACtE,IAAI,UAAU,OAAO,KAAK,KAAK,CAAC,UAAU,gBAAgB,KAAK,GAAG,OAAO;CAEzE,IAAI,QAAQ,iBAAiB,OAAO;CAIpC,OAAO,MAAM,QAAQ,KAAK,IAAI,cAAc,OAAO,KAAK,IAAI,aAAa,OAAO,KAAK;AACvF;;;;;;;;;AAUA,MAAM,iBAAiB,SAAiC,UAA2B;CACjF,KAAK,MAAM,UAAU,SACnB,IAAI,CAAC,MAAM,QAAQ,QAAQ,CAAC,GAAG,OAAO;CAExC,OAAO;AACT;;;;;;;;;;AAWA,MAAM,gBAAgB,OAAe,UAA2B;CAC9D,IAAI,CAAC,UAAU,iBAAiB,KAAK,GAAG,OAAO;CAC/C,KAAK,MAAM,OAAO,OAAO,KAAK,KAAK,GACjC,IAAI,CAAC,MAAM,MAAM,MAAM,QAAQ,CAAC,GAAG,OAAO;CAE5C,OAAO;AACT;;;;;;;;;;;;;;AAeA,MAAa,WAAmC,OAAO,QAAkB,YAAY;CACnF,YAAY;CACZ,UAAU;AACZ,CAAC"}
1
+ {"version":3,"file":"xml-value.js","names":[],"sources":["../src/xml-value.ts"],"sourcesContent":["import { Predicate, Schema } from 'effect';\n\n/**\n * @description A record of child elements, attributes and character data. A key starting with `@` is an attribute, the reserved `#text` key is character data, and\n * every other key is a child element name. An `undefined` value means the field is absent, which is how an absent optional field stays\n * distinguishable from an empty one.\n */\nexport interface XmlRecord {\n readonly [name: string]: XmlValue | undefined;\n}\n\n/**\n * @description One value in an XML document: nothing at all, character data, a repeated run of children, or a record of attributes, text and child elements.\n * `undefined` is a value of its own rather than an omission, because that is how an absent optional field survives a round trip. A field with no\n * value stays distinguishable from a field whose value is the empty string, and the renderer writes neither of them. The shape is the same one\n * `Schema.toCodecStringTree` derives, and sharing that shape is what lets every schema feature Effect supports round-trip through this package\n * without re-implementing the derivation.\n */\nexport type XmlValue = string | undefined | ReadonlyArray<XmlValue> | XmlRecord;\n\n/**\n * @description How deep {@link isXmlValue} will walk before giving up. A document nested deeper than this is treated as invalid rather than allowed to exhaust the\n * stack.\n */\nconst MAX_GUARD_DEPTH = 512;\n\n/**\n * @description Whether an arbitrary value is a well-formed {@link XmlValue}.\n *\n * @param input - The candidate value.\n *\n * @returns Whether the value is absent, character data, an array of `XmlValue`, or a plain record of them.\n */\nexport const isXmlValue = (input: unknown): input is XmlValue => check(input, 0);\n\n/**\n * @description One level of {@link isXmlValue}, with the depth it was reached at. The depth is the whole defence against a value built to be hostile. A\n * self-referential object would otherwise recurse until the stack gave out, and a value nested thousands deep would take it with it. This rejects\n * both, so it is a bounded recursion rather than a loop. The model is a tree, and walking a tree means walking it.\n *\n * @param input - The candidate value.\n * @param depth - How many levels down this value sits.\n *\n * @returns Whether this level is legal, and the rest of the value with it.\n */\nconst check = (input: unknown, depth: number): boolean => {\n // `undefined` is a value in its own right: it is how an absent optional field survives a round trip, so a\n // record is allowed to hold one and a document is allowed to be missing a field.\n if (Predicate.isUndefined(input) || Predicate.isString(input)) return true;\n if (Predicate.isNull(input) || !Predicate.isObjectOrArray(input)) return false;\n\n if (depth > MAX_GUARD_DEPTH) return false;\n\n // Arrays and records are the only two things left, and each is a walk of its\n // own rather than another branch here: an array of them or a record of them.\n return Array.isArray(input) ? everyMemberIs(input, depth) : everyFieldIs(input, depth);\n};\n\n/**\n * @description Whether an array holds nothing but legal values one level down.\n *\n * @param members - The array's members.\n * @param depth - The depth the array itself sits at.\n *\n * @returns Whether every member is a legal `XmlValue`.\n */\nconst everyMemberIs = (members: ReadonlyArray<unknown>, depth: number): boolean => {\n for (const member of members) {\n if (!check(member, depth + 1)) return false;\n }\n return true;\n};\n\n/**\n * @description Whether an object is a plain record of legal values one level down. Plainness is checked here rather than by the caller because a `Date` or a `Map`\n * has values a record cannot hold, and walking them would be walking something the model has no way to represent.\n *\n * @param input - The object to walk.\n * @param depth - The depth the object itself sits at.\n *\n * @returns Whether the object is a plain record whose every field is a legal `XmlValue`.\n */\nconst everyFieldIs = (input: object, depth: number): boolean => {\n if (!Predicate.isReadonlyObject(input)) return false;\n for (const key of Object.keys(input)) {\n if (!check(input[key], depth + 1)) return false;\n }\n return true;\n};\n\n/**\n * @description A schema for {@link XmlValue}, so a value can be validated on its own. This helps when it arrives from a store or a queue rather than from\n * {@link parseXml}, and the schema it belongs to is not in hand.\n *\n * @example\n * ```typescript\n * import { Schema } from 'effect';\n * import { XmlValue } from '@endevops/effect-codec-xml';\n *\n * Schema.decodeUnknownSync(XmlValue)({ book: { '@id': '1', title: 'Dune' } }); // => { book: { '@id': '1', title: 'Dune' } }\n * Schema.decodeUnknownSync(XmlValue)({ book: { title: 42 } }); // => throws XmlValue\n * ```;\n */\nexport const XmlValue: Schema.Codec<XmlValue> = Schema.declare<XmlValue>(isXmlValue, {\n identifier: 'XmlValue',\n expected: 'an XML value: character data, an array of them, or a record of them',\n});\n"],"mappings":";;;;;;AAwBA,MAAM,kBAAkB;;;;;;;;AASxB,MAAa,cAAc,UAAsC,MAAM,OAAO,CAAC;;;;;;;;;;;AAY/E,MAAM,SAAS,OAAgB,UAA2B;CAGxD,IAAI,UAAU,YAAY,KAAK,KAAK,UAAU,SAAS,KAAK,GAAG,OAAO;CACtE,IAAI,UAAU,OAAO,KAAK,KAAK,CAAC,UAAU,gBAAgB,KAAK,GAAG,OAAO;CAEzE,IAAI,QAAQ,iBAAiB,OAAO;CAIpC,OAAO,MAAM,QAAQ,KAAK,IAAI,cAAc,OAAO,KAAK,IAAI,aAAa,OAAO,KAAK;AACvF;;;;;;;;;AAUA,MAAM,iBAAiB,SAAiC,UAA2B;CACjF,KAAK,MAAM,UAAU,SACnB,IAAI,CAAC,MAAM,QAAQ,QAAQ,CAAC,GAAG,OAAO;CAExC,OAAO;AACT;;;;;;;;;;AAWA,MAAM,gBAAgB,OAAe,UAA2B;CAC9D,IAAI,CAAC,UAAU,iBAAiB,KAAK,GAAG,OAAO;CAC/C,KAAK,MAAM,OAAO,OAAO,KAAK,KAAK,GACjC,IAAI,CAAC,MAAM,MAAM,MAAM,QAAQ,CAAC,GAAG,OAAO;CAE5C,OAAO;AACT;;;;;;;;;;;;;;AAeA,MAAa,WAAmC,OAAO,QAAkB,YAAY;CACnF,YAAY;CACZ,UAAU;AACZ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@endevops/effect-codec-xml",
3
- "version": "0.0.1",
3
+ "version": "0.1.0-beta.1",
4
4
  "description": "A round-trip Effect Schema codec for XML, with the entity decoder and XML name validators it reads documents with. Derives an XML representation from any Effect Schema and reads it back, with attributes as `@`-prefixed fields.",
5
5
  "keywords": [
6
6
  "codec",
package/src/codec.ts CHANGED
@@ -3,18 +3,18 @@
3
3
  // `toCodecXml` is this package's counterpart to `Schema.toCodecJson`. It
4
4
  // returns a `Schema` whose `Type` is the source schema's `Type` and whose
5
5
  // `Encoded` is XML text, so a value is written with `Schema.encodeSync(codec)`
6
- // and read back with `Schema.decodeSync(codec)` — one call each, the way the
7
- // JSON codec works. There is no value tree at the call site and no second call
8
- // to a renderer or a parser.
6
+ // and read back with `Schema.decodeSync(codec)`. That is one call each, as with
7
+ // the JSON codec. There is no value tree at the call site and no second call to
8
+ // a renderer or a parser.
9
9
  //
10
10
  // The derivation underneath is Effect's own `Schema.toCodecStringTree`, so
11
11
  // every schema feature Effect supports composes here without this package
12
- // re-implementing the walk over a schema AST. On the way out the codec runs the
13
- // value tree through `renderXml`; on the way back in it runs the document
14
- // through `parseXml`. Both are the same text layer this package exports on
15
- // their own, and both failures — an illegal name, a document that is not
16
- // well-formed — arrive as the `SchemaIssue.Issue` a schema is expected to
17
- // report, with the underlying message preserved.
12
+ // re-implementing the walk over a schema AST. On encode the codec runs the value
13
+ // tree through `renderXml`; on decode it runs the document through `parseXml`.
14
+ // Both are the same text layer this package exports on its own, and both
15
+ // failures arrive as the `SchemaIssue.Issue` a schema is expected to report,
16
+ // with the underlying message preserved. Those failures are an illegal name and
17
+ // a document that is not well-formed.
18
18
  //
19
19
  // The conventions stay in the keys: a key starting with `@` is an attribute,
20
20
  // `#text` is character data, and every other key is a child element. The root
@@ -22,8 +22,9 @@
22
22
  // has one, and from the `rootName` option otherwise; it defaults to `'root'`,
23
23
  // the same name Effect's own XML encoder uses.
24
24
 
25
- import { Effect, Predicate, Schema, SchemaAST, SchemaIssue, SchemaTransformation } from 'effect';
25
+ import { Effect, Function, Predicate, Schema, SchemaAST, SchemaIssue, SchemaTransformation } from 'effect';
26
26
 
27
+ import type { NamespacePlan } from './namespaces.ts';
27
28
  import type { XmlParseOptions } from './parse.ts';
28
29
  import type { XmlRenderOptions } from './render.ts';
29
30
  import type { XmlValue } from './xml-value.ts';
@@ -36,8 +37,8 @@ import { renderXml } from './render.ts';
36
37
  /**
37
38
  * @description Options for {@link toCodecXml}.\
38
39
  * The render options name and shape the document; the parse options decide how strictly it is read back.\
39
- * `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
40
- * back to `'root'`.
40
+ * `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
41
+ * falls back to `'root'`.
41
42
  */
42
43
  export type XmlCodecOptions = XmlRenderOptions & XmlParseOptions;
43
44
 
@@ -49,18 +50,40 @@ export interface toCodecXml<S extends Schema.Constraint> extends Schema.decodeTo
49
50
  readonly Rebuild: toCodecXml<S>;
50
51
  }
51
52
 
53
+ // 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
54
+ // alone on both sides, so the codec skips the name walk.
55
+ const isActivePlan = (plan: NamespacePlan): boolean =>
56
+ plan.byKey.size > 0 ||
57
+ plan.nameByKey.size > 0 ||
58
+ plan.attributeKeys.size > 0 ||
59
+ plan.valueByElement.size > 0 ||
60
+ plan.root !== undefined ||
61
+ plan.rootName !== undefined;
62
+
63
+ // 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`
64
+ // or `title` annotation, then `'root'`. A root namespace with a prefix qualifies the name unless the caller already wrote one.
65
+ const resolveRenderOptions = <S extends Schema.Constraint>(schema: S, plan: NamespacePlan, options: XmlCodecOptions): XmlRenderOptions => {
66
+ const rootName =
67
+ options.rootName ?? plan.rootName ?? SchemaAST.resolveIdentifier(schema.ast) ?? SchemaAST.resolveTitle(schema.ast) ?? DEFAULT_ROOT_NAME;
68
+ const wireRootName = plan.root !== undefined && plan.root.prefix !== '' && !rootName.includes(':') ? `${plan.root.prefix}:${rootName}` : rootName;
69
+ return { ...options, rootName: wireRootName };
70
+ };
71
+
52
72
  /**
53
73
  * @description Derives the XML codec for a schema: a `Schema` whose `Encoded` is an XML document, so `Schema.encodeSync(codec)` writes text and
54
74
  * `Schema.decodeSync(codec)` reads it back. The derivation is Effect's `Schema.toCodecStringTree`; the text layer is this package's {@link renderXml}
55
- * and {@link parseXml}.
75
+ * and {@link parseXml}. Call it data-first, `toCodecXml(schema, options)`, or data-last, `toCodecXml(options)(schema)`, so it drops into `pipe` beside
76
+ * 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
77
+ * options object as data-last. `pipe(schema, toCodecXml)` carries no options and works because a schema on its own is data-first.
56
78
  *
57
79
  * @example
58
80
  * ```typescript
59
- * import { Schema } from 'effect';
60
- * import { toCodecXml } from '@endevops/effect-xml-codec';
81
+ * import { Schema, pipe } from 'effect';
82
+ * import { toCodecXml } from '@endevops/effect-codec-xml';
61
83
  *
62
84
  * const Book = Schema.Struct({ '@id': Schema.String, title: Schema.String, pages: Schema.Number });
63
85
  * const codec = toCodecXml(Book, { rootName: 'book' });
86
+ * const piped = pipe(Book, toCodecXml({ rootName: 'book' }));
64
87
  *
65
88
  * const value = { '@id': '1', title: 'Dune', pages: 412 };
66
89
  *
@@ -71,66 +94,63 @@ export interface toCodecXml<S extends Schema.Constraint> extends Schema.decodeTo
71
94
  * // => { '@id': '1', title: 'Dune', pages: 412 }
72
95
  * ```;
73
96
  *
74
- * @param schema - The schema describing the value.
75
- * @param options - Render and parse options. `rootName` defaults to the schema's `identifier` or `title` annotation, then to `'root'`.
97
+ * @param schema - The schema describing the value, in the data-first form.
98
+ * @param options - Render and parse options. `rootName` defaults to the schema's `identifier` or `title` annotation, then to `'root'`. In the
99
+ * data-last form this is the only argument, and the schema arrives from `pipe`.
76
100
  *
77
- * @returns The codec, with the source schema's `Type` and the same service requirements.
101
+ * @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
102
+ * codec.
78
103
  */
79
- export const toCodecXml = <S extends Schema.Constraint>(schema: S, options: XmlCodecOptions = {}): toCodecXml<S> => {
80
- const planned = namespacePlan(schema);
81
- if (Predicate.hasProperty(planned, 'error')) {
82
- throw new Error(`Invalid XML namespace annotation:\n\t- ${planned.error}.`);
83
- }
84
- const plan = planned.plan;
85
- const active =
86
- plan.byKey.size > 0 ||
87
- plan.nameByKey.size > 0 ||
88
- plan.attributeKeys.size > 0 ||
89
- plan.valueByElement.size > 0 ||
90
- plan.root !== undefined ||
91
- plan.rootName !== undefined;
104
+ export const toCodecXml: {
105
+ <S extends Schema.Constraint>(schema: S, options?: XmlCodecOptions): toCodecXml<S>;
106
+ (options?: XmlCodecOptions): <S extends Schema.Constraint>(schema: S) => toCodecXml<S>;
107
+ } = Function.dual(
108
+ args => Schema.isSchema(args[0]),
109
+ <S extends Schema.Constraint>(schema: S, options: XmlCodecOptions = {}): toCodecXml<S> => {
110
+ const planned = namespacePlan(schema);
111
+ if (Predicate.hasProperty(planned, 'error')) {
112
+ throw new Error(`Invalid XML namespace annotation:\n\t- ${planned.error}.`);
113
+ }
114
+ const plan = planned.plan;
115
+ const active = isActivePlan(plan);
116
+ const renderOptions = resolveRenderOptions(schema, plan, options);
92
117
 
93
- const rootName =
94
- options.rootName ?? plan.rootName ?? SchemaAST.resolveIdentifier(schema.ast) ?? SchemaAST.resolveTitle(schema.ast) ?? DEFAULT_ROOT_NAME;
95
- const wireRootName = plan.root !== undefined && plan.root.prefix !== '' && !rootName.includes(':') ? `${plan.root.prefix}:${rootName}` : rootName;
118
+ return Schema.String.pipe(
119
+ Schema.decodeTo(
120
+ Schema.toCodecStringTree(schema),
121
+ SchemaTransformation.transformEffect({
122
+ decode: (text, parseOptions) => {
123
+ if (!Predicate.isString(text)) {
124
+ return Effect.fail(new SchemaIssue.InvalidValue({ message: `Expected a string, but received ${typeof text}.` }, text, parseOptions));
125
+ }
96
126
 
97
- const renderOptions: XmlRenderOptions = { ...options, rootName: wireRootName };
98
-
99
- return Schema.String.pipe(
100
- Schema.decodeTo(
101
- Schema.toCodecStringTree(schema),
102
- SchemaTransformation.transformEffect({
103
- decode: (text, parseOptions) => {
104
- if (!Predicate.isString(text)) {
105
- return Effect.fail(new SchemaIssue.InvalidValue({ message: `Expected a string, but received ${typeof text}.` }, text, parseOptions));
106
- }
127
+ return parseXml(text, options).pipe(
128
+ Effect.map(value => (active ? decodeNames(value, plan, {}, ROOT_ELEMENT) : value)),
129
+ Effect.tapError(error =>
130
+ Effect.logError(`XML parse error: ${error.message}`).pipe(Effect.annotateLogs({ cause: error, message: 'XML parse error' }))
131
+ ),
132
+ Effect.mapError(error => new SchemaIssue.InvalidValue({ message: error.message }, text, parseOptions))
133
+ );
134
+ },
107
135
 
108
- return parseXml(text, options).pipe(
109
- Effect.map(value => (active ? decodeNames(value, plan, {}, ROOT_ELEMENT) : value)),
110
- Effect.tapError(error =>
111
- Effect.logError(`XML parse error: ${error.message}`).pipe(Effect.annotateLogs({ cause: error, message: 'XML parse error' }))
112
- ),
113
- Effect.mapError(error => new SchemaIssue.InvalidValue({ message: error.message }, text, parseOptions))
114
- );
115
- },
136
+ encode: (value, parseOptions) => {
137
+ if (active && Array.isArray(value) && (plan.root !== undefined || plan.rootName !== undefined)) {
138
+ return Effect.fail(
139
+ new SchemaIssue.InvalidValue(
140
+ { message: 'An array at the root of a namespaced schema cannot carry the root namespace or name.' },
141
+ value,
142
+ parseOptions
143
+ )
144
+ );
145
+ }
116
146
 
117
- encode: (value, parseOptions) => {
118
- if (active && Array.isArray(value) && (plan.root !== undefined || plan.rootName !== undefined)) {
119
- return Effect.fail(
120
- new SchemaIssue.InvalidValue(
121
- { message: 'An array at the root of a namespaced schema cannot carry the root namespace or name.' },
122
- value,
123
- parseOptions
124
- )
147
+ const wire = active ? encodeNames(value as XmlValue, plan, plan.root, {}, ROOT_ELEMENT) : (value as XmlValue);
148
+ return renderXml(wire, renderOptions).pipe(
149
+ Effect.mapError(error => new SchemaIssue.InvalidValue({ message: error.message }, value, parseOptions))
125
150
  );
126
- }
127
-
128
- const wire = active ? encodeNames(value as XmlValue, plan, plan.root, {}, ROOT_ELEMENT) : (value as XmlValue);
129
- return renderXml(wire, renderOptions).pipe(
130
- Effect.mapError(error => new SchemaIssue.InvalidValue({ message: error.message }, value, parseOptions))
131
- );
132
- },
133
- })
134
- )
135
- );
136
- };
151
+ },
152
+ })
153
+ )
154
+ );
155
+ }
156
+ );
@@ -87,9 +87,9 @@ export const isTextKey = (key: string): boolean => key === TEXT_KEY;
87
87
  export const isReservedKey = (key: string): boolean => isTextKey(key);
88
88
 
89
89
  /**
90
- * @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
91
- * — 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
92
- * an {@link XmlParseError}; the callers that need it in a typed channel use {@link resolveName}, which wraps this.
90
+ * @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
91
+ * 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
92
+ * illegal name by throwing an {@link XmlParseError}; the callers that need it in a typed channel use {@link resolveName}, which wraps this.
93
93
  *
94
94
  * @param name - The candidate element or attribute name.
95
95
  * @param options - Repair mode and XML version.
@@ -131,10 +131,10 @@ const resolveNameResult = (name: string, options: ResolveNameOptions): Result.Re
131
131
  };
132
132
 
133
133
  /**
134
- * @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
135
- * illegal name under `'error'` mode is a rejection rather than a value, so this returns an `Effect` with the `XmlParseError` in its error channel
136
- * 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
137
- * `render.ts` use {@link resolveNameSync} directly, because their walks are synchronous hot paths.
134
+ * @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
135
+ * 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
136
+ * 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`
137
+ * use {@link resolveNameSync} directly, because their walks are synchronous hot paths.
138
138
  *
139
139
  * @param name - The candidate element or attribute name.
140
140
  * @param options - Repair mode and XML version.