xml-model 2.0.0-beta.7 → 2.0.0

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/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { DATA, isModel, model } from "./model.js";
2
2
  import XML, { ZXMLCommentNode, ZXMLElementNode, ZXMLNode, ZXMLRoot, ZXMLTextNode } from "./xml/xml-js.js";
3
3
  import { xml } from "./xml/schema-meta.js";
4
- import { XMLCodecError, normalizeCodecOptions, registerDefault, xmlStateSchema } from "./xml/codec.js";
4
+ import { XMLCodecError, normalizeCodecOptions, parseXML, registerDefault, stringifyXML, toXML, xmlCodec, xmlStateSchema } from "./xml/codec.js";
5
5
  import { xmlModel } from "./xml/model.js";
6
6
  import "./xml/index.js";
7
- export { DATA, XML, XMLCodecError, ZXMLCommentNode, ZXMLElementNode, ZXMLNode, ZXMLRoot, ZXMLTextNode, isModel, model, normalizeCodecOptions, registerDefault, xml, xmlModel, xmlStateSchema };
7
+ export { DATA, XML, XMLCodecError, ZXMLCommentNode, ZXMLElementNode, ZXMLNode, ZXMLRoot, ZXMLTextNode, isModel, model, normalizeCodecOptions, parseXML, registerDefault, stringifyXML, toXML, xml, xmlCodec, xmlModel, xmlStateSchema };
package/dist/model.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { z, GlobalMeta } from 'zod';
2
+ /** Stores the raw data object on model instances. */
3
+ export declare const DATA: unique symbol;
2
4
  /**
3
5
  * Constructor type for model classes.
4
6
  *
@@ -36,8 +38,6 @@ export type ModelConstructor<S extends z.ZodObject<any> = z.ZodObject<any>, Inst
36
38
  */
37
39
  extend<Self extends ModelConstructor<S, Inst>, U extends z.core.$ZodLooseShape>(this: Self, extension: U, meta?: GlobalMeta): Omit<Self, keyof ModelConstructor<S, Inst>> & ModelConstructor<z.ZodObject<z.util.Extend<S["shape"], U>>, InstanceType<Self> & z.infer<z.ZodObject<z.util.Extend<S["shape"], U>>>>;
38
40
  };
39
- /** Stores the raw data object on model instances. */
40
- export declare const DATA: unique symbol;
41
41
  /** Returns true if `cls` is a class produced by `model()` (or a subclass of one). */
42
42
  export declare function isModel(cls: unknown): cls is ModelConstructor;
43
43
  /**
package/dist/model.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  //#region src/model.ts
3
- var schemaSymbol = Symbol("model:schema");
3
+ var SCHEMA = Symbol("model:schema");
4
4
  /** Marker placed on every class returned by `model()`. Used by `isModel()`. */
5
5
  var MODEL_MARKER = Symbol("model:marker");
6
6
  /** Stores the raw data object on model instances. */
@@ -34,8 +34,10 @@ function model(schema) {
34
34
  class Base {
35
35
  static dataSchema = schema;
36
36
  static [MODEL_MARKER] = true;
37
+ static [SCHEMA];
38
+ [DATA];
37
39
  static schema() {
38
- if (!Object.prototype.hasOwnProperty.call(this, schemaSymbol)) this[schemaSymbol] = z.codec(this.dataSchema, z.instanceof(this), {
40
+ if (!Object.prototype.hasOwnProperty.call(this, SCHEMA)) this[SCHEMA] = z.codec(this.dataSchema, z.instanceof(this), {
39
41
  decode: (data) => {
40
42
  return this.fromData(data);
41
43
  },
@@ -43,7 +45,7 @@ function model(schema) {
43
45
  return instance[DATA];
44
46
  }
45
47
  });
46
- return this[schemaSymbol];
48
+ return this[SCHEMA];
47
49
  }
48
50
  static extend(extension, meta) {
49
51
  let extended = this.dataSchema.extend(extension);
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { XMLElement } from './xml-js';
2
+ import { XMLElement, XMLRoot, StringifyOptions } from './xml-js';
3
3
  export declare class XMLCodecError extends Error {
4
4
  readonly path: readonly (string | number)[];
5
5
  readonly rawMessage: string;
@@ -12,22 +12,24 @@ export declare function assertSingleRoot(xml: XMLElement[]): asserts xml is [XML
12
12
  type PropKey<S extends z.ZodObject> = keyof z.input<S> & string;
13
13
  export interface CodecOptions<S extends z.ZodType> {
14
14
  schema: S;
15
- tagname(ctx: RootEncodingContext<S>): string;
16
- decode(ctx: RootDecodingContext<S>): z.input<S>;
17
- encode(ctx: RootEncodingContext<S>): XMLElement;
18
- propertyTagname: (ctx: {
15
+ /** Resolved options of the wrapped inner schema, if any (e.g. the inner type of ZodOptional). */
16
+ parent: CodecOptions<z.ZodType> | undefined;
17
+ tagname(this: void, ctx: RootEncodingContext<S>): string;
18
+ decode(this: void, ctx: RootDecodingContext<S>): z.input<S>;
19
+ encode(this: void, ctx: RootEncodingContext<S>): XMLElement;
20
+ propertyTagname: (this: void, ctx: {
19
21
  name: string;
20
22
  options: CodecOptions<z.ZodType>;
21
23
  }) => string;
22
24
  /** if true, XML representation is not contained in a single XML tag */
23
25
  inlineProperty: boolean;
24
- propertyMatch: (el: XMLElement, ctx: {
26
+ propertyMatch: (this: void, el: XMLElement, ctx: {
25
27
  name: string;
26
28
  tagname: string;
27
29
  options: CodecOptions<z.ZodType>;
28
30
  }) => boolean;
29
- decodeAsProperty(ctx: PropertyDecodingContext): void;
30
- encodeAsProperty(ctx: PropertyEncodingContext): void;
31
+ decodeAsProperty(this: void, ctx: PropertyDecodingContext): void;
32
+ encodeAsProperty(this: void, ctx: PropertyEncodingContext): void;
31
33
  }
32
34
  /**
33
35
  * Stored in schema meta under the single `@@xml-model` key.
@@ -72,7 +74,7 @@ export interface PropertyEncodingContext<S extends z.ZodObject = z.ZodObject, K
72
74
  };
73
75
  result: XMLElement;
74
76
  }
75
- export declare function normalizeCodecOptions<S extends z.ZodType>(schema: S, options?: UserCodecOptions<S>): CodecOptions<S>;
77
+ export declare function normalizeCodecOptions<S extends z.ZodType>(schema: S, options?: UserCodecOptions<S>, parent?: CodecOptions<z.ZodType> | undefined): CodecOptions<S>;
76
78
  type OrderEntry = string | XMLElement;
77
79
  export interface XMLState {
78
80
  /** Preserves element ordering and unknown elements across a decode → encode round-trip. */
@@ -108,10 +110,63 @@ export declare function xmlStateSchema(options: {
108
110
  }): z.ZodOptional<z.ZodCustom<XMLState & {
109
111
  source: XMLElement;
110
112
  }>>;
111
- export declare function decode<S extends z.ZodType>(schema: S, xml: XMLElement): z.input<S>;
113
+ /**
114
+ * Converts an `XMLElement` into the **input type** of `schema` (`z.input<S>`).
115
+ *
116
+ * This is a pure XML-to-data adapter: it does not run Zod's parse pipeline, so
117
+ * `z.codec` transforms and default values are **not** applied. The result is the
118
+ * raw decoded value suitable for passing to `schema.parse()`.
119
+ */
120
+ export declare function decode<S extends z.ZodType>(schema: S, xml: XMLElement | null): z.input<S>;
121
+ /**
122
+ * Converts the **input type** of `schema` (`z.input<S>`) into an `XMLElement`.
123
+ *
124
+ * This is a pure data-to-XML adapter: it expects values at `z.input<S>` level,
125
+ * meaning `z.codec` transforms must already have been reversed (via `schema.encode()`)
126
+ * before calling this function.
127
+ */
112
128
  export declare function encode<S extends z.ZodType>(schema: S, data: z.input<S>): XMLElement;
129
+ /**
130
+ * Parses an XML string, `XMLRoot`, or `XMLElement` into the **output type** of
131
+ * `schema` (`z.output<S>`), running the full pipeline:
132
+ * XML → `decode` → `schema.parse()`.
133
+ *
134
+ * `z.codec` transforms (e.g. string → Date) and default values are applied.
135
+ * Use the lower-level {@link decode} if you need the raw input-type value without
136
+ * running the Zod parse pipeline.
137
+ */
138
+ export declare function parseXML<S extends z.ZodType>(schema: S, input: string | XMLRoot | XMLElement): z.output<S>;
139
+ /**
140
+ * Converts a value at the **output type** of `schema` (`z.output<S>`) into an
141
+ * `XMLElement`, running the full pipeline: `schema.encode()` → `encode`.
142
+ *
143
+ * `z.codec` transforms are reversed before the XML adapter runs.
144
+ * Use the lower-level {@link encode} if you already have an input-type value and
145
+ * do not need to run the Zod encode pipeline.
146
+ *
147
+ * Does not accept nullable values — check for `null`/`undefined` before calling.
148
+ */
149
+ export declare function toXML<S extends z.ZodType>(schema: S, data: z.output<S>): XMLElement;
150
+ /**
151
+ * Converts a value at the **output type** of `schema` (`z.output<S>`) into an
152
+ * XML string, running the full pipeline: `schema.encode()` → `encode` → `XML.stringify`.
153
+ *
154
+ * Equivalent to `XML.stringify({ elements: [toXML(schema, data)] }, options)`.
155
+ */
156
+ export declare function stringifyXML<S extends z.ZodType>(schema: S, data: z.output<S>, options?: StringifyOptions): string;
113
157
  type DefaultResolver<S extends z.ZodType = z.ZodType> = (schema: S) => CodecOptions<S> | void;
114
158
  export declare function registerDefault(resolve: DefaultResolver): void;
159
+ /**
160
+ * Creates a `z.codec` that converts between an XML string and the **input type**
161
+ * of `schema` (`z.input<S>`).
162
+ *
163
+ * The codec sits at the XML ↔ `z.input<S>` boundary only — it does not run Zod's
164
+ * parse pipeline. `z.codec` transforms (e.g. string → Date) and class instantiation
165
+ * are left to `schema.parse()` / `schema.encode()`, which you call separately if needed.
166
+ *
167
+ * Typical use: `xmlCodec(MyClass.dataSchema)` for standalone encode/decode without
168
+ * going through the full `fromXML` / `toXMLString` class API.
169
+ */
115
170
  export declare function xmlCodec<S extends z.ZodType>(schema: S): z.ZodCodec<z.ZodString, S>;
116
171
  export {};
117
172
  //# sourceMappingURL=codec.d.ts.map
package/dist/xml/codec.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import XML from "./xml-js.js";
2
- import { isZodType } from "../util/zod.js";
2
+ import { getParentSchema, isZodType } from "../util/zod.js";
3
3
  import { kebabCase } from "../util/kebab-case.js";
4
- import { getUserOptions, prop } from "./schema-meta.js";
4
+ import { getOwnUserOptions, prop, root } from "./schema-meta.js";
5
5
  import { z } from "zod";
6
6
  //#region src/xml/codec.ts
7
7
  var XMLCodecError = class extends Error {
@@ -22,35 +22,37 @@ function rethrow(e, segment) {
22
22
  function assertSingleElement(xml) {
23
23
  if (xml.length !== 1) throw new Error(`Expected single XML element, got ${xml.length}`);
24
24
  }
25
- function normalizeCodecOptions(schema, options = {}) {
25
+ function normalizeCodecOptions(schema, options = {}, parent = void 0) {
26
26
  let _defaultOptions;
27
27
  const defaultOptions = () => {
28
28
  if (!_defaultOptions) {
29
- _defaultOptions = resolveDefault(schema);
30
- if (!_defaultOptions) throw new Error(`Failed to resolve default codec options for schema of type ${schema.type}`);
29
+ const resolved = resolveDefault(schema);
30
+ if (!resolved) throw new Error(`Failed to resolve default codec options for schema of type ${schema.type}`);
31
+ _defaultOptions = resolved;
31
32
  }
32
33
  return _defaultOptions;
33
34
  };
34
35
  const userTagname = options.tagname;
35
- const tagname = typeof userTagname === "string" ? () => userTagname : typeof userTagname === "function" ? userTagname : () => {
36
+ const tagname = typeof userTagname === "string" ? () => userTagname : typeof userTagname === "function" ? userTagname : parent ? (ctx) => parent.tagname(ctx) : () => {
36
37
  throw new Error("tagname is not defined");
37
38
  };
38
39
  const userPropTagname = options.propertyTagname;
39
- const propertyTagname = typeof userPropTagname === "string" ? () => userPropTagname : typeof userPropTagname === "function" ? userPropTagname : (ctx) => kebabCase(ctx.name);
40
- const inlineProperty = options.inlineProperty ?? false;
40
+ const propertyTagname = typeof userPropTagname === "string" ? () => userPropTagname : typeof userPropTagname === "function" ? userPropTagname : parent ? (ctx) => parent.propertyTagname(ctx) : (ctx) => kebabCase(ctx.name);
41
+ const inlineProperty = options.inlineProperty ?? parent?.inlineProperty ?? false;
41
42
  const userMatch = options.propertyMatch;
42
- const propertyMatch = userMatch instanceof RegExp ? (el) => userMatch.test(el.name) : typeof userMatch === "function" ? userMatch : (el, ctx) => el.name === ctx.tagname;
43
+ const propertyMatch = userMatch instanceof RegExp ? (el) => userMatch.test(el.name) : typeof userMatch === "function" ? userMatch : parent ? (el, ctx) => parent.propertyMatch(el, ctx) : (el, ctx) => el.name === ctx.tagname;
43
44
  const result = {
44
45
  schema,
46
+ parent,
45
47
  tagname,
46
- decode: options.decode ? (ctx) => options.decode(ctx, () => defaultOptions().decode(ctx)) : (ctx) => defaultOptions().decode(ctx),
47
- encode: options.encode ? (ctx) => options.encode(ctx, () => defaultOptions().encode(ctx)) : (ctx) => defaultOptions().encode(ctx),
48
+ decode: options.decode ? (ctx) => options.decode(ctx, () => defaultOptions().decode(ctx)) : defaultOptions().decode,
49
+ encode: options.encode ? (ctx) => options.encode(ctx, () => defaultOptions().encode(ctx)) : defaultOptions().encode,
48
50
  propertyTagname,
49
51
  inlineProperty,
50
52
  propertyMatch,
51
- decodeAsProperty: options.decodeAsProperty ?? function(ctx) {
52
- const res = result.decode({
53
- options: result,
53
+ decodeAsProperty: options.decodeAsProperty ?? parent?.decodeAsProperty ?? function(ctx) {
54
+ const res = ctx.property.options.decode({
55
+ options: ctx.property.options,
54
56
  xml: ctx.property.xml
55
57
  });
56
58
  ctx.result[ctx.property.name] = res;
@@ -66,11 +68,13 @@ function normalizeCodecOptions(schema, options = {}) {
66
68
  data: property.value
67
69
  });
68
70
  if (XML.isEmpty(res)) return;
69
- if (property.options.inlineProperty) ctx.result.elements.push(...res.elements.map((el) => el.type === "element" ? {
70
- ...el,
71
- name: property.tagname
72
- } : el));
73
- else ctx.result.elements.push(res);
71
+ if (property.options.inlineProperty) {
72
+ const elements = res.elements?.map((el) => el.type === "element" ? {
73
+ ...el,
74
+ name: property.tagname
75
+ } : el);
76
+ if (elements) (ctx.result.elements ??= []).push(...elements);
77
+ } else (ctx.result.elements ??= []).push(res);
74
78
  }
75
79
  };
76
80
  return result;
@@ -79,14 +83,21 @@ var cache = /* @__PURE__ */ new Map();
79
83
  function resolveCodecOptions(schema) {
80
84
  const cached = cache.get(schema);
81
85
  if (cached) return cached;
82
- const options = normalizeCodecOptions(schema, getUserOptions(schema));
83
- cache.set(schema, options);
84
- return options;
86
+ const placeholder = {};
87
+ cache.set(schema, placeholder);
88
+ const parentSchema = getParentSchema(schema);
89
+ const parent = parentSchema ? resolveCodecOptions(parentSchema) : void 0;
90
+ const options = normalizeCodecOptions(schema, getOwnUserOptions(schema), parent);
91
+ Object.assign(placeholder, options);
92
+ return placeholder;
85
93
  }
86
94
  /** Tracks all schemas created by `xmlStateSchema()` for fast detection at setup time. */
87
95
  var xmlStateSchemas = /* @__PURE__ */ new WeakSet();
88
96
  function xmlStateSchema(options) {
89
- const result = prop(z.custom().optional(), {
97
+ const result = prop(root(z.custom(), {
98
+ decode: () => ({}),
99
+ encode: () => ({})
100
+ }).optional(), {
90
101
  decode: options?.source ? (ctx, _next) => {
91
102
  (ctx.result[ctx.property.name] ??= {}).source = ctx.xml;
92
103
  } : () => {},
@@ -95,6 +106,15 @@ function xmlStateSchema(options) {
95
106
  xmlStateSchemas.add(result);
96
107
  return result;
97
108
  }
109
+ /** Returns true if any schema in the wrapper chain has a user-defined tagname. */
110
+ function hasUserTagname(schema) {
111
+ let s = schema;
112
+ while (s) {
113
+ if (getOwnUserOptions(s).tagname) return true;
114
+ s = getParentSchema(s);
115
+ }
116
+ return false;
117
+ }
98
118
  function resolvePropertiesCodecOptions(schema) {
99
119
  const shape = schema.def.shape;
100
120
  const options = {};
@@ -111,6 +131,13 @@ function findXmlStateKey(shape) {
111
131
  if (keys.length > 1) throw new Error(`Only one xmlStateSchema field is allowed per object schema, found: ${keys.join(", ")}`);
112
132
  return keys[0];
113
133
  }
134
+ /**
135
+ * Converts an `XMLElement` into the **input type** of `schema` (`z.input<S>`).
136
+ *
137
+ * This is a pure XML-to-data adapter: it does not run Zod's parse pipeline, so
138
+ * `z.codec` transforms and default values are **not** applied. The result is the
139
+ * raw decoded value suitable for passing to `schema.parse()`.
140
+ */
114
141
  function decode(schema, xml) {
115
142
  const options = resolveCodecOptions(schema);
116
143
  return options.decode({
@@ -118,6 +145,13 @@ function decode(schema, xml) {
118
145
  xml
119
146
  });
120
147
  }
148
+ /**
149
+ * Converts the **input type** of `schema` (`z.input<S>`) into an `XMLElement`.
150
+ *
151
+ * This is a pure data-to-XML adapter: it expects values at `z.input<S>` level,
152
+ * meaning `z.codec` transforms must already have been reversed (via `schema.encode()`)
153
+ * before calling this function.
154
+ */
121
155
  function encode(schema, data) {
122
156
  const options = resolveCodecOptions(schema);
123
157
  return options.encode({
@@ -125,6 +159,46 @@ function encode(schema, data) {
125
159
  data
126
160
  });
127
161
  }
162
+ /**
163
+ * Parses an XML string, `XMLRoot`, or `XMLElement` into the **output type** of
164
+ * `schema` (`z.output<S>`), running the full pipeline:
165
+ * XML → `decode` → `schema.parse()`.
166
+ *
167
+ * `z.codec` transforms (e.g. string → Date) and default values are applied.
168
+ * Use the lower-level {@link decode} if you need the raw input-type value without
169
+ * running the Zod parse pipeline.
170
+ */
171
+ function parseXML(schema, input) {
172
+ if (typeof input === "string") input = XML.parse(input);
173
+ if (XML.isRoot(input)) {
174
+ const el = XML.elementFromRoot(input);
175
+ if (!el) throw new Error("Failed to resolve XML element from root");
176
+ input = el;
177
+ }
178
+ return schema.parse(decode(schema, input));
179
+ }
180
+ /**
181
+ * Converts a value at the **output type** of `schema` (`z.output<S>`) into an
182
+ * `XMLElement`, running the full pipeline: `schema.encode()` → `encode`.
183
+ *
184
+ * `z.codec` transforms are reversed before the XML adapter runs.
185
+ * Use the lower-level {@link encode} if you already have an input-type value and
186
+ * do not need to run the Zod encode pipeline.
187
+ *
188
+ * Does not accept nullable values — check for `null`/`undefined` before calling.
189
+ */
190
+ function toXML(schema, data) {
191
+ return encode(schema, schema.encode(data));
192
+ }
193
+ /**
194
+ * Converts a value at the **output type** of `schema` (`z.output<S>`) into an
195
+ * XML string, running the full pipeline: `schema.encode()` → `encode` → `XML.stringify`.
196
+ *
197
+ * Equivalent to `XML.stringify({ elements: [toXML(schema, data)] }, options)`.
198
+ */
199
+ function stringifyXML(schema, data, options) {
200
+ return XML.stringify({ elements: [toXML(schema, data)] }, options);
201
+ }
128
202
  function registerDefault(resolve) {
129
203
  defaults.push(resolve);
130
204
  }
@@ -141,12 +215,12 @@ registerDefault((schema) => {
141
215
  const elSchema = schema.def.element;
142
216
  if (!isZodType(elSchema)) throw new Error(`Expected a ZodType, got ${elSchema}`);
143
217
  const elOptions = resolveCodecOptions(elSchema);
144
- const elHasOwnTagname = Boolean(getUserOptions(elSchema).tagname);
218
+ const elHasOwnTagname = hasUserTagname(elSchema);
145
219
  return normalizeCodecOptions(schema, {
146
220
  decode(ctx) {
147
221
  const { xml } = ctx;
148
222
  if (!xml) return [];
149
- return xml.elements.filter((el) => el.type === "element").map((el) => elOptions.decode({
223
+ return (xml.elements ?? []).filter((el) => el.type === "element").map((el) => elOptions.decode({
150
224
  options: elOptions,
151
225
  xml: el
152
226
  }));
@@ -189,7 +263,7 @@ registerDefault((schema) => {
189
263
  encodeAsProperty(ctx) {
190
264
  if (typeof ctx.property.value !== "undefined") innerOptions.encodeAsProperty(ctx);
191
265
  }
192
- });
266
+ }, innerOptions);
193
267
  }
194
268
  if (schema instanceof z.ZodDefault) {
195
269
  const { innerType: inner, defaultValue } = schema.def;
@@ -204,7 +278,7 @@ registerDefault((schema) => {
204
278
  encode(ctx) {
205
279
  return innerOptions.encode(ctx);
206
280
  }
207
- });
281
+ }, innerOptions);
208
282
  }
209
283
  if (schema instanceof z.ZodLazy) {
210
284
  const inner = schema.def.getter();
@@ -232,11 +306,11 @@ registerDefault((schema) => {
232
306
  data: ctx.data
233
307
  });
234
308
  }
235
- });
309
+ }, inputCodecOptions);
236
310
  }
237
311
  if (schema instanceof z.ZodLiteral) {
238
312
  const values = schema.def.values;
239
- const valuesFromString = Object.fromEntries(values.map((v) => [v.toString(), v]));
313
+ const valuesFromString = Object.fromEntries(values.map((v) => [v?.toString(), v]));
240
314
  return normalizeCodecOptions(schema, {
241
315
  decode(ctx) {
242
316
  const raw = XML.getContent(ctx.xml);
@@ -398,16 +472,21 @@ function formatReason(errors) {
398
472
  function peekDiscriminatorValue(discriminator, propertyOptions, ctx) {
399
473
  const errors = [];
400
474
  for (const options of propertyOptions) try {
475
+ const tagname = options.propertyTagname({
476
+ name: discriminator,
477
+ options
478
+ });
401
479
  const propCtx = {
402
480
  name: discriminator,
403
481
  options,
404
- tagname: options.propertyTagname({
405
- name: discriminator,
406
- options
407
- }),
408
- xml: { elements: [] }
482
+ tagname,
483
+ xml: {
484
+ type: "element",
485
+ name: tagname,
486
+ elements: []
487
+ }
409
488
  };
410
- ctx.xml.elements.forEach((el) => {
489
+ ctx.xml?.elements?.forEach((el) => {
411
490
  if (el.type !== "element") return;
412
491
  if (options.propertyMatch(el, propCtx)) propCtx.xml.elements.push(el);
413
492
  });
@@ -499,7 +578,30 @@ registerDefault((schema) => {
499
578
  });
500
579
  }
501
580
  });
581
+ /**
582
+ * Creates a `z.codec` that converts between an XML string and the **input type**
583
+ * of `schema` (`z.input<S>`).
584
+ *
585
+ * The codec sits at the XML ↔ `z.input<S>` boundary only — it does not run Zod's
586
+ * parse pipeline. `z.codec` transforms (e.g. string → Date) and class instantiation
587
+ * are left to `schema.parse()` / `schema.encode()`, which you call separately if needed.
588
+ *
589
+ * Typical use: `xmlCodec(MyClass.dataSchema)` for standalone encode/decode without
590
+ * going through the full `fromXML` / `toXMLString` class API.
591
+ */
592
+ function xmlCodec(schema) {
593
+ return z.codec(z.string(), schema, {
594
+ decode(xml) {
595
+ const xmlRoot = XML.parse(xml);
596
+ return decode(schema, XML.elementFromRoot(xmlRoot) ?? null);
597
+ },
598
+ encode(value) {
599
+ const xmlEl = encode(schema, value);
600
+ return XML.stringify({ elements: [xmlEl] });
601
+ }
602
+ });
603
+ }
502
604
  //#endregion
503
- export { XMLCodecError, decode, encode, normalizeCodecOptions, registerDefault, xmlStateSchema };
605
+ export { XMLCodecError, decode, encode, normalizeCodecOptions, parseXML, registerDefault, stringifyXML, toXML, xmlCodec, xmlStateSchema };
504
606
 
505
607
  //# sourceMappingURL=codec.js.map
@@ -1,6 +1,7 @@
1
1
  export * from './xml-js';
2
2
  export { xml } from './schema-meta';
3
3
  export type { UserCodecOptions, XMLState } from './codec';
4
- export { registerDefault, normalizeCodecOptions, XMLCodecError, xmlStateSchema } from './codec';
4
+ export { xmlCodec, // if not re-exported here, xmlCodec is not exported from "./codex.js" but only from "./codec.d.ts"
5
+ registerDefault, normalizeCodecOptions, XMLCodecError, xmlStateSchema, parseXML, toXML, stringifyXML, } from './codec';
5
6
  export { xmlModel, type XmlModelConstructor } from './model';
6
7
  //# sourceMappingURL=index.d.ts.map
package/dist/xml/model.js CHANGED
@@ -9,7 +9,11 @@ function xmlModel(schema, options) {
9
9
  return class extends model(_schema) {
10
10
  static fromXML(input) {
11
11
  if (typeof input === "string") input = XML.parse(input);
12
- if (XML.isRoot(input)) input = XML.elementFromRoot(input);
12
+ if (XML.isRoot(input)) {
13
+ const el = XML.elementFromRoot(input);
14
+ if (!el) throw new TypeError("No root element");
15
+ input = el;
16
+ }
13
17
  const rawData = decode(this.dataSchema, input);
14
18
  return this.fromData(this.dataSchema.parse(rawData));
15
19
  }
@@ -67,6 +67,5 @@ export declare const xml: {
67
67
  attr: typeof attr;
68
68
  };
69
69
  export declare function getOwnUserOptions<S extends z.ZodType>(schema: S): UserCodecOptions<S>;
70
- export declare function getUserOptions<S extends z.ZodType>(schema: S): UserCodecOptions<S>;
71
70
  export {};
72
71
  //# sourceMappingURL=schema-meta.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import XML from "./xml-js.js";
2
- import { getParentSchema, isZodType } from "../util/zod.js";
2
+ import { isZodType } from "../util/zod.js";
3
3
  import { kebabCase } from "../util/kebab-case.js";
4
4
  import "zod";
5
5
  //#region src/xml/schema-meta.ts
@@ -45,11 +45,13 @@ function normalizePropOptions(options) {
45
45
  data: property.value
46
46
  });
47
47
  if (XML.isEmpty(res)) return;
48
- if (property.options.inlineProperty) ctx.result.elements.push(...res.elements.map((el) => el.type === "element" ? {
49
- ...el,
50
- name: property.tagname
51
- } : el));
52
- else ctx.result.elements.push({
48
+ if (property.options.inlineProperty) {
49
+ const elements = res.elements?.map((el) => el.type === "element" ? {
50
+ ...el,
51
+ name: property.tagname
52
+ } : el);
53
+ if (elements) (ctx.result.elements ??= []).push(...elements);
54
+ } else (ctx.result.elements ??= []).push({
53
55
  ...res,
54
56
  name: property.tagname
55
57
  });
@@ -79,7 +81,7 @@ function attr(optionsOrSchema, options) {
79
81
  encodeAsProperty(ctx) {
80
82
  const { value } = ctx.property;
81
83
  const attrName = opts.name ?? kebabCase(ctx.property.name);
82
- ctx.result.attributes[attrName] = value.toString();
84
+ (ctx.result.attributes ??= {})[attrName] = String(value);
83
85
  }
84
86
  };
85
87
  if (isZodType(optionsOrSchema)) return setMeta(optionsOrSchema, partial);
@@ -94,27 +96,7 @@ var xml = {
94
96
  function getOwnUserOptions(schema) {
95
97
  return schema.meta()?.[metaKey] ?? {};
96
98
  }
97
- var INHERITABLE_KEYS = [
98
- "tagname",
99
- "propertyTagname",
100
- "inlineProperty",
101
- "propertyMatch",
102
- "decodeAsProperty",
103
- "encodeAsProperty"
104
- ];
105
- function getUserOptions(schema) {
106
- const own = getOwnUserOptions(schema);
107
- const parentSchema = getParentSchema(schema);
108
- if (!parentSchema) return own;
109
- const parentOptions = getUserOptions(parentSchema);
110
- const inherited = {};
111
- for (const key of INHERITABLE_KEYS) if (parentOptions[key] !== void 0 && own[key] === void 0) inherited[key] = parentOptions[key];
112
- return {
113
- ...inherited,
114
- ...own
115
- };
116
- }
117
99
  //#endregion
118
- export { getUserOptions, prop, root, xml };
100
+ export { getOwnUserOptions, prop, root, xml };
119
101
 
120
102
  //# sourceMappingURL=schema-meta.js.map
@@ -59,7 +59,7 @@ function isEmpty(xml) {
59
59
  * @throws {TypeError} When the element has multiple or non-text children.
60
60
  */
61
61
  function getContent(xml) {
62
- if (!xml.elements.length) return "";
62
+ if (!xml.elements?.length) return "";
63
63
  if (xml.elements.length === 1) {
64
64
  const content = xml.elements[0];
65
65
  if (content.type === "text") return content.text;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xml-model",
3
- "version": "2.0.0-beta.7",
3
+ "version": "2.0.0",
4
4
  "description": "allows transparent XML <-> Object conversion in typescript",
5
5
  "license": "MIT",
6
6
  "author": "MathisTLD",
@@ -53,4 +53,4 @@
53
53
  "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
54
54
  },
55
55
  "packageManager": "npm@11.12.0"
56
- }
56
+ }