@zipbul/baker 5.0.0 → 5.2.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.
Files changed (207) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/README.md +11 -14
  3. package/dist/index.d.ts +11 -12
  4. package/dist/index.js +1 -1
  5. package/dist/src/baker.d.ts +19 -9
  6. package/dist/src/baker.js +1 -1
  7. package/dist/src/common/enums.d.ts +10 -0
  8. package/dist/src/common/enums.js +1 -0
  9. package/dist/src/common/index.d.ts +6 -0
  10. package/dist/src/common/index.js +1 -0
  11. package/dist/src/common/interfaces.d.ts +4 -0
  12. package/dist/src/common/types.d.ts +2 -0
  13. package/dist/src/config/config-normalizer.d.ts +7 -0
  14. package/dist/src/config/config-normalizer.js +1 -0
  15. package/dist/src/config/constants.d.ts +6 -0
  16. package/dist/src/config/constants.js +1 -0
  17. package/dist/src/config/index.d.ts +3 -0
  18. package/dist/src/config/index.js +1 -0
  19. package/dist/src/{configure.d.ts → config/interfaces.d.ts} +1 -8
  20. package/dist/src/config/interfaces.js +0 -0
  21. package/dist/src/decorators/constants.d.ts +2 -0
  22. package/dist/src/decorators/constants.js +1 -0
  23. package/dist/src/decorators/enums.d.ts +5 -0
  24. package/dist/src/decorators/enums.js +1 -0
  25. package/dist/src/decorators/field.d.ts +3 -54
  26. package/dist/src/decorators/field.js +1 -1
  27. package/dist/src/decorators/index.d.ts +2 -2
  28. package/dist/src/decorators/index.js +1 -1
  29. package/dist/src/decorators/interfaces.d.ts +50 -0
  30. package/dist/src/decorators/interfaces.js +0 -0
  31. package/dist/src/decorators/public.d.ts +2 -0
  32. package/dist/src/decorators/public.js +1 -0
  33. package/dist/src/decorators/types.d.ts +6 -0
  34. package/dist/src/decorators/types.js +0 -0
  35. package/dist/src/metadata/enums.d.ts +5 -0
  36. package/dist/src/metadata/enums.js +1 -0
  37. package/dist/src/metadata/index.d.ts +3 -0
  38. package/dist/src/metadata/index.js +1 -0
  39. package/dist/src/metadata/interfaces.d.ts +90 -0
  40. package/dist/src/metadata/interfaces.js +0 -0
  41. package/dist/src/metadata/meta-store.d.ts +49 -0
  42. package/dist/src/metadata/meta-store.js +1 -0
  43. package/dist/src/metadata/types.d.ts +10 -0
  44. package/dist/src/metadata/types.js +0 -0
  45. package/dist/src/rules/array.d.ts +2 -2
  46. package/dist/src/rules/array.js +1 -1
  47. package/dist/src/rules/binary.d.ts +2 -2
  48. package/dist/src/rules/binary.js +1 -1
  49. package/dist/src/rules/combinators.d.ts +1 -1
  50. package/dist/src/rules/combinators.js +1 -1
  51. package/dist/src/rules/common.d.ts +3 -3
  52. package/dist/src/rules/common.js +1 -1
  53. package/dist/src/rules/constants.d.ts +10 -0
  54. package/dist/src/rules/constants.js +1 -0
  55. package/dist/src/{create-rule.d.ts → rules/create-rule.d.ts} +1 -1
  56. package/dist/src/rules/create-rule.js +1 -0
  57. package/dist/src/rules/date.d.ts +1 -1
  58. package/dist/src/rules/date.js +1 -1
  59. package/dist/src/{enums.d.ts → rules/enums.d.ts} +0 -20
  60. package/dist/src/rules/enums.js +1 -0
  61. package/dist/src/rules/index.d.ts +5 -13
  62. package/dist/src/rules/index.js +1 -1
  63. package/dist/src/rules/interfaces.d.ts +43 -0
  64. package/dist/src/rules/interfaces.js +0 -0
  65. package/dist/src/rules/locales.d.ts +1 -1
  66. package/dist/src/rules/locales.js +1 -1
  67. package/dist/src/rules/number.d.ts +3 -3
  68. package/dist/src/rules/number.js +1 -1
  69. package/dist/src/rules/object.d.ts +1 -1
  70. package/dist/src/rules/object.js +1 -1
  71. package/dist/src/rules/public.d.ts +14 -0
  72. package/dist/src/rules/public.js +1 -0
  73. package/dist/src/{rule-metadata.d.ts → rules/rule-metadata.d.ts} +1 -1
  74. package/dist/src/{rule-metadata.js → rules/rule-metadata.js} +1 -1
  75. package/dist/src/{rule-plan.d.ts → rules/rule-plan.d.ts} +4 -7
  76. package/dist/src/rules/rule-plan.js +1 -0
  77. package/dist/src/rules/string-basic.d.ts +23 -0
  78. package/dist/src/rules/string-basic.js +1 -0
  79. package/dist/src/rules/string-crypto.d.ts +5 -0
  80. package/dist/src/rules/string-crypto.js +1 -0
  81. package/dist/src/rules/string-datetime.d.ts +3 -0
  82. package/dist/src/rules/string-datetime.js +1 -0
  83. package/dist/src/rules/string-encoding.d.ts +14 -0
  84. package/dist/src/rules/string-encoding.js +1 -0
  85. package/dist/src/rules/string-finance.d.ts +18 -0
  86. package/dist/src/rules/string-finance.js +10 -0
  87. package/dist/src/rules/string-format.d.ts +38 -0
  88. package/dist/src/rules/string-format.js +1 -0
  89. package/dist/src/rules/string-geo.d.ts +5 -0
  90. package/dist/src/rules/string-geo.js +1 -0
  91. package/dist/src/rules/string-identifier.d.ts +16 -0
  92. package/dist/src/rules/string-identifier.js +3 -0
  93. package/dist/src/rules/string-shared.d.ts +3 -0
  94. package/dist/src/rules/string-shared.js +1 -0
  95. package/dist/src/rules/string-width.d.ts +6 -0
  96. package/dist/src/rules/string-width.js +1 -0
  97. package/dist/src/rules/string.d.ts +14 -110
  98. package/dist/src/rules/string.js +1 -12
  99. package/dist/src/rules/typechecker.d.ts +10 -10
  100. package/dist/src/rules/typechecker.js +5 -5
  101. package/dist/src/rules/types.d.ts +26 -0
  102. package/dist/src/rules/types.js +0 -0
  103. package/dist/src/{functions → runtime}/check-call-options.d.ts +1 -1
  104. package/dist/src/runtime/check-call-options.js +1 -0
  105. package/dist/src/runtime/constants.d.ts +3 -0
  106. package/dist/src/runtime/constants.js +1 -0
  107. package/dist/src/runtime/deserialize.d.ts +6 -0
  108. package/dist/src/runtime/deserialize.js +1 -0
  109. package/dist/src/runtime/index.d.ts +3 -0
  110. package/dist/src/runtime/index.js +1 -0
  111. package/dist/src/runtime/serialize.d.ts +11 -0
  112. package/dist/src/runtime/serialize.js +1 -0
  113. package/dist/src/runtime/validate.d.ts +6 -0
  114. package/dist/src/runtime/validate.js +1 -0
  115. package/dist/src/seal/async-analyzer.d.ts +20 -0
  116. package/dist/src/seal/async-analyzer.js +1 -0
  117. package/dist/src/seal/circular-analyzer.d.ts +9 -6
  118. package/dist/src/seal/circular-analyzer.js +1 -1
  119. package/dist/src/seal/circular-placeholder.d.ts +20 -0
  120. package/dist/src/seal/circular-placeholder.js +1 -0
  121. package/dist/src/seal/codegen-utils.d.ts +15 -0
  122. package/dist/src/seal/codegen-utils.js +1 -1
  123. package/dist/src/seal/compile-cache.d.ts +38 -0
  124. package/dist/src/seal/compile-cache.js +1 -0
  125. package/dist/src/seal/constants.d.ts +62 -0
  126. package/dist/src/seal/constants.js +1 -0
  127. package/dist/src/seal/deserialize-builder.d.ts +6 -9
  128. package/dist/src/seal/deserialize-builder.js +199 -262
  129. package/dist/src/seal/deserialize-codegen.d.ts +58 -0
  130. package/dist/src/seal/deserialize-codegen.js +64 -0
  131. package/dist/src/seal/enums.d.ts +1 -2
  132. package/dist/src/seal/enums.js +1 -1
  133. package/dist/src/seal/expose-validator.d.ts +2 -2
  134. package/dist/src/seal/expose-validator.js +1 -1
  135. package/dist/src/seal/index.d.ts +3 -0
  136. package/dist/src/seal/index.js +1 -0
  137. package/dist/src/seal/inheritance-merger.d.ts +18 -0
  138. package/dist/src/seal/inheritance-merger.js +1 -0
  139. package/dist/src/seal/interfaces.d.ts +89 -0
  140. package/dist/src/seal/interfaces.js +0 -0
  141. package/dist/src/seal/meta-validator.d.ts +16 -0
  142. package/dist/src/seal/meta-validator.js +1 -0
  143. package/dist/src/seal/seal.d.ts +5 -30
  144. package/dist/src/seal/seal.js +1 -1
  145. package/dist/src/seal/serialize-builder.d.ts +6 -4
  146. package/dist/src/seal/serialize-builder.js +63 -63
  147. package/dist/src/seal/type-normalizer.d.ts +9 -0
  148. package/dist/src/seal/type-normalizer.js +1 -0
  149. package/dist/src/seal/type-resolver.d.ts +2 -0
  150. package/dist/src/seal/type-resolver.js +1 -0
  151. package/dist/src/seal/types.d.ts +6 -0
  152. package/dist/src/seal/types.js +0 -0
  153. package/dist/src/symbols.d.ts +2 -4
  154. package/dist/src/symbols.js +1 -1
  155. package/dist/src/transformers/{collection.transformer.d.ts → collection.d.ts} +1 -1
  156. package/dist/src/transformers/constants.d.ts +2 -0
  157. package/dist/src/transformers/constants.js +1 -0
  158. package/dist/src/transformers/{date.transformer.d.ts → date.d.ts} +1 -1
  159. package/dist/src/transformers/date.js +1 -0
  160. package/dist/src/transformers/index.d.ts +3 -8
  161. package/dist/src/transformers/index.js +1 -1
  162. package/dist/src/transformers/interfaces.d.ts +26 -0
  163. package/dist/src/transformers/interfaces.js +0 -0
  164. package/dist/src/transformers/luxon.d.ts +3 -0
  165. package/dist/src/transformers/luxon.js +1 -0
  166. package/dist/src/transformers/moment.d.ts +3 -0
  167. package/dist/src/transformers/moment.js +1 -0
  168. package/dist/src/transformers/{number.transformer.d.ts → number.d.ts} +1 -1
  169. package/dist/src/transformers/public.d.ts +7 -0
  170. package/dist/src/transformers/public.js +1 -0
  171. package/dist/src/transformers/{string.transformer.d.ts → string.d.ts} +1 -1
  172. package/dist/src/transformers/types.d.ts +3 -0
  173. package/dist/src/transformers/types.js +0 -0
  174. package/package.json +7 -7
  175. package/dist/src/collect.d.ts +0 -15
  176. package/dist/src/collect.js +0 -1
  177. package/dist/src/configure.js +0 -1
  178. package/dist/src/create-rule.js +0 -1
  179. package/dist/src/enums.js +0 -1
  180. package/dist/src/functions/check-call-options.js +0 -1
  181. package/dist/src/functions/deserialize.d.ts +0 -19
  182. package/dist/src/functions/deserialize.js +0 -1
  183. package/dist/src/functions/serialize.d.ts +0 -16
  184. package/dist/src/functions/serialize.js +0 -1
  185. package/dist/src/functions/validate.d.ts +0 -18
  186. package/dist/src/functions/validate.js +0 -1
  187. package/dist/src/interfaces.d.ts +0 -32
  188. package/dist/src/meta-access.d.ts +0 -19
  189. package/dist/src/meta-access.js +0 -1
  190. package/dist/src/rule-plan.js +0 -1
  191. package/dist/src/seal/validate-meta.d.ts +0 -13
  192. package/dist/src/seal/validate-meta.js +0 -1
  193. package/dist/src/transformers/date.transformer.js +0 -1
  194. package/dist/src/transformers/luxon.transformer.d.ts +0 -8
  195. package/dist/src/transformers/luxon.transformer.js +0 -1
  196. package/dist/src/transformers/moment.transformer.d.ts +0 -7
  197. package/dist/src/transformers/moment.transformer.js +0 -1
  198. package/dist/src/types.d.ts +0 -177
  199. /package/dist/src/{errors.d.ts → common/errors.d.ts} +0 -0
  200. /package/dist/src/{errors.js → common/errors.js} +0 -0
  201. /package/dist/src/{interfaces.js → common/interfaces.js} +0 -0
  202. /package/dist/src/{types.js → common/types.js} +0 -0
  203. /package/dist/src/{utils.d.ts → common/utils.d.ts} +0 -0
  204. /package/dist/src/{utils.js → common/utils.js} +0 -0
  205. /package/dist/src/transformers/{collection.transformer.js → collection.js} +0 -0
  206. /package/dist/src/transformers/{number.transformer.js → number.js} +0 -0
  207. /package/dist/src/transformers/{string.transformer.js → string.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,106 @@
1
1
  # @zipbul/baker
2
2
 
3
+ ## 5.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - f768694: Fix four correctness bugs found in a package-wide audit. Two of them change observable behavior for
8
+ input that previously "worked", so review before upgrading:
9
+
10
+ - **`@IsEnum` with numeric enums (behavior change).** TypeScript numeric enums compile to a reverse-mapped
11
+ object (`{ 0: 'Inactive', 1: 'Active', Active: 1, Inactive: 0 }`), so the previous `Object.values()`
12
+ lookup wrongly accepted the member-_name_ strings (e.g. `'Active'`) as valid values. Values are now read
13
+ through the non-numeric keys, so only real members pass — correct for string, numeric, and heterogeneous
14
+ enums. Input that relied on the member-name strings being accepted will now be rejected.
15
+
16
+ - **`momentTransformer` parses in UTC (behavior change).** It now uses `moment.utc(value)` so a zoneless
17
+ datetime string resolves to the same instant on every host; previously local-time parsing made the
18
+ serialized output depend on the machine timezone. Zoneless inputs that were parsed in local time will now
19
+ be parsed as UTC. Matches `luxonTransformer`'s UTC default.
20
+
21
+ - **`luxonTransformer` invalid-date passthrough.** An unparseable date string / `Date` now passes through
22
+ untouched instead of being laundered into an Invalid `DateTime` (which serialized to `null` /
23
+ `"Invalid DateTime"` and corrupted data). Matches `momentTransformer`'s pass-through contract.
24
+
25
+ - **Per-call `groups` option validation.** A non-`string[]` `groups` value now throws a clear `BakerError`
26
+ at the call boundary instead of silently misbehaving inside the generated executor.
27
+
28
+ - 26e13af: Fix declared-collection element validation (RED tests added first), speed up collection `validate`, and
29
+ land an internal layering cleanup. One item changes observable behavior — review before upgrading:
30
+
31
+ - **Declared `@Type(() => Set)` / `@Type(() => Map)` now validate their elements (behavior change).** The
32
+ declared-collection codegen path hand-rolled its per-element loop separately from the canonical
33
+ (`type: null`) path and had three defects: a declared **Map** dropped every per-element `each` rule
34
+ entirely; declared Set/Map `each` rules ignored the runtime `groups` filter; and a function `message` on
35
+ an `each` rule received the whole collection as `value` instead of the failing element. All four sites
36
+ (Set/Map × deserialize/validate) now route through one shared emitter with the same rule-major ordering,
37
+ group filtering, per-element `value` binding, and `field[i]` paths as the canonical path. Input that was
38
+ silently accepted because a Map's element rules never ran will now be validated.
39
+
40
+ - **Collection `validate` is ~4.7× faster on large arrays.** The inline-nested validate path eagerly
41
+ allocated a per-element error-path string (`field[i].`) on every element even for valid input; it is now
42
+ built only at the (cold) error-push sites. A 1000-element nested-DTO `validate` drops from ~10µs to
43
+ ~2.2µs (now on par with TypeBox and ahead of Ajv). `deserialize` and all error paths are byte-identical.
44
+
45
+ - **`createRule` is now also exported from the `@zipbul/baker/rules` subpath** (it was already exported from
46
+ the package root).
47
+
48
+ - **`luxonTransformer` / `momentTransformer` peer-dep error is now precise.** A genuinely-missing peer still
49
+ throws the "install it" `BakerError`; a peer that IS installed but throws during evaluation now surfaces
50
+ its real error instead of the misleading install hint.
51
+
52
+ Internal-only (no API change): the seal stage's TypeDef normalization was extracted out of the `sealOne`
53
+ god-function, large static lookup tables and the `string-format` validators were split into cohesive
54
+ modules, and several stateless helpers were simplified. Public surface is unchanged except the `createRule`
55
+ subpath export above (verified by an export-diff).
56
+
57
+ - 96ed92c: Fix five reproduced correctness bugs (each added as a RED test first) and unify the unknown-key failure
58
+ model. Several change observable behavior — review before upgrading:
59
+
60
+ - **Discriminated arrays now work (was broken).** A field typed `type: () => [Base]` with a `discriminator`
61
+ previously read the discriminator off the _array itself_ (`undefined`) and rejected every valid input with
62
+ `invalidDiscriminator`. `deserialize`/`validate` now dispatch the discriminator switch **per element**,
63
+ reporting nested errors at `field[i].path` and the invalid-discriminator error at the `field[i]` element
64
+ path. (serialize already handled arrays.)
65
+
66
+ - **serialize throws on an unmatched discriminator subtype (behavior change).** When an instance matched no
67
+ `instanceof` branch, serialize silently emitted the raw, un-serialized object (leaking undeclared fields).
68
+ It now throws a `BakerError`, symmetric with deserialize rejecting an unknown discriminator value.
69
+
70
+ - **`each` rule messages receive the failing element (behavior change).** A `message`/`context` function on
71
+ an `arrayOf(...)` rule was passed the whole collection as `value` while the path pointed at `field[i]`.
72
+ It now receives the failing element, consistent with the element-level path.
73
+
74
+ - **`isDateString` / `isISO8601({ strict: true })` leap-year for years 0–99.** Calendar validity used
75
+ `new Date(year, …)`, which remaps a 0–99 year argument to 1900–1999 — so `0000-02-29` (a valid leap date
76
+ by the 400 rule) was wrongly rejected. Now computed with the proleptic Gregorian rule for all years (and
77
+ without allocating a `Date`).
78
+
79
+ - **`isHash` / `isTaxId` reject an unknown algorithm/locale at construction (behavior change).** They
80
+ previously returned a rule that always failed at runtime; they now throw a `BakerError` when called with
81
+ an unsupported key, matching `isMobilePhone`/`isPostalCode`/`isIdentityCard`/`isPassportNumber`.
82
+
83
+ - **`isURL` no longer shares its default-protocols array across rules.** With default protocols, every
84
+ `isURL()` rule exposed the same module-level `['http','https','ftp']` array on `rule.constraints`;
85
+ mutating one rule's constraints would have corrupted every other. Each rule now owns an independent copy.
86
+
87
+ ## 5.1.0
88
+
89
+ ### Minor Changes
90
+
91
+ - 2d61542: Baker-scoped runtime: `deserialize`/`validate`/`serialize` (plus the `*Sync`/`*Async`
92
+ variants) are now methods on a `Baker` instance — `app.deserialize(Dto, input)`. Each
93
+ baker compiles its own executor per class into its own map, so the **same class sealed by
94
+ two bakers with different configs behaves per each baker's config** — apps in one process
95
+ never mix. An undecorated subclass resolves to its nearest sealed ancestor within that
96
+ baker. Same-config bakers transparently share one compiled executor via a `(class, config)`
97
+ cache (compile once, no behavior change).
98
+
99
+ **BREAKING CHANGE:** the global `deserialize`/`validate`/`serialize` functions (and their
100
+ `*Sync`/`*Async` variants) are removed from the package entry, along with the published
101
+ `SEALED` symbol (`RAW` remains on `@zipbul/baker/symbols`). Migrate `deserialize(Dto, input)`
102
+ to `app.deserialize(Dto, input)` on the `Baker` instance that sealed the class.
103
+
3
104
  ## 5.0.0
4
105
 
5
106
  ### Major Changes
package/README.md CHANGED
@@ -27,7 +27,7 @@ Zero `reflect-metadata`. Sealed codegen.
27
27
  ## Quick Start
28
28
 
29
29
  ```typescript
30
- import { Baker, Field, deserialize, isBakerIssueSet } from '@zipbul/baker';
30
+ import { Baker, Field, isBakerIssueSet } from '@zipbul/baker';
31
31
  import { isString, isNumber, isEmail, min, minLength } from '@zipbul/baker/rules';
32
32
 
33
33
  const baker = new Baker();
@@ -43,7 +43,7 @@ class UserDto {
43
43
  baker.seal();
44
44
 
45
45
  // All rules here are sync, so deserialize returns the value directly (no await).
46
- const result = deserialize(UserDto, {
46
+ const result = baker.deserialize(UserDto, {
47
47
  name: 'Alice',
48
48
  age: 30,
49
49
  email: 'alice@test.com',
@@ -67,7 +67,7 @@ if (isBakerIssueSet(result)) {
67
67
  | `@app.Recipe` | Marks a class as a DTO of that baker. Only `@Field` properties are part of the contract. |
68
68
  | `@Field(...rules)` | Declares a validated field. Global — works with any baker. |
69
69
  | `app.seal()` | Compiles that baker's DTOs into executor functions. Call once, at startup. |
70
- | `deserialize` / `validate` / `serialize` | Global run the sealed executors stored on the class: parse+validate, validate-only, or emit a plain object. |
70
+ | `app.deserialize` / `app.validate` / `app.serialize` | Run that baker's compiled executors: parse+validate, validate-only, or emit a plain object. |
71
71
 
72
72
  > Examples below assume a `const baker = new Baker()` in scope and a single `baker.seal()` after the DTOs are defined.
73
73
 
@@ -143,8 +143,8 @@ class UserDto {
143
143
  displayName!: string;
144
144
  }
145
145
 
146
- deserialize(UserDto, input); // `ssn` is skipped
147
- deserialize(UserDto, input, { groups: ['admin'] }); // `ssn` is included
146
+ baker.deserialize(UserDto, input); // `ssn` is skipped
147
+ baker.deserialize(UserDto, input, { groups: ['admin'] }); // `ssn` is included
148
148
  ```
149
149
 
150
150
  A field with no `groups` is always included; a field tagged with `groups` participates only when a matching group is passed via [runtime options](#runtime-options). See [`RuntimeOptions`](#runtime-options) for the call-site shape.
@@ -351,9 +351,9 @@ const app = new Baker({
351
351
  });
352
352
  ```
353
353
 
354
- `deserialize` / `serialize` / `validate` are global they read the sealed executor off the class — and throw `BakerError` if the class is not sealed.
354
+ `app.deserialize` / `app.serialize` / `app.validate` run that baker's compiled executors and throw `BakerError` if the class was not sealed by this baker.
355
355
 
356
- **Isolation:** distinct classes are fully isolated (each sealed with its baker's config). A class shared across bakers is sealed once (first seal wins) use separate classes if you need different config.
356
+ **Isolation:** each baker compiles its own executor per class into its own map, so the **same class sealed by two bakers behaves per each baker's config** — apps never mix. (An undecorated subclass resolves to its nearest sealed ancestor within that baker.)
357
357
 
358
358
  ### `deserialize` / `serialize` / `validate`
359
359
 
@@ -418,7 +418,7 @@ baker separates two failure modes:
418
418
  - **`BakerIssueSet` (returned)** — a validation failure. `deserialize` and `validate` return it instead of throwing. Guard with `isBakerIssueSet` and read `.errors`.
419
419
 
420
420
  ```typescript
421
- const result = deserialize(UserDto, input);
421
+ const result = baker.deserialize(UserDto, input);
422
422
 
423
423
  if (isBakerIssueSet(result)) {
424
424
  for (const issue of result.errors) {
@@ -445,11 +445,11 @@ Yes. If any rule or transformer is async, baker automatically detects it at seal
445
445
 
446
446
  ### Can I use baker with NestJS?
447
447
 
448
- Yes. baker's `@Field` decorator works alongside NestJS pipes. Use `deserialize()` in a custom validation pipe.
448
+ Yes. baker's `@Field` decorator works alongside NestJS pipes. Use `app.deserialize()` (your `Baker` instance) in a custom validation pipe.
449
449
 
450
450
  ### How does the AOT code generation work?
451
451
 
452
- Calling `app.seal()` once at startup walks the baker's DTOs (and their nested DTOs), analyzes field metadata, generates optimized JavaScript executor functions, and stores them on each class. Subsequent `deserialize` / `serialize` / `validate` calls run the pre-compiled functions directly. There is no auto-seal — using a DTO before `app.seal()` raises `BakerError`.
452
+ Calling `app.seal()` once at startup walks the baker's DTOs (and their nested DTOs), analyzes field metadata, generates optimized JavaScript executor functions, and stores them in that baker's map. Subsequent `app.deserialize` / `app.serialize` / `app.validate` calls run the pre-compiled functions directly. There is no auto-seal — using a DTO before `app.seal()` raises `BakerError`.
453
453
 
454
454
  > baker builds its executors with `new Function()`. Under a strict Content-Security-Policy this requires `'unsafe-eval'`; baker will not run in environments that forbid runtime code generation.
455
455
 
@@ -457,10 +457,7 @@ Calling `app.seal()` once at startup walks the baker's DTOs (and their nested DT
457
457
 
458
458
  ```typescript
459
459
  import {
460
- Baker,
461
- deserialize, deserializeSync, deserializeAsync,
462
- validate, validateSync, validateAsync,
463
- serialize, serializeSync, serializeAsync,
460
+ Baker, // .deserialize / .validate / .serialize (+ *Sync / *Async) live on the instance
464
461
  createRule, Field, arrayOf, isBakerIssueSet, BakerError, RequiredType, ExcludeMode,
465
462
  } from '@zipbul/baker';
466
463
  import type { Transformer, TransformParams, BakerIssue, BakerIssueSet, FieldOptions, EmittableRule, RuntimeOptions, BakerConfig } from '@zipbul/baker';
package/dist/index.d.ts CHANGED
@@ -1,13 +1,12 @@
1
- export { deserialize, deserializeSync, deserializeAsync } from './src/functions/deserialize';
2
- export { validate, validateSync, validateAsync } from './src/functions/validate';
3
- export { serialize, serializeSync, serializeAsync } from './src/functions/serialize';
4
- export { createRule } from './src/create-rule';
5
- export { Field, arrayOf } from './src/decorators/index';
6
- export type { FieldOptions, ArrayOfMarker } from './src/decorators/index';
1
+ export { createRule } from './src/rules';
2
+ export { Field, arrayOf } from './src/decorators';
3
+ export type { FieldOptions, ArrayOfMarker } from './src/decorators';
7
4
  export { Baker } from './src/baker';
8
- export { ExcludeMode, RequiredType } from './src/enums';
9
- export type { BakerIssue, BakerIssueSet } from './src/errors';
10
- export { isBakerIssueSet, BakerError } from './src/errors';
11
- export type { EmittableRule, Transformer, TransformParams } from './src/types';
12
- export type { BakerConfig } from './src/configure';
13
- export type { RuntimeOptions } from './src/interfaces';
5
+ export { ExcludeMode } from './src/decorators';
6
+ export { RequiredType } from './src/rules';
7
+ export type { BakerIssue, BakerIssueSet } from './src/common';
8
+ export { isBakerIssueSet, BakerError } from './src/common';
9
+ export type { EmittableRule } from './src/rules';
10
+ export type { Transformer, TransformParams } from './src/transformers';
11
+ export type { BakerConfig } from './src/config';
12
+ export type { RuntimeOptions } from './src/common';
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export{deserialize,deserializeSync,deserializeAsync}from"./src/functions/deserialize.js";export{validate,validateSync,validateAsync}from"./src/functions/validate.js";export{serialize,serializeSync,serializeAsync}from"./src/functions/serialize.js";export{createRule}from"./src/create-rule.js";export{Field,arrayOf}from"./src/decorators/index.js";export{Baker}from"./src/baker.js";export{ExcludeMode,RequiredType}from"./src/enums.js";export{isBakerIssueSet,BakerError}from"./src/errors.js";
1
+ export{createRule}from"./src/rules/index.js";export{Field,arrayOf}from"./src/decorators/index.js";export{Baker}from"./src/baker.js";export{ExcludeMode}from"./src/decorators/index.js";export{RequiredType}from"./src/rules/index.js";export{isBakerIssueSet,BakerError}from"./src/common/index.js";
@@ -1,20 +1,21 @@
1
- import type { BakerConfig } from './configure';
1
+ import type { BakerIssueSet, ClassCtor, RuntimeOptions } from './common';
2
+ import type { BakerConfig } from './config';
2
3
  /**
3
- * A baker — an isolated registration + seal boundary. Each `new Baker()` owns its own registry and
4
- * config, so multiple bakers in one process (or a bundler-duplicated copy of the library) never
5
- * fragment each other. `@Field`, the rule factories, and `deserialize`/`serialize`/`validate` stay
6
- * global they read the metadata/executor stored on the class itself — so only the class-collecting
7
- * `Recipe` and `seal` belong to the instance.
4
+ * A baker — an isolated registration + seal + runtime boundary. Each `new Baker()` owns its own
5
+ * registry, config, and compiled executors, so multiple bakers in one process (or a bundler-duplicated
6
+ * copy of the library) never fragment each other. `@Field` and the rule factories stay global (they
7
+ * write class-intrinsic schema); registration (`Recipe`), sealing (`seal`), and running
8
+ * (`deserialize`/`serialize`/`validate`) all belong to the instance.
8
9
  *
9
10
  * ```ts
10
11
  * const app = new Baker({ autoConvert: true });
11
12
  * @app.Recipe class UserDto { @Field(isString) name!: string }
12
13
  * app.seal();
13
- * deserialize(UserDto, input);
14
+ * app.deserialize(UserDto, input);
14
15
  * ```
15
16
  *
16
- * Isolation boundary is class identity: distinct classes are fully isolated (each sealed with its
17
- * baker's config); a class shared across bakers is reused as one sealed form.
17
+ * Isolation boundary is class identity, scoped per baker: the SAME class sealed by two bakers with
18
+ * different configs behaves per each baker's config (each compiles its own executor into its own map).
18
19
  *
19
20
  * `Recipe` and `seal` are arrow-field properties, not prototype methods, by design: `@app.Recipe`
20
21
  * is applied as a detached value (the runtime calls the decorator with no `this` receiver), so it
@@ -28,4 +29,13 @@ export declare class Baker {
28
29
  readonly Recipe: (value: Function, _context: ClassDecoratorContext) => void;
29
30
  /** Seal every root registered to this baker (and its nested DTOs) with this baker's config. */
30
31
  readonly seal: () => void;
32
+ deserialize: <T>(Class: ClassCtor<T>, input: unknown, options?: RuntimeOptions) => T | BakerIssueSet | Promise<T | BakerIssueSet>;
33
+ deserializeSync: <T>(Class: ClassCtor<T>, input: unknown, options?: RuntimeOptions) => T | BakerIssueSet;
34
+ deserializeAsync: <T>(Class: ClassCtor<T>, input: unknown, options?: RuntimeOptions) => Promise<T | BakerIssueSet>;
35
+ validate: <T>(Class: ClassCtor<T>, input: unknown, options?: RuntimeOptions) => true | BakerIssueSet | Promise<true | BakerIssueSet>;
36
+ validateSync: <T>(Class: ClassCtor<T>, input: unknown, options?: RuntimeOptions) => true | BakerIssueSet;
37
+ validateAsync: <T>(Class: ClassCtor<T>, input: unknown, options?: RuntimeOptions) => Promise<true | BakerIssueSet>;
38
+ serialize: <T>(instance: T, options?: RuntimeOptions) => Record<string, unknown> | Promise<Record<string, unknown>>;
39
+ serializeSync: <T>(instance: T, options?: RuntimeOptions) => Record<string, unknown>;
40
+ serializeAsync: <T>(instance: T, options?: RuntimeOptions) => Promise<Record<string, unknown>>;
31
41
  }
package/dist/src/baker.js CHANGED
@@ -1 +1 @@
1
- export class Baker{#j=new Set;#q;#x=!1;constructor(j){this.#q=j===void 0?Object.freeze({}):q(j)}Recipe=(j,A)=>{this.#j.add(j)};seal=()=>{if(this.#x)return;x(this.#j,this.#q);this.#x=!0}}import{normalizeConfig as q}from"./configure.js";import{sealRegistry as x}from"./seal/seal.js";
1
+ export class Baker{#G=new Set;#F=new Map;#H;#I=!1;constructor(j){this.#H=L(j===void 0?{}:j)}Recipe=(j,F)=>{this.#G.add(j)};seal=()=>{if(this.#I)return;_(this.#G,this.#H,this.#F);this.#I=!0};#j(j){let F=j;while(F){const H=this.#F.get(F);if(H){if(F!==j)this.#F.set(j,H);return H}const J=Object.getPrototypeOf(F);F=typeof J==="function"?J:null}const G=j.name||"<anonymous class>";throw new K(`${G} is not sealed by this baker`)}deserialize=(j,F,G)=>M(this.#j(j),F,G);deserializeSync=(j,F,G)=>N(this.#j(j),j.name,F,G);deserializeAsync=(j,F,G)=>P(this.#j(j),F,G);validate=(j,F,G)=>X(this.#j(j),F,G);validateSync=(j,F,G)=>Y(this.#j(j),j.name,F,G);validateAsync=(j,F,G)=>Z(this.#j(j),F,G);serialize=(j,F)=>Q(this.#j(I(j,"serialize")),j,F);serializeSync=(j,F)=>{const G=I(j,"serializeSync");return U(this.#j(G),G.name,j,F)};serializeAsync=(j,F)=>W(this.#j(I(j,"serializeAsync")),j,F)}import{BakerError as K}from"./common/index.js";import{normalizeConfig as L}from"./config/index.js";import{runDeserialize as M,runDeserializeSync as N,runDeserializeAsync as P,resolveSerializeClass as I,runSerialize as Q,runSerializeSync as U,runSerializeAsync as W,runValidate as X,runValidateSync as Y,runValidateAsync as Z}from"./runtime/index.js";import{sealRegistry as _}from"./seal/index.js";
@@ -0,0 +1,10 @@
1
+ /** Direction of a (de)serialization pass. */
2
+ export declare enum Direction {
3
+ Deserialize = "deserialize",
4
+ Serialize = "serialize"
5
+ }
6
+ /** Cached accessor a RulePlan reuses across checks. */
7
+ export declare enum CacheKey {
8
+ Length = "length",
9
+ Time = "time"
10
+ }
@@ -0,0 +1 @@
1
+ export var Direction;((b)=>{b.Deserialize="deserialize";b.Serialize="serialize"})(Direction||={});export var CacheKey;((b)=>{b.Length="length";b.Time="time"})(CacheKey||={});
@@ -0,0 +1,6 @@
1
+ export { BakerError, isBakerIssueSet, toBakerIssueSet } from './errors';
2
+ export type { BakerIssue, BakerIssueSet } from './errors';
3
+ export { Direction, CacheKey } from './enums';
4
+ export type { ClassCtor } from './types';
5
+ export type { RuntimeOptions } from './interfaces';
6
+ export { isAsyncFunction, isPromiseLike } from './utils';
@@ -0,0 +1 @@
1
+ export{BakerError,isBakerIssueSet,toBakerIssueSet}from"./errors.js";export{Direction,CacheKey}from"./enums.js";export{isAsyncFunction,isPromiseLike}from"./utils.js";
@@ -0,0 +1,4 @@
1
+ export interface RuntimeOptions {
2
+ /** Per-request groups — passed at runtime since they may vary per request */
3
+ groups?: string[];
4
+ }
@@ -0,0 +1,2 @@
1
+ /** Generic class constructor — contravariant `never[]` args accept any user constructor */
2
+ export type ClassCtor<T = object> = new (...args: never[]) => T;
@@ -0,0 +1,7 @@
1
+ import type { SealOptions } from '../seal';
2
+ import type { BakerConfig } from './interfaces';
3
+ /**
4
+ * Validates a {@link BakerConfig} and maps it to the internal {@link SealOptions}. Used by
5
+ * `new Baker(config)`. Stateless — a plain function (no instance/class needed).
6
+ */
7
+ export declare function normalizeConfig(config: BakerConfig): SealOptions;
@@ -0,0 +1 @@
1
+ import{BakerError as v}from"../common/index.js";import{BAKER_CONFIG_KEYS as x}from"./constants.js";export function normalizeConfig(j){if(j===null||typeof j!=="object"||Array.isArray(j))throw new v(`[baker] config requires a plain object. Received: ${j===null?"null":Array.isArray(j)?"array":typeof j}.`);for(const q of Object.keys(j))if(!x.has(q))throw new v(`[baker] unknown key '${q}'. Valid keys: ${[...x].join(", ")}.`);return Object.freeze({enableImplicitConversion:j.autoConvert??!1,exposeDefaultValues:j.allowClassDefaults??!1,stopAtFirstError:j.stopAtFirstError??!1,whitelist:j.forbidUnknown??!1,debug:j.debug??!1})}
@@ -0,0 +1,6 @@
1
+ import type { BakerConfig } from './interfaces';
2
+ /**
3
+ * The valid {@link BakerConfig} keys. Shared single source: the ConfigNormalizer rejects unknown keys
4
+ * with it, and the per-call options guard (runtime) rejects a seal-time config key passed at call time.
5
+ */
6
+ export declare const BAKER_CONFIG_KEYS: Set<keyof BakerConfig>;
@@ -0,0 +1 @@
1
+ export const BAKER_CONFIG_KEYS=new Set(["autoConvert","allowClassDefaults","stopAtFirstError","forbidUnknown","debug"]);
@@ -0,0 +1,3 @@
1
+ export { normalizeConfig } from './config-normalizer';
2
+ export { BAKER_CONFIG_KEYS } from './constants';
3
+ export type { BakerConfig } from './interfaces';
@@ -0,0 +1 @@
1
+ export{normalizeConfig}from"./config-normalizer.js";export{BAKER_CONFIG_KEYS}from"./constants.js";
@@ -1,5 +1,4 @@
1
- import type { SealOptions } from './interfaces';
2
- interface BakerConfig {
1
+ export interface BakerConfig {
3
2
  /** Automatic type conversion ("123" → 123). @default false */
4
3
  autoConvert?: boolean;
5
4
  /** Use class default values when key is missing from input. @default false */
@@ -11,9 +10,3 @@ interface BakerConfig {
11
10
  /** Include field exclusion reasons as comments in generated code. @default false */
12
11
  debug?: boolean;
13
12
  }
14
- /**
15
- * Validate a BakerConfig and map it to the internal SealOptions. Used by `new Baker(config)`.
16
- */
17
- declare function normalizeConfig(config: BakerConfig): SealOptions;
18
- export { normalizeConfig };
19
- export type { BakerConfig };
File without changes
@@ -0,0 +1,2 @@
1
+ export declare const ARRAY_OF: unique symbol;
2
+ export declare const FIELD_OPTION_KEYS: ReadonlySet<string>;
@@ -0,0 +1 @@
1
+ export const ARRAY_OF=Symbol.for("baker:arrayOf");export const FIELD_OPTION_KEYS=new Set(Object.keys({type:!0,discriminator:!0,keepDiscriminatorProperty:!0,rules:!0,optional:!0,nullable:!0,name:!0,deserializeName:!0,serializeName:!0,exclude:!0,groups:!0,when:!0,transform:!0,message:!0,context:!0,mapValue:!0,setValue:!0}));
@@ -0,0 +1,5 @@
1
+ /** Direction in which a field is excluded. */
2
+ export declare enum ExcludeMode {
3
+ DeserializeOnly = "deserializeOnly",
4
+ SerializeOnly = "serializeOnly"
5
+ }
@@ -0,0 +1 @@
1
+ export var ExcludeMode;((i)=>{i.DeserializeOnly="deserializeOnly";i.SerializeOnly="serializeOnly"})(ExcludeMode||={});
@@ -1,9 +1,6 @@
1
- import type { ClassCtor, EmittableRule, Transformer } from '../types';
2
- import { ExcludeMode } from '../enums';
3
- interface ArrayOfMarker {
4
- readonly [key: symbol]: true;
5
- readonly rules: EmittableRule[];
6
- }
1
+ import type { EmittableRule } from '../rules';
2
+ import type { ArrayOfMarker, FieldOptions } from './interfaces';
3
+ import type { FieldDecorator, RuleArg } from './types';
7
4
  /**
8
5
  * Apply rules to each element of an array.
9
6
  *
@@ -14,54 +11,6 @@ interface ArrayOfMarker {
14
11
  * ```
15
12
  */
16
13
  declare function arrayOf(...rules: EmittableRule[]): ArrayOfMarker;
17
- interface FieldOptions {
18
- /** Nested DTO type. Thunk — supports circular references. [Dto] for arrays. */
19
- type?: () => ClassCtor | ClassCtor[] | MapConstructor | SetConstructor;
20
- /** Polymorphic discriminator configuration — used with type */
21
- discriminator?: {
22
- property: string;
23
- subTypes: {
24
- value: Function;
25
- name: string;
26
- }[];
27
- };
28
- /** Whether to keep the discriminator property in the result object */
29
- keepDiscriminatorProperty?: boolean;
30
- /** Validation rules array */
31
- rules?: (EmittableRule | ArrayOfMarker)[];
32
- /** Allow undefined */
33
- optional?: boolean;
34
- /** Allow null */
35
- nullable?: boolean;
36
- /** JSON key mapping (bidirectional) */
37
- name?: string;
38
- /** Deserialize direction key mapping (cannot be used with name) */
39
- deserializeName?: string;
40
- /** Serialize direction key mapping (cannot be used with name) */
41
- serializeName?: string;
42
- /** Field exclusion — true: bidirectional, 'deserializeOnly': deserialization only, 'serializeOnly': serialization only */
43
- exclude?: boolean | ExcludeMode;
44
- /** Groups — field visibility control + conditional validation rule application */
45
- groups?: string[];
46
- /** Conditional validation — skip all field validation when false */
47
- when?: (obj: Record<string, unknown>) => boolean;
48
- /** Transformer or array of transformers (serialize direction applies in reverse order) */
49
- transform?: Transformer | Transformer[];
50
- /** Error message on validation failure — applied to all rules of the field (rule's own message takes precedence) */
51
- message?: string | ((args: {
52
- property: string;
53
- value: unknown;
54
- constraints: Record<string, unknown>;
55
- }) => string);
56
- /** Error context on validation failure — applied to all rules of the field (rule's own context takes precedence) */
57
- context?: unknown;
58
- /** Nested DTO class thunk for Map values — used with type: () => Map */
59
- mapValue?: () => ClassCtor;
60
- /** Nested DTO class thunk for Set elements — used with type: () => Set */
61
- setValue?: () => ClassCtor;
62
- }
63
- type RuleArg = EmittableRule | ArrayOfMarker;
64
- type FieldDecorator = (value: undefined, context: ClassFieldDecoratorContext) => void;
65
14
  /** `@Field`() — empty field registration */
66
15
  declare function Field(): FieldDecorator;
67
16
  /** `@Field`(isString(), email()) — variadic rules */
@@ -1 +1 @@
1
- import{ensureMeta as L}from"../collect.js";import{Direction as h,ExcludeMode as N}from"../enums.js";import{BakerError as X}from"../errors.js";import{isAsyncFunction as P,isPromiseLike as Y}from"../utils.js";const I=Symbol.for("baker:arrayOf");function arrayOf(...q){return{rules:q,[I]:!0}}function $(q){return typeof q==="object"&&q!==null&&q[I]===!0}const j=new Set(["type","discriminator","keepDiscriminatorProperty","rules","optional","nullable","name","deserializeName","serializeName","exclude","groups","when","transform","message","context","mapValue","setValue"]);function S(q){if(typeof q==="function")return!1;if(typeof q!=="object"||q===null)return!1;if($(q))return!1;const C=Object.keys(q);if(C.length===0)return!0;return C.some((b)=>j.has(b))}function V(q,C,b){const J=b?`${C} ${b}`:C;if(typeof q==="function"){const Q=q;if(typeof Q.emit!=="function"||typeof Q.ruleName!=="string"){const G=Q.name?` Did you forget to call '${Q.name}()'? Factories must be invoked (e.g., '${Q.name}()'). Rule constants are passed directly (e.g., 'isString' without parentheses).`:" Use createRule() or import a rule from @zipbul/baker/rules.";throw new X(`@Field on ${J}: argument is not a baker rule.${G} Valid @Field forms: @Field(), @Field(rule, ...), @Field(options), @Field(rule, ..., options).`)}return}throw new X(`@Field on ${J}: expected a baker rule (function with .emit and .ruleName), got ${q===null?"null":typeof q}. Use createRule() or import a rule from @zipbul/baker/rules. Valid @Field forms: @Field(), @Field(rule, ...), @Field(options), @Field(rule, ..., options).`)}function z(q){if(q.length===0)return{rules:[],options:{}};if(q.length===1&&S(q[0])){const b=q[0];return{rules:b.rules??[],options:b}}const C=q[q.length-1];if(S(C)){const b=C;let J=q.slice(0,-1);if(b.rules)J=[...J,...b.rules];return{rules:J,options:b}}return{rules:q,options:{}}}function D(q,C,b){for(const J of C)if($(J))for(const H of J.rules){const Q={rule:H,each:!0};if(b.groups!==void 0)Q.groups=b.groups;if(b.message!==void 0)Q.message=b.message;if(b.context!==void 0)Q.context=b.context;q.validation.push(Q)}else{const H={rule:J};if(b.groups!==void 0)H.groups=b.groups;if(b.message!==void 0)H.message=b.message;if(b.context!==void 0)H.context=b.context;q.validation.push(H)}}function _(q,C){if(C.name){const b={name:C.name};if(C.groups!==void 0)b.groups=C.groups;q.expose.push(b)}else if(C.deserializeName||C.serializeName){if(C.deserializeName){const b={name:C.deserializeName,deserializeOnly:!0};if(C.groups!==void 0)b.groups=C.groups;q.expose.push(b)}if(C.serializeName){const b={name:C.serializeName,serializeOnly:!0};if(C.groups!==void 0)b.groups=C.groups;q.expose.push(b)}}else if(C.groups)q.expose.push({groups:C.groups});else q.expose.push({})}function w(q,C,b){const J=P(b);return{fn:(Q)=>{const G=b(Q);if(!J&&Y(G))throw new X(`@Field(${q}) ${C} transform returned Promise. Declare the transform with async if it is asynchronous.`);return G},isAsync:J}}function T(q,C,b){if(!b.transform)return;const J=Array.isArray(b.transform)?b.transform:[b.transform];for(const H of J){const Q=w(C,h.Deserialize,H.deserialize),G=w(C,h.Serialize,H.serialize);q.transform.push({fn:Q.fn,isAsync:Q.isAsync,options:{deserializeOnly:!0}},{fn:G.fn,isAsync:G.isAsync,options:{serializeOnly:!0}})}}function Field(...q){return(C,b)=>{if(b.static)throw new X("@Field cannot decorate static fields.");if(b.private)throw new X("@Field cannot decorate private fields.");if(typeof b.name==="symbol")throw new X("@Field: symbol property keys are not supported. Use a string property name.");const J=b.name,H=L(b.metadata,J),{rules:Q,options:G}=z(q);if(G.name&&(G.deserializeName||G.serializeName))throw new X(`@Field on ${J}: 'name' cannot be combined with 'deserializeName'/'serializeName'. Use one or the other.`);for(let U=0;U<Q.length;U++){const W=Q[U];if($(W))for(let Z=0;Z<W.rules.length;Z++)V(W.rules[Z],J,`arrayOf[${Z}]`);else V(W,J)}D(H,Q,G);if(G.context!==void 0)H.context=G.context;if(G.message!==void 0)H.message=G.message;if(G.optional)H.flags.isOptional=!0;if(G.nullable)H.flags.isNullable=!0;if(G.when)H.flags.validateIf=G.when;if(G.type){const U={fn:G.type};if(G.discriminator!==void 0)U.discriminator=G.discriminator;if(G.keepDiscriminatorProperty!==void 0)U.keepDiscriminatorProperty=G.keepDiscriminatorProperty;const W=G.mapValue??G.setValue;if(W!==void 0)U.collectionValue=W;H.type=U}_(H,G);if(G.exclude){if(G.exclude===!0)H.exclude={};else if(G.exclude===N.DeserializeOnly)H.exclude={deserializeOnly:!0};else if(G.exclude===N.SerializeOnly)H.exclude={serializeOnly:!0}}T(H,J,G)}}export{arrayOf,Field};
1
+ import{Direction as I,BakerError as X,isAsyncFunction as j,isPromiseLike as z}from"../common/index.js";import{metaStore as _}from"../metadata/index.js";import{ARRAY_OF as h,FIELD_OPTION_KEYS as w}from"./constants.js";import{ExcludeMode as L}from"./enums.js";function arrayOf(...b){return{rules:b,[h]:!0}}function V(b){return typeof b==="object"&&b!==null&&b[h]===!0}function P(b){if(typeof b==="function")return!1;if(typeof b!=="object"||b===null)return!1;if(V(b))return!1;const q=Object.keys(b);if(q.length===0)return!0;return q.some((H)=>w.has(H))}function S(b,q,H){const J=H?`${q} ${H}`:q;if(typeof b==="function"){const U=b;if(typeof U.emit!=="function"||typeof U.ruleName!=="string"){const C=U.name?` Did you forget to call '${U.name}()'? Factories must be invoked (e.g., '${U.name}()'). Rule constants are passed directly (e.g., 'isString' without parentheses).`:" Use createRule() or import a rule from @zipbul/baker/rules.";throw new X(`@Field on ${J}: argument is not a baker rule.${C} Valid @Field forms: @Field(), @Field(rule, ...), @Field(options), @Field(rule, ..., options).`)}return}throw new X(`@Field on ${J}: expected a baker rule (function with .emit and .ruleName), got ${b===null?"null":typeof b}. Use createRule() or import a rule from @zipbul/baker/rules. Valid @Field forms: @Field(), @Field(rule, ...), @Field(options), @Field(rule, ..., options).`)}function M(b){if(b.length===0)return{rules:[],options:{}};if(b.length===1&&P(b[0])){const H=b[0];return{rules:H.rules??[],options:H}}const q=b[b.length-1];if(P(q)){const H=q;let J=b.slice(0,-1);if(H.rules)J=[...J,...H.rules];return{rules:J,options:H}}return{rules:b,options:{}}}function G(b,q){if(q.groups!==void 0)b.groups=q.groups;if(q.message!==void 0)b.message=q.message;if(q.context!==void 0)b.context=q.context;return b}function N(b,q){if(q.groups!==void 0)b.groups=q.groups;return b}function T(b,q,H){for(const J of q)if(V(J))for(const Q of J.rules)b.validation.push(G({rule:Q,each:!0},H));else b.validation.push(G({rule:J},H))}function D(b,q){if(q.name)b.expose.push(N({name:q.name},q));else if(q.deserializeName||q.serializeName){if(q.deserializeName)b.expose.push(N({name:q.deserializeName,deserializeOnly:!0},q));if(q.serializeName)b.expose.push(N({name:q.serializeName,serializeOnly:!0},q))}else if(q.groups)b.expose.push({groups:q.groups});else b.expose.push({})}function Y(b,q,H){const J=j(H);return{fn:(U)=>{const C=H(U);if(!J&&z(C))throw new X(`@Field(${b}) ${q} transform returned Promise. Declare the transform with async if it is asynchronous.`);return C},isAsync:J}}function B(b,q,H){if(!H.transform)return;const J=Array.isArray(H.transform)?H.transform:[H.transform];for(const Q of J){const U=Y(q,I.Deserialize,Q.deserialize),C=Y(q,I.Serialize,Q.serialize);b.transform.push({fn:U.fn,isAsync:U.isAsync,options:{deserializeOnly:!0}},{fn:C.fn,isAsync:C.isAsync,options:{serializeOnly:!0}})}}function Field(...b){return(q,H)=>{if(H.static)throw new X("@Field cannot decorate static fields.");if(H.private)throw new X("@Field cannot decorate private fields.");if(typeof H.name==="symbol")throw new X("@Field: symbol property keys are not supported. Use a string property name.");const J=H.name,Q=_.ensure(H.metadata,J),{rules:U,options:C}=M(b);if(C.name&&(C.deserializeName||C.serializeName))throw new X(`@Field on ${J}: 'name' cannot be combined with 'deserializeName'/'serializeName'. Use one or the other.`);if(C.mapValue!==void 0&&C.setValue!==void 0)throw new X(`@Field on ${J}: 'mapValue' and 'setValue' cannot both be set \u2014 use 'mapValue' for a Map value type and 'setValue' for a Set element type.`);for(let W=0;W<U.length;W++){const Z=U[W];if(V(Z))for(let $=0;$<Z.rules.length;$++)S(Z.rules[$],J,`arrayOf[${$}]`);else S(Z,J)}T(Q,U,C);if(C.context!==void 0)Q.context=C.context;if(C.message!==void 0)Q.message=C.message;if(C.optional)Q.flags.isOptional=!0;if(C.nullable)Q.flags.isNullable=!0;if(C.when)Q.flags.validateIf=C.when;if(C.type){const W={fn:C.type};if(C.discriminator!==void 0)W.discriminator=C.discriminator;if(C.keepDiscriminatorProperty!==void 0)W.keepDiscriminatorProperty=C.keepDiscriminatorProperty;const Z=C.mapValue??C.setValue;if(Z!==void 0)W.collectionValue=Z;Q.type=W}D(Q,C);if(C.exclude){if(C.exclude===!0)Q.exclude={};else if(C.exclude===L.DeserializeOnly)Q.exclude={deserializeOnly:!0};else if(C.exclude===L.SerializeOnly)Q.exclude={serializeOnly:!0}}B(Q,J,C)}}export{arrayOf,Field};
@@ -1,2 +1,2 @@
1
- export { Field, arrayOf } from './field';
2
- export type { FieldOptions, ArrayOfMarker } from './field';
1
+ export * from './public';
2
+ export { ExcludeMode } from './enums';
@@ -1 +1 @@
1
- export{Field,arrayOf}from"./field.js";
1
+ export*from"./public.js";export{ExcludeMode}from"./enums.js";
@@ -0,0 +1,50 @@
1
+ import type { ClassCtor } from '../common';
2
+ import type { DiscriminatorDef } from '../metadata';
3
+ import type { EmittableRule } from '../rules';
4
+ import type { Transformer } from '../transformers';
5
+ import type { ExcludeMode } from './enums';
6
+ import { ARRAY_OF } from './constants';
7
+ export interface ArrayOfMarker {
8
+ readonly [ARRAY_OF]: true;
9
+ readonly rules: EmittableRule[];
10
+ }
11
+ export interface FieldOptions {
12
+ /** Nested DTO type. Thunk — supports circular references. [Dto] for arrays. */
13
+ type?: () => ClassCtor | ClassCtor[] | MapConstructor | SetConstructor;
14
+ /** Polymorphic discriminator configuration — used with type */
15
+ discriminator?: DiscriminatorDef;
16
+ /** Whether to keep the discriminator property in the result object */
17
+ keepDiscriminatorProperty?: boolean;
18
+ /** Validation rules array */
19
+ rules?: (EmittableRule | ArrayOfMarker)[];
20
+ /** Allow undefined */
21
+ optional?: boolean;
22
+ /** Allow null */
23
+ nullable?: boolean;
24
+ /** JSON key mapping (bidirectional) */
25
+ name?: string;
26
+ /** Deserialize direction key mapping (cannot be used with name) */
27
+ deserializeName?: string;
28
+ /** Serialize direction key mapping (cannot be used with name) */
29
+ serializeName?: string;
30
+ /** Field exclusion — true: bidirectional, 'deserializeOnly': deserialization only, 'serializeOnly': serialization only */
31
+ exclude?: boolean | ExcludeMode;
32
+ /** Groups — field visibility control + conditional validation rule application */
33
+ groups?: string[];
34
+ /** Conditional validation — skip all field validation when false */
35
+ when?: (obj: Record<string, unknown>) => boolean;
36
+ /** Transformer or array of transformers (serialize direction applies in reverse order) */
37
+ transform?: Transformer | Transformer[];
38
+ /** Error message on validation failure — applied to all rules of the field (rule's own message takes precedence) */
39
+ message?: string | ((args: {
40
+ property: string;
41
+ value: unknown;
42
+ constraints: Record<string, unknown>;
43
+ }) => string);
44
+ /** Error context on validation failure — applied to all rules of the field (rule's own context takes precedence) */
45
+ context?: unknown;
46
+ /** Nested DTO class thunk for Map values — used with type: () => Map */
47
+ mapValue?: () => ClassCtor;
48
+ /** Nested DTO class thunk for Set elements — used with type: () => Set */
49
+ setValue?: () => ClassCtor;
50
+ }
File without changes
@@ -0,0 +1,2 @@
1
+ export { Field, arrayOf } from './field';
2
+ export type { FieldOptions, ArrayOfMarker } from './interfaces';
@@ -0,0 +1 @@
1
+ export{Field,arrayOf}from"./field.js";
@@ -0,0 +1,6 @@
1
+ import type { EmittableRule } from '../rules';
2
+ import type { ArrayOfMarker } from './interfaces';
3
+ /** A positional @Field argument — either a rule or an arrayOf(...) element-rules marker. */
4
+ export type RuleArg = EmittableRule | ArrayOfMarker;
5
+ /** The class-field decorator @Field returns — TC39 field decorators receive `undefined` as the value. */
6
+ export type FieldDecorator = (value: undefined, context: ClassFieldDecoratorContext) => void;
File without changes
@@ -0,0 +1,5 @@
1
+ /** Collection container type for a nested field. */
2
+ export declare enum CollectionType {
3
+ Map = "Map",
4
+ Set = "Set"
5
+ }
@@ -0,0 +1 @@
1
+ export var CollectionType;((e)=>{e.Map="Map";e.Set="Set"})(CollectionType||={});
@@ -0,0 +1,3 @@
1
+ export type { RawClassMeta, RawPropertyMeta, RuleDef, TransformDef, ExposeDef, TypeDef, MessageArgs, DiscriminatorDef, } from './interfaces';
2
+ export { CollectionType } from './enums';
3
+ export { MetaStore, metaStore } from './meta-store';
@@ -0,0 +1 @@
1
+ export{CollectionType}from"./enums.js";export{MetaStore,metaStore}from"./meta-store.js";