@voidhash/mimic 0.0.1-alpha.7 → 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 +224 -765
- package/dist/index.d.cts +5 -1152
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +5 -1152
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +69 -569
- 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 +16 -4
- package/src/primitives/Array.ts +25 -14
- package/src/primitives/Boolean.ts +29 -17
- package/src/primitives/Either.ts +30 -17
- package/src/primitives/Lazy.ts +16 -2
- package/src/primitives/Literal.ts +29 -18
- package/src/primitives/Number.ts +35 -24
- package/src/primitives/String.ts +36 -23
- package/src/primitives/Struct.ts +74 -26
- package/src/primitives/Tree.ts +30 -14
- package/src/primitives/Union.ts +21 -21
- package/src/primitives/shared.ts +27 -34
- package/tests/primitives/Array.test.ts +108 -0
- package/tests/primitives/Struct.test.ts +2 -2
- package/tests/primitives/Tree.test.ts +128 -0
- package/tsdown.config.ts +1 -1
- /package/dist/{chunk-C6wwvPpM.mjs → chunk-CLMFDpHK.mjs} +0 -0
package/src/primitives/Union.ts
CHANGED
|
@@ -4,11 +4,11 @@ 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, InferState, InferProxy, InferSnapshot } from "../Primitive";
|
|
7
|
+
import type { Primitive, PrimitiveInternal, MaybeUndefined, AnyPrimitive, InferState, InferProxy, InferSnapshot, InferSetInput } from "../Primitive";
|
|
8
8
|
import { ValidationError } from "../Primitive";
|
|
9
9
|
import { LiteralPrimitive } from "./Literal";
|
|
10
10
|
import { StructPrimitive, InferStructState } from "./Struct";
|
|
11
|
-
import { runValidators, applyDefaults
|
|
11
|
+
import { runValidators, applyDefaults } from "./shared";
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -32,20 +32,18 @@ export type InferUnionSnapshot<TVariants extends UnionVariants> = {
|
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* Compute the input type for union.set() operations.
|
|
35
|
-
*
|
|
35
|
+
* Uses each variant's TSetInput type.
|
|
36
36
|
*/
|
|
37
37
|
export type UnionSetInput<TVariants extends UnionVariants> = {
|
|
38
|
-
[K in keyof TVariants]: TVariants[K]
|
|
39
|
-
? StructSetInput<TFields>
|
|
40
|
-
: InferState<TVariants[K]>;
|
|
38
|
+
[K in keyof TVariants]: InferSetInput<TVariants[K]>;
|
|
41
39
|
}[keyof TVariants];
|
|
42
40
|
|
|
43
41
|
/**
|
|
44
42
|
* Proxy for accessing union variants
|
|
45
43
|
*/
|
|
46
|
-
export interface UnionProxy<TVariants extends UnionVariants, _TDiscriminator extends string,
|
|
44
|
+
export interface UnionProxy<TVariants extends UnionVariants, _TDiscriminator extends string, TRequired extends boolean = false, THasDefault extends boolean = false> {
|
|
47
45
|
/** Gets the current union value */
|
|
48
|
-
get(): MaybeUndefined<InferUnionState<TVariants>,
|
|
46
|
+
get(): MaybeUndefined<InferUnionState<TVariants>, TRequired, THasDefault>;
|
|
49
47
|
|
|
50
48
|
/** Sets the entire union value (applies defaults for variant fields) */
|
|
51
49
|
set(value: UnionSetInput<TVariants>): void;
|
|
@@ -59,7 +57,7 @@ export interface UnionProxy<TVariants extends UnionVariants, _TDiscriminator ext
|
|
|
59
57
|
}): R | undefined;
|
|
60
58
|
|
|
61
59
|
/** Returns a readonly snapshot of the union for rendering */
|
|
62
|
-
toSnapshot(): MaybeUndefined<InferUnionSnapshot<TVariants>,
|
|
60
|
+
toSnapshot(): MaybeUndefined<InferUnionSnapshot<TVariants>, TRequired, THasDefault>;
|
|
63
61
|
}
|
|
64
62
|
|
|
65
63
|
interface UnionPrimitiveSchema<TVariants extends UnionVariants, TDiscriminator extends string> {
|
|
@@ -69,14 +67,16 @@ interface UnionPrimitiveSchema<TVariants extends UnionVariants, TDiscriminator e
|
|
|
69
67
|
readonly variants: TVariants;
|
|
70
68
|
}
|
|
71
69
|
|
|
72
|
-
export class UnionPrimitive<TVariants extends UnionVariants, TDiscriminator extends string = "type",
|
|
73
|
-
implements Primitive<InferUnionState<TVariants>, UnionProxy<TVariants, TDiscriminator,
|
|
70
|
+
export class UnionPrimitive<TVariants extends UnionVariants, TDiscriminator extends string = "type", TRequired extends boolean = false, THasDefault extends boolean = false>
|
|
71
|
+
implements Primitive<InferUnionState<TVariants>, UnionProxy<TVariants, TDiscriminator, TRequired, THasDefault>, TRequired, THasDefault, UnionSetInput<TVariants>, UnionSetInput<TVariants>>
|
|
74
72
|
{
|
|
75
73
|
readonly _tag = "UnionPrimitive" as const;
|
|
76
74
|
readonly _State!: InferUnionState<TVariants>;
|
|
77
|
-
readonly _Proxy!: UnionProxy<TVariants, TDiscriminator,
|
|
78
|
-
readonly
|
|
75
|
+
readonly _Proxy!: UnionProxy<TVariants, TDiscriminator, TRequired, THasDefault>;
|
|
76
|
+
readonly _TRequired!: TRequired;
|
|
79
77
|
readonly _THasDefault!: THasDefault;
|
|
78
|
+
readonly TSetInput!: UnionSetInput<TVariants>;
|
|
79
|
+
readonly TUpdateInput!: UnionSetInput<TVariants>;
|
|
80
80
|
|
|
81
81
|
private readonly _schema: UnionPrimitiveSchema<TVariants, TDiscriminator>;
|
|
82
82
|
|
|
@@ -153,18 +153,18 @@ export class UnionPrimitive<TVariants extends UnionVariants, TDiscriminator exte
|
|
|
153
153
|
return applyDefaults(variantPrimitive as AnyPrimitive, value) as InferUnionState<TVariants>;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
readonly _internal: PrimitiveInternal<InferUnionState<TVariants>, UnionProxy<TVariants, TDiscriminator,
|
|
156
|
+
readonly _internal: PrimitiveInternal<InferUnionState<TVariants>, UnionProxy<TVariants, TDiscriminator, TRequired, THasDefault>> = {
|
|
157
157
|
createProxy: (
|
|
158
158
|
env: ProxyEnvironment.ProxyEnvironment,
|
|
159
159
|
operationPath: OperationPath.OperationPath
|
|
160
|
-
): UnionProxy<TVariants, TDiscriminator,
|
|
160
|
+
): UnionProxy<TVariants, TDiscriminator, TRequired, THasDefault> => {
|
|
161
161
|
const variants = this._schema.variants;
|
|
162
162
|
const defaultValue = this._schema.defaultValue;
|
|
163
163
|
|
|
164
164
|
return {
|
|
165
|
-
get: (): MaybeUndefined<InferUnionState<TVariants>,
|
|
165
|
+
get: (): MaybeUndefined<InferUnionState<TVariants>, TRequired, THasDefault> => {
|
|
166
166
|
const state = env.getState(operationPath) as InferUnionState<TVariants> | undefined;
|
|
167
|
-
return (state ?? defaultValue) as MaybeUndefined<InferUnionState<TVariants>,
|
|
167
|
+
return (state ?? defaultValue) as MaybeUndefined<InferUnionState<TVariants>, TRequired, THasDefault>;
|
|
168
168
|
},
|
|
169
169
|
set: (value: UnionSetInput<TVariants>) => {
|
|
170
170
|
// Apply defaults for the variant
|
|
@@ -193,21 +193,21 @@ export class UnionPrimitive<TVariants extends UnionVariants, TDiscriminator exte
|
|
|
193
193
|
const variantProxy = variants[variantKey]!._internal.createProxy(env, operationPath) as InferProxy<TVariants[typeof variantKey]>;
|
|
194
194
|
return handler(variantProxy);
|
|
195
195
|
},
|
|
196
|
-
toSnapshot: (): MaybeUndefined<InferUnionSnapshot<TVariants>,
|
|
196
|
+
toSnapshot: (): MaybeUndefined<InferUnionSnapshot<TVariants>, TRequired, THasDefault> => {
|
|
197
197
|
const state = env.getState(operationPath) as InferUnionState<TVariants> | undefined;
|
|
198
198
|
const effectiveState = state ?? defaultValue;
|
|
199
199
|
if (!effectiveState) {
|
|
200
|
-
return undefined as MaybeUndefined<InferUnionSnapshot<TVariants>,
|
|
200
|
+
return undefined as MaybeUndefined<InferUnionSnapshot<TVariants>, TRequired, THasDefault>;
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
const variantKey = this._findVariantKey(effectiveState);
|
|
204
204
|
if (!variantKey) {
|
|
205
|
-
return undefined as MaybeUndefined<InferUnionSnapshot<TVariants>,
|
|
205
|
+
return undefined as MaybeUndefined<InferUnionSnapshot<TVariants>, TRequired, THasDefault>;
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
const variantPrimitive = variants[variantKey]!;
|
|
209
209
|
const variantProxy = variantPrimitive._internal.createProxy(env, operationPath);
|
|
210
|
-
return (variantProxy as unknown as { toSnapshot(): InferUnionSnapshot<TVariants> }).toSnapshot() as MaybeUndefined<InferUnionSnapshot<TVariants>,
|
|
210
|
+
return (variantProxy as unknown as { toSnapshot(): InferUnionSnapshot<TVariants> }).toSnapshot() as MaybeUndefined<InferUnionSnapshot<TVariants>, TRequired, THasDefault>;
|
|
211
211
|
},
|
|
212
212
|
};
|
|
213
213
|
},
|
package/src/primitives/shared.ts
CHANGED
|
@@ -17,13 +17,15 @@ import * as Transform from "../Transform";
|
|
|
17
17
|
* @typeParam TDefined - Whether the value is guaranteed to be defined (via required() or default())
|
|
18
18
|
* @typeParam THasDefault - Whether this primitive has a default value
|
|
19
19
|
*/
|
|
20
|
-
export interface Primitive<TState, TProxy,
|
|
20
|
+
export interface Primitive<TState, TProxy, TRequired extends boolean = false, THasDefault extends boolean = false, TSetInput = unknown, TUpdateInput = unknown> {
|
|
21
21
|
readonly _tag: string;
|
|
22
22
|
readonly _State: TState;
|
|
23
23
|
readonly _Proxy: TProxy;
|
|
24
|
-
readonly
|
|
24
|
+
readonly _TRequired: TRequired;
|
|
25
25
|
readonly _THasDefault: THasDefault;
|
|
26
26
|
readonly _internal: PrimitiveInternal<TState, TProxy>;
|
|
27
|
+
readonly TSetInput: TSetInput;
|
|
28
|
+
readonly TUpdateInput: TUpdateInput;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
/**
|
|
@@ -64,13 +66,32 @@ export interface Primitive<TState, TProxy, TDefined extends boolean = false, THa
|
|
|
64
66
|
* Infer the proxy type from a primitive.
|
|
65
67
|
*/
|
|
66
68
|
export type InferProxy<T> = T extends Primitive<any, infer P, any, any> ? P : never;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Infer the SetInput type from a primitive.
|
|
72
|
+
*/
|
|
73
|
+
export type InferSetInput<T> = T extends Primitive<any, any, any, any, infer S, any> ? S : never;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Infer the UpdateInput type from a primitive.
|
|
77
|
+
*/
|
|
78
|
+
export type InferUpdateInput<T> = T extends Primitive<any, any, any, any, any, infer U> ? U : never;
|
|
67
79
|
|
|
68
80
|
/**
|
|
69
|
-
* Helper type to conditionally add undefined based on
|
|
70
|
-
* When
|
|
71
|
-
*
|
|
81
|
+
* Helper type to conditionally add undefined based on TRequired and THasDefault.
|
|
82
|
+
* When TRequired is false and THasDefault is false, the value may be undefined.
|
|
83
|
+
* Otherwise, the value is guaranteed to be defined.
|
|
72
84
|
*/
|
|
73
|
-
export type MaybeUndefined<T,
|
|
85
|
+
export type MaybeUndefined<T, TRequired extends boolean, THasDefault extends boolean> = TRequired extends false ? THasDefault extends false ? Optional<T> : T : T;
|
|
86
|
+
|
|
87
|
+
export type Optional<T> = T | undefined;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Helper type to conditionally add undefined based on TRequired and THasDefault.
|
|
91
|
+
* When TRequired is true and THasDefault is false, the value must be provided.
|
|
92
|
+
* Otherwise, the value may be undefined.
|
|
93
|
+
*/
|
|
94
|
+
export type NeedsValue<T, TRequired extends boolean, THasDefault extends boolean> = TRequired extends true ? THasDefault extends false ? T : Optional<T> : Optional<T>;
|
|
74
95
|
|
|
75
96
|
/**
|
|
76
97
|
* Infer the snapshot type from a primitive.
|
|
@@ -90,34 +111,6 @@ export interface Primitive<TState, TProxy, TDefined extends boolean = false, THa
|
|
|
90
111
|
*/
|
|
91
112
|
export type IsDefined<T> = T extends Primitive<any, any, infer D, any> ? D : false;
|
|
92
113
|
|
|
93
|
-
/**
|
|
94
|
-
* Determines if a field is required for set() operations.
|
|
95
|
-
* A field is required if: TDefined is true AND THasDefault is false
|
|
96
|
-
*/
|
|
97
|
-
export type IsRequiredForSet<T> = T extends Primitive<any, any, true, false> ? true : false;
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Extract keys of fields that are required for set() (required without default).
|
|
101
|
-
*/
|
|
102
|
-
export type RequiredSetKeys<TFields extends Record<string, AnyPrimitive>> = {
|
|
103
|
-
[K in keyof TFields]: IsRequiredForSet<TFields[K]> extends true ? K : never;
|
|
104
|
-
}[keyof TFields];
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Extract keys of fields that are optional for set() (has default OR not required).
|
|
108
|
-
*/
|
|
109
|
-
export type OptionalSetKeys<TFields extends Record<string, AnyPrimitive>> = {
|
|
110
|
-
[K in keyof TFields]: IsRequiredForSet<TFields[K]> extends true ? never : K;
|
|
111
|
-
}[keyof TFields];
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Compute the input type for set() operations on a struct.
|
|
115
|
-
* Required fields (required without default) must be provided.
|
|
116
|
-
* Optional fields (has default or not required) can be omitted.
|
|
117
|
-
*/
|
|
118
|
-
export type StructSetInput<TFields extends Record<string, AnyPrimitive>> =
|
|
119
|
-
{ readonly [K in RequiredSetKeys<TFields>]: InferState<TFields[K]> } &
|
|
120
|
-
{ readonly [K in OptionalSetKeys<TFields>]?: InferState<TFields[K]> };
|
|
121
114
|
|
|
122
115
|
// =============================================================================
|
|
123
116
|
// Validation Errors
|
|
@@ -411,6 +411,114 @@ describe("ArrayPrimitive", () => {
|
|
|
411
411
|
expect(positions[1]! < positions[2]!).toBe(true);
|
|
412
412
|
});
|
|
413
413
|
});
|
|
414
|
+
describe("struct elements with defaults", () => {
|
|
415
|
+
// Define a struct element with required and optional fields
|
|
416
|
+
const TaskStruct = Primitive.Struct({
|
|
417
|
+
title: Primitive.String().required(), // Must provide
|
|
418
|
+
priority: Primitive.Number().default(0), // Has default, optional
|
|
419
|
+
completed: Primitive.Boolean().default(false), // Has default, optional
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
const taskArray = Primitive.Array(TaskStruct);
|
|
423
|
+
|
|
424
|
+
// Helper to create environment for task array
|
|
425
|
+
const createTaskEnv = (
|
|
426
|
+
state: Primitive.ArrayEntry<{ title: string; priority: number; completed: boolean }>[] = []
|
|
427
|
+
): { env: ReturnType<typeof ProxyEnvironment.make>; operations: Operation.Operation<any, any, any>[] } => {
|
|
428
|
+
const operations: Operation.Operation<any, any, any>[] = [];
|
|
429
|
+
let currentState = [...state];
|
|
430
|
+
let idCounter = 0;
|
|
431
|
+
|
|
432
|
+
const env = ProxyEnvironment.make({
|
|
433
|
+
onOperation: (op) => {
|
|
434
|
+
operations.push(op);
|
|
435
|
+
if (op.kind === "array.insert") {
|
|
436
|
+
currentState.push(op.payload);
|
|
437
|
+
} else if (op.kind === "array.set") {
|
|
438
|
+
currentState = op.payload;
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
getState: () => currentState,
|
|
442
|
+
generateId: () => `task-${++idCounter}`,
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
return { env, operations };
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
it("push() only requires fields without defaults", () => {
|
|
449
|
+
const { env, operations } = createTaskEnv();
|
|
450
|
+
const proxy = taskArray._internal.createProxy(env, OperationPath.make(""));
|
|
451
|
+
|
|
452
|
+
// Only provide required 'title', priority and completed should use defaults
|
|
453
|
+
proxy.push({ title: "New Task" });
|
|
454
|
+
|
|
455
|
+
expect(operations).toHaveLength(1);
|
|
456
|
+
expect(operations[0]!.kind).toBe("array.insert");
|
|
457
|
+
|
|
458
|
+
const payload = operations[0]!.payload as { value: { title: string; priority: number; completed: boolean } };
|
|
459
|
+
expect(payload.value.title).toBe("New Task");
|
|
460
|
+
expect(payload.value.priority).toBe(0); // Default value
|
|
461
|
+
expect(payload.value.completed).toBe(false); // Default value
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it("push() allows overriding defaults", () => {
|
|
465
|
+
const { env, operations } = createTaskEnv();
|
|
466
|
+
const proxy = taskArray._internal.createProxy(env, OperationPath.make(""));
|
|
467
|
+
|
|
468
|
+
// Provide title and override priority, let completed use default
|
|
469
|
+
proxy.push({ title: "Important Task", priority: 10 });
|
|
470
|
+
|
|
471
|
+
const payload = operations[0]!.payload as { value: { title: string; priority: number; completed: boolean } };
|
|
472
|
+
expect(payload.value.title).toBe("Important Task");
|
|
473
|
+
expect(payload.value.priority).toBe(10); // Overridden
|
|
474
|
+
expect(payload.value.completed).toBe(false); // Default value
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it("insertAt() applies defaults for omitted fields", () => {
|
|
478
|
+
const existingEntry = {
|
|
479
|
+
id: "existing",
|
|
480
|
+
pos: "a0",
|
|
481
|
+
value: { title: "Existing", priority: 5, completed: true },
|
|
482
|
+
};
|
|
483
|
+
const { env, operations } = createTaskEnv([existingEntry]);
|
|
484
|
+
const proxy = taskArray._internal.createProxy(env, OperationPath.make(""));
|
|
485
|
+
|
|
486
|
+
// Insert with only required field
|
|
487
|
+
proxy.insertAt(0, { title: "First Task" });
|
|
488
|
+
|
|
489
|
+
const payload = operations[0]!.payload as { value: { title: string; priority: number; completed: boolean } };
|
|
490
|
+
expect(payload.value.title).toBe("First Task");
|
|
491
|
+
expect(payload.value.priority).toBe(0); // Default
|
|
492
|
+
expect(payload.value.completed).toBe(false); // Default
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it("set() applies defaults to each element", () => {
|
|
496
|
+
const { env, operations } = createTaskEnv();
|
|
497
|
+
const proxy = taskArray._internal.createProxy(env, OperationPath.make(""));
|
|
498
|
+
|
|
499
|
+
// Set array with items that only have required fields
|
|
500
|
+
proxy.set([
|
|
501
|
+
{ title: "Task 1" },
|
|
502
|
+
{ title: "Task 2", priority: 5 },
|
|
503
|
+
{ title: "Task 3", completed: true },
|
|
504
|
+
]);
|
|
505
|
+
|
|
506
|
+
expect(operations).toHaveLength(1);
|
|
507
|
+
expect(operations[0]!.kind).toBe("array.set");
|
|
508
|
+
|
|
509
|
+
const entries = operations[0]!.payload as { value: { title: string; priority: number; completed: boolean } }[];
|
|
510
|
+
expect(entries).toHaveLength(3);
|
|
511
|
+
|
|
512
|
+
// First item: only title, defaults for others
|
|
513
|
+
expect(entries[0]!.value).toEqual({ title: "Task 1", priority: 0, completed: false });
|
|
514
|
+
|
|
515
|
+
// Second item: title and priority, default for completed
|
|
516
|
+
expect(entries[1]!.value).toEqual({ title: "Task 2", priority: 5, completed: false });
|
|
517
|
+
|
|
518
|
+
// Third item: title and completed, default for priority
|
|
519
|
+
expect(entries[2]!.value).toEqual({ title: "Task 3", priority: 0, completed: true });
|
|
520
|
+
});
|
|
521
|
+
});
|
|
414
522
|
});
|
|
415
523
|
|
|
416
524
|
// =============================================================================
|
|
@@ -93,10 +93,10 @@ describe("StructPrimitive", () => {
|
|
|
93
93
|
});
|
|
94
94
|
|
|
95
95
|
const structPrimitive = Primitive.Struct({
|
|
96
|
-
name: Primitive.String().required(),
|
|
96
|
+
name: Primitive.String().required().default("John Doe"),
|
|
97
97
|
age: Primitive.Number().required(),
|
|
98
98
|
email: Primitive.String(),
|
|
99
|
-
}).default({
|
|
99
|
+
}).default({ age: 30 });
|
|
100
100
|
|
|
101
101
|
const proxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
|
|
102
102
|
proxy.set({ age: 30 });
|
|
@@ -582,6 +582,134 @@ describe("TreePrimitive", () => {
|
|
|
582
582
|
expect(operations[0]!.path.toTokens()).toEqual(["file1", "size"]);
|
|
583
583
|
});
|
|
584
584
|
});
|
|
585
|
+
|
|
586
|
+
describe("proxy - insert with defaults", () => {
|
|
587
|
+
// Define node types with defaults
|
|
588
|
+
const ItemNodeWithDefaults = Primitive.TreeNode("item", {
|
|
589
|
+
data: Primitive.Struct({
|
|
590
|
+
title: Primitive.String().required(), // Must provide
|
|
591
|
+
count: Primitive.Number().default(0), // Has default, optional
|
|
592
|
+
active: Primitive.Boolean().default(true), // Has default, optional
|
|
593
|
+
}),
|
|
594
|
+
children: [] as const,
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
const ContainerNodeWithDefaults = Primitive.TreeNode("container", {
|
|
598
|
+
data: Primitive.Struct({
|
|
599
|
+
name: Primitive.String().required(), // Must provide
|
|
600
|
+
}),
|
|
601
|
+
children: (): readonly Primitive.AnyTreeNodePrimitive[] => [ContainerNodeWithDefaults, ItemNodeWithDefaults],
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
const treeWithDefaults = Primitive.Tree({
|
|
605
|
+
root: ContainerNodeWithDefaults,
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
// Helper to create a mock environment
|
|
609
|
+
const createEnvWithDefaultsTree = (
|
|
610
|
+
state: Primitive.TreeState<typeof ContainerNodeWithDefaults> = []
|
|
611
|
+
): { env: ReturnType<typeof ProxyEnvironment.make>; operations: Operation.Operation<any, any, any>[] } => {
|
|
612
|
+
const operations: Operation.Operation<any, any, any>[] = [];
|
|
613
|
+
let currentState = [...state] as Primitive.TreeState<typeof ContainerNodeWithDefaults>;
|
|
614
|
+
let idCounter = 0;
|
|
615
|
+
|
|
616
|
+
const env = ProxyEnvironment.make({
|
|
617
|
+
onOperation: (op) => {
|
|
618
|
+
operations.push(op);
|
|
619
|
+
currentState = treeWithDefaults._internal.applyOperation(currentState, op);
|
|
620
|
+
},
|
|
621
|
+
getState: () => currentState,
|
|
622
|
+
generateId: () => `node-${++idCounter}`,
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
return { env, operations };
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
it("insertFirst() only requires fields without defaults", () => {
|
|
629
|
+
const initialState: Primitive.TreeState<typeof ContainerNodeWithDefaults> = [
|
|
630
|
+
{ id: "root", type: "container", parentId: null, pos: "a0", data: { name: "Root" } },
|
|
631
|
+
];
|
|
632
|
+
const { env, operations } = createEnvWithDefaultsTree(initialState);
|
|
633
|
+
const proxy = treeWithDefaults._internal.createProxy(env, OperationPath.make(""));
|
|
634
|
+
|
|
635
|
+
// Only provide required field 'title', count and active should use defaults
|
|
636
|
+
const newId = proxy.insertFirst("root", ItemNodeWithDefaults, { title: "New Item" });
|
|
637
|
+
|
|
638
|
+
expect(operations).toHaveLength(1);
|
|
639
|
+
expect(operations[0]!.kind).toBe("tree.insert");
|
|
640
|
+
expect(newId).toBe("node-1");
|
|
641
|
+
|
|
642
|
+
const payload = operations[0]!.payload as { data: { title: string; count: number; active: boolean } };
|
|
643
|
+
expect(payload.data.title).toBe("New Item");
|
|
644
|
+
expect(payload.data.count).toBe(0); // Default value
|
|
645
|
+
expect(payload.data.active).toBe(true); // Default value
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
it("insertLast() applies defaults for omitted fields", () => {
|
|
649
|
+
const initialState: Primitive.TreeState<typeof ContainerNodeWithDefaults> = [
|
|
650
|
+
{ id: "root", type: "container", parentId: null, pos: "a0", data: { name: "Root" } },
|
|
651
|
+
];
|
|
652
|
+
const { env, operations } = createEnvWithDefaultsTree(initialState);
|
|
653
|
+
const proxy = treeWithDefaults._internal.createProxy(env, OperationPath.make(""));
|
|
654
|
+
|
|
655
|
+
// Provide title and override count, let active use default
|
|
656
|
+
proxy.insertLast("root", ItemNodeWithDefaults, { title: "Item", count: 42 });
|
|
657
|
+
|
|
658
|
+
const payload = operations[0]!.payload as { data: { title: string; count: number; active: boolean } };
|
|
659
|
+
expect(payload.data.title).toBe("Item");
|
|
660
|
+
expect(payload.data.count).toBe(42); // Overridden
|
|
661
|
+
expect(payload.data.active).toBe(true); // Default value
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
it("insertAt() allows omitting all optional fields with defaults", () => {
|
|
665
|
+
const initialState: Primitive.TreeState<typeof ContainerNodeWithDefaults> = [
|
|
666
|
+
{ id: "root", type: "container", parentId: null, pos: "a0", data: { name: "Root" } },
|
|
667
|
+
];
|
|
668
|
+
const { env, operations } = createEnvWithDefaultsTree(initialState);
|
|
669
|
+
const proxy = treeWithDefaults._internal.createProxy(env, OperationPath.make(""));
|
|
670
|
+
|
|
671
|
+
// Only provide required 'name' for container
|
|
672
|
+
proxy.insertAt("root", 0, ContainerNodeWithDefaults, { name: "Subfolder" });
|
|
673
|
+
|
|
674
|
+
const payload = operations[0]!.payload as { type: string; data: { name: string } };
|
|
675
|
+
expect(payload.type).toBe("container");
|
|
676
|
+
expect(payload.data.name).toBe("Subfolder");
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
it("insertAfter() uses defaults when fields are omitted", () => {
|
|
680
|
+
const initialState: Primitive.TreeState<typeof ContainerNodeWithDefaults> = [
|
|
681
|
+
{ id: "root", type: "container", parentId: null, pos: "a0", data: { name: "Root" } },
|
|
682
|
+
{ id: "item1", type: "item", parentId: "root", pos: "a0", data: { title: "First", count: 1, active: false } },
|
|
683
|
+
];
|
|
684
|
+
const { env, operations } = createEnvWithDefaultsTree(initialState);
|
|
685
|
+
const proxy = treeWithDefaults._internal.createProxy(env, OperationPath.make(""));
|
|
686
|
+
|
|
687
|
+
// Insert after sibling with only required field
|
|
688
|
+
proxy.insertAfter("item1", ItemNodeWithDefaults, { title: "Second" });
|
|
689
|
+
|
|
690
|
+
const payload = operations[0]!.payload as { data: { title: string; count: number; active: boolean } };
|
|
691
|
+
expect(payload.data.title).toBe("Second");
|
|
692
|
+
expect(payload.data.count).toBe(0); // Default
|
|
693
|
+
expect(payload.data.active).toBe(true); // Default
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
it("insertBefore() uses defaults when fields are omitted", () => {
|
|
697
|
+
const initialState: Primitive.TreeState<typeof ContainerNodeWithDefaults> = [
|
|
698
|
+
{ id: "root", type: "container", parentId: null, pos: "a0", data: { name: "Root" } },
|
|
699
|
+
{ id: "item1", type: "item", parentId: "root", pos: "a0", data: { title: "First", count: 1, active: false } },
|
|
700
|
+
];
|
|
701
|
+
const { env, operations } = createEnvWithDefaultsTree(initialState);
|
|
702
|
+
const proxy = treeWithDefaults._internal.createProxy(env, OperationPath.make(""));
|
|
703
|
+
|
|
704
|
+
// Insert before sibling with only required field, override active
|
|
705
|
+
proxy.insertBefore("item1", ItemNodeWithDefaults, { title: "Zeroth", active: false });
|
|
706
|
+
|
|
707
|
+
const payload = operations[0]!.payload as { data: { title: string; count: number; active: boolean } };
|
|
708
|
+
expect(payload.data.title).toBe("Zeroth");
|
|
709
|
+
expect(payload.data.count).toBe(0); // Default
|
|
710
|
+
expect(payload.data.active).toBe(false); // Overridden
|
|
711
|
+
});
|
|
712
|
+
});
|
|
585
713
|
});
|
|
586
714
|
|
|
587
715
|
// =============================================================================
|
package/tsdown.config.ts
CHANGED
|
File without changes
|