@zipbul/baker 2.1.0 → 3.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.
Files changed (91) hide show
  1. package/CHANGELOG.md +256 -0
  2. package/MIGRATION-3.0.md +104 -0
  3. package/README.md +121 -75
  4. package/dist/index.d.ts +8 -7
  5. package/dist/index.js +10 -229
  6. package/dist/src/collect.d.ts +13 -10
  7. package/dist/src/collect.js +26 -0
  8. package/dist/src/configure.d.ts +8 -11
  9. package/dist/src/configure.js +43 -0
  10. package/dist/src/create-rule.d.ts +1 -1
  11. package/dist/src/create-rule.js +41 -0
  12. package/dist/src/decorators/field.d.ts +22 -18
  13. package/dist/src/decorators/field.js +268 -0
  14. package/dist/src/decorators/index.d.ts +1 -0
  15. package/dist/src/decorators/index.js +2 -2
  16. package/dist/src/decorators/recipe.d.ts +17 -0
  17. package/dist/src/decorators/recipe.js +23 -0
  18. package/dist/src/errors.d.ts +28 -17
  19. package/dist/src/errors.js +52 -0
  20. package/dist/src/functions/check-call-options.d.ts +8 -0
  21. package/dist/src/functions/check-call-options.js +51 -0
  22. package/dist/src/functions/deserialize.d.ts +14 -6
  23. package/dist/src/functions/deserialize.js +57 -0
  24. package/dist/src/functions/serialize.d.ts +10 -3
  25. package/dist/src/functions/serialize.js +52 -0
  26. package/dist/src/functions/validate.d.ts +13 -8
  27. package/dist/src/functions/validate.js +49 -0
  28. package/dist/src/interfaces.d.ts +1 -1
  29. package/dist/src/interfaces.js +4 -0
  30. package/dist/src/meta-access.d.ts +19 -0
  31. package/dist/src/meta-access.js +75 -0
  32. package/dist/src/registry.js +8 -0
  33. package/dist/src/rule-metadata.d.ts +11 -0
  34. package/dist/src/rule-metadata.js +17 -0
  35. package/dist/src/rule-plan.d.ts +29 -0
  36. package/dist/src/rule-plan.js +117 -0
  37. package/dist/src/rules/array.d.ts +7 -6
  38. package/dist/src/rules/array.js +96 -0
  39. package/dist/src/rules/common.d.ts +2 -2
  40. package/dist/src/rules/common.js +77 -0
  41. package/dist/src/rules/date.js +35 -0
  42. package/dist/src/rules/index.d.ts +2 -4
  43. package/dist/src/rules/index.js +8 -11
  44. package/dist/src/rules/locales.d.ts +5 -4
  45. package/dist/src/rules/locales.js +249 -0
  46. package/dist/src/rules/number.d.ts +2 -2
  47. package/dist/src/rules/number.js +79 -0
  48. package/dist/src/rules/object.d.ts +1 -1
  49. package/dist/src/rules/object.js +49 -0
  50. package/dist/src/rules/string.d.ts +83 -80
  51. package/dist/src/rules/string.js +1998 -0
  52. package/dist/src/rules/typechecker.d.ts +6 -6
  53. package/dist/src/rules/typechecker.js +143 -0
  54. package/dist/src/seal/circular-analyzer.js +63 -0
  55. package/dist/src/seal/codegen-utils.d.ts +7 -0
  56. package/dist/src/seal/codegen-utils.js +18 -0
  57. package/dist/src/seal/deserialize-builder.d.ts +8 -3
  58. package/dist/src/seal/deserialize-builder.js +1546 -0
  59. package/dist/src/seal/expose-validator.d.ts +3 -2
  60. package/dist/src/seal/expose-validator.js +65 -0
  61. package/dist/src/seal/seal-state.d.ts +10 -0
  62. package/dist/src/seal/seal-state.js +18 -0
  63. package/dist/src/seal/seal.d.ts +22 -21
  64. package/dist/src/seal/seal.js +431 -0
  65. package/dist/src/seal/serialize-builder.d.ts +3 -2
  66. package/dist/src/seal/serialize-builder.js +374 -0
  67. package/dist/src/seal/validate-meta.d.ts +13 -0
  68. package/dist/src/seal/validate-meta.js +61 -0
  69. package/dist/src/symbols.d.ts +1 -1
  70. package/dist/src/symbols.js +13 -2
  71. package/dist/src/transformers/collection.transformer.js +25 -0
  72. package/dist/src/transformers/date.transformer.js +18 -0
  73. package/dist/src/transformers/index.js +6 -2
  74. package/dist/src/transformers/luxon.transformer.d.ts +4 -2
  75. package/dist/src/transformers/luxon.transformer.js +34 -0
  76. package/dist/src/transformers/moment.transformer.d.ts +4 -2
  77. package/dist/src/transformers/moment.transformer.js +32 -0
  78. package/dist/src/transformers/number.transformer.js +8 -0
  79. package/dist/src/transformers/string.transformer.js +12 -0
  80. package/dist/src/types.d.ts +68 -28
  81. package/dist/src/types.js +1 -0
  82. package/dist/src/utils.d.ts +4 -2
  83. package/dist/src/utils.js +10 -0
  84. package/package.json +80 -67
  85. package/dist/index-fnv35wrf.js +0 -3
  86. package/dist/index-k3d659ad.js +0 -3
  87. package/dist/index-s0n74vx1.js +0 -3
  88. package/dist/index-xdn55cz3.js +0 -1
  89. package/dist/src/functions/_run-sealed.d.ts +0 -7
  90. package/dist/src/functions/index.d.ts +0 -3
  91. package/dist/src/seal/index.d.ts +0 -5
package/CHANGELOG.md ADDED
@@ -0,0 +1,256 @@
1
+ # @zipbul/baker
2
+
3
+ ## 3.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 421fd54: 3.0 — error system redesign and API hardening (breaking).
8
+
9
+ **Error channel.** A single `BakerError` class is now thrown for every developer/config/schema
10
+ misuse (it carries `cause`). The validation-result types are renamed for clarity:
11
+
12
+ - `SealError` → `BakerError` (the thrown class)
13
+ - the field-error interface `BakerError` → `BakerIssue`
14
+ - `BakerErrors` → `BakerIssueSet`
15
+ - `isBakerError` → `isBakerIssueSet`
16
+
17
+ The split is now explicit: **throw `BakerError`** for misuse discoverable without input;
18
+ **return `BakerIssueSet`** for external-input validation failures from `deserialize`/`validate`.
19
+
20
+ **API hardening.** `validate(Class, input)` is DTO-only (the ad-hoc `validate(value, ...rules)`
21
+ mode was removed — call a rule directly instead). `configure()` rejects unknown keys and
22
+ post-`seal()` calls, and seal-time options can no longer be passed per-call.
23
+
24
+ See `MIGRATION-3.0.md` for the full upgrade guide.
25
+
26
+ ### Minor Changes
27
+
28
+ - 421fd54: Add the `isHttpToken` rule — validates the RFC 9110 §5.6.2 HTTP `token` production
29
+ (`1*tchar`), used for HTTP method names and header field-names. Usable as a predicate
30
+ (`isHttpToken(value)`) or as `@Field(isHttpToken)`, and exported from `@zipbul/baker/rules`.
31
+
32
+ ## 3.0.0
33
+
34
+ ### DX reform — breaking changes
35
+
36
+ - **Auto-seal removed.** Call `seal()` once at app startup, after every DTO module is loaded. Without it, the first `deserialize` / `serialize` / `validate` call throws `SealError`.
37
+ - Migration: import `seal` and call `seal()` once before any deserialize/serialize/validate call. For tests, call `seal()` after each `unseal()` / `configure(...)` reconfiguration.
38
+ - **Per-call options are validated.** Only `groups` is a valid per-call option. Passing any other key (`stopAtFirstError`, `autoConvert`, `allowClassDefaults`, `forbidUnknown`, `debug`, …) throws `SealError`. Move those keys into `configure({...})` before `seal()`.
39
+ - **`@Field` argument validation.** Passing a non-rule value (e.g. `@Field(isNumber)` instead of `@Field(isNumber())`) now throws `SealError` immediately with the four valid forms listed in the message.
40
+ - **Map non-string keys.** Serializing a `Map<K, V>` whose key is not a `string` throws `TypeError` — previously the key was silently coerced via `[object Object]` and collided.
41
+
42
+ ### API additions
43
+
44
+ - `seal(...classes?)` — explicit AOT seal trigger.
45
+ - `deserializeSync<T>` / `deserializeAsync<T>` / `serializeSync<T>` / `serializeAsync<T>` / `validateSync` / `validateAsync` — strict variants. `*Sync` throws `SealError` when the DTO is async on the relevant direction; `*Async` always returns `Promise`.
46
+
47
+ ### Defect fixes
48
+
49
+ - **F-1** `circular-analyzer.walk()` now walks `meta.type.collectionValue` — Set/Map nested DTO cycles are caught at seal time, no more `stack overflow` at runtime.
50
+ - **F-2** Discriminator / Set·Map / inheritance invariants now run before codegen via the new `validate-meta` pass — invalid metadata throws `SealError` with a precise message instead of producing invalid generated JS.
51
+ - **F-3** Discriminator default branch now reports `context: { received, validSubTypes: [...] }` so callers can show the user the allowed values.
52
+ - **F-4** Per-call options other than `groups` are rejected with `SealError` instead of being silently dropped.
53
+ - **F-8** FR passport regex now anchors both ends (`/^[A-Z0-9]{9}$/i`).
54
+ - **F-9** `MAGNET_URI_RE` is anchored on the trailing end.
55
+ - **N-3** Circular-detection `WeakSet` is now allocated per call via `Symbol.for('baker:circular-seen')` threaded through `_opts` — concurrent async calls no longer false-circular on shared input objects.
56
+ - **N-4** `extractCode` checks `Object.hasOwn(input, key)` before reading — prototype-chain values no longer leak into DTO results.
57
+ - **N-6** `mergeInheritance` validation dedup now compares by `ruleName`, so a child redeclaring the same rule (e.g. `minLength(5)`) replaces the parent's rule instead of producing duplicate errors.
58
+
59
+ ### Dead code
60
+
61
+ - `src/functions/_run-sealed.ts` removed. The corresponding internal-only tests in `test/e2e/change-coverage.test.ts` were dropped — their coverage is now provided by public-API tests.
62
+
63
+ ## 2.2.0
64
+
65
+ ### Minor Changes
66
+
67
+ - 78d701a: feat: validate-only executor with inline nested code generation
68
+
69
+ - Add `_validate` sealed executor — validates input without Object.create or property assignment
70
+ - validate() now uses dedicated `_validate` executor instead of routing through `_deserialize`
71
+ - Inline nested DTO validation: nested DTO fields are expanded directly into the parent function body, eliminating per-item function call overhead
72
+ - Recursive inline for all nesting patterns: nested objects, arrays of nested, discriminator, collections (Set/Map), transforms, groups
73
+ - Only circular references fall back to function call (physically impossible to inline)
74
+ - 14 refs-based validators converted to inline emit (isISBN, isISIN, isIBAN, isFQDN, etc.)
75
+ - Type gate dead code removal: 11 redundant checks eliminated in gated paths
76
+ - Rule Plan IR `stripSelfComparison` for AST-level optimization inside type gates
77
+ - Shared codegen utilities extracted to `codegen-utils.ts`
78
+ - GEN constants centralized in serialize-builder to prevent typo-related bugs
79
+ - `makeRule`/`makePlannedRule` factory functions for cleaner rule creation
80
+ - Sync/async contract enforcement for declared-sync rules
81
+
82
+ Performance:
83
+
84
+ - validate() nested 3-level: 8.79ns (typebox: 11.56ns) — 1.3x faster than typebox
85
+ - validate() array 1000 items: 2.35µs (typebox: 2.37µs) — equivalent to typebox
86
+ - validate() vs deserialize(): 2-5x faster across all benchmarks
87
+ - Zero memory leaks verified under 10M sustained operations
88
+ - 26.6M ops/sec throughput (validate valid, flat DTO)
89
+
90
+ ## 2.1.0
91
+
92
+ ### Minor Changes
93
+
94
+ - 5696199: feat: Transformer interface, built-in transformers, isULID, isCUID2
95
+
96
+ ### Breaking Changes
97
+
98
+ - `FieldOptions.transform` now accepts `Transformer | Transformer[]` instead of function
99
+ - `FieldTransformParams`, `FieldTransformFn` types removed — use `Transformer` interface
100
+ - `transformDirection` option removed — use passthrough in the unused direction method
101
+ - Serialize direction applies transforms in reverse order (codec stack)
102
+
103
+ ### New Features
104
+
105
+ - `Transformer` interface with separate `deserialize`/`serialize` methods
106
+ - `transform` option accepts arrays — serialize applies in reverse order
107
+ - `type` + `transform` combination support in serialize (nested serialize → transform)
108
+ - 9 built-in core transformers: trim, toLowerCase, toUpperCase, round, unixSeconds, unixMillis, isoString, csv, json
109
+ - 2 optional peer transformers: luxon, moment (async factory, `await import()`)
110
+ - `isULID()` validator
111
+ - `isCUID2()` validator
112
+ - `@zipbul/baker/transformers` subpath export
113
+
114
+ ### Improvements
115
+
116
+ - `when` callback typed as `(obj: Record<string, any>)` instead of `any`
117
+ - Sourcemap removed from build output
118
+ - README rewritten with GEO optimization (FAQ, benchmarks, comparison tables)
119
+ - package.json description and keywords optimized
120
+
121
+ ## 2.0.0
122
+
123
+ ### Major Changes
124
+
125
+ - 5d01955: feat!: v2 API overhaul — isBakerError, validate, performance optimization
126
+
127
+ ### Breaking Changes
128
+
129
+ - `deserialize()` no longer throws on validation failure — returns `T | BakerErrors | Promise<T | BakerErrors>`
130
+ - `serialize()` returns directly for sync DTOs — `Record<string, unknown> | Promise<Record<string, unknown>>`
131
+ - `BakerValidationError` class removed — use `isBakerError()` type guard
132
+ - `toJsonSchema()` removed
133
+ - `@Schema` decorator and `schema` field option removed
134
+ - `JsonSchemaOverride`, `ToJsonSchemaOptions` types removed
135
+ - `BAKER_ERROR` symbol no longer exported (internal only)
136
+ - `README.ko.md` removed
137
+
138
+ ### New Features
139
+
140
+ - `validate(Class, input, options?)` — DTO-level validation without instantiation
141
+ - `validate(input, ...rules)` — ad-hoc single value validation
142
+ - `isBakerError()` — type guard for narrowing validation results
143
+ - Sync DTOs return directly (no Promise wrapper) across all APIs
144
+ - Memory leak detection CI step
145
+
146
+ ### Performance
147
+
148
+ - Valid path: 188ns → 38ns (5x improvement)
149
+ - Invalid path: 6.08µs → 76ns (80x improvement)
150
+
151
+ ### Bug Fixes
152
+
153
+ - WeakSet circular detection false positive on same-object reuse
154
+ - serialize-builder async discriminator array syntax error
155
+ - 13 rules missing constraints in metadata
156
+
157
+ ## 1.1.0
158
+
159
+ ### Minor Changes
160
+
161
+ - b27cdf6: ## New Features
162
+
163
+ - **Sync API optimization** — `deserialize()` and `serialize()` are no longer `async function`. Sync DTOs (no async transforms/rules) skip `Promise` allocation via `Promise.resolve()`. Async DTOs use the executor's native `Promise`. Return type remains `Promise<T>` for backward compatibility.
164
+
165
+ - **Map/Set auto-conversion** — New `type: () => Map` and `type: () => Set` support in `@Field()`:
166
+
167
+ - `Set<T>`: JSON array ↔ `Set`, with optional `setValue: () => DtoClass` for nested DTOs
168
+ - `Map<string, T>`: JSON object ↔ `Map`, with optional `mapValue: () => DtoClass` for nested DTOs
169
+ - JSON Schema: Set → `{ type: 'array', uniqueItems: true }`, Map → `{ type: 'object', additionalProperties }`
170
+
171
+ - **Per-field error messages** — `message` and `context` options on `@Field()` apply to all rules on the field. Supports static strings, dynamic functions with `{ property, value, constraints }`, and arbitrary context values including falsy ones (`0`, `false`, `''`).
172
+
173
+ ## Chores
174
+
175
+ - Translate all Korean comments and documentation to English (82 files)
176
+ - Delete REVIEW.md (all 42 items completed)
177
+ - 1808 tests, 2639 assertions
178
+
179
+ ## 1.0.0
180
+
181
+ ### Major Changes
182
+
183
+ - b7ea675: ## Breaking Changes
184
+
185
+ - **`@Field()` unified decorator** — Replaces 30+ individual decorators with a single `@Field()` that accepts rules as arguments and options as an object.
186
+ - **Auto-seal** — `seal()` removed. DTOs auto-seal on first `deserialize()`/`serialize()` call.
187
+ - **`configure()` replaces `seal()` options** — `configure({ autoConvert, stopAtFirstError, forbidUnknown, ... })`.
188
+ - **`configure()` returns `{ warnings: string[] }`** instead of `void`.
189
+ - **`enableCircularCheck` removed** — Circular detection always runs automatically.
190
+ - **`stripUnknown` renamed to `forbidUnknown`** — `stripUnknown` kept as deprecated alias.
191
+
192
+ ## Bug Fixes
193
+
194
+ - C-1: Fix analyzeAsync discriminator visited Set sharing (infinite recursion risk)
195
+ - C-2: Fix Set/Map stopAtFirstError error path missing element index
196
+ - C-3: Fix discriminator JSON Schema $ref+properties sibling (allOf wrapper)
197
+ - C-5: Throw on isDivisibleBy(0)
198
+ - C-6: Fix isURL accepting ports 65536-99999
199
+ - C-7: Fix isNumber maxDecimalPlaces scientific notation bypass
200
+ - C-8: Implement serialize discriminator with instanceof dispatch
201
+ - C-9: Fix nullable $ref invalid JSON Schema (oneOf wrapper)
202
+ - C-11: Null guard for nested array serialize
203
+ - C-12: Throw on min(NaN)/max(Infinity)
204
+ - C-13~C-17, B-1~B-11: 11 additional safety guards and silent failure fixes
205
+
206
+ ## New Features
207
+
208
+ - Debug mode: `configure({ debug: true })`
209
+ - `onUnmappedRule` callback for `toJsonSchema()`
210
+ - `forbidUnknown` option (renamed from `stripUnknown`)
211
+
212
+ ## Refactoring
213
+
214
+ - Decompose buildRulesCode (250 lines → 5 functions) and Field() (125 lines → 4 helpers)
215
+ - Deduplicate Array/Set/Map each codegen, extract GEN constants, strategy pattern for nullable/optional
216
+ - 1730 tests, 2509 assertions, 99.94% Funcs / 99.83% Lines
217
+
218
+ ## 0.1.2
219
+
220
+ ### Patch Changes
221
+
222
+ - 76657db: fix: pin CI Bun version to 1.3.9 to avoid 1.3.10 bundler regression, optimize isIn/isNotIn with Set, improve npm packaging and test coverage
223
+
224
+ ## 0.1.1
225
+
226
+ ### Patch Changes
227
+
228
+ - 95ce993: Add coverage badge gist configuration
229
+
230
+ ## 0.1.0
231
+
232
+ ### Minor Changes
233
+
234
+ - 214f664: ### Breaking Changes
235
+
236
+ - Remove `src/aot/` module and `@zipbul/baker/aot` subpath export. The zipbul CLI now reads baker decorators directly via AST.
237
+ - `MessageArgs.constraints` type changed from `unknown[]` to `Record<string, unknown>`.
238
+ - Default behavior for fields without `@IsOptional`/`@IsNullable`: `undefined`/`null` input now emits `isDefined` error code instead of falling through to type gate errors (e.g., `isString`).
239
+
240
+ ### Features
241
+
242
+ - **`@Nested(fn, opts?)`** — Single-decorator shorthand for `@ValidateNested()` + `@Type(fn)` with discriminator support.
243
+ - **`@IsNullable()`** — Allow `null` (skip validation), reject `undefined`. Complements `@IsOptional()` for OAS 3.0 `nullable: true` semantics.
244
+ - **`@Schema(schema)`** — Attach JSON Schema Draft 2020-12 metadata at class or property level. Supports object and function forms.
245
+ - **`toJsonSchema(Class, opts?)`** — Generate JSON Schema Draft 2020-12 from DTO decorators. Supports `direction`, `groups`, circular references, discriminator `oneOf`, and `@Schema()` overrides.
246
+ - **`seal({ whitelist: true })`** — Reject undeclared fields with `whitelistViolation` error code.
247
+ - **`@Min(n, { exclusive: true })` / `@Max(n, { exclusive: true })`** — Exclusive minimum/maximum support.
248
+ - **`enableImplicitConversion`** — Automatic type conversion (string/number/boolean/date) based on `requiresType` and `@Type()` hints.
249
+ - **`EmittableRule.constraints`** — All built-in rules now expose their parameters via `constraints` for JSON Schema mapping and `message` callback access.
250
+ - **`requiresType` expansion** — Added `'boolean'` and `'date'` variants. Fixed silent rule loss for non-string/non-number `requiresType` values.
251
+
252
+ ### Internal
253
+
254
+ - Upgrade `@zipbul/result` from `^0.0.3` to `^0.1.4` and adopt `Result<T, E>` / `ResultAsync<T, E>` type aliases.
255
+ - Fix seal placeholder to throw `SealError` instead of bare `Error`.
256
+ - Remove dead branch in deserialize input type guard.
@@ -0,0 +1,104 @@
1
+ # Baker 2.x → 3.x Migration
2
+
3
+ This release replaces the implicit "auto-seal on first call" model with explicit, user-triggered `seal()`. It also tightens per-call options validation and adds strict sync/async variants.
4
+
5
+ ## Required changes
6
+
7
+ ### 1. Call `seal()` once at app startup
8
+
9
+ **Before**
10
+
11
+ ```ts
12
+ // Module load registers DTOs; first deserialize implicitly seals everything.
13
+ const r = await deserialize(UserDto, payload);
14
+ ```
15
+
16
+ **After**
17
+
18
+ ```ts
19
+ import { seal, deserialize } from '@zipbul/baker';
20
+ // Call after every DTO module has been imported (before HTTP server / job runner starts).
21
+ seal();
22
+ const r = await deserialize(UserDto, payload);
23
+ ```
24
+
25
+ `deserialize` / `serialize` / `validate` throw `BakerError` if the DTO is not sealed.
26
+
27
+ ### 2. Move per-call options into `configure(...)`
28
+
29
+ Only `groups` survives as a per-call option.
30
+
31
+ **Before**
32
+
33
+ ```ts
34
+ await deserialize(UserDto, payload, { stopAtFirstError: true });
35
+ ```
36
+
37
+ **After**
38
+
39
+ ```ts
40
+ import { configure, seal } from '@zipbul/baker';
41
+ configure({ stopAtFirstError: true });
42
+ seal();
43
+ await deserialize(UserDto, payload);
44
+ ```
45
+
46
+ All other keys (`stopAtFirstError`, `autoConvert`, `allowClassDefaults`, `forbidUnknown`, `debug`) and their legacy `SealOptions` aliases (`enableImplicitConversion`, `exposeDefaultValues`, `whitelist`) now throw `BakerError` when passed per-call.
47
+
48
+ ### 3. `configure()` must run before `seal()`
49
+
50
+ After `seal()`, `configure(...)` throws `BakerError`. Tests that need to reconfigure must call the test-only `unseal()` helper, change config, then `seal()` again.
51
+
52
+ ### 4. `@Field` argument validation is strict
53
+
54
+ Passing a non-rule value (factory not invoked, primitive, plain function without `.emit` / `.ruleName`) throws `BakerError` at decorator-evaluation time with the four valid forms listed.
55
+
56
+ ```ts
57
+ @Field(isNumber) // ✗ factory not invoked → BakerError
58
+ @Field(isNumber()) // ✓
59
+ @Field(isString) // ✓ constant rule
60
+ @Field() // ✓ marker only
61
+ @Field(isString, { optional: true }) // ✓
62
+ @Field({ type: () => NestedDto }) // ✓
63
+ ```
64
+
65
+ ### 5. `Map<K, V>` requires string keys at serialize
66
+
67
+ Serializing a `Map` with non-string keys throws `TypeError`. Previously the key was silently coerced via `String(key)`, producing collisions like `'[object Object]'`.
68
+
69
+ ### 6. New strict sync/async variants
70
+
71
+ Six new entry points enforce the call-direction asymmetry at the type level:
72
+
73
+ | Integrated | Strict sync | Strict async |
74
+ | ------------- | ----------------- | ------------------ |
75
+ | `deserialize` | `deserializeSync` | `deserializeAsync` |
76
+ | `serialize` | `serializeSync` | `serializeAsync` |
77
+ | `validate` | `validateSync` | `validateAsync` |
78
+
79
+ `*Sync` throws `BakerError` if the DTO is async on that direction (e.g. async transform on deserialize side for `deserializeSync`). `*Async` always returns `Promise` (sync DTOs are wrapped via `Promise.resolve`). The integrated `deserialize` / `serialize` / `validate` remain available for ergonomic use.
80
+
81
+ ## Defect fixes (no migration needed)
82
+
83
+ The following bugs in 2.x are silently fixed in 3.x:
84
+
85
+ - **Set/Map nested DTO cycles** no longer cause stack overflow (`circular-analyzer` now walks `collectionValue`).
86
+ - **Set/Map value DTOs marked async** now correctly propagate `_isAsync` / `_isSerializeAsync` to the parent.
87
+ - **Discriminator with empty `subTypes`** throws `BakerError` at seal time instead of producing invalid generated JS.
88
+ - **Concurrent async deserialize on the same input** no longer reports a false `circular` error (per-call `WeakSet` via `Symbol.for('baker:circular-seen')`).
89
+ - **`Object.hasOwn` checks** prevent prototype-chain values from leaking into DTO results.
90
+ - **Discriminator default branch** error now reports `context: { received, validSubTypes: [...] }`.
91
+ - **FR passport regex** now anchors both ends.
92
+ - **MAGNET URI regex** now anchors the trailing end.
93
+ - **Inheritance dedup** now compares by `ruleName` — a child re-declaring the same rule replaces the parent's rule.
94
+ - **`seal(Class)` failure** is now transactional: a failed seal removes the placeholder so retry can succeed.
95
+ - **`collectionValue` thunk** errors are wrapped in `BakerError` with the field name.
96
+
97
+ ## Removed APIs
98
+
99
+ - `_runSealed` (was internal, but some test code depended on it). Use the public functions instead.
100
+
101
+ ## Notes
102
+
103
+ - The decorator-side registry (`globalRegistry`) is still used internally as an index of decorated classes so `seal()` (no args) can seal everything. This is not auto-seal — `seal()` must be called explicitly.
104
+ - `unseal()` is exported by `test/integration/helpers/unseal.ts` for testing only. It is not part of the public API.