@voidhash/mimic 0.0.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +17 -0
  2. package/package.json +33 -0
  3. package/src/Document.ts +256 -0
  4. package/src/FractionalIndex.ts +1249 -0
  5. package/src/Operation.ts +59 -0
  6. package/src/OperationDefinition.ts +23 -0
  7. package/src/OperationPath.ts +197 -0
  8. package/src/Presence.ts +142 -0
  9. package/src/Primitive.ts +32 -0
  10. package/src/Proxy.ts +8 -0
  11. package/src/ProxyEnvironment.ts +52 -0
  12. package/src/Transaction.ts +72 -0
  13. package/src/Transform.ts +13 -0
  14. package/src/client/ClientDocument.ts +1163 -0
  15. package/src/client/Rebase.ts +309 -0
  16. package/src/client/StateMonitor.ts +307 -0
  17. package/src/client/Transport.ts +318 -0
  18. package/src/client/WebSocketTransport.ts +572 -0
  19. package/src/client/errors.ts +145 -0
  20. package/src/client/index.ts +61 -0
  21. package/src/index.ts +12 -0
  22. package/src/primitives/Array.ts +457 -0
  23. package/src/primitives/Boolean.ts +128 -0
  24. package/src/primitives/Lazy.ts +89 -0
  25. package/src/primitives/Literal.ts +128 -0
  26. package/src/primitives/Number.ts +169 -0
  27. package/src/primitives/String.ts +189 -0
  28. package/src/primitives/Struct.ts +348 -0
  29. package/src/primitives/Tree.ts +1120 -0
  30. package/src/primitives/TreeNode.ts +113 -0
  31. package/src/primitives/Union.ts +329 -0
  32. package/src/primitives/shared.ts +122 -0
  33. package/src/server/ServerDocument.ts +267 -0
  34. package/src/server/errors.ts +90 -0
  35. package/src/server/index.ts +40 -0
  36. package/tests/Document.test.ts +556 -0
  37. package/tests/FractionalIndex.test.ts +377 -0
  38. package/tests/OperationPath.test.ts +151 -0
  39. package/tests/Presence.test.ts +321 -0
  40. package/tests/Primitive.test.ts +381 -0
  41. package/tests/client/ClientDocument.test.ts +1398 -0
  42. package/tests/client/WebSocketTransport.test.ts +992 -0
  43. package/tests/primitives/Array.test.ts +418 -0
  44. package/tests/primitives/Boolean.test.ts +126 -0
  45. package/tests/primitives/Lazy.test.ts +143 -0
  46. package/tests/primitives/Literal.test.ts +122 -0
  47. package/tests/primitives/Number.test.ts +133 -0
  48. package/tests/primitives/String.test.ts +128 -0
  49. package/tests/primitives/Struct.test.ts +311 -0
  50. package/tests/primitives/Tree.test.ts +467 -0
  51. package/tests/primitives/TreeNode.test.ts +50 -0
  52. package/tests/primitives/Union.test.ts +210 -0
  53. package/tests/server/ServerDocument.test.ts +528 -0
  54. package/tsconfig.build.json +24 -0
  55. package/tsconfig.json +8 -0
  56. package/tsdown.config.ts +18 -0
  57. package/vitest.mts +11 -0
@@ -0,0 +1,113 @@
1
+ import { Effect, Schema } from "effect";
2
+ import * as OperationDefinition from "../OperationDefinition";
3
+ import * as Operation from "../Operation";
4
+ import * as OperationPath from "../OperationPath";
5
+ import * as ProxyEnvironment from "../ProxyEnvironment";
6
+ import * as Transform from "../Transform";
7
+ import type { AnyPrimitive, InferState } from "../Primitive";
8
+ import { StructPrimitive } from "./Struct";
9
+
10
+
11
+ /**
12
+ * Any TreeNodePrimitive type - used for generic constraints.
13
+ */
14
+ export type AnyTreeNodePrimitive = TreeNodePrimitive<string, StructPrimitive<any>, readonly AnyTreeNodePrimitive[] | (() => readonly AnyTreeNodePrimitive[])>;
15
+
16
+ /**
17
+ * Resolves children type - handles both array and lazy thunk
18
+ */
19
+ export type ResolveChildren<TChildren extends readonly AnyTreeNodePrimitive[] | (() => readonly AnyTreeNodePrimitive[])> =
20
+ TChildren extends () => readonly AnyTreeNodePrimitive[] ? ReturnType<TChildren> : TChildren;
21
+
22
+ /**
23
+ * Infer the data state type from a TreeNodePrimitive
24
+ */
25
+ export type InferTreeNodeDataState<T extends AnyTreeNodePrimitive> =
26
+ T extends TreeNodePrimitive<any, infer TData, any> ? InferState<TData> : never;
27
+
28
+ /**
29
+ * Infer the type literal from a TreeNodePrimitive
30
+ */
31
+ export type InferTreeNodeType<T extends AnyTreeNodePrimitive> =
32
+ T extends TreeNodePrimitive<infer TType, any, any> ? TType : never;
33
+
34
+ /**
35
+ * Infer the allowed children from a TreeNodePrimitive
36
+ */
37
+ export type InferTreeNodeChildren<T extends AnyTreeNodePrimitive> =
38
+ T extends TreeNodePrimitive<any, any, infer TChildren> ? ResolveChildren<TChildren>[number] : never;
39
+
40
+ /**
41
+ * Configuration for a TreeNode primitive
42
+ */
43
+ export interface TreeNodeConfig<
44
+ TData extends StructPrimitive<any>,
45
+ TChildren extends readonly AnyTreeNodePrimitive[] | (() => readonly AnyTreeNodePrimitive[])
46
+ > {
47
+ readonly data: TData;
48
+ readonly children: TChildren;
49
+ }
50
+
51
+ /**
52
+ * TreeNodePrimitive - defines a node type with its data schema and allowed children
53
+ */
54
+ export class TreeNodePrimitive<
55
+ TType extends string,
56
+ TData extends StructPrimitive<any>,
57
+ TChildren extends readonly AnyTreeNodePrimitive[] | (() => readonly AnyTreeNodePrimitive[])
58
+ > {
59
+ readonly _tag = "TreeNodePrimitive" as const;
60
+ readonly _Type!: TType;
61
+ readonly _Data!: TData;
62
+ readonly _Children!: TChildren;
63
+
64
+ private readonly _type: TType;
65
+ private readonly _data: TData;
66
+ private readonly _children: TChildren;
67
+ private _resolvedChildren: readonly AnyTreeNodePrimitive[] | undefined;
68
+
69
+ constructor(type: TType, config: TreeNodeConfig<TData, TChildren>) {
70
+ this._type = type;
71
+ this._data = config.data;
72
+ this._children = config.children;
73
+ }
74
+
75
+ /** Get the node type identifier */
76
+ get type(): TType {
77
+ return this._type;
78
+ }
79
+
80
+ /** Get the data primitive */
81
+ get data(): TData {
82
+ return this._data;
83
+ }
84
+
85
+ /** Get resolved children (resolves lazy thunk if needed) */
86
+ get children(): ResolveChildren<TChildren> {
87
+ if (this._resolvedChildren === undefined) {
88
+ if (typeof this._children === "function") {
89
+ this._resolvedChildren = (this._children as () => readonly AnyTreeNodePrimitive[])();
90
+ } else {
91
+ this._resolvedChildren = this._children as readonly AnyTreeNodePrimitive[];
92
+ }
93
+ }
94
+ return this._resolvedChildren as ResolveChildren<TChildren>;
95
+ }
96
+
97
+ /** Check if a child type is allowed */
98
+ isChildAllowed(childType: string): boolean {
99
+ return this.children.some(child => child.type === childType);
100
+ }
101
+ }
102
+
103
+ /** Creates a new TreeNodePrimitive with the given type and config */
104
+ export const TreeNode = <
105
+ TType extends string,
106
+ TData extends StructPrimitive<any>,
107
+ TChildren extends readonly AnyTreeNodePrimitive[] | (() => readonly AnyTreeNodePrimitive[])
108
+ >(
109
+ type: TType,
110
+ config: TreeNodeConfig<TData, TChildren>
111
+ ): TreeNodePrimitive<TType, TData, TChildren> =>
112
+ new TreeNodePrimitive(type, config);
113
+
@@ -0,0 +1,329 @@
1
+ import { Effect, Schema } from "effect";
2
+ import * as OperationDefinition from "../OperationDefinition";
3
+ import * as Operation from "../Operation";
4
+ import * as OperationPath from "../OperationPath";
5
+ import * as ProxyEnvironment from "../ProxyEnvironment";
6
+ import * as Transform from "../Transform";
7
+ import type { Primitive, PrimitiveInternal, MaybeUndefined, AnyPrimitive, InferState, InferProxy, InferSnapshot } from "../Primitive";
8
+ import { ValidationError } from "../Primitive";
9
+ import { LiteralPrimitive } from "./Literal";
10
+ import { StructPrimitive } from "./Struct";
11
+ import { runValidators } from "./shared";
12
+
13
+
14
+ /**
15
+ * Type constraint for union variants - must be struct primitives
16
+ */
17
+ export type UnionVariants = Record<string, StructPrimitive<any, any>>;
18
+
19
+ /**
20
+ * Infer the union state type from variants
21
+ */
22
+ export type InferUnionState<TVariants extends UnionVariants> = {
23
+ [K in keyof TVariants]: InferState<TVariants[K]>;
24
+ }[keyof TVariants];
25
+
26
+ /**
27
+ * Infer the union snapshot type from variants
28
+ */
29
+ export type InferUnionSnapshot<TVariants extends UnionVariants> = {
30
+ [K in keyof TVariants]: InferSnapshot<TVariants[K]>;
31
+ }[keyof TVariants];
32
+
33
+ /**
34
+ * Proxy for accessing union variants
35
+ */
36
+ export interface UnionProxy<TVariants extends UnionVariants, TDiscriminator extends string, TDefined extends boolean = false> {
37
+ /** Gets the current union value */
38
+ get(): MaybeUndefined<InferUnionState<TVariants>, TDefined>;
39
+
40
+ /** Sets the entire union value */
41
+ set(value: InferUnionState<TVariants>): void;
42
+
43
+ /** Access a specific variant's proxy (assumes the variant is active) */
44
+ as<K extends keyof TVariants>(variant: K): InferProxy<TVariants[K]>;
45
+
46
+ /** Pattern match on the variant type */
47
+ match<R>(handlers: {
48
+ [K in keyof TVariants]: (proxy: InferProxy<TVariants[K]>) => R;
49
+ }): R | undefined;
50
+
51
+ /** Returns a readonly snapshot of the union for rendering */
52
+ toSnapshot(): MaybeUndefined<InferUnionSnapshot<TVariants>, TDefined>;
53
+ }
54
+
55
+ interface UnionPrimitiveSchema<TVariants extends UnionVariants, TDiscriminator extends string> {
56
+ readonly required: boolean;
57
+ readonly defaultValue: InferUnionState<TVariants> | undefined;
58
+ readonly discriminator: TDiscriminator;
59
+ readonly variants: TVariants;
60
+ }
61
+
62
+ export class UnionPrimitive<TVariants extends UnionVariants, TDiscriminator extends string = "type", TDefined extends boolean = false>
63
+ implements Primitive<InferUnionState<TVariants>, UnionProxy<TVariants, TDiscriminator, TDefined>>
64
+ {
65
+ readonly _tag = "UnionPrimitive" as const;
66
+ readonly _State!: InferUnionState<TVariants>;
67
+ readonly _Proxy!: UnionProxy<TVariants, TDiscriminator, TDefined>;
68
+
69
+ private readonly _schema: UnionPrimitiveSchema<TVariants, TDiscriminator>;
70
+
71
+ private readonly _opDefinitions = {
72
+ set: OperationDefinition.make({
73
+ kind: "union.set" as const,
74
+ payload: Schema.Unknown,
75
+ target: Schema.Unknown,
76
+ apply: (payload) => payload,
77
+ }),
78
+ };
79
+
80
+ constructor(schema: UnionPrimitiveSchema<TVariants, TDiscriminator>) {
81
+ this._schema = schema;
82
+ }
83
+
84
+ /** Mark this union as required */
85
+ required(): UnionPrimitive<TVariants, TDiscriminator, true> {
86
+ return new UnionPrimitive({
87
+ ...this._schema,
88
+ required: true,
89
+ });
90
+ }
91
+
92
+ /** Set a default value for this union */
93
+ default(defaultValue: InferUnionState<TVariants>): UnionPrimitive<TVariants, TDiscriminator, true> {
94
+ return new UnionPrimitive({
95
+ ...this._schema,
96
+ defaultValue,
97
+ });
98
+ }
99
+
100
+ /** Get the discriminator field name */
101
+ get discriminator(): TDiscriminator {
102
+ return this._schema.discriminator;
103
+ }
104
+
105
+ /** Get the variants */
106
+ get variants(): TVariants {
107
+ return this._schema.variants;
108
+ }
109
+
110
+ /** Find the variant key from a state value */
111
+ private _findVariantKey(state: InferUnionState<TVariants>): keyof TVariants | undefined {
112
+ if (typeof state !== "object" || state === null) {
113
+ return undefined;
114
+ }
115
+ const discriminatorValue = (state as Record<string, unknown>)[this._schema.discriminator];
116
+
117
+ // Find the variant that matches this discriminator value
118
+ for (const key in this._schema.variants) {
119
+ const variant = this._schema.variants[key]!;
120
+ const discriminatorField = variant.fields[this._schema.discriminator];
121
+ if (discriminatorField && discriminatorField._tag === "LiteralPrimitive") {
122
+ const literalPrimitive = discriminatorField as LiteralPrimitive<any, any>;
123
+ if (literalPrimitive.literal === discriminatorValue) {
124
+ return key;
125
+ }
126
+ }
127
+ }
128
+ return undefined;
129
+ }
130
+
131
+ readonly _internal: PrimitiveInternal<InferUnionState<TVariants>, UnionProxy<TVariants, TDiscriminator, TDefined>> = {
132
+ createProxy: (
133
+ env: ProxyEnvironment.ProxyEnvironment,
134
+ operationPath: OperationPath.OperationPath
135
+ ): UnionProxy<TVariants, TDiscriminator, TDefined> => {
136
+ const variants = this._schema.variants;
137
+ const defaultValue = this._schema.defaultValue;
138
+
139
+ return {
140
+ get: (): MaybeUndefined<InferUnionState<TVariants>, TDefined> => {
141
+ const state = env.getState(operationPath) as InferUnionState<TVariants> | undefined;
142
+ return (state ?? defaultValue) as MaybeUndefined<InferUnionState<TVariants>, TDefined>;
143
+ },
144
+ set: (value: InferUnionState<TVariants>) => {
145
+ env.addOperation(
146
+ Operation.fromDefinition(operationPath, this._opDefinitions.set, value)
147
+ );
148
+ },
149
+ as: <K extends keyof TVariants>(variant: K): InferProxy<TVariants[K]> => {
150
+ const variantPrimitive = variants[variant];
151
+ if (!variantPrimitive) {
152
+ throw new ValidationError(`Unknown variant: ${globalThis.String(variant)}`);
153
+ }
154
+ return variantPrimitive._internal.createProxy(env, operationPath) as InferProxy<TVariants[K]>;
155
+ },
156
+ match: <R,>(handlers: { [K in keyof TVariants]: (proxy: InferProxy<TVariants[K]>) => R }): R | undefined => {
157
+ const state = env.getState(operationPath) as InferUnionState<TVariants> | undefined;
158
+ if (!state) return undefined;
159
+
160
+ const variantKey = this._findVariantKey(state);
161
+ if (!variantKey) return undefined;
162
+
163
+ const handler = handlers[variantKey];
164
+ if (!handler) return undefined;
165
+
166
+ const variantProxy = variants[variantKey]!._internal.createProxy(env, operationPath) as InferProxy<TVariants[typeof variantKey]>;
167
+ return handler(variantProxy);
168
+ },
169
+ toSnapshot: (): MaybeUndefined<InferUnionSnapshot<TVariants>, TDefined> => {
170
+ const state = env.getState(operationPath) as InferUnionState<TVariants> | undefined;
171
+ const effectiveState = state ?? defaultValue;
172
+ if (!effectiveState) {
173
+ return undefined as MaybeUndefined<InferUnionSnapshot<TVariants>, TDefined>;
174
+ }
175
+
176
+ const variantKey = this._findVariantKey(effectiveState);
177
+ if (!variantKey) {
178
+ return undefined as MaybeUndefined<InferUnionSnapshot<TVariants>, TDefined>;
179
+ }
180
+
181
+ const variantPrimitive = variants[variantKey]!;
182
+ const variantProxy = variantPrimitive._internal.createProxy(env, operationPath);
183
+ return (variantProxy as unknown as { toSnapshot(): InferUnionSnapshot<TVariants> }).toSnapshot() as MaybeUndefined<InferUnionSnapshot<TVariants>, TDefined>;
184
+ },
185
+ };
186
+ },
187
+
188
+ applyOperation: (
189
+ state: InferUnionState<TVariants> | undefined,
190
+ operation: Operation.Operation<any, any, any>
191
+ ): InferUnionState<TVariants> => {
192
+ const path = operation.path;
193
+ const tokens = path.toTokens().filter((t: string) => t !== "");
194
+
195
+ // If path is empty, this is a union-level operation
196
+ if (tokens.length === 0) {
197
+ if (operation.kind !== "union.set") {
198
+ throw new ValidationError(`UnionPrimitive root cannot apply operation of kind: ${operation.kind}`);
199
+ }
200
+
201
+ const payload = operation.payload;
202
+ if (typeof payload !== "object" || payload === null) {
203
+ throw new ValidationError(`UnionPrimitive.set requires an object payload`);
204
+ }
205
+
206
+ // Validate that the discriminator field exists and matches a variant
207
+ const discriminatorValue = (payload as Record<string, unknown>)[this._schema.discriminator];
208
+ if (discriminatorValue === undefined) {
209
+ throw new ValidationError(`UnionPrimitive.set requires a "${this._schema.discriminator}" discriminator field`);
210
+ }
211
+
212
+ return payload as InferUnionState<TVariants>;
213
+ }
214
+
215
+ // Otherwise, delegate to the active variant
216
+ // We need to determine which variant is active based on current state
217
+ if (state === undefined) {
218
+ throw new ValidationError(`Cannot apply nested operation to undefined union state`);
219
+ }
220
+
221
+ const variantKey = this._findVariantKey(state);
222
+ if (variantKey === undefined) {
223
+ throw new ValidationError(`Cannot determine active variant from state`);
224
+ }
225
+
226
+ const variantPrimitive = this._schema.variants[variantKey]!;
227
+ const newState = variantPrimitive._internal.applyOperation(
228
+ state as InferState<typeof variantPrimitive>,
229
+ operation
230
+ );
231
+
232
+ return newState as InferUnionState<TVariants>;
233
+ },
234
+
235
+ getInitialState: (): InferUnionState<TVariants> | undefined => {
236
+ return this._schema.defaultValue;
237
+ },
238
+
239
+ transformOperation: (
240
+ clientOp: Operation.Operation<any, any, any>,
241
+ serverOp: Operation.Operation<any, any, any>
242
+ ): Transform.TransformResult => {
243
+ const clientPath = clientOp.path;
244
+ const serverPath = serverOp.path;
245
+
246
+ // If paths don't overlap at all, no transformation needed
247
+ if (!OperationPath.pathsOverlap(clientPath, serverPath)) {
248
+ return { type: "transformed", operation: clientOp };
249
+ }
250
+
251
+ const clientTokens = clientPath.toTokens().filter((t: string) => t !== "");
252
+ const serverTokens = serverPath.toTokens().filter((t: string) => t !== "");
253
+
254
+ // If both are at root level (union.set operations)
255
+ if (clientTokens.length === 0 && serverTokens.length === 0) {
256
+ // Client wins (last-write-wins)
257
+ return { type: "transformed", operation: clientOp };
258
+ }
259
+
260
+ // If server set entire union and client is updating a field
261
+ if (serverTokens.length === 0 && serverOp.kind === "union.set") {
262
+ // Client's field operation proceeds - optimistic update
263
+ // Server will validate/reject if needed
264
+ return { type: "transformed", operation: clientOp };
265
+ }
266
+
267
+ // If client set entire union and server is updating a field
268
+ if (clientTokens.length === 0 && clientOp.kind === "union.set") {
269
+ // Client's union.set supersedes server's field update
270
+ return { type: "transformed", operation: clientOp };
271
+ }
272
+
273
+ // Both operations target fields within the union
274
+ // Since union variants are struct primitives, delegate to the first variant
275
+ // that matches (they all should have the same field structure for the overlapping field)
276
+ if (clientTokens.length > 0 && serverTokens.length > 0) {
277
+ const clientField = clientTokens[0];
278
+ const serverField = serverTokens[0];
279
+
280
+ // Different fields - no conflict
281
+ if (clientField !== serverField) {
282
+ return { type: "transformed", operation: clientOp };
283
+ }
284
+
285
+ // Same field - delegate to a variant (use first variant as they share structure)
286
+ const variantKeys = Object.keys(this._schema.variants);
287
+ if (variantKeys.length === 0) {
288
+ return { type: "transformed", operation: clientOp };
289
+ }
290
+
291
+ const firstVariant = this._schema.variants[variantKeys[0]!]!;
292
+ const result = firstVariant._internal.transformOperation(clientOp, serverOp);
293
+
294
+ return result;
295
+ }
296
+
297
+ // Default: no transformation needed
298
+ return { type: "transformed", operation: clientOp };
299
+ },
300
+ };
301
+ }
302
+
303
+ /** Options for creating a Union primitive */
304
+ export interface UnionOptions<TVariants extends UnionVariants, TDiscriminator extends string> {
305
+ /** The field name used to discriminate between variants (defaults to "type") */
306
+ readonly discriminator?: TDiscriminator;
307
+ /** The variant struct primitives */
308
+ readonly variants: TVariants;
309
+ }
310
+
311
+ /** Creates a new UnionPrimitive with the given variants */
312
+ export function Union<TVariants extends UnionVariants>(
313
+ options: UnionOptions<TVariants, "type">
314
+ ): UnionPrimitive<TVariants, "type", false>;
315
+ export function Union<TVariants extends UnionVariants, TDiscriminator extends string>(
316
+ options: UnionOptions<TVariants, TDiscriminator>
317
+ ): UnionPrimitive<TVariants, TDiscriminator, false>;
318
+ export function Union<TVariants extends UnionVariants, TDiscriminator extends string = "type">(
319
+ options: UnionOptions<TVariants, TDiscriminator>
320
+ ): UnionPrimitive<TVariants, TDiscriminator, false> {
321
+ const discriminator = (options.discriminator ?? "type") as TDiscriminator;
322
+ return new UnionPrimitive({
323
+ required: false,
324
+ defaultValue: undefined,
325
+ discriminator,
326
+ variants: options.variants,
327
+ });
328
+ }
329
+
@@ -0,0 +1,122 @@
1
+ import * as Operation from "../Operation";
2
+ import * as OperationDefinition from "../OperationDefinition";
3
+ import * as ProxyEnvironment from "../ProxyEnvironment";
4
+ import * as OperationPath from "../OperationPath";
5
+ import * as Transform from "../Transform";
6
+
7
+ // =============================================================================
8
+ // Primitive Interface & Type Utilities
9
+ // =============================================================================
10
+
11
+ /**
12
+ * Base interface that all primitives must implement.
13
+ * Provides type inference helpers and internal operations.
14
+ */
15
+ export interface Primitive<TState, TProxy> {
16
+ readonly _tag: string;
17
+ readonly _State: TState;
18
+ readonly _Proxy: TProxy;
19
+ readonly _internal: PrimitiveInternal<TState, TProxy>;
20
+ }
21
+
22
+ /**
23
+ * Internal operations that each primitive must provide.
24
+ */
25
+ export interface PrimitiveInternal<TState, TProxy> {
26
+ /** Creates a proxy for generating operations */
27
+ readonly createProxy: (env: ProxyEnvironment.ProxyEnvironment, path: OperationPath.OperationPath) => TProxy;
28
+ /** Applies an operation to the current state, returning the new state */
29
+ readonly applyOperation: (state: TState | undefined, operation: Operation.Operation<any, any, any>) => TState;
30
+ /** Returns the initial/default state for this primitive */
31
+ readonly getInitialState: () => TState | undefined;
32
+ /**
33
+ * Transforms a client operation against a server operation.
34
+ * Used for operational transformation (OT) conflict resolution.
35
+ *
36
+ * @param clientOp - The client's operation to transform
37
+ * @param serverOp - The server's operation that has already been applied
38
+ * @returns TransformResult indicating how the client operation should be handled
39
+ */
40
+ readonly transformOperation: (
41
+ clientOp: Operation.Operation<any, any, any>,
42
+ serverOp: Operation.Operation<any, any, any>
43
+ ) => Transform.TransformResult;
44
+ }
45
+
46
+ /**
47
+ * Any primitive type - used for generic constraints.
48
+ */
49
+ export type AnyPrimitive = Primitive<any, any>;
50
+
51
+ /**
52
+ * Infer the state type from a primitive.
53
+ */
54
+ export type InferState<T> = T extends Primitive<infer S, any> ? S : never;
55
+
56
+ /**
57
+ * Infer the proxy type from a primitive.
58
+ */
59
+ export type InferProxy<T> = T extends Primitive<any, infer P> ? P : never;
60
+
61
+ /**
62
+ * Helper type to conditionally add undefined based on TDefined.
63
+ * When TDefined is true, the value is guaranteed to be defined (via required() or default()).
64
+ * When TDefined is false, the value may be undefined.
65
+ */
66
+ export type MaybeUndefined<T, TDefined extends boolean> = TDefined extends true ? T : T | undefined;
67
+
68
+ /**
69
+ * Infer the snapshot type from a primitive.
70
+ * The snapshot is a readonly, type-safe structure suitable for rendering.
71
+ */
72
+ export type InferSnapshot<T> = T extends Primitive<any, infer P>
73
+ ? P extends { toSnapshot(): infer S } ? S : never
74
+ : never;
75
+
76
+ // =============================================================================
77
+ // Validation Errors
78
+ // =============================================================================
79
+
80
+ export class ValidationError extends Error {
81
+ readonly _tag = "ValidationError";
82
+ constructor(message: string) {
83
+ super(message);
84
+ this.name = "ValidationError";
85
+ }
86
+ }
87
+
88
+ // =============================================================================
89
+ // Validation Infrastructure
90
+ // =============================================================================
91
+
92
+ /**
93
+ * A validator that checks a value and returns whether it's valid.
94
+ */
95
+ export interface Validator<T> {
96
+ readonly validate: (value: T) => boolean;
97
+ readonly message: string;
98
+ }
99
+
100
+
101
+ /**
102
+ * Runs all validators against a value, throwing ValidationError if any fail.
103
+ */
104
+ export function runValidators<T>(value: T, validators: readonly { validate: (value: T) => boolean; message: string }[]): void {
105
+ for (const validator of validators) {
106
+ if (!validator.validate(value)) {
107
+ throw new ValidationError(validator.message);
108
+ }
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Checks if an operation is compatible with the given operation definitions.
114
+ * @param operation - The operation to check.
115
+ * @param operationDefinitions - The operation definitions to check against.
116
+ * @returns True if the operation is compatible, false otherwise.
117
+ */
118
+ export function isCompatibleOperation(operation: Operation.Operation<any, any, any>, operationDefinitions: Record<string, OperationDefinition.OperationDefinition<any, any, any>>) {
119
+ const values = Object.values(operationDefinitions);
120
+ return values.some(value => value.kind === operation.kind);
121
+ }
122
+