@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,89 @@
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, AnyPrimitive, InferState, InferProxy, InferSnapshot } from "../Primitive";
8
+ import { ValidationError } from "../Primitive";
9
+ import { runValidators } from "./shared";
10
+
11
+
12
+ /**
13
+ * Type to infer state from a lazy thunk
14
+ */
15
+ export type InferLazyState<T extends () => AnyPrimitive> = InferState<ReturnType<T>>;
16
+
17
+ /**
18
+ * Type to infer proxy from a lazy thunk
19
+ */
20
+ export type InferLazyProxy<T extends () => AnyPrimitive> = InferProxy<ReturnType<T>>;
21
+
22
+ /**
23
+ * Type to infer snapshot from a lazy thunk
24
+ */
25
+ export type InferLazySnapshot<T extends () => AnyPrimitive> = InferSnapshot<ReturnType<T>>;
26
+
27
+ export class LazyPrimitive<TThunk extends () => AnyPrimitive>
28
+ implements Primitive<InferLazyState<TThunk>, InferLazyProxy<TThunk>>
29
+ {
30
+ readonly _tag = "LazyPrimitive" as const;
31
+ readonly _State!: InferLazyState<TThunk>;
32
+ readonly _Proxy!: InferLazyProxy<TThunk>;
33
+
34
+ private readonly _thunk: TThunk;
35
+ private _resolved: ReturnType<TThunk> | undefined;
36
+
37
+ constructor(thunk: TThunk) {
38
+ this._thunk = thunk;
39
+ }
40
+
41
+ /** Resolve and cache the lazy primitive */
42
+ private _resolve(): ReturnType<TThunk> {
43
+ if (this._resolved === undefined) {
44
+ this._resolved = this._thunk() as ReturnType<TThunk>;
45
+ }
46
+ return this._resolved;
47
+ }
48
+
49
+ /** Mark this lazy primitive as required (delegates to resolved) */
50
+ required(): LazyPrimitive<TThunk> {
51
+ // Note: For lazy, we can't easily propagate required to the resolved primitive
52
+ // without resolving it first. This is a limitation.
53
+ return this;
54
+ }
55
+
56
+ readonly _internal: PrimitiveInternal<InferLazyState<TThunk>, InferLazyProxy<TThunk>> = {
57
+ createProxy: (env: ProxyEnvironment.ProxyEnvironment, operationPath: OperationPath.OperationPath): InferLazyProxy<TThunk> => {
58
+ const resolved = this._resolve();
59
+ return resolved._internal.createProxy(env, operationPath) as InferLazyProxy<TThunk>;
60
+ },
61
+
62
+ applyOperation: (
63
+ state: InferLazyState<TThunk> | undefined,
64
+ operation: Operation.Operation<any, any, any>
65
+ ): InferLazyState<TThunk> => {
66
+ const resolved = this._resolve();
67
+ return resolved._internal.applyOperation(state, operation) as InferLazyState<TThunk>;
68
+ },
69
+
70
+ getInitialState: (): InferLazyState<TThunk> | undefined => {
71
+ const resolved = this._resolve();
72
+ return resolved._internal.getInitialState() as InferLazyState<TThunk> | undefined;
73
+ },
74
+
75
+ transformOperation: (
76
+ clientOp: Operation.Operation<any, any, any>,
77
+ serverOp: Operation.Operation<any, any, any>
78
+ ): Transform.TransformResult => {
79
+ // Delegate to resolved primitive
80
+ const resolved = this._resolve();
81
+ return resolved._internal.transformOperation(clientOp, serverOp);
82
+ },
83
+ };
84
+ }
85
+
86
+ /** Creates a new LazyPrimitive with the given thunk */
87
+ export const Lazy = <TThunk extends () => AnyPrimitive>(thunk: TThunk): LazyPrimitive<TThunk> =>
88
+ new LazyPrimitive(thunk);
89
+
@@ -0,0 +1,128 @@
1
+ import { 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, Validator } from "../Primitive";
8
+ import { ValidationError } from "../Primitive";
9
+ import { runValidators, isCompatibleOperation } from "./shared";
10
+
11
+
12
+ /** Valid literal types */
13
+ export type LiteralValue = string | number | boolean | null;
14
+
15
+ export interface LiteralProxy<T extends LiteralValue, TDefined extends boolean = false> {
16
+ /** Gets the current literal value */
17
+ get(): MaybeUndefined<T, TDefined>;
18
+ /** Sets the literal value (must match the exact literal type) */
19
+ set(value: T): void;
20
+ /** Returns a readonly snapshot of the literal value for rendering */
21
+ toSnapshot(): MaybeUndefined<T, TDefined>;
22
+ }
23
+
24
+ interface LiteralPrimitiveSchema<T extends LiteralValue> {
25
+ readonly required: boolean;
26
+ readonly defaultValue: T | undefined;
27
+ readonly literal: T;
28
+ }
29
+
30
+ export class LiteralPrimitive<T extends LiteralValue, TDefined extends boolean = false> implements Primitive<T, LiteralProxy<T, TDefined>> {
31
+ readonly _tag = "LiteralPrimitive" as const;
32
+ readonly _State!: T;
33
+ readonly _Proxy!: LiteralProxy<T, TDefined>;
34
+
35
+ private readonly _schema: LiteralPrimitiveSchema<T>;
36
+
37
+ private readonly _opDefinitions = {
38
+ set: OperationDefinition.make({
39
+ kind: "literal.set" as const,
40
+ payload: Schema.Unknown,
41
+ target: Schema.Unknown,
42
+ apply: (payload) => payload,
43
+ }),
44
+ };
45
+
46
+ constructor(schema: LiteralPrimitiveSchema<T>) {
47
+ this._schema = schema;
48
+ }
49
+
50
+ /** Mark this literal as required */
51
+ required(): LiteralPrimitive<T, true> {
52
+ return new LiteralPrimitive({
53
+ ...this._schema,
54
+ required: true,
55
+ });
56
+ }
57
+
58
+ /** Set a default value for this literal */
59
+ default(defaultValue: T): LiteralPrimitive<T, true> {
60
+ return new LiteralPrimitive({
61
+ ...this._schema,
62
+ defaultValue,
63
+ });
64
+ }
65
+
66
+ /** Get the literal value this primitive represents */
67
+ get literal(): T {
68
+ return this._schema.literal;
69
+ }
70
+
71
+ readonly _internal: PrimitiveInternal<T, LiteralProxy<T, TDefined>> = {
72
+ createProxy: (env: ProxyEnvironment.ProxyEnvironment, operationPath: OperationPath.OperationPath): LiteralProxy<T, TDefined> => {
73
+ const defaultValue = this._schema.defaultValue;
74
+ return {
75
+ get: (): MaybeUndefined<T, TDefined> => {
76
+ const state = env.getState(operationPath) as T | undefined;
77
+ return (state ?? defaultValue) as MaybeUndefined<T, TDefined>;
78
+ },
79
+ set: (value: T) => {
80
+ env.addOperation(
81
+ Operation.fromDefinition(operationPath, this._opDefinitions.set, value)
82
+ );
83
+ },
84
+ toSnapshot: (): MaybeUndefined<T, TDefined> => {
85
+ const state = env.getState(operationPath) as T | undefined;
86
+ return (state ?? defaultValue) as MaybeUndefined<T, TDefined>;
87
+ },
88
+ };
89
+ },
90
+
91
+ applyOperation: (state: T | undefined, operation: Operation.Operation<any, any, any>): T => {
92
+ if (operation.kind !== "literal.set") {
93
+ throw new ValidationError(`LiteralPrimitive cannot apply operation of kind: ${operation.kind}`);
94
+ }
95
+
96
+ const payload = operation.payload;
97
+ if (payload !== this._schema.literal) {
98
+ throw new ValidationError(
99
+ `LiteralPrimitive.set requires the exact literal value "${globalThis.String(this._schema.literal)}", got: "${globalThis.String(payload)}"`
100
+ );
101
+ }
102
+
103
+ return payload as T;
104
+ },
105
+
106
+ getInitialState: (): T | undefined => {
107
+ return this._schema.defaultValue;
108
+ },
109
+
110
+ transformOperation: (
111
+ clientOp: Operation.Operation<any, any, any>,
112
+ serverOp: Operation.Operation<any, any, any>
113
+ ): Transform.TransformResult => {
114
+ // If paths don't overlap, no transformation needed
115
+ if (!OperationPath.pathsOverlap(clientOp.path, serverOp.path)) {
116
+ return { type: "transformed", operation: clientOp };
117
+ }
118
+
119
+ // For same path, client wins (last-write-wins)
120
+ return { type: "transformed", operation: clientOp };
121
+ },
122
+ };
123
+ }
124
+
125
+ /** Creates a new LiteralPrimitive with the given literal value */
126
+ export const Literal = <T extends LiteralValue>(literal: T): LiteralPrimitive<T, false> =>
127
+ new LiteralPrimitive({ required: false, defaultValue: undefined, literal });
128
+
@@ -0,0 +1,169 @@
1
+ import { 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, Validator } from "../Primitive";
8
+ import { ValidationError } from "../Primitive";
9
+ import { runValidators, isCompatibleOperation } from "./shared";
10
+
11
+
12
+ export interface NumberProxy<TDefined extends boolean = false> {
13
+ /** Gets the current number value */
14
+ get(): MaybeUndefined<number, TDefined>;
15
+ /** Sets the number value, generating a number.set operation */
16
+ set(value: number): void;
17
+ /** Returns a readonly snapshot of the number value for rendering */
18
+ toSnapshot(): MaybeUndefined<number, TDefined>;
19
+ }
20
+
21
+ interface NumberPrimitiveSchema {
22
+ readonly required: boolean;
23
+ readonly defaultValue: number | undefined;
24
+ readonly validators: readonly Validator<number>[];
25
+ }
26
+
27
+ export class NumberPrimitive<TDefined extends boolean = false> implements Primitive<number, NumberProxy<TDefined>> {
28
+ readonly _tag = "NumberPrimitive" as const;
29
+ readonly _State!: number;
30
+ readonly _Proxy!: NumberProxy<TDefined>;
31
+
32
+ private readonly _schema: NumberPrimitiveSchema;
33
+
34
+ private readonly _opDefinitions = {
35
+ set: OperationDefinition.make({
36
+ kind: "number.set" as const,
37
+ payload: Schema.Number,
38
+ target: Schema.Number,
39
+ apply: (payload) => payload,
40
+ }),
41
+ };
42
+
43
+ constructor(schema: NumberPrimitiveSchema) {
44
+ this._schema = schema;
45
+ }
46
+
47
+ /** Mark this number as required */
48
+ required(): NumberPrimitive<true> {
49
+ return new NumberPrimitive({
50
+ ...this._schema,
51
+ required: true,
52
+ });
53
+ }
54
+
55
+ /** Set a default value for this number */
56
+ default(defaultValue: number): NumberPrimitive<true> {
57
+ return new NumberPrimitive({
58
+ ...this._schema,
59
+ defaultValue,
60
+ });
61
+ }
62
+
63
+ /** Add a custom validation rule */
64
+ refine(fn: (value: number) => boolean, message: string): NumberPrimitive<TDefined> {
65
+ return new NumberPrimitive({
66
+ ...this._schema,
67
+ validators: [...this._schema.validators, { validate: fn, message }],
68
+ });
69
+ }
70
+
71
+ /** Minimum value (inclusive) */
72
+ min(value: number): NumberPrimitive<TDefined> {
73
+ return this.refine(
74
+ (v) => v >= value,
75
+ `Number must be at least ${value}`
76
+ );
77
+ }
78
+
79
+ /** Maximum value (inclusive) */
80
+ max(value: number): NumberPrimitive<TDefined> {
81
+ return this.refine(
82
+ (v) => v <= value,
83
+ `Number must be at most ${value}`
84
+ );
85
+ }
86
+
87
+ /** Must be positive (> 0) */
88
+ positive(): NumberPrimitive<TDefined> {
89
+ return this.refine(
90
+ (v) => v > 0,
91
+ "Number must be positive"
92
+ );
93
+ }
94
+
95
+ /** Must be negative (< 0) */
96
+ negative(): NumberPrimitive<TDefined> {
97
+ return this.refine(
98
+ (v) => v < 0,
99
+ "Number must be negative"
100
+ );
101
+ }
102
+
103
+ /** Must be an integer */
104
+ int(): NumberPrimitive<TDefined> {
105
+ return this.refine(
106
+ (v) => globalThis.Number.isInteger(v),
107
+ "Number must be an integer"
108
+ );
109
+ }
110
+
111
+ readonly _internal: PrimitiveInternal<number, NumberProxy<TDefined>> = {
112
+ createProxy: (env: ProxyEnvironment.ProxyEnvironment, operationPath: OperationPath.OperationPath): NumberProxy<TDefined> => {
113
+ const defaultValue = this._schema.defaultValue;
114
+ return {
115
+ get: (): MaybeUndefined<number, TDefined> => {
116
+ const state = env.getState(operationPath) as number | undefined;
117
+ return (state ?? defaultValue) as MaybeUndefined<number, TDefined>;
118
+ },
119
+ set: (value: number) => {
120
+ env.addOperation(
121
+ Operation.fromDefinition(operationPath, this._opDefinitions.set, value)
122
+ );
123
+ },
124
+ toSnapshot: (): MaybeUndefined<number, TDefined> => {
125
+ const state = env.getState(operationPath) as number | undefined;
126
+ return (state ?? defaultValue) as MaybeUndefined<number, TDefined>;
127
+ },
128
+ };
129
+ },
130
+
131
+ applyOperation: (state: number | undefined, operation: Operation.Operation<any, any, any>): number => {
132
+ if (operation.kind !== "number.set") {
133
+ throw new ValidationError(`NumberPrimitive cannot apply operation of kind: ${operation.kind}`);
134
+ }
135
+
136
+ const payload = operation.payload;
137
+ if (typeof payload !== "number") {
138
+ throw new ValidationError(`NumberPrimitive.set requires a number payload, got: ${typeof payload}`);
139
+ }
140
+
141
+ // Run validators
142
+ runValidators(payload, this._schema.validators);
143
+
144
+ return payload;
145
+ },
146
+
147
+ getInitialState: (): number | undefined => {
148
+ return this._schema.defaultValue;
149
+ },
150
+
151
+ transformOperation: (
152
+ clientOp: Operation.Operation<any, any, any>,
153
+ serverOp: Operation.Operation<any, any, any>
154
+ ): Transform.TransformResult => {
155
+ // If paths don't overlap, no transformation needed
156
+ if (!OperationPath.pathsOverlap(clientOp.path, serverOp.path)) {
157
+ return { type: "transformed", operation: clientOp };
158
+ }
159
+
160
+ // For same path, client wins (last-write-wins)
161
+ return { type: "transformed", operation: clientOp };
162
+ },
163
+ };
164
+ }
165
+
166
+ /** Creates a new NumberPrimitive */
167
+ export const Number = (): NumberPrimitive<false> =>
168
+ new NumberPrimitive({ required: false, defaultValue: undefined, validators: [] });
169
+
@@ -0,0 +1,189 @@
1
+ import { 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, Validator } from "./shared";
8
+ import { runValidators, isCompatibleOperation, ValidationError } from "./shared";
9
+
10
+ // =============================================================================
11
+ // String Primitive
12
+ // =============================================================================
13
+
14
+ export interface StringProxy<TDefined extends boolean = false> {
15
+ /** Gets the current string value */
16
+ get(): MaybeUndefined<string, TDefined>;
17
+ /** Sets the string value, generating a string.set operation */
18
+ set(value: string): void;
19
+ /** Returns a readonly snapshot of the string value for rendering */
20
+ toSnapshot(): MaybeUndefined<string, TDefined>;
21
+ }
22
+
23
+ interface StringPrimitiveSchema {
24
+ readonly required: boolean;
25
+ readonly defaultValue: string | undefined;
26
+ readonly validators: readonly Validator<string>[];
27
+ }
28
+
29
+ export class StringPrimitive<TDefined extends boolean = false> implements Primitive<string, StringProxy<TDefined>> {
30
+ readonly _tag = "StringPrimitive" as const;
31
+ readonly _State!: string;
32
+ readonly _Proxy!: StringProxy<TDefined>;
33
+
34
+ private readonly _schema: StringPrimitiveSchema;
35
+
36
+ private readonly _opDefinitions = {
37
+ set: OperationDefinition.make({
38
+ kind: "string.set" as const,
39
+ payload: Schema.String,
40
+ target: Schema.String,
41
+ apply: (payload) => payload,
42
+ }),
43
+ };
44
+
45
+ constructor(schema: StringPrimitiveSchema) {
46
+ this._schema = schema;
47
+ }
48
+
49
+ /** Mark this string as required */
50
+ required(): StringPrimitive<true> {
51
+ return new StringPrimitive({
52
+ ...this._schema,
53
+ required: true,
54
+ });
55
+ }
56
+
57
+ /** Set a default value for this string */
58
+ default(defaultValue: string): StringPrimitive<true> {
59
+ return new StringPrimitive({
60
+ ...this._schema,
61
+ defaultValue,
62
+ });
63
+ }
64
+
65
+ /** Add a custom validation rule */
66
+ refine(fn: (value: string) => boolean, message: string): StringPrimitive<TDefined> {
67
+ return new StringPrimitive({
68
+ ...this._schema,
69
+ validators: [...this._schema.validators, { validate: fn, message }],
70
+ });
71
+ }
72
+
73
+ /** Minimum string length */
74
+ min(length: number): StringPrimitive<TDefined> {
75
+ return this.refine(
76
+ (v) => v.length >= length,
77
+ `String must be at least ${length} characters`
78
+ );
79
+ }
80
+
81
+ /** Maximum string length */
82
+ max(length: number): StringPrimitive<TDefined> {
83
+ return this.refine(
84
+ (v) => v.length <= length,
85
+ `String must be at most ${length} characters`
86
+ );
87
+ }
88
+
89
+ /** Exact string length */
90
+ length(exact: number): StringPrimitive<TDefined> {
91
+ return this.refine(
92
+ (v) => v.length === exact,
93
+ `String must be exactly ${exact} characters`
94
+ );
95
+ }
96
+
97
+ /** Match a regex pattern */
98
+ regex(pattern: RegExp, message?: string): StringPrimitive<TDefined> {
99
+ return this.refine(
100
+ (v) => pattern.test(v),
101
+ message ?? `String must match pattern ${pattern}`
102
+ );
103
+ }
104
+
105
+ /** Validate as email format */
106
+ email(): StringPrimitive<TDefined> {
107
+ // Simple email regex - covers most common cases
108
+ const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
109
+ return this.refine(
110
+ (v) => emailPattern.test(v),
111
+ "Invalid email format"
112
+ );
113
+ }
114
+
115
+ /** Validate as URL format */
116
+ url(): StringPrimitive<TDefined> {
117
+ return this.refine(
118
+ (v) => {
119
+ try {
120
+ new URL(v);
121
+ return true;
122
+ } catch {
123
+ return false;
124
+ }
125
+ },
126
+ "Invalid URL format"
127
+ );
128
+ }
129
+
130
+ readonly _internal: PrimitiveInternal<string, StringProxy<TDefined>> = {
131
+ createProxy: (env: ProxyEnvironment.ProxyEnvironment, operationPath: OperationPath.OperationPath): StringProxy<TDefined> => {
132
+ const defaultValue = this._schema.defaultValue;
133
+ return {
134
+ get: (): MaybeUndefined<string, TDefined> => {
135
+ const state = env.getState(operationPath) as string | undefined;
136
+ return (state ?? defaultValue) as MaybeUndefined<string, TDefined>;
137
+ },
138
+ set: (value: string) => {
139
+ env.addOperation(
140
+ Operation.fromDefinition(operationPath, this._opDefinitions.set, value)
141
+ );
142
+ },
143
+ toSnapshot: (): MaybeUndefined<string, TDefined> => {
144
+ const state = env.getState(operationPath) as string | undefined;
145
+ return (state ?? defaultValue) as MaybeUndefined<string, TDefined>;
146
+ },
147
+ };
148
+ },
149
+
150
+ applyOperation: (state: string | undefined, operation: Operation.Operation<any, any, any>): string => {
151
+ if (!isCompatibleOperation(operation, this._opDefinitions)) {
152
+ throw new ValidationError(`StringPrimitive cannot apply operation of kind: ${operation.kind}`);
153
+ }
154
+
155
+ const payload = operation.payload;
156
+ if (typeof payload !== "string") {
157
+ throw new ValidationError(`StringPrimitive.set requires a string payload, got: ${typeof payload}`);
158
+ }
159
+
160
+ // Run validators
161
+ runValidators(payload, this._schema.validators);
162
+
163
+ return payload;
164
+ },
165
+
166
+ getInitialState: (): string | undefined => {
167
+ return this._schema.defaultValue;
168
+ },
169
+
170
+ transformOperation: (
171
+ clientOp: Operation.Operation<any, any, any>,
172
+ serverOp: Operation.Operation<any, any, any>
173
+ ): Transform.TransformResult => {
174
+ // If paths don't overlap, no transformation needed
175
+ if (!OperationPath.pathsOverlap(clientOp.path, serverOp.path)) {
176
+ return { type: "transformed", operation: clientOp };
177
+ }
178
+
179
+ // For same path, client wins (last-write-wins)
180
+ // Client operation proceeds as-is
181
+ return { type: "transformed", operation: clientOp };
182
+ },
183
+ };
184
+ }
185
+
186
+ /** Creates a new StringPrimitive */
187
+ export const String = (): StringPrimitive<false> =>
188
+ new StringPrimitive({ required: false, defaultValue: undefined, validators: [] });
189
+