@gialicus/smart-object 1.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +38 -1
  2. package/README.md +127 -19
  3. package/dist/errors.d.ts +20 -0
  4. package/dist/errors.d.ts.map +1 -0
  5. package/dist/errors.js +40 -0
  6. package/dist/index.d.ts +4 -2
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +2 -1
  9. package/dist/smart-object/apply-operations.d.ts +5 -0
  10. package/dist/smart-object/apply-operations.d.ts.map +1 -0
  11. package/dist/smart-object/apply-operations.js +21 -0
  12. package/dist/smart-object/build-class.d.ts +3 -0
  13. package/dist/smart-object/build-class.d.ts.map +1 -0
  14. package/dist/smart-object/build-class.js +31 -0
  15. package/dist/smart-object/codecs.d.ts +15 -0
  16. package/dist/smart-object/codecs.d.ts.map +1 -0
  17. package/dist/smart-object/codecs.js +163 -0
  18. package/dist/smart-object/define-prototype.d.ts +4 -0
  19. package/dist/smart-object/define-prototype.d.ts.map +1 -0
  20. package/dist/smart-object/define-prototype.js +76 -0
  21. package/dist/{smart-object.d.ts → smart-object/factory.d.ts} +4 -2
  22. package/dist/smart-object/factory.d.ts.map +1 -0
  23. package/dist/smart-object/factory.js +27 -0
  24. package/dist/smart-object/index.d.ts +2 -0
  25. package/dist/smart-object/index.d.ts.map +1 -0
  26. package/dist/smart-object/index.js +1 -0
  27. package/dist/smart-object/instance-state.d.ts +9 -0
  28. package/dist/smart-object/instance-state.d.ts.map +1 -0
  29. package/dist/smart-object/instance-state.js +23 -0
  30. package/dist/smart-object/json-patch.d.ts +4 -0
  31. package/dist/smart-object/json-patch.d.ts.map +1 -0
  32. package/dist/smart-object/json-patch.js +27 -0
  33. package/dist/smart-object/read-field.d.ts +2 -0
  34. package/dist/smart-object/read-field.d.ts.map +1 -0
  35. package/dist/smart-object/read-field.js +13 -0
  36. package/dist/smart-object/setters/object-field.d.ts +5 -0
  37. package/dist/smart-object/setters/object-field.d.ts.map +1 -0
  38. package/dist/smart-object/setters/object-field.js +56 -0
  39. package/dist/smart-object/setters/record-field.d.ts +7 -0
  40. package/dist/smart-object/setters/record-field.d.ts.map +1 -0
  41. package/dist/smart-object/setters/record-field.js +133 -0
  42. package/dist/smart-object/setters/union-field.d.ts +4 -0
  43. package/dist/smart-object/setters/union-field.d.ts.map +1 -0
  44. package/dist/smart-object/setters/union-field.js +25 -0
  45. package/dist/smart-object/setters/variant-switch.d.ts +5 -0
  46. package/dist/smart-object/setters/variant-switch.d.ts.map +1 -0
  47. package/dist/smart-object/setters/variant-switch.js +37 -0
  48. package/dist/smart-object/union-variant.d.ts +5 -0
  49. package/dist/smart-object/union-variant.d.ts.map +1 -0
  50. package/dist/smart-object/union-variant.js +38 -0
  51. package/dist/types.d.ts +36 -3
  52. package/dist/types.d.ts.map +1 -1
  53. package/dist/zod-introspect.d.ts +50 -0
  54. package/dist/zod-introspect.d.ts.map +1 -0
  55. package/dist/zod-introspect.js +252 -0
  56. package/package.json +1 -1
  57. package/dist/smart-object.d.ts.map +0 -1
  58. package/dist/smart-object.js +0 -157
package/CHANGELOG.md CHANGED
@@ -5,14 +5,51 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.0.1] - 2026-06-27
9
+
10
+ ### Added
11
+
12
+ - Support for `z.date()` and `z.coerce.date()` with ISO 8601 operation values and `Date` getters
13
+ - Support for `z.bigint()`, `z.map` (string keys), and `z.set` via explicit JSON-safe codecs
14
+ - Per-entry API for `z.record` fields: `get{Field}Entry`, `set{Field}Entry`, `delete{Field}Entry`
15
+ - Union variant switching: `switchVariant` and generated `switchTo{Variant}` for discriminated unions
16
+ - Root schema variants: `z.intersection` and `z.lazy`
17
+ - Zod schema introspection module (`src/zod-introspect.ts`) and codec layer (`src/smart-object/codecs.ts`)
18
+
19
+ ## [2.0.0] - 2026-06-27
20
+
21
+ ### Breaking
22
+
23
+ - Getters for object and array fields always return deep clones; in-place mutation no longer affects internal state
24
+ - Removed `SmartObjectOptions` and the `immutableReads` factory option
25
+ - `operations` getter returns a defensive copy instead of the internal array reference
26
+
27
+ ### Added
28
+
29
+ - `toJSON()` instance method for deep-cloned, JSON-safe snapshots (also used by `JSON.stringify`)
30
+ - `SmartObjectError` with structured `code`, optional `field`, and `cause` for programmatic error handling
31
+ - Zod schema introspection isolated in `src/zod-introspect.ts`
32
+ - CI matrix job testing Zod peer compatibility (`4.0.0` and `latest`)
33
+
34
+ ### Changed
35
+
36
+ - Getter and setter methods are defined on the class prototype (one definition per schema, not per instance)
37
+ - Object field setters use a single `deepClone` per write instead of two
38
+ - `fromOperations` re-validates state with Zod after applying patches; invalid replay throws `SmartObjectError` and rolls back
39
+ - Setter and union validation errors throw `SmartObjectError` instead of generic `Error`
40
+ - Unsupported schema at factory time throws `SmartObjectError` with code `UnsupportedSchema`
41
+
8
42
  ## [1.0.0] - 2026-06-27
9
43
 
10
44
  ### Added
11
45
 
12
46
  - `SmartObject(schema)` factory: typed getters and `set*` methods generated from a Zod object schema
47
+ - Support for `z.union([...])` and `z.discriminatedUnion(...)` at schema root
13
48
  - RFC 6902 operation log (`operations`) for every validated change
14
49
  - `clearOperations()` to reset the audit trail without rolling back state
15
50
  - `fromOperations(initial, operations)` static method for deterministic replay
16
- - Exported types: `Operation`, `SetMethods`, `OperationsAccessor`, `SmartObjectConstructor`, `SmartObjectInstance`
51
+ - Exported types: `Operation`, `SetMethods`, `SetMethodsUnion`, `AllKeys`, `UnionDataShape`, `OperationsAccessor`, `SmartObjectConstructor`, `SmartObjectInstance`
17
52
 
53
+ [2.0.1]: https://github.com/gialicus/smart-object/compare/v2.0.0...v2.0.1
54
+ [2.0.0]: https://github.com/gialicus/smart-object/compare/v1.0.0...v2.0.0
18
55
  [1.0.0]: https://github.com/gialicus/smart-object/releases/tag/v1.0.0
package/README.md CHANGED
@@ -23,7 +23,7 @@ const Person = SmartObject(z.object({
23
23
 
24
24
  const person = new Person({ name: "Mario", age: 30 });
25
25
 
26
- // Reads expose validated state without side effects
26
+ // Reads expose validated state
27
27
  console.log(person.name); // "Mario"
28
28
 
29
29
  // Writes validate first, then append patches to person.operations
@@ -40,6 +40,9 @@ person.setName("Luigi"); // Unchanged value — no operation added (keeps sync p
40
40
 
41
41
  person.clearOperations(); // Drops the audit trail after persist/sync; state is unchanged
42
42
 
43
+ // Snapshot for serialization — deep clone, safe for JSON.stringify
44
+ console.log(person.toJSON());
45
+
43
46
  // Initial construction is the replay baseline — it never emits operations
44
47
  console.log(new Person({ name: "Mario", age: 30 }).operations); // []
45
48
 
@@ -48,37 +51,85 @@ const initial = { name: "Mario", age: 30 };
48
51
  const person2 = Person.fromOperations(initial, [...person.operations]);
49
52
  ```
50
53
 
54
+ ### Union root schemas
55
+
56
+ `SmartObject` also accepts `z.discriminatedUnion(...)` and `z.union([...])` when every option is a `z.object(...)`:
57
+
58
+ ```typescript
59
+ const Event = SmartObject(z.discriminatedUnion("type", [
60
+ z.object({ type: z.literal("click"), x: z.number(), y: z.number() }),
61
+ z.object({ type: z.literal("scroll"), delta: z.number() }),
62
+ ]));
63
+
64
+ const event = new Event({ type: "click", x: 10, y: 20 });
65
+ event.setX(15);
66
+
67
+ // Switch the active union variant atomically
68
+ event.switchToScroll({ delta: 5 });
69
+ // or: event.switchVariant({ type: "scroll", delta: 5 });
70
+ ```
71
+
72
+ See [`examples/event.ts`](examples/event.ts) and [`examples/profile.ts`](examples/profile.ts) for full demos.
73
+
74
+ Object and array getters always return deep clones — in-place mutation does not affect internal state or the operation log. Only `set*` methods generate RFC 6902 operations.
75
+
51
76
  ## API
52
77
 
53
78
  ### `SmartObject(schema)`
54
79
 
55
- Factory that accepts a `z.object(...)` and returns an instantiable class.
80
+ Factory that accepts a Zod schema and returns an instantiable class.
56
81
 
57
82
  | Parameter | Type | Description |
58
83
  |-----------|------|-------------|
59
- | `schema` | `z.ZodObject` | Zod schema defining the object shape |
84
+ | `schema` | `z.ZodObject` \| `z.ZodUnion` \| `z.ZodDiscriminatedUnion` \| `z.ZodIntersection` \| `z.ZodLazy` | Zod schema defining the object shape |
60
85
 
61
86
  **Members generated for each schema field `foo`:**
62
87
 
63
88
  | Member | Type | Description |
64
89
  |--------|------|-------------|
65
- | `foo` | getter | Exposes the current validated field value |
90
+ | `foo` | getter | Exposes the current validated field value (deep clone for objects and arrays) |
66
91
  | `setFoo(value)` | `(value: T) => void` | Validates, updates state, and records patches only when the value actually changes |
67
92
 
93
+ `set*` method names follow camelCase with the field name capitalized (`name` → `setName`, `userId` → `setUserId`).
94
+
95
+ **Union root extras** (discriminated and generic unions):
96
+
97
+ | Member | Type | Description |
98
+ |--------|------|-------------|
99
+ | `switchVariant(value)` | `(variant) => void` | Replaces the entire active variant after full schema validation |
100
+ | `switchTo{Variant}(fields)` | `(fields) => void` | Discriminated unions only — switches to a variant without repeating the discriminator (e.g. `switchToScroll({ delta: 5 })`) |
101
+
102
+ **Record field extras** (for each `z.record(...)` field `tags`):
103
+
104
+ | Member | Type | Description |
105
+ |--------|------|-------------|
106
+ | `getTagsEntry(key)` | `(key: string) => V \| undefined` | Reads a single record entry |
107
+ | `setTagsEntry(key, value)` | `(key: string, value: V) => void` | Validates and patches a single entry (`/tags/{key}`) |
108
+ | `deleteTagsEntry(key)` | `(key: string) => void` | Removes a record entry |
109
+
68
110
  **Instance members:**
69
111
 
70
112
  | Member | Type | Description |
71
113
  |--------|------|-------------|
72
- | `operations` | `readonly Operation[]` | Chronological RFC 6902 patch log |
114
+ | `operations` | `readonly Operation[]` | Chronological RFC 6902 patch log (defensive copy) |
73
115
  | `clearOperations()` | `() => void` | Clears the patch log without rolling back state |
74
-
75
- `set*` method names follow camelCase with the field name capitalized (`name` → `setName`, `address` → `setAddress`).
116
+ | `toJSON()` | `() => T` | Deep clone of current state, safe for `JSON.stringify` |
76
117
 
77
118
  **Static members:**
78
119
 
79
120
  | Member | Type | Description |
80
121
  |--------|------|-------------|
81
- | `fromOperations(initial, operations)` | `(initial, Operation[]) => Instance` | Builds an instance from a baseline, replays operations, and copies them into the accumulator |
122
+ | `fromOperations(initial, operations)` | `(initial, Operation[]) => Instance` | Builds an instance from a baseline, replays and validates operations, and copies them into the accumulator |
123
+
124
+ ### `SmartObjectError`
125
+
126
+ Structured error thrown on validation failures, invalid union field access, and failed replay:
127
+
128
+ | Property | Type | Description |
129
+ |----------|------|-------------|
130
+ | `code` | `"InvalidValue"` \| `"InvalidUnionField"` \| `"InvalidReplay"` \| `"UnsupportedSchema"` | Error category |
131
+ | `field` | `string` \| `undefined` | Schema field path when applicable |
132
+ | `cause` | `unknown` | Original error (e.g. `ZodError`) |
82
133
 
83
134
  ### `Operation`
84
135
 
@@ -93,8 +144,24 @@ RFC 6902 operation emitted by [fast-json-patch](https://github.com/Starcounter-J
93
144
 
94
145
  - `Operation` — JSON Patch operation (re-export from `fast-json-patch`)
95
146
  - `SetMethods<T>` — mapped type of inferred `set*` methods for shape `T`
147
+ - `SetMethodsUnion<T>` — `set*` methods for union root schemas
148
+ - `AllKeys<T>` — all keys across union members
149
+ - `UnionDataShape<U>` — flattened data shape for union roots
150
+ - `VariantSwitchMethods<T>` — `switchVariant` for union roots
151
+ - `DiscriminatedVariantSwitchMethods<T, D>` — `switchVariant` plus generated `switchTo*` methods
152
+ - `RecordFieldMethods<T>` — dynamic entry accessors for `z.record` fields
96
153
  - `OperationsAccessor` — `operations` and `clearOperations()`
154
+ - `SnapshotAccessor<T>` — `toJSON()`
97
155
  - `SmartObjectConstructor<T>` — constructor type including `fromOperations`
156
+ - `SmartObjectInstance<T>` — full instance type (getters + set* + operations + toJSON)
157
+
158
+ ## Limitations
159
+
160
+ - **Partial discriminator write** — Changing a discriminated union discriminator alone via `setType(...)` without providing the new variant fields throws `SmartObjectError`. Use `switchVariant(...)` or `switchTo{Variant}(...)` instead.
161
+ - **Union field on wrong variant** — Setting a field that does not exist on the active variant throws `SmartObjectError`.
162
+ - **Date fields** — `z.date()` and `z.coerce.date()` are supported; operations store ISO 8601 strings while getters return `Date` instances.
163
+ - **Map, Set, and bigint** — `z.map` (string keys), `z.set`, and `z.bigint()` are supported with explicit codecs; operations use JSON-safe plain objects, arrays, and decimal strings respectively. Whole-field replace is used for Map/Set updates. Non-string map keys are not supported.
164
+ - **Transforms** — `z.transform` / `z.pipe` with preprocessing work at runtime; operations store the **output** value after validation. TypeScript setter input types may not reflect transforms.
98
165
 
99
166
  ## Design rationale
100
167
 
@@ -103,25 +170,66 @@ RFC 6902 operation emitted by [fast-json-patch](https://github.com/Starcounter-J
103
170
  3. **No-op writes** — Identical values are skipped to keep the patch log minimal and suitable for network sync.
104
171
  4. **Patch-based updates** — Changes are expressed as RFC 6902 operations so deltas are standard, composable, and replayable.
105
172
  5. **Operation accumulation** — Patches from `compare` are appended in order, preserving causality for audit and replay.
106
- 6. **Replay** — `fromOperations(initial, operations)` requires the same baseline used when the operations were produced, enabling deterministic reconstruction on another client or after persistence.
107
-
108
- - `SmartObjectInstance<T>` — full instance type (getters + set* + operations)
173
+ 6. **Replay** — `fromOperations(initial, operations)` replays patches, re-validates with Zod, and requires the same baseline used when the operations were produced.
109
174
 
110
- ## Full example
175
+ ## Examples
111
176
 
112
- See [`examples/person.ts`](examples/person.ts) for a demo with primitives, nested objects, and arrays.
177
+ - [`examples/person.ts`](examples/person.ts) primitives, nested objects, and arrays
178
+ - [`examples/event.ts`](examples/event.ts) — discriminated union root
179
+ - [`examples/profile.ts`](examples/profile.ts) — generic union root
113
180
 
114
181
  ## Project structure
115
182
 
116
183
  ```
117
184
  smart-object/
118
185
  ├── src/
119
- │ ├── index.ts # Public API barrel export
120
- │ ├── smart-object.ts # SmartObject factory
121
- └── types.ts # Operation and inferred types
186
+ │ ├── index.ts # Public API barrel export
187
+ │ ├── types.ts # Operation and inferred types
188
+ ├── errors.ts # SmartObjectError
189
+ │ ├── zod-introspect.ts # Zod schema introspection
190
+ │ └── smart-object/
191
+ │ ├── index.ts # Re-export SmartObject
192
+ │ ├── factory.ts # Public SmartObject() factory
193
+ │ ├── build-class.ts # Class generation orchestration
194
+ │ ├── instance-state.ts # WeakMap-backed instance storage
195
+ │ ├── read-field.ts # Defensive getter reads
196
+ │ ├── json-patch.ts # fast-json-patch wrapper + Date-safe deepClone
197
+ │ ├── codecs.ts # ISO 8601 serialization for date fields
198
+ │ ├── apply-operations.ts # Replay and rollback
199
+ │ ├── union-variant.ts # Union variant matching
200
+ │ ├── define-prototype.ts # Getter/setter prototype setup
201
+ │ └── setters/
202
+ │ ├── object-field.ts
203
+ │ ├── union-field.ts
204
+ │ ├── variant-switch.ts
205
+ │ └── record-field.ts
122
206
  ├── examples/
123
- └── person.ts # Usage demo
207
+ ├── person.ts
208
+ │ ├── event.ts
209
+ │ └── profile.ts
124
210
  ├── tests/
125
- └── smart-object.test.ts
126
- └── dist/ # Build output (generated)
211
+ ├── fixtures/
212
+ │ │ ├── person.ts
213
+ │ │ ├── entity.ts
214
+ │ │ ├── event.ts
215
+ │ │ └── profile.ts
216
+ │ ├── smart-object/
217
+ │ │ ├── construction.test.ts
218
+ │ │ ├── getters.test.ts
219
+ │ │ ├── setters.test.ts
220
+ │ │ ├── clear-operations.test.ts
221
+ │ │ ├── from-operations.test.ts
222
+ │ │ ├── union-fields.test.ts
223
+ │ │ ├── discriminated-union-root.test.ts
224
+ │ │ ├── generic-union-root.test.ts
225
+ │ │ ├── robustness.test.ts
226
+ │ │ ├── setter-naming.test.ts
227
+ │ │ ├── to-json.test.ts
228
+ │ │ ├── schema-variants.test.ts
229
+ │ │ ├── record-fields.test.ts
230
+ │ │ ├── date-codec.test.ts
231
+ │ │ ├── intersection-lazy.test.ts
232
+ │ │ └── types.test.ts
233
+ │ └── zod-introspect.test.ts
234
+ └── dist/ # Build output (generated)
127
235
  ```
@@ -0,0 +1,20 @@
1
+ export type SmartObjectErrorCode = "InvalidValue" | "InvalidUnionField" | "InvalidReplay" | "UnsupportedSchema";
2
+ /**
3
+ * Structured error for SmartObject operations — enables programmatic handling
4
+ * without parsing generic Error messages.
5
+ */
6
+ export declare class SmartObjectError extends Error {
7
+ readonly code: SmartObjectErrorCode;
8
+ readonly field?: string;
9
+ readonly cause?: unknown;
10
+ constructor(code: SmartObjectErrorCode, message: string, options?: {
11
+ field?: string;
12
+ cause?: unknown;
13
+ });
14
+ static invalidValue(field: string, cause: unknown): SmartObjectError;
15
+ static invalidUnionField(field: string, message?: string): SmartObjectError;
16
+ static invalidUnionState(): SmartObjectError;
17
+ static invalidReplay(cause: unknown): SmartObjectError;
18
+ static unsupportedSchema(message: string): SmartObjectError;
19
+ }
20
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,oBAAoB,GAC5B,cAAc,GACd,mBAAmB,GACnB,eAAe,GACf,mBAAmB,CAAC;AAExB;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;gBAGvB,IAAI,EAAE,oBAAoB,EAC1B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;IAS/C,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,gBAAgB;IAapE,MAAM,CAAC,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,gBAAgB;IAQ3E,MAAM,CAAC,iBAAiB,IAAI,gBAAgB;IAI5C,MAAM,CAAC,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB;IAStD,MAAM,CAAC,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB;CAG5D"}
package/dist/errors.js ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Structured error for SmartObject operations — enables programmatic handling
3
+ * without parsing generic Error messages.
4
+ */
5
+ export class SmartObjectError extends Error {
6
+ code;
7
+ field;
8
+ cause;
9
+ constructor(code, message, options) {
10
+ super(message);
11
+ this.name = "SmartObjectError";
12
+ this.code = code;
13
+ this.field = options?.field;
14
+ this.cause = options?.cause;
15
+ }
16
+ static invalidValue(field, cause) {
17
+ if (cause instanceof SmartObjectError) {
18
+ return cause;
19
+ }
20
+ const message = cause instanceof Error && "issues" in cause
21
+ ? `Invalid value for "${field}": ${cause.issues.map((i) => i.message).join(", ")}`
22
+ : `Invalid value for "${field}"`;
23
+ return new SmartObjectError("InvalidValue", message, { field, cause });
24
+ }
25
+ static invalidUnionField(field, message) {
26
+ return new SmartObjectError("InvalidUnionField", message ?? `Cannot set "${field}" on the active union variant`, { field });
27
+ }
28
+ static invalidUnionState() {
29
+ return new SmartObjectError("InvalidUnionField", "Cannot set field on invalid union state");
30
+ }
31
+ static invalidReplay(cause) {
32
+ const message = cause instanceof Error
33
+ ? `Operation replay failed: ${cause.message}`
34
+ : "Operation replay failed";
35
+ return new SmartObjectError("InvalidReplay", message, { cause });
36
+ }
37
+ static unsupportedSchema(message) {
38
+ return new SmartObjectError("UnsupportedSchema", message);
39
+ }
40
+ }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
- export { SmartObject } from "./smart-object.js";
2
- export type { AllKeys, Operation, OperationsAccessor, SetMethods, SetMethodsUnion, SmartObjectConstructor, SmartObjectInstance, SmartObjectSchema, UnionDataShape, } from "./types.js";
1
+ export type { SmartObjectErrorCode } from "./errors.js";
2
+ export { SmartObjectError } from "./errors.js";
3
+ export { SmartObject } from "./smart-object/index.js";
4
+ export type { AllKeys, DiscriminatedVariantSwitchMethods, Operation, OperationsAccessor, RecordFieldMethods, SetMethods, SetMethodsUnion, SmartObjectConstructor, SmartObjectInstance, SmartObjectSchema, SnapshotAccessor, UnionDataShape, VariantSwitchMethods, } from "./types.js";
3
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EACV,OAAO,EACP,SAAS,EACT,kBAAkB,EAClB,UAAU,EACV,eAAe,EACf,sBAAsB,EACtB,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,GACf,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,YAAY,EACV,OAAO,EACP,iCAAiC,EACjC,SAAS,EACT,kBAAkB,EAClB,kBAAkB,EAClB,UAAU,EACV,eAAe,EACf,sBAAsB,EACtB,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,oBAAoB,GACrB,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1 +1,2 @@
1
- export { SmartObject } from "./smart-object.js";
1
+ export { SmartObjectError } from "./errors.js";
2
+ export { SmartObject } from "./smart-object/index.js";
@@ -0,0 +1,5 @@
1
+ import type { Operation } from "fast-json-patch";
2
+ import type { SmartObjectSchema } from "../types.js";
3
+ import type { InstanceState } from "./instance-state.js";
4
+ export declare function applyOperations<T>(state: InstanceState<T>, zodSchema: SmartObjectSchema, instance: object, operations: Operation[]): void;
5
+ //# sourceMappingURL=apply-operations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply-operations.d.ts","sourceRoot":"","sources":["../../src/smart-object/apply-operations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzD,wBAAgB,eAAe,CAAC,CAAC,EAC/B,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EACvB,SAAS,EAAE,iBAAiB,EAC5B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,SAAS,EAAE,GACtB,IAAI,CAkBN"}
@@ -0,0 +1,21 @@
1
+ import { SmartObjectError } from "../errors.js";
2
+ import { deserializeDataFromPatch } from "./codecs.js";
3
+ import { applyPatch, deepClone } from "./json-patch.js";
4
+ export function applyOperations(state, zodSchema, instance, operations) {
5
+ if (operations.length === 0) {
6
+ return;
7
+ }
8
+ const data = state.getData(instance);
9
+ const snapshot = deepClone(data);
10
+ try {
11
+ applyPatch(data, operations, false, true);
12
+ const deserialized = deserializeDataFromPatch(data, zodSchema);
13
+ const validated = zodSchema.parse(deserialized);
14
+ state.setData(instance, validated);
15
+ state.getOperations(instance).push(...operations);
16
+ }
17
+ catch (cause) {
18
+ state.setData(instance, snapshot);
19
+ throw SmartObjectError.invalidReplay(cause);
20
+ }
21
+ }
@@ -0,0 +1,3 @@
1
+ import type { SmartObjectConstructor, SmartObjectSchema } from "../types.js";
2
+ export declare function buildSmartObjectClass<T extends SmartObjectSchema>(zodSchema: T): SmartObjectConstructor<T>;
3
+ //# sourceMappingURL=build-class.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-class.d.ts","sourceRoot":"","sources":["../../src/smart-object/build-class.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAuB,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAOlG,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,iBAAiB,EAC/D,SAAS,EAAE,CAAC,GACX,sBAAsB,CAAC,CAAC,CAAC,CAqC3B"}
@@ -0,0 +1,31 @@
1
+ import { getSchemaShapeKeys } from "../zod-introspect.js";
2
+ import { applyOperations } from "./apply-operations.js";
3
+ import { definePrototype } from "./define-prototype.js";
4
+ import { createInstanceState } from "./instance-state.js";
5
+ import { deepClone } from "./json-patch.js";
6
+ export function buildSmartObjectClass(zodSchema) {
7
+ const keys = getSchemaShapeKeys(zodSchema);
8
+ const state = createInstanceState();
9
+ class SmartObjectClass {
10
+ static fromOperations(initial, operations) {
11
+ const instance = new SmartObjectClass(initial);
12
+ applyOperations(state, zodSchema, instance, operations);
13
+ return instance;
14
+ }
15
+ get operations() {
16
+ return [...state.getOperations(this)];
17
+ }
18
+ clearOperations() {
19
+ state.getOperations(this).length = 0;
20
+ }
21
+ toJSON() {
22
+ return deepClone(state.getData(this));
23
+ }
24
+ constructor(initial) {
25
+ state.setData(this, zodSchema.parse(initial ?? {}));
26
+ state.initOperations(this);
27
+ }
28
+ }
29
+ definePrototype(SmartObjectClass.prototype, state, zodSchema, keys);
30
+ return SmartObjectClass;
31
+ }
@@ -0,0 +1,15 @@
1
+ import type { z } from "zod";
2
+ import type { SmartObjectSchema } from "../types.js";
3
+ export declare function serializeValue(schema: z.ZodType, value: unknown): unknown;
4
+ export declare function deserializeValue(schema: z.ZodType, value: unknown): unknown;
5
+ export declare function serializeDataForPatch<T extends Record<string, unknown>>(data: T, rootSchema: SmartObjectSchema): T;
6
+ export declare function deserializeDataFromPatch<T extends Record<string, unknown>>(data: T, rootSchema: SmartObjectSchema): T;
7
+ export declare function serializePatchValue(rootSchema: SmartObjectSchema, key: string, value: unknown): unknown;
8
+ export declare function serializeEntryPatchValue(fieldSchema: z.ZodType, valueSchema: z.ZodType, value: unknown): unknown;
9
+ /** Serialize a single entry container for RFC 6902 compare (record or map field). */
10
+ export declare function serializeEntryContainer(fieldSchema: z.ZodType, container: unknown): Record<string, unknown>;
11
+ /** @deprecated Use serializeValue */
12
+ export declare function serializeFieldValue(fieldSchema: z.ZodType, value: unknown): unknown;
13
+ /** @deprecated Use deserializeValue */
14
+ export declare function deserializeFieldValue(fieldSchema: z.ZodType, value: unknown): unknown;
15
+ //# sourceMappingURL=codecs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codecs.d.ts","sourceRoot":"","sources":["../../src/smart-object/codecs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAwBrD,wBAAgB,cAAc,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CA6DzE;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAmE3E;AAED,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrE,IAAI,EAAE,CAAC,EACP,UAAU,EAAE,iBAAiB,GAC5B,CAAC,CAeH;AAED,wBAAgB,wBAAwB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxE,IAAI,EAAE,CAAC,EACP,UAAU,EAAE,iBAAiB,GAC5B,CAAC,CAeH;AAED,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,iBAAiB,EAC7B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,OAAO,GACb,OAAO,CAQT;AAED,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,CAAC,CAAC,OAAO,EACtB,WAAW,EAAE,CAAC,CAAC,OAAO,EACtB,KAAK,EAAE,OAAO,GACb,OAAO,CAYT;AAED,qFAAqF;AACrF,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,CAAC,CAAC,OAAO,EACtB,SAAS,EAAE,OAAO,GACjB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAGzB;AAED,qCAAqC;AACrC,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAEnF;AAED,uCAAuC;AACvC,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAErF"}
@@ -0,0 +1,163 @@
1
+ import { getFieldSchema, getMergedObjectShape, isZodRecord, unwrapFieldSchema, } from "../zod-introspect.js";
2
+ function getSchemaDef(schema) {
3
+ return schema._zod.def;
4
+ }
5
+ function isPlainObject(value) {
6
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7
+ }
8
+ export function serializeValue(schema, value) {
9
+ const inner = unwrapFieldSchema(schema);
10
+ const def = getSchemaDef(inner);
11
+ if (value instanceof Date && def.type === "date") {
12
+ return value.toISOString();
13
+ }
14
+ if (typeof value === "bigint" && def.type === "bigint") {
15
+ return value.toString();
16
+ }
17
+ if (value instanceof Map && def.type === "map") {
18
+ const keySchema = def.keyType;
19
+ const valueSchema = def.valueType;
20
+ const result = {};
21
+ for (const [key, entryValue] of value.entries()) {
22
+ const serializedKey = serializeValue(keySchema, key);
23
+ if (typeof serializedKey === "string") {
24
+ result[serializedKey] = serializeValue(valueSchema, entryValue);
25
+ }
26
+ }
27
+ return result;
28
+ }
29
+ if (value instanceof Set && def.type === "set") {
30
+ const valueSchema = def.valueType;
31
+ return [...value].map((item) => serializeValue(valueSchema, item));
32
+ }
33
+ if (def.type === "object" && isPlainObject(value)) {
34
+ const shape = def.shape ?? {};
35
+ const result = {};
36
+ for (const [key, fieldValue] of Object.entries(value)) {
37
+ const fieldSchema = shape[key];
38
+ result[key] = fieldSchema ? serializeValue(fieldSchema, fieldValue) : fieldValue;
39
+ }
40
+ return result;
41
+ }
42
+ if (def.type === "array" && Array.isArray(value)) {
43
+ const elementSchema = def.element;
44
+ return value.map((item) => serializeValue(elementSchema, item));
45
+ }
46
+ if (def.type === "record" && isPlainObject(value)) {
47
+ const valueSchema = def.valueType;
48
+ const result = {};
49
+ for (const [key, fieldValue] of Object.entries(value)) {
50
+ result[key] = serializeValue(valueSchema, fieldValue);
51
+ }
52
+ return result;
53
+ }
54
+ return value;
55
+ }
56
+ export function deserializeValue(schema, value) {
57
+ const inner = unwrapFieldSchema(schema);
58
+ const def = getSchemaDef(inner);
59
+ if (def.type === "date" && typeof value === "string") {
60
+ const parsed = new Date(value);
61
+ if (!Number.isNaN(parsed.getTime())) {
62
+ return parsed;
63
+ }
64
+ return value;
65
+ }
66
+ if (def.type === "bigint" && typeof value === "string") {
67
+ try {
68
+ return BigInt(value);
69
+ }
70
+ catch {
71
+ return value;
72
+ }
73
+ }
74
+ if (def.type === "map" && isPlainObject(value)) {
75
+ const keySchema = def.keyType;
76
+ const valueSchema = def.valueType;
77
+ const map = new Map();
78
+ for (const [key, entryValue] of Object.entries(value)) {
79
+ map.set(deserializeValue(keySchema, key), deserializeValue(valueSchema, entryValue));
80
+ }
81
+ return map;
82
+ }
83
+ if (def.type === "set" && Array.isArray(value)) {
84
+ const valueSchema = def.valueType;
85
+ return new Set(value.map((item) => deserializeValue(valueSchema, item)));
86
+ }
87
+ if (def.type === "object" && isPlainObject(value)) {
88
+ const shape = def.shape ?? {};
89
+ const result = {};
90
+ for (const [key, fieldValue] of Object.entries(value)) {
91
+ const fieldSchema = shape[key];
92
+ result[key] = fieldSchema ? deserializeValue(fieldSchema, fieldValue) : fieldValue;
93
+ }
94
+ return result;
95
+ }
96
+ if (def.type === "array" && Array.isArray(value)) {
97
+ const elementSchema = def.element;
98
+ return value.map((item) => deserializeValue(elementSchema, item));
99
+ }
100
+ if (def.type === "record" && isPlainObject(value)) {
101
+ const valueSchema = def.valueType;
102
+ const result = {};
103
+ for (const [key, fieldValue] of Object.entries(value)) {
104
+ result[key] = deserializeValue(valueSchema, fieldValue);
105
+ }
106
+ return result;
107
+ }
108
+ return value;
109
+ }
110
+ export function serializeDataForPatch(data, rootSchema) {
111
+ const shape = getMergedObjectShape(rootSchema);
112
+ if (Object.keys(shape).length === 0) {
113
+ return data;
114
+ }
115
+ const result = {};
116
+ for (const [key, fieldValue] of Object.entries(data)) {
117
+ const fieldSchema = shape[key];
118
+ result[key] = fieldSchema ? serializeValue(fieldSchema, fieldValue) : fieldValue;
119
+ }
120
+ return result;
121
+ }
122
+ export function deserializeDataFromPatch(data, rootSchema) {
123
+ const shape = getMergedObjectShape(rootSchema);
124
+ if (Object.keys(shape).length === 0) {
125
+ return data;
126
+ }
127
+ const result = {};
128
+ for (const [key, fieldValue] of Object.entries(data)) {
129
+ const fieldSchema = shape[key];
130
+ result[key] = fieldSchema ? deserializeValue(fieldSchema, fieldValue) : fieldValue;
131
+ }
132
+ return result;
133
+ }
134
+ export function serializePatchValue(rootSchema, key, value) {
135
+ const fieldSchema = getFieldSchema(rootSchema, key);
136
+ if (!fieldSchema) {
137
+ return value;
138
+ }
139
+ return serializeValue(fieldSchema, value);
140
+ }
141
+ export function serializeEntryPatchValue(fieldSchema, valueSchema, value) {
142
+ const inner = unwrapFieldSchema(fieldSchema);
143
+ if (isZodRecord(inner)) {
144
+ return serializeValue(valueSchema, value);
145
+ }
146
+ if (isPlainObject(value)) {
147
+ return serializeValue(fieldSchema, value);
148
+ }
149
+ return serializeValue(valueSchema, value);
150
+ }
151
+ /** Serialize a single entry container for RFC 6902 compare (record or map field). */
152
+ export function serializeEntryContainer(fieldSchema, container) {
153
+ const serialized = serializeValue(fieldSchema, container);
154
+ return isPlainObject(serialized) ? serialized : {};
155
+ }
156
+ /** @deprecated Use serializeValue */
157
+ export function serializeFieldValue(fieldSchema, value) {
158
+ return serializeValue(fieldSchema, value);
159
+ }
160
+ /** @deprecated Use deserializeValue */
161
+ export function deserializeFieldValue(fieldSchema, value) {
162
+ return deserializeValue(fieldSchema, value);
163
+ }
@@ -0,0 +1,4 @@
1
+ import type { SmartObjectSchema } from "../types.js";
2
+ import type { InstanceState } from "./instance-state.js";
3
+ export declare function definePrototype<T>(prototype: object, state: InstanceState<T>, zodSchema: SmartObjectSchema, keys: string[]): void;
4
+ //# sourceMappingURL=define-prototype.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-prototype.d.ts","sourceRoot":"","sources":["../../src/smart-object/define-prototype.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAarD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAoEzD,wBAAgB,eAAe,CAAC,CAAC,EAC/B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EACvB,SAAS,EAAE,iBAAiB,EAC5B,IAAI,EAAE,MAAM,EAAE,GACb,IAAI,CAiCN"}