@voidhash/mimic 0.0.1-alpha.6 → 0.0.1-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +43 -15
- package/dist/Document-ChuFrTk1.cjs +571 -0
- package/dist/Document-CwiAFTIq.mjs +438 -0
- package/dist/Document-CwiAFTIq.mjs.map +1 -0
- package/dist/Presence-DKKP4v5X.d.cts +91 -0
- package/dist/Presence-DKKP4v5X.d.cts.map +1 -0
- package/dist/Presence-DdMVKcOv.mjs +110 -0
- package/dist/Presence-DdMVKcOv.mjs.map +1 -0
- package/dist/Presence-N8u7Eppr.d.mts +91 -0
- package/dist/Presence-N8u7Eppr.d.mts.map +1 -0
- package/dist/Presence-gWrmGBeu.cjs +126 -0
- package/dist/Primitive-BK7kfHJZ.d.cts +1165 -0
- package/dist/Primitive-BK7kfHJZ.d.cts.map +1 -0
- package/dist/Primitive-D1kdB6za.d.mts +1165 -0
- package/dist/Primitive-D1kdB6za.d.mts.map +1 -0
- package/dist/client/index.cjs +1456 -0
- package/dist/client/index.d.cts +692 -0
- package/dist/client/index.d.cts.map +1 -0
- package/dist/client/index.d.mts +692 -0
- package/dist/client/index.d.mts.map +1 -0
- package/dist/client/index.mjs +1413 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/index.cjs +309 -757
- package/dist/index.d.cts +5 -1054
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +5 -1054
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +168 -575
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.cjs +191 -0
- package/dist/server/index.d.cts +148 -0
- package/dist/server/index.d.cts.map +1 -0
- package/dist/server/index.d.mts +148 -0
- package/dist/server/index.d.mts.map +1 -0
- package/dist/server/index.mjs +182 -0
- package/dist/server/index.mjs.map +1 -0
- package/package.json +17 -5
- package/src/primitives/Array.ts +57 -22
- package/src/primitives/Boolean.ts +32 -18
- package/src/primitives/Either.ts +39 -24
- package/src/primitives/Lazy.ts +16 -2
- package/src/primitives/Literal.ts +32 -19
- package/src/primitives/Number.ts +38 -25
- package/src/primitives/String.ts +39 -24
- package/src/primitives/Struct.ts +124 -27
- package/src/primitives/Tree.ts +117 -30
- package/src/primitives/Union.ts +56 -29
- package/src/primitives/shared.ts +103 -9
- package/tests/primitives/Array.test.ts +108 -0
- package/tests/primitives/Struct.test.ts +250 -0
- package/tests/primitives/Tree.test.ts +250 -0
- package/tsdown.config.ts +1 -1
- /package/dist/{chunk-C6wwvPpM.mjs → chunk-CLMFDpHK.mjs} +0 -0
package/src/primitives/Struct.ts
CHANGED
|
@@ -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>,
|
|
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>,
|
|
37
|
-
/** Sets the entire struct value */
|
|
38
|
-
set(value:
|
|
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>,
|
|
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>,
|
|
51
|
-
implements Primitive<InferStructState<TFields>, StructProxy<TFields,
|
|
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,
|
|
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:
|
|
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,
|
|
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,
|
|
102
|
-
createProxy: (env: ProxyEnvironment.ProxyEnvironment, operationPath: OperationPath.OperationPath): StructProxy<TFields,
|
|
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>,
|
|
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>,
|
|
201
|
+
return (state ?? defaultValue) as MaybeUndefined<InferStructState<TFields>, TRequired, THasDefault>;
|
|
138
202
|
},
|
|
139
|
-
set: (value:
|
|
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,
|
|
207
|
+
Operation.fromDefinition(operationPath, this._opDefinitions.set, merged)
|
|
142
208
|
);
|
|
143
209
|
},
|
|
144
|
-
|
|
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>,
|
|
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,
|
|
245
|
+
return new globalThis.Proxy(base as StructProxy<TFields, TRequired, THasDefault>, {
|
|
152
246
|
get: (target, prop, _receiver) => {
|
|
153
|
-
// Return base methods (get, set, toSnapshot)
|
|
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
|
}
|
|
@@ -176,7 +273,7 @@ export class StructPrimitive<TFields extends Record<string, AnyPrimitive>, TDefi
|
|
|
176
273
|
return undefined;
|
|
177
274
|
},
|
|
178
275
|
has: (_target, prop) => {
|
|
179
|
-
if (prop === "get" || prop === "set" || prop === "toSnapshot") return true;
|
|
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
|
|
package/src/primitives/Tree.ts
CHANGED
|
@@ -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
|
/**
|
|
@@ -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,6 +142,8 @@ 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
|
/**
|
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
233
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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,
|