@endevops/effect-codec-xml 0.0.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.
- package/LICENSE +21 -0
- package/LICENSE-is-entities +21 -0
- package/LICENSE-is-xml-naming +21 -0
- package/README.md +415 -0
- package/dist/codec.d.ts +48 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +63 -0
- package/dist/codec.js.map +1 -0
- package/dist/conventions.d.ts +88 -0
- package/dist/conventions.d.ts.map +1 -0
- package/dist/conventions.js +113 -0
- package/dist/conventions.js.map +1 -0
- package/dist/entities/entity-decoder.d.ts +333 -0
- package/dist/entities/entity-decoder.d.ts.map +1 -0
- package/dist/entities/entity-decoder.js +841 -0
- package/dist/entities/entity-decoder.js.map +1 -0
- package/dist/entities/entity-tables.js +16 -0
- package/dist/entities/entity-tables.js.map +1 -0
- package/dist/errors.d.ts +49 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +48 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/namespaces.d.ts +101 -0
- package/dist/namespaces.d.ts.map +1 -0
- package/dist/namespaces.js +663 -0
- package/dist/namespaces.js.map +1 -0
- package/dist/naming.d.ts +149 -0
- package/dist/naming.d.ts.map +1 -0
- package/dist/naming.js +296 -0
- package/dist/naming.js.map +1 -0
- package/dist/parse.d.ts +75 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +437 -0
- package/dist/parse.js.map +1 -0
- package/dist/render.d.ts +99 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +509 -0
- package/dist/render.js.map +1 -0
- package/dist/xml-error.d.ts +172 -0
- package/dist/xml-error.d.ts.map +1 -0
- package/dist/xml-error.js +157 -0
- package/dist/xml-error.js.map +1 -0
- package/dist/xml-value.d.ts +42 -0
- package/dist/xml-value.d.ts.map +1 -0
- package/dist/xml-value.js +79 -0
- package/dist/xml-value.js.map +1 -0
- package/package.json +69 -0
- package/src/codec.ts +136 -0
- package/src/conventions.ts +145 -0
- package/src/entities/entity-decoder.ts +1248 -0
- package/src/entities/entity-tables.ts +18 -0
- package/src/errors.ts +55 -0
- package/src/index.ts +79 -0
- package/src/namespaces.ts +968 -0
- package/src/naming.ts +519 -0
- package/src/parse.ts +597 -0
- package/src/render.ts +708 -0
- package/src/xml-error.ts +168 -0
- package/src/xml-value.ts +108 -0
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@endevops/effect-codec-xml",
|
|
3
|
+
"version": "0.0.1",
|
|
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
|
+
"keywords": [
|
|
6
|
+
"codec",
|
|
7
|
+
"decode",
|
|
8
|
+
"effect",
|
|
9
|
+
"effect-schema",
|
|
10
|
+
"entities",
|
|
11
|
+
"entity-decoder",
|
|
12
|
+
"ncname",
|
|
13
|
+
"nmtoken",
|
|
14
|
+
"qname",
|
|
15
|
+
"xml",
|
|
16
|
+
"xml-codec",
|
|
17
|
+
"xml-naming",
|
|
18
|
+
"xml-parser",
|
|
19
|
+
"xml-serialization"
|
|
20
|
+
],
|
|
21
|
+
"homepage": "https://github.com/Endevops/effect-xml/tree/master/packages/effect-xml-codec#readme",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/Endevops/effect-xml/issues"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"author": "Endevops",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/Endevops/effect-xml.git",
|
|
30
|
+
"directory": "packages/effect-xml-codec"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"src/**/*.ts",
|
|
35
|
+
"!src/**/*.{spec,test}.ts"
|
|
36
|
+
],
|
|
37
|
+
"type": "module",
|
|
38
|
+
"sideEffects": false,
|
|
39
|
+
"imports": {
|
|
40
|
+
"#/*": "./src/*",
|
|
41
|
+
"#/test/*": "./test/*"
|
|
42
|
+
},
|
|
43
|
+
"exports": {
|
|
44
|
+
".": "./dist/index.js",
|
|
45
|
+
"./package.json": "./package.json"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public",
|
|
49
|
+
"provenance": true
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@arethetypeswrong/core": "^0.18.5",
|
|
53
|
+
"@effect/vitest": "^4.0.0",
|
|
54
|
+
"effect": "^4.0.0",
|
|
55
|
+
"publint": "^0.3.24",
|
|
56
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@1.0.0",
|
|
57
|
+
"vite-plus": "1.0.0"
|
|
58
|
+
},
|
|
59
|
+
"peerDependencies": {
|
|
60
|
+
"effect": "^4.0.0"
|
|
61
|
+
},
|
|
62
|
+
"engines": {
|
|
63
|
+
"node": ">=26"
|
|
64
|
+
},
|
|
65
|
+
"scripts": {
|
|
66
|
+
"build": "vp pack",
|
|
67
|
+
"dev": "vp pack --watch"
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/codec.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// The codec: a schema, and the XML text that carries it.
|
|
2
|
+
//
|
|
3
|
+
// `toCodecXml` is this package's counterpart to `Schema.toCodecJson`. It
|
|
4
|
+
// returns a `Schema` whose `Type` is the source schema's `Type` and whose
|
|
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.
|
|
9
|
+
//
|
|
10
|
+
// The derivation underneath is Effect's own `Schema.toCodecStringTree`, so
|
|
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.
|
|
18
|
+
//
|
|
19
|
+
// The conventions stay in the keys: a key starting with `@` is an attribute,
|
|
20
|
+
// `#text` is character data, and every other key is a child element. The root
|
|
21
|
+
// element is named from the schema's `identifier` or `title` annotation when it
|
|
22
|
+
// has one, and from the `rootName` option otherwise; it defaults to `'root'`,
|
|
23
|
+
// the same name Effect's own XML encoder uses.
|
|
24
|
+
|
|
25
|
+
import { Effect, Predicate, Schema, SchemaAST, SchemaIssue, SchemaTransformation } from 'effect';
|
|
26
|
+
|
|
27
|
+
import type { XmlParseOptions } from './parse.ts';
|
|
28
|
+
import type { XmlRenderOptions } from './render.ts';
|
|
29
|
+
import type { XmlValue } from './xml-value.ts';
|
|
30
|
+
|
|
31
|
+
import { DEFAULT_ROOT_NAME } from './conventions.ts';
|
|
32
|
+
import { decodeNames, encodeNames, namespacePlan, ROOT_ELEMENT } from './namespaces.ts';
|
|
33
|
+
import { parseXml } from './parse.ts';
|
|
34
|
+
import { renderXml } from './render.ts';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @description Options for {@link toCodecXml}.\
|
|
38
|
+
* 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'`.
|
|
41
|
+
*/
|
|
42
|
+
export type XmlCodecOptions = XmlRenderOptions & XmlParseOptions;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @description The XML codec for a schema, as a `Schema`. `Type` is the schema's own `Type` and `Encoded` is XML text, so it encodes a value to a document and
|
|
46
|
+
* decodes a document to a value in one step each. The service requirements of the source schema are preserved.
|
|
47
|
+
*/
|
|
48
|
+
export interface toCodecXml<S extends Schema.Constraint> extends Schema.decodeTo<Schema.toCodecStringTree<S>, Schema.String> {
|
|
49
|
+
readonly Rebuild: toCodecXml<S>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @description Derives the XML codec for a schema: a `Schema` whose `Encoded` is an XML document, so `Schema.encodeSync(codec)` writes text and
|
|
54
|
+
* `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}.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* import { Schema } from 'effect';
|
|
60
|
+
* import { toCodecXml } from '@endevops/effect-xml-codec';
|
|
61
|
+
*
|
|
62
|
+
* const Book = Schema.Struct({ '@id': Schema.String, title: Schema.String, pages: Schema.Number });
|
|
63
|
+
* const codec = toCodecXml(Book, { rootName: 'book' });
|
|
64
|
+
*
|
|
65
|
+
* const value = { '@id': '1', title: 'Dune', pages: 412 };
|
|
66
|
+
*
|
|
67
|
+
* Schema.encodeSync(codec)(value);
|
|
68
|
+
* // => '<book id="1"><title>Dune</title><pages>412</pages></book>'
|
|
69
|
+
*
|
|
70
|
+
* Schema.decodeSync(codec)('<book id="1"><title>Dune</title><pages>412</pages></book>');
|
|
71
|
+
* // => { '@id': '1', title: 'Dune', pages: 412 }
|
|
72
|
+
* ```;
|
|
73
|
+
*
|
|
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'`.
|
|
76
|
+
*
|
|
77
|
+
* @returns The codec, with the source schema's `Type` and the same service requirements.
|
|
78
|
+
*/
|
|
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;
|
|
92
|
+
|
|
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;
|
|
96
|
+
|
|
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
|
+
}
|
|
107
|
+
|
|
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
|
+
},
|
|
116
|
+
|
|
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
|
+
)
|
|
125
|
+
);
|
|
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
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { Effect, Predicate, Result } from 'effect';
|
|
2
|
+
|
|
3
|
+
import type { XmlVersion } from './naming.ts';
|
|
4
|
+
|
|
5
|
+
import { XmlParseError } from './errors.ts';
|
|
6
|
+
import { isQName, sanitize, validate } from './naming.ts';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @description The key prefix that marks a field as an XML attribute. `@xmlns` is written as `xmlns="…"`.
|
|
10
|
+
*/
|
|
11
|
+
export const ATTRIBUTE_PREFIX = '@';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @description The reserved key holding an element's character data, alongside its attributes and child elements.
|
|
15
|
+
*/
|
|
16
|
+
export const TEXT_KEY = '#text';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @description The element name used when nothing else names the root. Matches the default Effect uses in `Schema.toEncoderXml`.
|
|
20
|
+
*/
|
|
21
|
+
export const DEFAULT_ROOT_NAME = 'root';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @description The element name used for array members that have no natural name of their own. Matches the default in `Schema.toEncoderXml`.
|
|
25
|
+
*/
|
|
26
|
+
export const DEFAULT_ITEM_NAME = 'item';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @description What to do with a name that is not a legal XML name.
|
|
30
|
+
*
|
|
31
|
+
* - `repair` rewrites it into the nearest legal name. The default, because a serializer that silently produces a different tag name is worse than one
|
|
32
|
+
* that produces a legal one.
|
|
33
|
+
* - `error` fails the render. Use it when a rewritten name would silently change the meaning of the document.
|
|
34
|
+
* - `ignore` writes the name as given, producing a document that is not well-formed. Only useful when a downstream step rewrites names anyway.
|
|
35
|
+
*/
|
|
36
|
+
export type NameMode = 'error' | 'ignore' | 'repair';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @description Options for {@link resolveName}.
|
|
40
|
+
*/
|
|
41
|
+
export interface ResolveNameOptions {
|
|
42
|
+
/**
|
|
43
|
+
* @description What to do with an illegal name. Defaults to `'repair'`.
|
|
44
|
+
*/
|
|
45
|
+
readonly mode?: NameMode | undefined;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @description XML version to validate against. Defaults to `'1.0'`.
|
|
49
|
+
*/
|
|
50
|
+
readonly xmlVersion?: XmlVersion | undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @description Whether a record key names an attribute rather than a child element.
|
|
55
|
+
*
|
|
56
|
+
* @param key - The key to classify.
|
|
57
|
+
*
|
|
58
|
+
* @returns Whether the key carries the {@link ATTRIBUTE_PREFIX}.
|
|
59
|
+
*/
|
|
60
|
+
export const isAttributeKey = (key: string): boolean => key.charCodeAt(0) === 64 && key.length > 1;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @description The attribute name a record key stands for: `@xmlns` becomes `xmlns`.
|
|
64
|
+
*
|
|
65
|
+
* @param key - A key that {@link isAttributeKey} accepted.
|
|
66
|
+
*
|
|
67
|
+
* @returns The name with the prefix removed.
|
|
68
|
+
*/
|
|
69
|
+
export const attributeName = (key: string): string => key.slice(1);
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @description Whether a record key holds character data rather than a child element or an attribute.
|
|
73
|
+
*
|
|
74
|
+
* @param key - The key to classify.
|
|
75
|
+
*
|
|
76
|
+
* @returns Whether the key is the reserved {@link TEXT_KEY}.
|
|
77
|
+
*/
|
|
78
|
+
export const isTextKey = (key: string): boolean => key === TEXT_KEY;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @description Whether a key is one this package reserves. Only {@link TEXT_KEY} is reserved today; every other key is read as a child element name.
|
|
82
|
+
*
|
|
83
|
+
* @param key - The key to classify.
|
|
84
|
+
*
|
|
85
|
+
* @returns Whether the key is reserved.
|
|
86
|
+
*/
|
|
87
|
+
export const isReservedKey = (key: string): boolean => isTextKey(key);
|
|
88
|
+
|
|
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.
|
|
93
|
+
*
|
|
94
|
+
* @param name - The candidate element or attribute name.
|
|
95
|
+
* @param options - Repair mode and XML version.
|
|
96
|
+
*
|
|
97
|
+
* @returns The name to write, unchanged when it was already legal.
|
|
98
|
+
*
|
|
99
|
+
* @throws {XmlParseError} When the name is illegal and the mode is `'error'`.
|
|
100
|
+
*/
|
|
101
|
+
export const resolveNameSync = (name: string, { mode = 'repair', xmlVersion = '1.0' }: ResolveNameOptions = {}): string => {
|
|
102
|
+
if (isQName(name, { xmlVersion })) return name;
|
|
103
|
+
if (mode === 'ignore') return name;
|
|
104
|
+
if (mode === 'repair') return sanitize(name, 'name', { replacement: '_' });
|
|
105
|
+
|
|
106
|
+
// `validate` only fails for an unknown production, which `'qName'` is not, so
|
|
107
|
+
// this runs a plain result and the failure is the reason the name is refused.
|
|
108
|
+
const result = Effect.runSync(validate(name, 'qName', { xmlVersion }));
|
|
109
|
+
const reason = !result.valid ? result.reason : 'is not a legal XML name';
|
|
110
|
+
throw new XmlParseError({ message: `Invalid XML name ${JSON.stringify(name)}: ${reason}`, position: -1, input: name });
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @description {@link resolveNameSync} with the thrown failure folded into a {@link Result}, so an effectful caller can carry it in a typed channel without a
|
|
115
|
+
* try/catch of its own.
|
|
116
|
+
*
|
|
117
|
+
* @param name - The candidate element or attribute name.
|
|
118
|
+
* @param options - Repair mode and XML version.
|
|
119
|
+
*
|
|
120
|
+
* @returns The resolved name, or the failure to report.
|
|
121
|
+
*/
|
|
122
|
+
const resolveNameResult = (name: string, options: ResolveNameOptions): Result.Result<string, XmlParseError> => {
|
|
123
|
+
try {
|
|
124
|
+
return Result.succeed(resolveNameSync(name, options));
|
|
125
|
+
} catch (cause) {
|
|
126
|
+
if (cause instanceof XmlParseError) {
|
|
127
|
+
return Result.fail(cause);
|
|
128
|
+
}
|
|
129
|
+
return Result.fail(new XmlParseError({ message: Predicate.isError(cause) ? cause.message : String(cause), position: -1, input: name }));
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
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.
|
|
138
|
+
*
|
|
139
|
+
* @param name - The candidate element or attribute name.
|
|
140
|
+
* @param options - Repair mode and XML version.
|
|
141
|
+
*
|
|
142
|
+
* @returns An effect producing the name to write, unchanged when it was already legal.
|
|
143
|
+
*/
|
|
144
|
+
export const resolveName = (name: string, options: ResolveNameOptions = {}): Effect.Effect<string, XmlParseError> =>
|
|
145
|
+
Effect.suspend(() => Effect.fromResult(resolveNameResult(name, options)));
|