@endevops/effect-codec-xml 0.1.0-beta.1 → 0.1.0-effect.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 +62 -63
  2. package/dist/codec.d.ts +9 -17
  3. package/dist/codec.d.ts.map +1 -1
  4. package/dist/codec.js +16 -25
  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 +64 -63
  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 +7 -6
  35. package/dist/xml-value.js.map +1 -1
  36. package/package.json +1 -1
  37. package/src/codec.ts +71 -91
  38. package/src/conventions.ts +7 -7
  39. package/src/entities/entity-decoder.ts +99 -98
  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 +35 -34
  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 +11 -10
@@ -5,13 +5,13 @@
5
5
  // `@nodable/entities@2.2.0` (`src/EntityDecoder.js`) as native TypeScript.
6
6
  //
7
7
  // Three entity tiers exist and the distinction is the security model, not a performance detail. `input` and
8
- // `external` entities are injected at runtime (DOCTYPE declarations, and whatever a caller hands the
9
- // decoder), so they are the untrusted surface and are what the expansion limits count by default. `base` is
8
+ // `external` entities are injected at runtime — DOCTYPE declarations, and whatever a caller hands the
9
+ // decoder — so they are the untrusted surface and are what the expansion limits count by default. `base` is
10
10
  // the five XML predefined entities plus the caller's own `namedEntities`. Numeric references are always
11
11
  // `base`: they cannot recurse.
12
12
  //
13
- // Behaviour is transcribed, not corrected. Several upstream quirks are relied on by the output a caller
14
- // already sees. A `&` inside a registered value is not filtered the way the docs claim, `postCheck` is
13
+ // Behaviour is transcribed, not corrected. Several upstream quirks are load-bearing for the output a caller
14
+ // already sees — a `&` inside a registered value is not filtered the way the docs claim, `postCheck` is
15
15
  // skipped on the two fast paths, C1 codepoints and the FFFE/FFFF noncharacters are not classified at all.
16
16
  // Each is called out where it appears, and none of them is repaired, because this class sits in front of XXE
17
17
  // and entity-expansion handling where a silent fix is a change to every consumer's output.
@@ -26,7 +26,7 @@ import { XML as DEFAULT_XML_ENTITIES } from './entity-tables.ts';
26
26
  // Character codes
27
27
  //
28
28
  // The scan is a hand-rolled `charCodeAt` loop, so the three codes it tests against are named here rather than
29
- // written as literals. `&` opens a reference, `;` closes one, `#` marks a reference as numeric.
29
+ // written as literals. `&` opens a reference, `;` closes one, `#` is what makes a reference numeric.
30
30
  // ---------------------------------------------------------------------------
31
31
 
32
32
  const CODE_AMPERSAND = 38;
@@ -37,8 +37,8 @@ const CODE_UPPER_X = 88;
37
37
 
38
38
  /**
39
39
  * @description The widest entity name {@link EntityDecoder.decode} will look for, in characters. The forward scan for `;` stops once more than this many characters
40
- * have passed since the `&`, so a longer run is not treated as one entity. It is copied through as literal text. The bound keeps a document with a
41
- * megabyte of non-entity text between two ampersands from being sliced.
40
+ * have passed since the `&`, so a longer run is not treated as one entity — it is copied through as literal text. The bound is what keeps a document
41
+ * with a megabyte of non-entity text between two ampersands from being sliced.
42
42
  */
43
43
  const MAX_TOKEN_LENGTH = 32;
44
44
 
@@ -49,8 +49,8 @@ const MAX_TOKEN_LENGTH = 32;
49
49
  const MAX_CODE_POINT = 0x10ffff;
50
50
 
51
51
  /**
52
- * @description Returned by `#classifyNCR` for a codepoint that carries no minimum action level. This value distinguishes "no restriction" from `NCR_LEVEL.allow`:
53
- * both end up expanding, but only the first lets `numericAllowed: false` short-circuit the whole pipeline.
52
+ * @description Returned by `#classifyNCR` for a codepoint that carries no minimum action level, which is what distinguishes "no restriction" from
53
+ * `NCR_LEVEL.allow` — both end up expanding, but only the first lets `numericAllowed: false` short-circuit the whole pipeline.
54
54
  */
55
55
  const NO_MINIMUM_LEVEL = -1;
56
56
 
@@ -61,7 +61,7 @@ const NO_MINIMUM_LEVEL = -1;
61
61
  /**
62
62
  * @description Characters that may not appear in an entity name registered through {@link EntityDecoder.setExternalEntities} or
63
63
  * {@link EntityDecoder.addExternalEntity}. A name carrying one of these cannot be written as `&name;` at all, so registration refuses it rather than
64
- * storing a name no document could ever reference. The set is the upstream string verbatim, including its duplicated backslash. A `Set` discards the
64
+ * storing a name no document could ever reference. The set is the upstream string verbatim, including its duplicated backslash — a `Set` discards the
65
65
  * duplicate, so the effective set is the eighteen characters below.
66
66
  */
67
67
  const SPECIAL_CHARS: ReadonlySet<string> = new Set('!?\\/[]$%{}^&*()<>|+');
@@ -94,7 +94,7 @@ type LimitTier = typeof LIMIT_TIER_ALL | typeof LIMIT_TIER_BASE | typeof LIMIT_T
94
94
 
95
95
  /**
96
96
  * @description The NCR action levels, in severity order. A higher number is a stricter action, and the resolver takes the maximum of the configured level and the
97
- * minimum a codepoint range imposes. A range can therefore only make an entity stricter than the caller asked for, never more lenient.
97
+ * minimum a codepoint range imposes, so a range can only ever make an entity stricter than the caller asked for — never more lenient.
98
98
  */
99
99
  const NCR_LEVEL = Object.freeze({ allow: 0, leave: 1, remove: 2, throw: 3 });
100
100
 
@@ -111,7 +111,7 @@ type NcrLevelName = keyof typeof NCR_LEVEL;
111
111
  type XmlVersion = 1 | 1.1;
112
112
 
113
113
  /**
114
- * @description The C0 control codes XML 1.0 §2.2 permits as literal characters. Every other code in U+0001 to U+001F is prohibited.
114
+ * @description The C0 control codes XML 1.0 §2.2 permits as literal characters. Every other code in U+0001–U+001F is prohibited.
115
115
  */
116
116
  const XML10_ALLOWED_C0: ReadonlySet<number> = new Set([0x09, 0x0a, 0x0d]);
117
117
 
@@ -126,8 +126,8 @@ const XML10_ALLOWED_C0: ReadonlySet<number> = new Set([0x09, 0x0a, 0x0d]);
126
126
  export type EntityHookAction = 'allow' | 'block' | 'throw';
127
127
 
128
128
  /**
129
- * @description A function-valued entity replacement: the `val` of the legacy `{ regex, val }` form when it is not a string. This decoder cannot use one. A
130
- * function has no meaning without the regex it was meant to be matched against, so such an entry is dropped at registration rather than expanded.
129
+ * @description A function-valued entity replacement: the `val` of the legacy `{ regex, val }` form when it is not a string. This decoder cannot use one — a
130
+ * function has no meaning without the regex it was meant to be matched against — so such an entry is dropped at registration rather than expanded.
131
131
  */
132
132
  export type EntityValFn = (match: string, captured: string, ...rest: Array<unknown>) => string;
133
133
 
@@ -167,16 +167,16 @@ export const ENTITY_ACTION: Readonly<{ ALLOW: 'allow'; BLOCK: 'block'; THROW: 't
167
167
  /**
168
168
  * @description Which entity categories count toward the expansion limits.
169
169
  *
170
- * - `'external'`: only input/runtime + persistent external entities. The default, and the only one that ignores the built-in XML entities.
171
- * - `'base'`: only the built-in XML entities, the caller's `namedEntities`, and numeric references.
172
- * - `'all'`: every entity regardless of tier.
173
- * - `Array<'external' | 'base'>`: an explicit combination. An empty array is honoured literally: nothing counts, so the limits can never trip.
170
+ * - `'external'` — only input/runtime + persistent external entities. The default, and the only one that ignores the built-in XML entities.
171
+ * - `'base'` — only the built-in XML entities, the caller's `namedEntities`, and numeric references.
172
+ * - `'all'` — every entity regardless of tier.
173
+ * - `Array<'external' | 'base'>` — an explicit combination. An empty array is honoured literally: nothing counts, so the limits can never trip.
174
174
  */
175
175
  export type ApplyLimitsTo = 'external' | 'base' | 'all' | Array<'external' | 'base'>;
176
176
 
177
177
  /**
178
178
  * @description Ceilings on what a single document's entity references may cost. Both are cumulative across {@link EntityDecoder.decode} calls until
179
- * {@link EntityDecoder.reset}, and both default to `0`, meaning unlimited. `0`, and any negative or non-numeric value, is unlimited, because the
179
+ * {@link EntityDecoder.reset}, and both default to `0`, meaning unlimited. `0` — and any negative or non-numeric value — is unlimited, because the
180
180
  * runtime tests `> 0` rather than truthiness of the configured number.
181
181
  */
182
182
  export interface EntityDecoderLimitOptions {
@@ -197,8 +197,8 @@ export interface EntityDecoderLimitOptions {
197
197
  maxExpandedLength?: number;
198
198
 
199
199
  /**
200
- * @description Which tiers count against both limits. Defaults to `'external'`, which keeps the built-in entities, including every numeric reference, from being
201
- * able to trip a limit on a document the caller already trusts.
200
+ * @description Which tiers count against both limits. Defaults to `'external'`, which is what keeps the built-in entities — including every numeric reference —
201
+ * from being able to trip a limit on a document the caller already trusts.
202
202
  *
203
203
  * @default 'external'
204
204
  */
@@ -211,7 +211,7 @@ export interface EntityDecoderLimitOptions {
211
211
  */
212
212
  export interface EntityDecoderNCROptions {
213
213
  /**
214
- * @description The XML version whose codepoint restrictions apply. `1.0` prohibits the C0 controls U+0001 to U+001F other than tab, newline and carriage return;
214
+ * @description The XML version whose codepoint restrictions apply. `1.0` prohibits the C0 controls U+0001–U+001F other than tab, newline and carriage return;
215
215
  * `1.1` does not, since it permits them when written as references. Any value other than `1.1` is read as `1.0`.
216
216
  *
217
217
  * @default 1.0
@@ -219,8 +219,8 @@ export interface EntityDecoderNCROptions {
219
219
  xmlVersion?: 1.0 | 1.1;
220
220
 
221
221
  /**
222
- * @description The base action for every numeric reference. Codepoint ranges that carry a minimum (surrogates always, the XML 1.0 C0 controls under `1.0`, and
223
- * null under `nullNCR`) take the stricter of the two, so this is a floor and not an override.
222
+ * @description The base action for every numeric reference. Codepoint ranges that carry a minimum — surrogates always, the XML 1.0 C0 controls under `1.0`, and
223
+ * null under `nullNCR` — take the stricter of the two, so this is a floor and not an override.
224
224
  *
225
225
  * @default 'allow'
226
226
  */
@@ -240,10 +240,10 @@ export interface EntityDecoderNCROptions {
240
240
  export interface EntityDecoderOptions {
241
241
  /**
242
242
  * @description Extra named entities merged into the `base` map alongside the five XML predefined ones. A string value is used directly; a `{ regex, val }` or `{
243
- * regx, val }` envelope is unwrapped to its `val`. Anything else (a number, `null`, a function, an envelope whose `val` is a function) is dropped,
244
- * leaving the name unresolvable rather than failing the construction. Upstream's documentation says a value containing `&` is skipped here, to
245
- * prevent recursive expansion. It is not: the code stores the value unchanged, and only {@link EntityDecoder.addExternalEntity} checks for `&`.
246
- * Preserved as-is; see the note on the class.
243
+ * regx, val }` envelope is unwrapped to its `val`. Anything else — a number, `null`, a function, an envelope whose `val` is a function — is
244
+ * dropped, leaving the name unresolvable rather than failing the construction. Upstream's documentation says a value containing `&` is skipped
245
+ * here, to prevent recursive expansion. It is not: the code stores the value unchanged, and only {@link EntityDecoder.addExternalEntity} checks for
246
+ * `&`. Preserved as-is; see the note on the class.
247
247
  *
248
248
  * @default null
249
249
  */
@@ -251,7 +251,7 @@ export interface EntityDecoderOptions {
251
251
 
252
252
  /**
253
253
  * @description Called once on the finished string. Receives `(resolved, original)` and must return a string; return `original` to reject the expansion outright,
254
- * or a sanitised form of `resolved` to clean it. It is _not_ called for a string that never reaches the scanning loop: an empty string, a
254
+ * or a sanitised form of `resolved` to clean it. It is _not_ called for a string that never reaches the scanning loop — an empty string, a
255
255
  * non-string, or any string with no `&` in it. A caller relying on `postCheck` to sanitise therefore has to know that a string with no ampersand is
256
256
  * never inspected.
257
257
  *
@@ -260,8 +260,8 @@ export interface EntityDecoderOptions {
260
260
  postCheck?: ((resolved: string, original: string) => string) | null;
261
261
 
262
262
  /**
263
- * @description Whether numeric references expand at all. Turning it off leaves every one of them in the output verbatim, _except_ the codepoints that carry a
264
- * minimum action of `remove` or stricter, which are still handled, because that classification runs first. This is why the option is safe to rely
263
+ * @description Whether numeric references expand at all. Turning it off leaves every one of them in the output verbatim — _except_ the codepoints that carry a
264
+ * minimum action of `remove` or stricter, which are still handled, because that classification runs first and is what makes the option safe to rely
265
265
  * on.
266
266
  *
267
267
  * @default true
@@ -278,7 +278,7 @@ export interface EntityDecoderOptions {
278
278
  /**
279
279
  * @description Names to delete outright, matched the same way as {@link EntityDecoderOptions.leave}. A removed reference is charged to the `external` tier even
280
280
  * when the name is a built-in one, so a document full of removed built-ins can trip an `applyLimitsTo: 'external'` limit it would not otherwise be
281
- * subject to. Preserved as-is; the only in-code comment claims the charge is for unknown references, which does not distinguish them.
281
+ * subject to. Preserved as-is; the only in-code comment claims the charge is for unknown references, which is not what distinguishes them.
282
282
  *
283
283
  * @default [ ]
284
284
  */
@@ -305,7 +305,7 @@ export interface EntityDecoderOptions {
305
305
 
306
306
  /**
307
307
  * @description Called once per entity as it is registered through {@link EntityDecoder.addInputEntities}. Same contract as
308
- * {@link EntityDecoderOptions.onExternalEntity}, and unlike it the hook is not the only filter. See the class note on name validation.
308
+ * {@link EntityDecoderOptions.onExternalEntity}, and unlike it the hook is not the only filter — see the class note on name validation.
309
309
  *
310
310
  * @default null
311
311
  */
@@ -336,7 +336,7 @@ type EntityInputMap = Readonly<Record<string, EntityInputValue>> | null | undefi
336
336
 
337
337
  /**
338
338
  * @description What one reference expanded to, tagged with the tier its limit accounting charges. The field is the replacement text itself for a named entity, the
339
- * character for a numeric reference, and `''` for a removed one. Those are the three shapes the walk pushes into its output.
339
+ * character for a numeric reference, and `''` for a removed one — the three shapes the walk pushes into its output.
340
340
  */
341
341
  type ResolvedEntity = { value: string; tier: LimitTier };
342
342
 
@@ -357,7 +357,7 @@ type HookContext = 'external' | 'input';
357
357
  *
358
358
  * @returns An effect producing the name, unchanged, so the call can be inlined. Fails with {@link XmlError} and the `InvalidEntityName` reason,
359
359
  * carrying the offending character. The `[EntityReplacer]` prefix in the message is preserved verbatim from the original throw, despite naming a
360
- * class this decoder does not have. It is relied on by anything matching the message text.
360
+ * class this decoder does not have — it is load-bearing for anything matching on it.
361
361
  */
362
362
  const checkEntityName = (name: string): Effect.Effect<string, XmlError> => {
363
363
  if (name.charCodeAt(0) === CODE_HASH) {
@@ -383,11 +383,11 @@ const checkEntityName = (name: string): Effect.Effect<string, XmlError> => {
383
383
 
384
384
  /**
385
385
  * @description Flatten registration maps into one name to string map, later maps winning over earlier ones for the same name. The result is a null-prototype
386
- * object, not a `Map`. That is intentional. A `Map` iterates in pure insertion order, while `Object.keys` lifts array-index-like names to the front
387
- * in numeric order, and the registration hooks observe that order. A name of `"2"` registered after `"brand"` reaches the hook first here and second
388
- * in a `Map`.
386
+ * object, not a `Map`. That is not incidental: a `Map` iterates in pure insertion order, while `Object.keys` lifts array-index-like names to the
387
+ * front in numeric order, and the registration hooks observe that order. A name of `"2"` registered after `"brand"` reaches the hook first here and
388
+ * second in a `Map`.
389
389
  *
390
- * @param maps - The maps to merge. A falsy entry (`null`, `undefined`, `''`, `0`) contributes nothing rather than throwing.
390
+ * @param maps - The maps to merge. A falsy entry — `null`, `undefined`, `''`, `0` — contributes nothing rather than throwing.
391
391
  *
392
392
  * @returns A null-prototype object of own string-valued entries. Nothing from `Object.prototype` can be read out of it, so a document naming
393
393
  * `constructor` or `toString` finds nothing. Each entry is read through {@link flattenEntityValue}, so an entry that cannot be reduced to a string
@@ -407,15 +407,15 @@ function mergeEntityMaps(...maps: ReadonlyArray<EntityInputMap>): Record<string,
407
407
 
408
408
  /**
409
409
  * @description Reduce one registration entry to the string a reference to it expands to, or to nothing when the entry is a form the scanner has no use for. Three
410
- * shapes survive: the string itself, and a `{ regex | regx, val }` envelope whose `val` is a string. Everything else (a number, `null`, `undefined`,
411
- * a bare function, an envelope whose `val` is a function) has no string to substitute, so the name is dropped and a reference to it comes back out as
412
- * the text it was written as. Dropping is silent on purpose: the runtime inspects whatever it is handed, and failing the construction over one
410
+ * shapes survive: the string itself, and a `{ regex | regx, val }` envelope whose `val` is a string. Everything else — a number, `null`, `undefined`,
411
+ * a bare function, an envelope whose `val` is a function — has no string to substitute, so the name is dropped and a reference to it comes back out
412
+ * as the text it was written as. Dropping is silent on purpose: the runtime inspects whatever it is handed, and failing the construction over one
413
413
  * unreadable entry would take every other entity in the table down with it.
414
414
  *
415
- * @param raw - The entry as it arrived, in whatever shape the caller supplied it, including no entry at all, which a table with a hole in it
415
+ * @param raw - The entry as it arrived, in whatever shape the caller supplied it — including no entry at all, which a table with a hole in it
416
416
  * produces.
417
417
  *
418
- * @returns The replacement string, or `undefined` when the entry cannot be read. A name registered to the empty string yields `''`. That is why
418
+ * @returns The replacement string, or `undefined` when the entry cannot be read. A name registered to the empty string yields `''`, which is why
419
419
  * callers compare against `undefined` rather than testing for emptiness.
420
420
  */
421
421
  function flattenEntityValue(raw: EntityInputValue | undefined): string | undefined {
@@ -436,12 +436,12 @@ function flattenEntityValue(raw: EntityInputValue | undefined): string | undefin
436
436
  * @param map - The map to read.
437
437
  * @param key - The entity name.
438
438
  *
439
- * @returns The registered string, or `undefined` when the name is not an own key. A name registered to the empty string returns `''`. That is why
439
+ * @returns The registered string, or `undefined` when the name is not an own key. A name registered to the empty string returns `''`, which is why
440
440
  * callers must compare against `undefined` rather than test for emptiness.
441
441
  */
442
442
  function ownEntity(map: Readonly<Record<string, string>>, key: string): string | undefined {
443
443
  // Upstream tests `name in map`, which reads as "is this name present at all". `Object.hasOwn` is
444
- // the same question asked explicitly, and it is the right shape for the answer: an absent key
444
+ // the same question asked explicitly, and it is the honest shape for the answer: an absent key
445
445
  // yields `undefined` and a present one yields the stored string, so the `string | undefined` this
446
446
  // returns is the real type rather than something an assertion has to paper over. The two maps are
447
447
  // null-prototype objects, so `in` and `hasOwn` cannot disagree here.
@@ -455,7 +455,8 @@ function ownEntity(map: Readonly<Record<string, string>>, key: string): string |
455
455
  * @param raw - The configured value.
456
456
  *
457
457
  * @returns The tier set. An unrecognised string falls back to `external` rather than to no filtering at all, so a typo cannot silently disable the
458
- * limits. An array is taken as given. An empty array therefore disables limit accounting entirely, while an empty string falls back to `external`.
458
+ * limits. An array is taken as given, which is why an empty array disables limit accounting entirely while an empty string falls back to
459
+ * `external`.
459
460
  */
460
461
  function parseLimitTiers(raw: ApplyLimitsTo | undefined): ReadonlySet<LimitTier> {
461
462
  if (!raw || raw === LIMIT_TIER_EXTERNAL) return new Set([LIMIT_TIER_EXTERNAL]);
@@ -517,7 +518,7 @@ function readPostCheck(raw: EntityDecoderOptions['postCheck']): (resolved: strin
517
518
  *
518
519
  * @param raw - The configured hook, or nothing.
519
520
  *
520
- * @returns The hook itself, or `null` for an absent option and for a value that is not a function. `null` lets every registration path ask
521
+ * @returns The hook itself, or `null` for an absent option and for a value that is not a function. `null` is what lets every registration path ask
521
522
  * unconditionally: a hook that is not there accepts.
522
523
  */
523
524
  function readHook(raw: EntityRegistrationHook | null | undefined): EntityRegistrationHook | null {
@@ -527,9 +528,9 @@ function readHook(raw: EntityRegistrationHook | null | undefined): EntityRegistr
527
528
 
528
529
  /**
529
530
  * @description Read one of the two entity-name lists as a set, under the same missing-value rule as the other options: absent is empty, not an error. The
530
- * `Array.isArray` test rather than a truthiness one keeps a mistyped list from reaching `new Set` and throwing there, so a caller's typo disables the
531
- * list instead of taking the decoder down. Matching against a set also means a name in both lists is decided by the order {@link EntityDecoder.decode}
532
- * consults them in, not by the order the caller wrote them in.
531
+ * `Array.isArray` test rather than a truthiness one is what keeps a mistyped list from reaching `new Set` and throwing there, so a caller's typo
532
+ * disables the list instead of taking the decoder down. Matching against a set is also why a name in both lists is decided by the order
533
+ * {@link EntityDecoder.decode} consults them in, not by the order the caller wrote them in.
533
534
  *
534
535
  * @param raw - The configured list, or nothing.
535
536
  *
@@ -563,25 +564,25 @@ function scanTokenEnd(str: string, ampersand: number): number {
563
564
  *
564
565
  * ### Entity lookup priority
565
566
  *
566
- * 1. **input / runtime**: injected per document through {@link EntityDecoder.addInputEntities}
567
- * 2. **persistent external**: set through {@link EntityDecoder.setExternalEntities} and {@link EntityDecoder.addExternalEntity}, surviving
567
+ * 1. **input / runtime** — injected per document through {@link EntityDecoder.addInputEntities}
568
+ * 2. **persistent external** — set through {@link EntityDecoder.setExternalEntities} and {@link EntityDecoder.addExternalEntity}, surviving
568
569
  * {@link EntityDecoder.reset}
569
- * 3. **base**: the five XML predefined entities plus the constructor's `namedEntities` Both input and external resolve as the `external` tier for limit
570
+ * 3. **base** — the five XML predefined entities plus the constructor's `namedEntities` Both input and external resolve as the `external` tier for limit
570
571
  * purposes, because both are injected at runtime. Numeric references (`&#NNN;`, `&#xHH;`) resolve directly through `String.fromCodePoint` and are
571
572
  * always `base` tier: they cannot recurse, so a limit that counted them would only punish a document that spells its characters out.
572
573
  *
573
574
  * ### Upstream behaviour preserved
574
575
  *
575
- * Several quirks of the original are kept intentionally, because a consumer's output already depends on them:
576
+ * Several quirks of the original are kept deliberately, because a consumer's output already depends on them:
576
577
  *
577
578
  * - A value containing `&` is **not** filtered from `namedEntities` or `setExternalEntities`, contrary to the documentation. Only
578
579
  * {@link EntityDecoder.addExternalEntity} checks, and it drops the entry rather than storing it, so the same name registered either way can resolve
579
580
  * to nothing.
580
- * - {@link EntityDecoderOptions.postCheck} is skipped entirely for input that never reaches the scan: an empty string, a non-string, or a string with
581
+ * - {@link EntityDecoderOptions.postCheck} is skipped entirely for input that never reaches the scan — an empty string, a non-string, or a string with
581
582
  * no `&`.
582
583
  * - {@link EntityDecoder.decode} returns a non-string argument unchanged, despite being typed `string`.
583
584
  * - The expansion-limit errors are prefixed `EntityReplacer`, not `EntityDecoder`.
584
- * - Nothing in XML 1.0 §2.2 is enforced for U+007F to U+009F or for the U+FFFE/U+FFFF noncharacters, and the sweep for `&` leaves a name of
585
+ * - Nothing in XML 1.0 §2.2 is enforced for U+007F–U+009F or for the U+FFFE/U+FFFF noncharacters, and the sweep for `&` leaves a name of
585
586
  * {@link MAX_TOKEN_LENGTH} + 1 characters unresolvable.
586
587
  * - Numeric references are parsed with `parseInt`, so a leading space, sign, or trailing garbage is accepted: `&# 41;`, `&#x+41;` and `&#41zz;` all
587
588
  * decode, and `&#0x41;` parses as a null reference rather than `A`.
@@ -595,7 +596,7 @@ function scanTokenEnd(str: string, ampersand: number): number {
595
596
  * decoder.addInputEntities({ version: '1.0' });
596
597
  *
597
598
  * decoder.decode('&brand; v&version; &copy;'); // 'Acme v1.0 ©'
598
- * decoder.decode('&#x26;#38;'); // '&&', one pass, the output is never re-scanned
599
+ * decoder.decode('&#x26;#38;'); // '&&' — one pass, the output is never re-scanned
599
600
  *
600
601
  * decoder.reset(); // drops the input entities and the counters, keeps the external ones
601
602
  * ```;
@@ -613,7 +614,7 @@ export class EntityDecoder {
613
614
  readonly #maxExpandedLength: number;
614
615
 
615
616
  /**
616
- * @description {@link EntityDecoderOptions.postCheck}, or the identity function. That lets the decode loop call it unconditionally on the path that actually
617
+ * @description {@link EntityDecoderOptions.postCheck}, or the identity function — so the decode loop can call it unconditionally on the path that actually
617
618
  * scanned, and never on the two fast paths that return early.
618
619
  */
619
620
  readonly #postCheck: (resolved: string, original: string) => string;
@@ -636,7 +637,7 @@ export class EntityDecoder {
636
637
 
637
638
  /**
638
639
  * @description Persistent external entities, as a null-prototype object. Replaced wholesale by {@link EntityDecoder.setExternalEntities} and added to by
639
- * {@link EntityDecoder.addExternalEntity}, and never touched by {@link EntityDecoder.reset}. That is the whole distinction from the input map.
640
+ * {@link EntityDecoder.addExternalEntity}, and never touched by {@link EntityDecoder.reset} — that is the whole distinction from the input map.
640
641
  */
641
642
  #externalMap: Record<string, string>;
642
643
 
@@ -647,8 +648,8 @@ export class EntityDecoder {
647
648
  #inputMap: Record<string, string>;
648
649
 
649
650
  /**
650
- * @description Tracked expansions since the last reset. Cumulative across {@link EntityDecoder.decode} calls, which makes a limit a per-document budget rather
651
- * than a per-call one. Intentionally not reset by a thrown limit error, so the error message reports the over-limit count.
651
+ * @description Tracked expansions since the last reset. Cumulative across {@link EntityDecoder.decode} calls, which is what makes a limit a per-document budget
652
+ * rather than a per-call one. Deliberately not reset by a thrown limit error, so the over-limit count is what the error message reports.
652
653
  */
653
654
  #totalExpansions: number;
654
655
 
@@ -699,7 +700,7 @@ export class EntityDecoder {
699
700
 
700
701
  /**
701
702
  * @description Create a decoder. A factory rather than a constructor, because it refuses a `null` options object. Every field is optional, so `null` is not "a
702
- * decoder with the defaults". A caller who wrote it meant something the signature does not allow, and a decoder built from it would be
703
+ * decoder with the defaults" — a caller who wrote it meant something the signature does not allow, and a decoder built from it would be
703
704
  * indistinguishable from one built from `{}` while hiding the mistake. Saying so is worth a factory; `EntityDecoderOptions` is a plain object and
704
705
  * nothing else about construction can fail.
705
706
  *
@@ -725,15 +726,15 @@ export class EntityDecoder {
725
726
 
726
727
  /**
727
728
  * @description Create a decoder. Every option is resolved here into the flat fields the decode loop reads, so nothing per-reference has to re-derive it. The
728
- * options whose wrong type disables them rather than failing the construction (the two hooks, the two name lists) are read through {@link readHook}
729
- * and {@link readNameList}, so that rule is written once instead of four times.
729
+ * options whose wrong type disables them rather than failing the construction — the two hooks, the two name lists — are read through
730
+ * {@link readHook} and {@link readNameList}, so that rule is written once instead of four times.
730
731
  *
731
732
  * @param resolved - Configuration, already checked. See {@link EntityDecoderOptions}.
732
733
  */
733
734
  private constructor(resolved: EntityDecoderOptions) {
734
- // `options.limit` is read first, intentionally: it is the first property the original touched, so
735
+ // `options.limit` is read first, deliberately: it is the first property the original touched, so
735
736
  // the property a `null` would have faulted on, and keeping that order means the reason still
736
- // names it. The option stays a local. Every value the decode loop needs is flattened out of it
737
+ // names it. The option stays a local — every value the decode loop needs is flattened out of it
737
738
  // below, so retaining it on the instance would only be a way to observe the option back.
738
739
  const limit = resolved.limit ?? {};
739
740
  this.#maxTotalExpansions = limit.maxTotalExpansions || 0;
@@ -763,7 +764,7 @@ export class EntityDecoder {
763
764
  /**
764
765
  * @description Ask a registration hook about one name and value.
765
766
  *
766
- * @param hook - The hook, or `null`. A `null` hook accepts, so {@link EntityDecoder.addExternalEntity} can call this unconditionally.
767
+ * @param hook - The hook, or `null`. A `null` hook accepts, which is what lets {@link EntityDecoder.addExternalEntity} call this unconditionally.
767
768
  * @param name - The entity name, without `&` or `;`.
768
769
  * @param value - The resolved value, after any `{ regex, val }` envelope was unwrapped.
769
770
  * @param context - Which registration is in progress, for the error message.
@@ -772,7 +773,7 @@ export class EntityDecoder {
772
773
  * hook returns `throw`. The message quotes the entity, so it is the only record left that a document was rejected.
773
774
  */
774
775
  #applyRegistrationHook(hook: EntityRegistrationHook | null, name: string, value: string, context: HookContext): Effect.Effect<boolean, XmlError> {
775
- if (!hook) return Effect.succeed(true); // nothing to ask
776
+ if (!hook) return Effect.succeed(true); // no hook to ask
776
777
  const action = hook(name, value);
777
778
  if (action === ENTITY_ACTION.BLOCK) return Effect.succeed(false);
778
779
  if (action === ENTITY_ACTION.THROW) {
@@ -834,7 +835,7 @@ export class EntityDecoder {
834
835
  */
835
836
  addExternalEntity = Effect.fnUntraced(function* (this: EntityDecoder, key: string, value: string): Effect.fn.Return<void, XmlError> {
836
837
  yield* checkEntityName(key);
837
- // The two guards are unreachable from typed code (`value` is a `string`) and are kept for
838
+ // The two guards are unreachable from typed code — `value` is a `string` — and are kept for
838
839
  // untyped callers, which is the only way to reach them.
839
840
  if (Predicate.isString(value) && value.indexOf('&') === -1) {
840
841
  if (yield* this.#applyRegistrationHook(this.#onExternalEntity, key, value, 'external')) {
@@ -858,7 +859,7 @@ export class EntityDecoder {
858
859
  map: Record<string, string | { regx: RegExp; val: string | EntityValFn } | { regex: RegExp; val: string | EntityValFn }>
859
860
  ): Effect.fn.Return<void, XmlError> {
860
861
  // Cleared first and unconditionally, so registering entities is itself the start of a new
861
- // document's budget, including when the call goes on to fail.
862
+ // document's budget — including when the call goes on to fail.
862
863
  this.#totalExpansions = 0;
863
864
  this.#expandedLength = 0;
864
865
  if (!this.#onInputEntity) {
@@ -903,14 +904,14 @@ export class EntityDecoder {
903
904
  /**
904
905
  * @description Expand every entity reference in a string, in one pass. The output is never re-scanned, so no expansion can produce a _second_ one: a registered
905
906
  * value that itself contains reference text reaches the caller as that literal text, unexpanded. What the limits bound is the growth of this single
906
- * pass, meaning how much one round of expansion can add. Three inputs return before the scan and therefore never reach
907
+ * pass — how much one round of expansion can add. Three inputs return before the scan and therefore never reach
907
908
  * {@link EntityDecoderOptions.postCheck}: a non-string, the empty string, and any string with no `&` in it. The scan itself is `#expandAll`; what
908
909
  * this method adds is the three inputs that skip it and the single join of what it collected.
909
910
  *
910
911
  * @example
911
912
  * ```typescript
912
913
  * import { Effect } from 'effect';
913
- * import { EntityDecoder } from '@endevops/effect-codec-xml';
914
+ * import { EntityDecoder } from '@endevops/effect-xml-codec';
914
915
  *
915
916
  * const decoder = new EntityDecoder({ namedEntities: { copy: '©' } });
916
917
  * Effect.runSync(Effect.orElseSucceed(decoder.addExternalEntity('brand', 'Acme'), () => undefined));
@@ -940,15 +941,15 @@ export class EntityDecoder {
940
941
  /**
941
942
  * @description Walk the string once and collect the pieces of every reference that resolved. Two advance rules make the walk terminate and keep it correct: an
942
943
  * `&` that turns out to open nothing moves the cursor by one character rather than to the end of its run, so a second `&` in the same text is still
943
- * found; and a reference that did resolve moves it to just past the `;`, so the text that was substituted for it is never looked at again. The pass
944
- * stays single because of that, and a registered value containing `&` cannot expand a second level. What a reference becomes is `#resolveToken`'s
945
- * to decide and what it costs is `#chargeExpansion`'s to apply, which leaves the scanning here as the only thing with a rule of its own.
944
+ * found; and a reference that did resolve moves it to just past the `;`, so the text that was substituted for it is never looked at again — that is
945
+ * what makes the pass single, and a registered value containing `&` cannot expand a second level. What a reference becomes is `#resolveToken`'s to
946
+ * decide and what it costs is `#chargeExpansion`'s to apply, which leaves the scanning here as the only thing with a rule of its own.
946
947
  *
947
948
  * @param str - The string to expand. It always holds at least one `&` and is never empty, or the caller would have returned before reaching the
948
949
  * walk.
949
950
  *
950
951
  * @returns An effect producing the pieces in order. The array is empty exactly when nothing was replaced, which the caller reads as "the input is
951
- * its own result". Fails with {@link XmlError} and the reason the offending reference carries: `ProhibitedCharacterReference`,
952
+ * its own result". Fails with {@link XmlError} and the reason the offending reference carries — `ProhibitedCharacterReference`,
952
953
  * `ExpansionLimitExceeded` or `ExpandedLengthLimitExceeded`.
953
954
  */
954
955
  #expandAll = Effect.fnUntraced(function* (this: EntityDecoder, str: string): Effect.fn.Return<Array<string>, XmlError> {
@@ -995,25 +996,25 @@ export class EntityDecoder {
995
996
  /**
996
997
  * @description Decide what one reference expands to. The lists and maps are consulted in the one order the runtime uses, and the first that matches wins:
997
998
  *
998
- * 1. `remove`: deleted outright, without the name ever being resolved, so the name need not exist.
999
- * 2. `leave`: emitted as the original `&token;`, and charged to nothing.
1000
- * 3. A `#`-prefixed token: the numeric pipeline, which is the only one of the four that can fail. Classification runs before any decision about
999
+ * 1. `remove` — deleted outright, without the name ever being resolved, so the name need not exist.
1000
+ * 2. `leave` — emitted as the original `&token;`, and charged to nothing.
1001
+ * 3. A `#`-prefixed token — the numeric pipeline, which is the only one of the four that can fail. Classification runs before any decision about
1001
1002
  * `numericAllowed`, because the ranges that carry a minimum have to be caught whichever way that option is set.
1002
- * 4. Anything else: resolved against the input map, then the external map, then the base map.
1003
+ * 4. Anything else — resolved against the input map, then the external map, then the base map.
1003
1004
  *
1004
1005
  * @param token - The reference's token, e.g. `brand` or `#38`, with the `&` and the `;` already stripped. Never empty: the scanner drops `&;`
1005
1006
  * before calling.
1006
1007
  *
1007
1008
  * @returns An effect producing what the reference expands to and the tier to charge it to, or `undefined` to leave it as written and charge it
1008
- * nothing. `undefined` covers all three ways of leaving a reference alone (a listed `leave` name, a numeric reference that is out of range, and a
1009
- * name registered nowhere), and none of them is distinguishable from outside. Fails with {@link XmlError} and the `ProhibitedCharacterReference`
1010
- * reason when the numeric policy throws on the codepoint.
1009
+ * nothing. `undefined` covers all three ways of leaving a reference alone — a listed `leave` name, a numeric reference that is out of range, and
1010
+ * a name registered nowhere — and none of them is distinguishable from outside. Fails with {@link XmlError} and the
1011
+ * `ProhibitedCharacterReference` reason when the numeric policy throws on the codepoint.
1011
1012
  */
1012
1013
  #resolveToken = Effect.fnUntraced(function* (this: EntityDecoder, token: string): Effect.fn.Return<ResolvedEntity | undefined, XmlError> {
1013
1014
  if (this.#removeSet.has(token)) {
1014
1015
  // Deleted without being resolved, so the name need not exist. Upstream guards this charge with
1015
1016
  // `if (tier === undefined)`, and its `tier` is declared without an initialiser, so the branch is
1016
- // unconditionally taken and the charge always lands on `external`, whatever tier the name would
1017
+ // unconditionally taken and the charge always lands on `external` — whatever tier the name would
1017
1018
  // have resolved in. That is why a document full of removed built-ins can trip an `external` limit
1018
1019
  // nothing it wrote could otherwise reach. Kept as written, since that is a behaviour a caller may
1019
1020
  // already be relying on.
@@ -1021,7 +1022,7 @@ export class EntityDecoder {
1021
1022
  }
1022
1023
 
1023
1024
  // Emitted as the original `&token;`. The walk advances only past the `&` and leaves the `;` to be
1024
- // copied by the next literal run, which keeps the text coming back unchanged.
1025
+ // copied by the next literal run, which is what makes the text come back unchanged.
1025
1026
  if (this.#leaveSet.has(token)) return undefined;
1026
1027
 
1027
1028
  if (token.charCodeAt(0) === CODE_HASH) {
@@ -1065,9 +1066,9 @@ export class EntityDecoder {
1065
1066
 
1066
1067
  /**
1067
1068
  * @description Add one expansion to the running total and compare it against {@link EntityDecoderLimitOptions.maxTotalExpansions}. The comparison is `>` rather
1068
- * than `>=`, so a limit of `n` allows exactly `n` expansions and throws on the `n + 1`th. That is a contract, and the option's own documentation
1069
- * states it. It is also the kind of off-by-one a tidy-up changes by accident. The counter is intentionally not reset before failing: the over-limit
1070
- * total reports the over-limit total, and {@link EntityDecoder.reset} is the caller's way to start a new document.
1069
+ * than `>=`, so a limit of `n` allows exactly `n` expansions and throws on the `n + 1`th. That is a contract — the option's own documentation
1070
+ * states it — and the kind of off-by-one a tidy-up changes by accident. The counter is deliberately not reset before failing: the over-limit total
1071
+ * is what the error message reports, and {@link EntityDecoder.reset} is the caller's way to start a new document.
1071
1072
  *
1072
1073
  * @returns An effect that fails with {@link XmlError} and the `ExpansionLimitExceeded` reason once the count is past the ceiling, and succeeds
1073
1074
  * otherwise. The `EntityReplacer` prefix in the message is preserved verbatim from the original throw, despite naming a class this decoder does
@@ -1089,7 +1090,7 @@ export class EntityDecoder {
1089
1090
  /**
1090
1091
  * @description Add one expansion's surplus to the running total and compare it against {@link EntityDecoderLimitOptions.maxExpandedLength}. Only the surplus
1091
1092
  * counts, and only upward: a reference whose replacement is no longer than the `&token;` it replaces contributes zero, and a shrinking one
1092
- * contributes nothing and cannot trip the limit at all. That keeps the ceiling a bound on growth rather than on document size.
1093
+ * contributes nothing and cannot trip the limit at all. That is what makes the ceiling a bound on growth rather than on document size.
1093
1094
  *
1094
1095
  * @param token - The reference's token, with the `&` and `;` stripped. The two delimiters count towards what the expansion displaced.
1095
1096
  * @param replacement - What the reference expanded to, including `''` for a removal.
@@ -1117,8 +1118,8 @@ export class EntityDecoder {
1117
1118
  /**
1118
1119
  * @description Decide whether an entity of a given tier is charged against the limits.
1119
1120
  *
1120
- * @param tier - The tier the replacement is charged to. Every expansion that reaches here carries one (a name deleted before it was ever resolved
1121
- * still carries the `external` tier), so there is no absent case to answer.
1121
+ * @param tier - The tier the replacement is charged to. Every expansion that reaches here carries one — a name deleted before it was ever resolved
1122
+ * still carries the `external` tier — so there is no absent case to answer.
1122
1123
  *
1123
1124
  * @returns `true` when it counts. `'all'` short-circuits, so a filter naming every tier charges everything regardless of which map it came from.
1124
1125
  */
@@ -1153,9 +1154,9 @@ export class EntityDecoder {
1153
1154
  /**
1154
1155
  * @description Find the strictest action a codepoint's range requires. Checked in this order:
1155
1156
  *
1156
- * 1. U+0000: governed by `nullNCR`, already clamped to `remove` or stricter
1157
- * 2. U+D800 to U+DFFF: surrogates, always `remove`, under every policy and both XML versions
1158
- * 3. U+0001 to U+001F other than tab, newline, carriage return: XML 1.0 only, `remove` Nothing else is classified. U+007F to U+009F (C1) and the
1157
+ * 1. U+0000 — governed by `nullNCR`, already clamped to `remove` or stricter
1158
+ * 2. U+D800–U+DFFF — surrogates, always `remove`, under every policy and both XML versions
1159
+ * 3. U+0001–U+001F other than tab, newline, carriage return — XML 1.0 only, `remove` Nothing else is classified. U+007F–U+009F (C1) and the
1159
1160
  * U+FFFE/U+FFFF noncharacters are not checked, even though XML 1.0 §2.2 prohibits them and the `xmlVersion` option's own documentation claims C1
1160
1161
  * is only permitted under 1.1. Both gaps are upstream's and are kept.
1161
1162
  *
@@ -1179,11 +1180,11 @@ export class EntityDecoder {
1179
1180
  * @description Turn a resolved action level into a replacement.
1180
1181
  *
1181
1182
  * @param action - A level from {@link NCR_LEVEL}. A level outside the four known ones falls through to the allow behaviour, so a bad level cannot
1182
- * produce a wrong string. It can only fail open.
1183
+ * produce a wrong string — it can only fail open.
1183
1184
  * @param token - The raw token, e.g. `#38`, for the error message.
1184
1185
  * @param cp - The codepoint, for the error message.
1185
1186
  *
1186
- * @returns An effect producing the character for `allow`, `''` for `remove`, and `undefined` for `leave`, which the caller reads as "emit the
1187
+ * @returns An effect producing the character for `allow`, `''` for `remove`, and `undefined` for `leave` — which the caller reads as "emit the
1187
1188
  * original `&token;`". Fails with {@link XmlError} and the `ProhibitedCharacterReference` reason for `throw`, naming both the token and the
1188
1189
  * codepoint.
1189
1190
  */
@@ -1220,7 +1221,7 @@ export class EntityDecoder {
1220
1221
  *
1221
1222
  * @param token - The raw token without `&` and `;`, e.g. `#38`, `#x26`, `#X26`.
1222
1223
  *
1223
- * @returns An effect producing the replacement (the empty string meaning "delete") or `undefined` to leave the reference as written. Fails with
1224
+ * @returns An effect producing the replacement — the empty string meaning "delete" — or `undefined` to leave the reference as written. Fails with
1224
1225
  * {@link XmlError} and the `ProhibitedCharacterReference` reason when the effective action is `throw`.
1225
1226
  */
1226
1227
  #resolveNCR(token: string): Effect.Effect<string | undefined, XmlError> {
package/src/errors.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // The one way XML serialization can fail that a `SchemaIssue.Issue` does not already describe.
2
2
  //
3
- // A schema mismatch, such as a `number` where the document says `text`, is a
3
+ // A schema mismatch — a `number` where the document says `text` — is a
4
4
  // `SchemaIssue.Issue` and comes from Effect's own parser. What is left is the
5
5
  // part Effect knows nothing about: a document that is not well-formed XML, and a
6
6
  // field name that cannot be written as one.
@@ -12,7 +12,7 @@ import { Schema } from 'effect';
12
12
  *
13
13
  * @example
14
14
  * ```typescript
15
- * import { XmlParseError } from '@endevops/effect-codec-xml';
15
+ * import { XmlParseError } from '@endevops/effect-xml-codec';
16
16
  *
17
17
  * const error = new XmlParseError({ message: 'Unclosed element', position: 12, input: '<a><b>' });
18
18
  * ```;
@@ -42,7 +42,7 @@ export class XmlParseError extends Schema.TaggedError<XmlParseError>()('XmlParse
42
42
  *
43
43
  * @example
44
44
  * ```typescript
45
- * import { XmlRenderError } from '@endevops/effect-codec-xml';
45
+ * import { XmlRenderError } from '@endevops/effect-xml-codec';
46
46
  *
47
47
  * const error = new XmlRenderError({ message: 'Invalid XML name "not a name"' });
48
48
  * ```;
package/src/index.ts CHANGED
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * @description A round-trip Effect Schema codec for XML. `toCodecXml(schema)` returns a `Schema` whose `Encoded` is XML text, so `Schema.encodeSync` writes a
3
- * document and `Schema.decodeSync` reads one back, as `Schema.toCodecJson` does for JSON. Attributes are the fields whose names start with `@`, so
4
- * `@xmlns` is written as `xmlns="…"`, and `#text` holds an element's character data. A schema node annotated with `xmlNamespace` is placed in that
3
+ * document and `Schema.decodeSync` reads one back, the way `Schema.toCodecJson` works for JSON. Attributes are the fields whose names start with `@`,
4
+ * so `@xmlns` is written as `xmlns="…"`, and `#text` holds an element's character data. A schema node annotated with `xmlNamespace` is placed in that
5
5
  * namespace, and the codec resolves the document's own prefixes back to it. `renderXml` and `parseXml` are the text layer the codec runs underneath,
6
6
  * and remain available on their own.
7
7
  *
8
8
  * @example
9
9
  * ```typescript
10
10
  * import { Schema } from 'effect';
11
- * import { toCodecXml } from '@endevops/effect-codec-xml';
11
+ * import { toCodecXml } from '@endevops/effect-xml-codec';
12
12
  *
13
13
  * const Book = Schema.Struct({
14
14
  * '@id': Schema.String,