@voidhash/mimic 0.0.1-alpha.1 → 0.0.1-alpha.111

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 (63) hide show
  1. package/.turbo/turbo-build.log +51 -0
  2. package/LICENSE.md +663 -0
  3. package/dist/Document-ChuFrTk1.cjs +571 -0
  4. package/dist/Document-CwiAFTIq.mjs +438 -0
  5. package/dist/Document-CwiAFTIq.mjs.map +1 -0
  6. package/dist/Presence-DKKP4v5X.d.cts +91 -0
  7. package/dist/Presence-DKKP4v5X.d.cts.map +1 -0
  8. package/dist/Presence-DdMVKcOv.mjs +110 -0
  9. package/dist/Presence-DdMVKcOv.mjs.map +1 -0
  10. package/dist/Presence-N8u7Eppr.d.mts +91 -0
  11. package/dist/Presence-N8u7Eppr.d.mts.map +1 -0
  12. package/dist/Presence-gWrmGBeu.cjs +126 -0
  13. package/dist/Primitive-CvFVxR8_.d.cts +1175 -0
  14. package/dist/Primitive-CvFVxR8_.d.cts.map +1 -0
  15. package/dist/Primitive-lEhQyGVL.d.mts +1175 -0
  16. package/dist/Primitive-lEhQyGVL.d.mts.map +1 -0
  17. package/dist/chunk-CLMFDpHK.mjs +18 -0
  18. package/dist/client/index.cjs +1456 -0
  19. package/dist/client/index.d.cts +692 -0
  20. package/dist/client/index.d.cts.map +1 -0
  21. package/dist/client/index.d.mts +692 -0
  22. package/dist/client/index.d.mts.map +1 -0
  23. package/dist/client/index.mjs +1413 -0
  24. package/dist/client/index.mjs.map +1 -0
  25. package/dist/index.cjs +2577 -0
  26. package/dist/index.d.cts +143 -0
  27. package/dist/index.d.cts.map +1 -0
  28. package/dist/index.d.mts +143 -0
  29. package/dist/index.d.mts.map +1 -0
  30. package/dist/index.mjs +2526 -0
  31. package/dist/index.mjs.map +1 -0
  32. package/dist/server/index.cjs +191 -0
  33. package/dist/server/index.d.cts +148 -0
  34. package/dist/server/index.d.cts.map +1 -0
  35. package/dist/server/index.d.mts +148 -0
  36. package/dist/server/index.d.mts.map +1 -0
  37. package/dist/server/index.mjs +182 -0
  38. package/dist/server/index.mjs.map +1 -0
  39. package/package.json +25 -13
  40. package/src/EffectSchema.ts +374 -0
  41. package/src/Primitive.ts +3 -0
  42. package/src/client/ClientDocument.ts +1 -1
  43. package/src/client/errors.ts +10 -10
  44. package/src/index.ts +1 -0
  45. package/src/primitives/Array.ts +57 -22
  46. package/src/primitives/Boolean.ts +33 -19
  47. package/src/primitives/Either.ts +379 -0
  48. package/src/primitives/Lazy.ts +16 -2
  49. package/src/primitives/Literal.ts +33 -20
  50. package/src/primitives/Number.ts +39 -26
  51. package/src/primitives/String.ts +40 -25
  52. package/src/primitives/Struct.ts +126 -29
  53. package/src/primitives/Tree.ts +119 -32
  54. package/src/primitives/TreeNode.ts +77 -30
  55. package/src/primitives/Union.ts +56 -29
  56. package/src/primitives/shared.ts +111 -9
  57. package/src/server/errors.ts +6 -6
  58. package/tests/EffectSchema.test.ts +546 -0
  59. package/tests/primitives/Array.test.ts +108 -0
  60. package/tests/primitives/Either.test.ts +707 -0
  61. package/tests/primitives/Struct.test.ts +250 -0
  62. package/tests/primitives/Tree.test.ts +250 -0
  63. package/tsdown.config.ts +1 -1
@@ -4,9 +4,57 @@ import * as Operation from "../Operation";
4
4
  import * as OperationPath from "../OperationPath";
5
5
  import * as ProxyEnvironment from "../ProxyEnvironment";
6
6
  import * as Transform from "../Transform";
7
- import type { Primitive, PrimitiveInternal, MaybeUndefined, AnyPrimitive, Validator, InferState, InferProxy, InferSnapshot } from "../Primitive";
7
+ import type { Primitive, PrimitiveInternal, MaybeUndefined, AnyPrimitive, Validator, InferState, InferProxy, InferSnapshot, NeedsValue, InferUpdateInput, InferSetInput } from "../Primitive";
8
8
  import { ValidationError } from "../Primitive";
9
- import { runValidators } from "./shared";
9
+ import { runValidators, applyDefaults } from "./shared";
10
+
11
+ // =============================================================================
12
+ // Struct Set Input Types
13
+ // =============================================================================
14
+
15
+ /**
16
+ * Determines if a field is required for set() operations.
17
+ * A field is required if: TRequired is true AND THasDefault is false
18
+ */
19
+ type IsRequiredForSet<T> = T extends Primitive<any, any, true, false> ? true : false;
20
+
21
+ /**
22
+ * Extract keys of fields that are required for set() (required without default).
23
+ */
24
+ type RequiredSetKeys<TFields extends Record<string, AnyPrimitive>> = {
25
+ [K in keyof TFields]: IsRequiredForSet<TFields[K]> extends true ? K : never;
26
+ }[keyof TFields];
27
+
28
+ /**
29
+ * Extract keys of fields that are optional for set() (has default OR not required).
30
+ */
31
+ type OptionalSetKeys<TFields extends Record<string, AnyPrimitive>> = {
32
+ [K in keyof TFields]: IsRequiredForSet<TFields[K]> extends true ? never : K;
33
+ }[keyof TFields];
34
+
35
+ /**
36
+ * Compute the input type for set() operations on a struct.
37
+ * Required fields (required without default) must be provided.
38
+ * Optional fields (has default or not required) can be omitted.
39
+ * Uses each field's TSetInput type to handle nested structs correctly.
40
+ */
41
+ export type StructSetInput<TFields extends Record<string, AnyPrimitive>> =
42
+ { readonly [K in RequiredSetKeys<TFields>]: InferSetInput<TFields[K]> } &
43
+ { readonly [K in OptionalSetKeys<TFields>]?: InferSetInput<TFields[K]> };
44
+
45
+ /**
46
+ * Input type for set() - respects required/default status of the struct.
47
+ * If the struct is required without a default, the value must be provided.
48
+ * The value itself uses StructSetInput which handles field-level required/default logic.
49
+ */
50
+ type InferStructSetInput<TFields extends Record<string, AnyPrimitive>, TRequired extends boolean, THasDefault extends boolean> =
51
+ NeedsValue<StructSetInput<TFields>, TRequired, THasDefault>;
52
+
53
+ /**
54
+ * Input type for update() - always partial since update only modifies specified fields.
55
+ * For nested structs, allows recursive partial updates.
56
+ */
57
+ type InferStructUpdateInput<TFields extends Record<string, AnyPrimitive>> = StructUpdateValue<TFields>;
10
58
 
11
59
 
12
60
  /**
@@ -25,19 +73,29 @@ export type InferStructSnapshot<TFields extends Record<string, AnyPrimitive>> =
25
73
  readonly [K in keyof TFields]: InferSnapshot<TFields[K]>;
26
74
  };
27
75
 
76
+ /**
77
+ * Maps a schema definition to a partial update type.
78
+ * Uses each field's TUpdateInput type, which handles nested updates recursively.
79
+ */
80
+ export type StructUpdateValue<TFields extends Record<string, AnyPrimitive>> = {
81
+ readonly [K in keyof TFields]?: InferUpdateInput<TFields[K]>;
82
+ };
83
+
28
84
  /**
29
85
  * Maps a schema definition to its proxy type.
30
86
  * Provides nested field access + get()/set()/toSnapshot() methods for the whole struct.
31
87
  */
32
- export type StructProxy<TFields extends Record<string, AnyPrimitive>, TDefined extends boolean = false> = {
88
+ export type StructProxy<TFields extends Record<string, AnyPrimitive>, TRequired extends boolean = false, THasDefault extends boolean = false> = {
33
89
  readonly [K in keyof TFields]: InferProxy<TFields[K]>;
34
90
  } & {
35
91
  /** Gets the entire struct value */
36
- get(): MaybeUndefined<InferStructState<TFields>, TDefined>;
37
- /** Sets the entire struct value */
38
- set(value: InferStructState<TFields>): void;
92
+ get(): MaybeUndefined<InferStructState<TFields>, TRequired, THasDefault>;
93
+ /** Sets the entire struct value (only fields that are required without defaults must be provided) */
94
+ set(value: InferStructSetInput<TFields, TRequired, THasDefault>): void;
95
+ /** Updates only the specified fields (partial update, handles nested structs recursively) */
96
+ update(value: InferStructUpdateInput<TFields>): void;
39
97
  /** Returns a readonly snapshot of the struct for rendering */
40
- toSnapshot(): MaybeUndefined<InferStructSnapshot<TFields>, TDefined>;
98
+ toSnapshot(): MaybeUndefined<InferStructSnapshot<TFields>, TRequired, THasDefault>;
41
99
  };
42
100
 
43
101
  interface StructPrimitiveSchema<TFields extends Record<string, AnyPrimitive>> {
@@ -47,12 +105,16 @@ interface StructPrimitiveSchema<TFields extends Record<string, AnyPrimitive>> {
47
105
  readonly validators: readonly Validator<InferStructState<TFields>>[];
48
106
  }
49
107
 
50
- export class StructPrimitive<TFields extends Record<string, AnyPrimitive>, TDefined extends boolean = false>
51
- implements Primitive<InferStructState<TFields>, StructProxy<TFields, TDefined>>
108
+ export class StructPrimitive<TFields extends Record<string, AnyPrimitive>, TRequired extends boolean = false, THasDefault extends boolean = false>
109
+ implements Primitive<InferStructState<TFields>, StructProxy<TFields, TRequired, THasDefault>, TRequired, THasDefault, InferStructSetInput<TFields, TRequired, THasDefault>, InferStructUpdateInput<TFields>>
52
110
  {
53
111
  readonly _tag = "StructPrimitive" as const;
54
112
  readonly _State!: InferStructState<TFields>;
55
- readonly _Proxy!: StructProxy<TFields, TDefined>;
113
+ readonly _Proxy!: StructProxy<TFields, TRequired, THasDefault>;
114
+ readonly _TRequired!: TRequired;
115
+ readonly _THasDefault!: THasDefault;
116
+ readonly TSetInput!: InferStructSetInput<TFields, TRequired, THasDefault>;
117
+ readonly TUpdateInput!: InferStructUpdateInput<TFields>;
56
118
 
57
119
  private readonly _schema: StructPrimitiveSchema<TFields>;
58
120
 
@@ -70,7 +132,7 @@ export class StructPrimitive<TFields extends Record<string, AnyPrimitive>, TDefi
70
132
  }
71
133
 
72
134
  /** Mark this struct as required */
73
- required(): StructPrimitive<TFields, true> {
135
+ required(): StructPrimitive<TFields, true, THasDefault> {
74
136
  return new StructPrimitive({
75
137
  ...this._schema,
76
138
  required: true,
@@ -78,10 +140,12 @@ export class StructPrimitive<TFields extends Record<string, AnyPrimitive>, TDefi
78
140
  }
79
141
 
80
142
  /** Set a default value for this struct */
81
- default(defaultValue: InferStructState<TFields>): StructPrimitive<TFields, true> {
143
+ default(defaultValue: StructSetInput<TFields>): StructPrimitive<TFields, TRequired, true> {
144
+ // Apply defaults to the provided value
145
+ const merged = applyDefaults(this as AnyPrimitive, defaultValue as Partial<InferStructState<TFields>>) as InferStructState<TFields>;
82
146
  return new StructPrimitive({
83
147
  ...this._schema,
84
- defaultValue,
148
+ defaultValue: merged,
85
149
  });
86
150
  }
87
151
 
@@ -91,15 +155,15 @@ export class StructPrimitive<TFields extends Record<string, AnyPrimitive>, TDefi
91
155
  }
92
156
 
93
157
  /** Add a custom validation rule (useful for cross-field validation) */
94
- refine(fn: (value: InferStructState<TFields>) => boolean, message: string): StructPrimitive<TFields, TDefined> {
158
+ refine(fn: (value: InferStructState<TFields>) => boolean, message: string): StructPrimitive<TFields, TRequired, THasDefault> {
95
159
  return new StructPrimitive({
96
160
  ...this._schema,
97
161
  validators: [...this._schema.validators, { validate: fn, message }],
98
162
  });
99
163
  }
100
164
 
101
- readonly _internal: PrimitiveInternal<InferStructState<TFields>, StructProxy<TFields, TDefined>> = {
102
- createProxy: (env: ProxyEnvironment.ProxyEnvironment, operationPath: OperationPath.OperationPath): StructProxy<TFields, TDefined> => {
165
+ readonly _internal: PrimitiveInternal<InferStructState<TFields>, StructProxy<TFields, TRequired, THasDefault>> = {
166
+ createProxy: (env: ProxyEnvironment.ProxyEnvironment, operationPath: OperationPath.OperationPath): StructProxy<TFields, TRequired, THasDefault> => {
103
167
  const fields = this._schema.fields;
104
168
  const defaultValue = this._schema.defaultValue;
105
169
 
@@ -130,33 +194,66 @@ export class StructPrimitive<TFields extends Record<string, AnyPrimitive>, TDefi
130
194
  return snapshot as InferStructSnapshot<TFields>;
131
195
  };
132
196
 
133
- // Create the base object with get/set/toSnapshot methods
197
+ // Create the base object with get/set/update/toSnapshot methods
134
198
  const base = {
135
- get: (): MaybeUndefined<InferStructState<TFields>, TDefined> => {
199
+ get: (): MaybeUndefined<InferStructState<TFields>, TRequired, THasDefault> => {
136
200
  const state = env.getState(operationPath) as InferStructState<TFields> | undefined;
137
- return (state ?? defaultValue) as MaybeUndefined<InferStructState<TFields>, TDefined>;
201
+ return (state ?? defaultValue) as MaybeUndefined<InferStructState<TFields>, TRequired, THasDefault>;
138
202
  },
139
- set: (value: InferStructState<TFields>) => {
203
+ set: (value: InferStructSetInput<TFields, TRequired, THasDefault>) => {
204
+ // Apply defaults for missing fields
205
+ const merged = applyDefaults(this as AnyPrimitive, value as Partial<InferStructState<TFields>>) as InferStructState<TFields>;
140
206
  env.addOperation(
141
- Operation.fromDefinition(operationPath, this._opDefinitions.set, value)
207
+ Operation.fromDefinition(operationPath, this._opDefinitions.set, merged)
142
208
  );
143
209
  },
144
- toSnapshot: (): MaybeUndefined<InferStructSnapshot<TFields>, TDefined> => {
210
+ update: (value: InferStructUpdateInput<TFields>) => {
211
+ for (const key in value) {
212
+ if (Object.prototype.hasOwnProperty.call(value, key)) {
213
+ const fieldValue = value[key as keyof TFields];
214
+ if (fieldValue === undefined) continue; // Skip undefined values
215
+
216
+ const fieldPrimitive = fields[key as keyof TFields];
217
+ if (!fieldPrimitive) continue; // Skip unknown fields
218
+
219
+ const fieldPath = operationPath.append(key);
220
+ const fieldProxy = fieldPrimitive._internal.createProxy(env, fieldPath);
221
+
222
+ // Check if this is a nested struct and value is a plain object (partial update)
223
+ if (
224
+ fieldPrimitive._tag === "StructPrimitive" &&
225
+ typeof fieldValue === "object" &&
226
+ fieldValue !== null &&
227
+ !Array.isArray(fieldValue)
228
+ ) {
229
+ // Recursively update nested struct
230
+ (fieldProxy as { update: (v: unknown) => void }).update(fieldValue);
231
+ } else {
232
+ // Set the field value directly
233
+ (fieldProxy as { set: (v: unknown) => void }).set(fieldValue);
234
+ }
235
+ }
236
+ }
237
+ },
238
+ toSnapshot: (): MaybeUndefined<InferStructSnapshot<TFields>, TRequired, THasDefault> => {
145
239
  const snapshot = buildSnapshot();
146
- return snapshot as MaybeUndefined<InferStructSnapshot<TFields>, TDefined>;
240
+ return snapshot as MaybeUndefined<InferStructSnapshot<TFields>, TRequired, THasDefault>;
147
241
  },
148
242
  };
149
243
 
150
244
  // Use a JavaScript Proxy to intercept field access
151
- return new globalThis.Proxy(base as StructProxy<TFields, TDefined>, {
152
- get: (target, prop, receiver) => {
153
- // Return base methods (get, set, toSnapshot)
245
+ return new globalThis.Proxy(base as StructProxy<TFields, TRequired, THasDefault>, {
246
+ get: (target, prop, _receiver) => {
247
+ // Return base methods (get, set, update, toSnapshot)
154
248
  if (prop === "get") {
155
249
  return target.get;
156
250
  }
157
251
  if (prop === "set") {
158
252
  return target.set;
159
253
  }
254
+ if (prop === "update") {
255
+ return target.update;
256
+ }
160
257
  if (prop === "toSnapshot") {
161
258
  return target.toSnapshot;
162
259
  }
@@ -175,8 +272,8 @@ export class StructPrimitive<TFields extends Record<string, AnyPrimitive>, TDefi
175
272
 
176
273
  return undefined;
177
274
  },
178
- has: (target, prop) => {
179
- if (prop === "get" || prop === "set" || prop === "toSnapshot") return true;
275
+ has: (_target, prop) => {
276
+ if (prop === "get" || prop === "set" || prop === "update" || prop === "toSnapshot") return true;
180
277
  if (typeof prop === "string" && prop in fields) return true;
181
278
  return false;
182
279
  },
@@ -343,6 +440,6 @@ export class StructPrimitive<TFields extends Record<string, AnyPrimitive>, TDefi
343
440
  /** Creates a new StructPrimitive with the given fields */
344
441
  export const Struct = <TFields extends Record<string, AnyPrimitive>>(
345
442
  fields: TFields
346
- ): StructPrimitive<TFields, false> =>
443
+ ): StructPrimitive<TFields, false, false> =>
347
444
  new StructPrimitive({ required: false, defaultValue: undefined, fields, validators: [] });
348
445
 
@@ -5,11 +5,12 @@ import * as OperationPath from "../OperationPath";
5
5
  import * as ProxyEnvironment from "../ProxyEnvironment";
6
6
  import * as Transform from "../Transform";
7
7
  import * as FractionalIndex from "../FractionalIndex";
8
- import type { Primitive, PrimitiveInternal, Validator, InferProxy } from "./shared";
9
- import { ValidationError } from "./shared";
8
+ import type { Primitive, PrimitiveInternal, Validator, InferProxy, AnyPrimitive, InferSetInput, InferUpdateInput } from "./shared";
9
+ import { ValidationError, applyDefaults } from "./shared";
10
10
  import { runValidators } from "./shared";
11
11
  import type { AnyTreeNodePrimitive, InferTreeNodeType, InferTreeNodeDataState, InferTreeNodeChildren } from "./TreeNode";
12
- import { InferStructState } from "./Struct";
12
+ import { InferStructState, StructSetInput, StructUpdateValue } from "./Struct";
13
+ import { StructPrimitive } from "./Struct";
13
14
 
14
15
 
15
16
  /**
@@ -37,7 +38,7 @@ export interface TypedTreeNodeState<TNode extends AnyTreeNodePrimitive> {
37
38
  /**
38
39
  * The state type for trees - a flat array of nodes
39
40
  */
40
- export type TreeState<TRoot extends AnyTreeNodePrimitive> = readonly TreeNodeState[];
41
+ export type TreeState<_TRoot extends AnyTreeNodePrimitive> = readonly TreeNodeState[];
41
42
 
42
43
  /**
43
44
  * Helper to get children sorted by position
@@ -105,6 +106,30 @@ export type TreeNodeSnapshot<TNode extends AnyTreeNodePrimitive> = {
105
106
  export type InferTreeSnapshot<T extends TreePrimitive<any>> =
106
107
  T extends TreePrimitive<infer TRoot> ? TreeNodeSnapshot<TRoot> : never;
107
108
 
109
+ /**
110
+ * Helper type to infer the update value type from a TreeNode's data.
111
+ * Uses StructUpdateValue directly to get field-level partial update semantics.
112
+ * All fields are optional in update operations.
113
+ */
114
+ export type TreeNodeUpdateValue<TNode extends AnyTreeNodePrimitive> =
115
+ TNode["data"] extends StructPrimitive<infer TFields, any, any>
116
+ ? StructUpdateValue<TFields>
117
+ : InferUpdateInput<TNode["data"]>;
118
+
119
+ /**
120
+ * Helper type to infer the input type for node data (respects field defaults).
121
+ * Uses StructSetInput directly so that:
122
+ * - Fields that are required AND have no default must be provided
123
+ * - Fields that are optional OR have defaults can be omitted
124
+ *
125
+ * This bypasses the struct-level NeedsValue wrapper since tree inserts
126
+ * always require a data object (even if empty for all-optional fields).
127
+ */
128
+ export type TreeNodeDataSetInput<TNode extends AnyTreeNodePrimitive> =
129
+ TNode["data"] extends StructPrimitive<infer TFields, any, any>
130
+ ? StructSetInput<TFields>
131
+ : InferSetInput<TNode["data"]>;
132
+
108
133
  /**
109
134
  * Typed proxy for a specific node type - provides type-safe data access
110
135
  */
@@ -117,12 +142,14 @@ export interface TypedNodeProxy<TNode extends AnyTreeNodePrimitive> {
117
142
  readonly data: InferProxy<TNode["data"]>;
118
143
  /** Get the raw node state */
119
144
  get(): TypedTreeNodeState<TNode>;
145
+ /** Updates only the specified data fields (partial update, handles nested structs recursively) */
146
+ update(value: TreeNodeUpdateValue<TNode>): void;
120
147
  }
121
148
 
122
149
  /**
123
150
  * Node proxy with type narrowing capabilities
124
151
  */
125
- export interface TreeNodeProxyBase<TRoot extends AnyTreeNodePrimitive> {
152
+ export interface TreeNodeProxyBase<_TRoot extends AnyTreeNodePrimitive> {
126
153
  /** The node ID */
127
154
  readonly id: string;
128
155
  /** The node type (string) */
@@ -158,40 +185,40 @@ export interface TreeProxy<TRoot extends AnyTreeNodePrimitive> {
158
185
  /** Gets a node proxy by ID with type narrowing capabilities */
159
186
  node(id: string): TreeNodeProxyBase<TRoot> | undefined;
160
187
 
161
- /** Insert a new node as the first child */
188
+ /** Insert a new node as the first child (applies defaults for node data) */
162
189
  insertFirst<TNode extends AnyTreeNodePrimitive>(
163
190
  parentId: string | null,
164
191
  nodeType: TNode,
165
- data: InferTreeNodeDataState<TNode>
192
+ data: TreeNodeDataSetInput<TNode>
166
193
  ): string;
167
194
 
168
- /** Insert a new node as the last child */
195
+ /** Insert a new node as the last child (applies defaults for node data) */
169
196
  insertLast<TNode extends AnyTreeNodePrimitive>(
170
197
  parentId: string | null,
171
198
  nodeType: TNode,
172
- data: InferTreeNodeDataState<TNode>
199
+ data: TreeNodeDataSetInput<TNode>
173
200
  ): string;
174
201
 
175
- /** Insert a new node at a specific index among siblings */
202
+ /** Insert a new node at a specific index among siblings (applies defaults for node data) */
176
203
  insertAt<TNode extends AnyTreeNodePrimitive>(
177
204
  parentId: string | null,
178
205
  index: number,
179
206
  nodeType: TNode,
180
- data: InferTreeNodeDataState<TNode>
207
+ data: TreeNodeDataSetInput<TNode>
181
208
  ): string;
182
209
 
183
- /** Insert a new node after a sibling */
210
+ /** Insert a new node after a sibling (applies defaults for node data) */
184
211
  insertAfter<TNode extends AnyTreeNodePrimitive>(
185
212
  siblingId: string,
186
213
  nodeType: TNode,
187
- data: InferTreeNodeDataState<TNode>
214
+ data: TreeNodeDataSetInput<TNode>
188
215
  ): string;
189
216
 
190
- /** Insert a new node before a sibling */
217
+ /** Insert a new node before a sibling (applies defaults for node data) */
191
218
  insertBefore<TNode extends AnyTreeNodePrimitive>(
192
219
  siblingId: string,
193
220
  nodeType: TNode,
194
- data: InferTreeNodeDataState<TNode>
221
+ data: TreeNodeDataSetInput<TNode>
195
222
  ): string;
196
223
 
197
224
  /** Remove a node and all its descendants */
@@ -218,6 +245,13 @@ export interface TreeProxy<TRoot extends AnyTreeNodePrimitive> {
218
245
  nodeType: TNode
219
246
  ): InferProxy<TNode["data"]>;
220
247
 
248
+ /** Updates only the specified data fields of a node (partial update) */
249
+ updateAt<TNode extends AnyTreeNodePrimitive>(
250
+ id: string,
251
+ nodeType: TNode,
252
+ value: TreeNodeUpdateValue<TNode>
253
+ ): void;
254
+
221
255
  /** Convert tree to a nested snapshot for UI rendering */
222
256
  toSnapshot(): TreeNodeSnapshot<TRoot> | undefined;
223
257
  }
@@ -229,12 +263,22 @@ interface TreePrimitiveSchema<TRoot extends AnyTreeNodePrimitive> {
229
263
  readonly validators: readonly Validator<TreeState<TRoot>>[];
230
264
  }
231
265
 
232
- export class TreePrimitive<TRoot extends AnyTreeNodePrimitive>
233
- implements Primitive<TreeState<TRoot>, TreeProxy<TRoot>>
266
+ /** Input type for tree set() - tree state */
267
+ export type TreeSetInput<TRoot extends AnyTreeNodePrimitive> = TreeState<TRoot>;
268
+
269
+ /** Input type for tree update() - same as set() for trees */
270
+ export type TreeUpdateInput<TRoot extends AnyTreeNodePrimitive> = TreeState<TRoot>;
271
+
272
+ export class TreePrimitive<TRoot extends AnyTreeNodePrimitive, TRequired extends boolean = false, THasDefault extends boolean = false>
273
+ implements Primitive<TreeState<TRoot>, TreeProxy<TRoot>, TRequired, THasDefault, TreeSetInput<TRoot>, TreeUpdateInput<TRoot>>
234
274
  {
235
275
  readonly _tag = "TreePrimitive" as const;
236
276
  readonly _State!: TreeState<TRoot>;
237
277
  readonly _Proxy!: TreeProxy<TRoot>;
278
+ readonly _TRequired!: TRequired;
279
+ readonly _THasDefault!: THasDefault;
280
+ readonly TSetInput!: TreeSetInput<TRoot>;
281
+ readonly TUpdateInput!: TreeUpdateInput<TRoot>;
238
282
 
239
283
  private readonly _schema: TreePrimitiveSchema<TRoot>;
240
284
  private _nodeTypeRegistry: Map<string, AnyTreeNodePrimitive> | undefined;
@@ -271,7 +315,7 @@ export class TreePrimitive<TRoot extends AnyTreeNodePrimitive>
271
315
  }
272
316
 
273
317
  /** Mark this tree as required */
274
- required(): TreePrimitive<TRoot> {
318
+ required(): TreePrimitive<TRoot, true, THasDefault> {
275
319
  return new TreePrimitive({
276
320
  ...this._schema,
277
321
  required: true,
@@ -279,7 +323,7 @@ export class TreePrimitive<TRoot extends AnyTreeNodePrimitive>
279
323
  }
280
324
 
281
325
  /** Set a default value for this tree */
282
- default(defaultValue: TreeState<TRoot>): TreePrimitive<TRoot> {
326
+ default(defaultValue: TreeState<TRoot>): TreePrimitive<TRoot, TRequired, true> {
283
327
  return new TreePrimitive({
284
328
  ...this._schema,
285
329
  defaultValue,
@@ -292,7 +336,7 @@ export class TreePrimitive<TRoot extends AnyTreeNodePrimitive>
292
336
  }
293
337
 
294
338
  /** Add a custom validation rule */
295
- refine(fn: (value: TreeState<TRoot>) => boolean, message: string): TreePrimitive<TRoot> {
339
+ refine(fn: (value: TreeState<TRoot>) => boolean, message: string): TreePrimitive<TRoot, TRequired, THasDefault> {
296
340
  return new TreePrimitive({
297
341
  ...this._schema,
298
342
  validators: [...this._schema.validators, { validate: fn, message }],
@@ -404,11 +448,16 @@ export class TreePrimitive<TRoot extends AnyTreeNodePrimitive>
404
448
  );
405
449
  }
406
450
  const nodePath = operationPath.append(nodeState.id);
451
+ const dataProxy = nodeType.data._internal.createProxy(env, nodePath) as InferProxy<TNode["data"]>;
407
452
  return {
408
453
  id: nodeState.id,
409
454
  type: nodeType.type as InferTreeNodeType<TNode>,
410
- data: nodeType.data._internal.createProxy(env, nodePath) as InferProxy<TNode["data"]>,
455
+ data: dataProxy,
411
456
  get: () => nodeState as TypedTreeNodeState<TNode>,
457
+ update: (value: TreeNodeUpdateValue<TNode>) => {
458
+ // Delegate to the data proxy's update method
459
+ (dataProxy as { update: (v: unknown) => void }).update(value);
460
+ },
412
461
  };
413
462
  },
414
463
 
@@ -474,7 +523,7 @@ export class TreePrimitive<TRoot extends AnyTreeNodePrimitive>
474
523
  insertFirst: <TNode extends AnyTreeNodePrimitive>(
475
524
  parentId: string | null,
476
525
  nodeType: TNode,
477
- data: InferTreeNodeDataState<TNode>
526
+ data: TreeNodeDataSetInput<TNode>
478
527
  ): string => {
479
528
  const state = getCurrentState();
480
529
  const siblings = getOrderedChildren(state, parentId);
@@ -496,13 +545,16 @@ export class TreePrimitive<TRoot extends AnyTreeNodePrimitive>
496
545
  throw new ValidationError("Tree already has a root node");
497
546
  }
498
547
 
548
+ // Apply defaults to node data
549
+ const mergedData = applyDefaults(nodeType.data as AnyPrimitive, data as Partial<InferTreeNodeDataState<TNode>>) as InferTreeNodeDataState<TNode>;
550
+
499
551
  env.addOperation(
500
552
  Operation.fromDefinition(operationPath, this._opDefinitions.insert, {
501
553
  id,
502
554
  type: nodeType.type,
503
555
  parentId,
504
556
  pos,
505
- data,
557
+ data: mergedData,
506
558
  })
507
559
  );
508
560
 
@@ -512,7 +564,7 @@ export class TreePrimitive<TRoot extends AnyTreeNodePrimitive>
512
564
  insertLast: <TNode extends AnyTreeNodePrimitive>(
513
565
  parentId: string | null,
514
566
  nodeType: TNode,
515
- data: InferTreeNodeDataState<TNode>
567
+ data: TreeNodeDataSetInput<TNode>
516
568
  ): string => {
517
569
  const state = getCurrentState();
518
570
  const siblings = getOrderedChildren(state, parentId);
@@ -534,13 +586,16 @@ export class TreePrimitive<TRoot extends AnyTreeNodePrimitive>
534
586
  throw new ValidationError("Tree already has a root node");
535
587
  }
536
588
 
589
+ // Apply defaults to node data
590
+ const mergedData = applyDefaults(nodeType.data as AnyPrimitive, data as Partial<InferTreeNodeDataState<TNode>>) as InferTreeNodeDataState<TNode>;
591
+
537
592
  env.addOperation(
538
593
  Operation.fromDefinition(operationPath, this._opDefinitions.insert, {
539
594
  id,
540
595
  type: nodeType.type,
541
596
  parentId,
542
597
  pos,
543
- data,
598
+ data: mergedData,
544
599
  })
545
600
  );
546
601
 
@@ -551,7 +606,7 @@ export class TreePrimitive<TRoot extends AnyTreeNodePrimitive>
551
606
  parentId: string | null,
552
607
  index: number,
553
608
  nodeType: TNode,
554
- data: InferTreeNodeDataState<TNode>
609
+ data: TreeNodeDataSetInput<TNode>
555
610
  ): string => {
556
611
  const state = getCurrentState();
557
612
  const siblings = getOrderedChildren(state, parentId);
@@ -575,13 +630,16 @@ export class TreePrimitive<TRoot extends AnyTreeNodePrimitive>
575
630
  throw new ValidationError("Tree already has a root node");
576
631
  }
577
632
 
633
+ // Apply defaults to node data
634
+ const mergedData = applyDefaults(nodeType.data as AnyPrimitive, data as Partial<InferTreeNodeDataState<TNode>>) as InferTreeNodeDataState<TNode>;
635
+
578
636
  env.addOperation(
579
637
  Operation.fromDefinition(operationPath, this._opDefinitions.insert, {
580
638
  id,
581
639
  type: nodeType.type,
582
640
  parentId,
583
641
  pos,
584
- data,
642
+ data: mergedData,
585
643
  })
586
644
  );
587
645
 
@@ -591,7 +649,7 @@ export class TreePrimitive<TRoot extends AnyTreeNodePrimitive>
591
649
  insertAfter: <TNode extends AnyTreeNodePrimitive>(
592
650
  siblingId: string,
593
651
  nodeType: TNode,
594
- data: InferTreeNodeDataState<TNode>
652
+ data: TreeNodeDataSetInput<TNode>
595
653
  ): string => {
596
654
  const state = getCurrentState();
597
655
  const sibling = state.find(n => n.id === siblingId);
@@ -610,13 +668,16 @@ export class TreePrimitive<TRoot extends AnyTreeNodePrimitive>
610
668
  const parentType = getParentType(parentId);
611
669
  this._validateChildType(parentType, nodeType.type);
612
670
 
671
+ // Apply defaults to node data
672
+ const mergedData = applyDefaults(nodeType.data as AnyPrimitive, data as Partial<InferTreeNodeDataState<TNode>>) as InferTreeNodeDataState<TNode>;
673
+
613
674
  env.addOperation(
614
675
  Operation.fromDefinition(operationPath, this._opDefinitions.insert, {
615
676
  id,
616
677
  type: nodeType.type,
617
678
  parentId,
618
679
  pos,
619
- data,
680
+ data: mergedData,
620
681
  })
621
682
  );
622
683
 
@@ -626,7 +687,7 @@ export class TreePrimitive<TRoot extends AnyTreeNodePrimitive>
626
687
  insertBefore: <TNode extends AnyTreeNodePrimitive>(
627
688
  siblingId: string,
628
689
  nodeType: TNode,
629
- data: InferTreeNodeDataState<TNode>
690
+ data: TreeNodeDataSetInput<TNode>
630
691
  ): string => {
631
692
  const state = getCurrentState();
632
693
  const sibling = state.find(n => n.id === siblingId);
@@ -645,13 +706,16 @@ export class TreePrimitive<TRoot extends AnyTreeNodePrimitive>
645
706
  const parentType = getParentType(parentId);
646
707
  this._validateChildType(parentType, nodeType.type);
647
708
 
709
+ // Apply defaults to node data
710
+ const mergedData = applyDefaults(nodeType.data as AnyPrimitive, data as Partial<InferTreeNodeDataState<TNode>>) as InferTreeNodeDataState<TNode>;
711
+
648
712
  env.addOperation(
649
713
  Operation.fromDefinition(operationPath, this._opDefinitions.insert, {
650
714
  id,
651
715
  type: nodeType.type,
652
716
  parentId,
653
717
  pos,
654
- data,
718
+ data: mergedData,
655
719
  })
656
720
  );
657
721
 
@@ -890,6 +954,29 @@ export class TreePrimitive<TRoot extends AnyTreeNodePrimitive>
890
954
  return nodeType.data._internal.createProxy(env, nodePath) as InferProxy<TNode["data"]>;
891
955
  },
892
956
 
957
+ updateAt: <TNode extends AnyTreeNodePrimitive>(
958
+ id: string,
959
+ nodeType: TNode,
960
+ value: TreeNodeUpdateValue<TNode>
961
+ ): void => {
962
+ // Get the node to verify its type
963
+ const state = getCurrentState();
964
+ const node = state.find(n => n.id === id);
965
+ if (!node) {
966
+ throw new ValidationError(`Node not found: ${id}`);
967
+ }
968
+ if (node.type !== nodeType.type) {
969
+ throw new ValidationError(
970
+ `Node is of type "${node.type}", not "${nodeType.type}"`
971
+ );
972
+ }
973
+
974
+ const nodePath = operationPath.append(id);
975
+ const dataProxy = nodeType.data._internal.createProxy(env, nodePath);
976
+ // Delegate to the data proxy's update method
977
+ (dataProxy as { update: (v: unknown) => void }).update(value);
978
+ },
979
+
893
980
  toSnapshot: (): TreeNodeSnapshot<TRoot> | undefined => {
894
981
  const state = getCurrentState();
895
982
  const rootNode = state.find(n => n.parentId === null);
@@ -1111,7 +1198,7 @@ export interface TreeOptions<TRoot extends AnyTreeNodePrimitive> {
1111
1198
  /** Creates a new TreePrimitive with the given root node type */
1112
1199
  export const Tree = <TRoot extends AnyTreeNodePrimitive>(
1113
1200
  options: TreeOptions<TRoot>
1114
- ): TreePrimitive<TRoot> =>
1201
+ ): TreePrimitive<TRoot, false, false> =>
1115
1202
  new TreePrimitive({
1116
1203
  required: false,
1117
1204
  defaultValue: undefined,