@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,348 @@
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, InferState, InferProxy, InferSnapshot } from "../Primitive";
8
+ import { ValidationError } from "../Primitive";
9
+ import { runValidators } from "./shared";
10
+
11
+
12
+ /**
13
+ * Maps a schema definition to its state type.
14
+ * { name: StringPrimitive, age: NumberPrimitive } -> { name: string, age: number }
15
+ */
16
+ export type InferStructState<TFields extends Record<string, AnyPrimitive>> = {
17
+ readonly [K in keyof TFields]: InferState<TFields[K]>;
18
+ };
19
+
20
+ /**
21
+ * Maps a schema definition to its snapshot type.
22
+ * Each field's snapshot type is inferred from the field primitive.
23
+ */
24
+ export type InferStructSnapshot<TFields extends Record<string, AnyPrimitive>> = {
25
+ readonly [K in keyof TFields]: InferSnapshot<TFields[K]>;
26
+ };
27
+
28
+ /**
29
+ * Maps a schema definition to its proxy type.
30
+ * Provides nested field access + get()/set()/toSnapshot() methods for the whole struct.
31
+ */
32
+ export type StructProxy<TFields extends Record<string, AnyPrimitive>, TDefined extends boolean = false> = {
33
+ readonly [K in keyof TFields]: InferProxy<TFields[K]>;
34
+ } & {
35
+ /** Gets the entire struct value */
36
+ get(): MaybeUndefined<InferStructState<TFields>, TDefined>;
37
+ /** Sets the entire struct value */
38
+ set(value: InferStructState<TFields>): void;
39
+ /** Returns a readonly snapshot of the struct for rendering */
40
+ toSnapshot(): MaybeUndefined<InferStructSnapshot<TFields>, TDefined>;
41
+ };
42
+
43
+ interface StructPrimitiveSchema<TFields extends Record<string, AnyPrimitive>> {
44
+ readonly required: boolean;
45
+ readonly defaultValue: InferStructState<TFields> | undefined;
46
+ readonly fields: TFields;
47
+ readonly validators: readonly Validator<InferStructState<TFields>>[];
48
+ }
49
+
50
+ export class StructPrimitive<TFields extends Record<string, AnyPrimitive>, TDefined extends boolean = false>
51
+ implements Primitive<InferStructState<TFields>, StructProxy<TFields, TDefined>>
52
+ {
53
+ readonly _tag = "StructPrimitive" as const;
54
+ readonly _State!: InferStructState<TFields>;
55
+ readonly _Proxy!: StructProxy<TFields, TDefined>;
56
+
57
+ private readonly _schema: StructPrimitiveSchema<TFields>;
58
+
59
+ private readonly _opDefinitions = {
60
+ set: OperationDefinition.make({
61
+ kind: "struct.set" as const,
62
+ payload: Schema.Unknown,
63
+ target: Schema.Unknown,
64
+ apply: (payload) => payload,
65
+ }),
66
+ };
67
+
68
+ constructor(schema: StructPrimitiveSchema<TFields>) {
69
+ this._schema = schema;
70
+ }
71
+
72
+ /** Mark this struct as required */
73
+ required(): StructPrimitive<TFields, true> {
74
+ return new StructPrimitive({
75
+ ...this._schema,
76
+ required: true,
77
+ });
78
+ }
79
+
80
+ /** Set a default value for this struct */
81
+ default(defaultValue: InferStructState<TFields>): StructPrimitive<TFields, true> {
82
+ return new StructPrimitive({
83
+ ...this._schema,
84
+ defaultValue,
85
+ });
86
+ }
87
+
88
+ /** Get the fields schema */
89
+ get fields(): TFields {
90
+ return this._schema.fields;
91
+ }
92
+
93
+ /** Add a custom validation rule (useful for cross-field validation) */
94
+ refine(fn: (value: InferStructState<TFields>) => boolean, message: string): StructPrimitive<TFields, TDefined> {
95
+ return new StructPrimitive({
96
+ ...this._schema,
97
+ validators: [...this._schema.validators, { validate: fn, message }],
98
+ });
99
+ }
100
+
101
+ readonly _internal: PrimitiveInternal<InferStructState<TFields>, StructProxy<TFields, TDefined>> = {
102
+ createProxy: (env: ProxyEnvironment.ProxyEnvironment, operationPath: OperationPath.OperationPath): StructProxy<TFields, TDefined> => {
103
+ const fields = this._schema.fields;
104
+ const defaultValue = this._schema.defaultValue;
105
+
106
+ // Helper to build a snapshot by calling toSnapshot on each field
107
+ const buildSnapshot = (): InferStructSnapshot<TFields> | undefined => {
108
+ const state = env.getState(operationPath);
109
+
110
+ // Build snapshot from field proxies (they handle their own defaults)
111
+ const snapshot: Record<string, unknown> = {};
112
+ let hasAnyDefinedField = false;
113
+
114
+ for (const key in fields) {
115
+ const fieldPrimitive = fields[key]!;
116
+ const fieldPath = operationPath.append(key);
117
+ const fieldProxy = fieldPrimitive._internal.createProxy(env, fieldPath);
118
+ const fieldSnapshot = (fieldProxy as { toSnapshot(): unknown }).toSnapshot();
119
+ snapshot[key] = fieldSnapshot;
120
+ if (fieldSnapshot !== undefined) {
121
+ hasAnyDefinedField = true;
122
+ }
123
+ }
124
+
125
+ // Return undefined only if there's no state, no struct default, and no field snapshots
126
+ if (state === undefined && defaultValue === undefined && !hasAnyDefinedField) {
127
+ return undefined;
128
+ }
129
+
130
+ return snapshot as InferStructSnapshot<TFields>;
131
+ };
132
+
133
+ // Create the base object with get/set/toSnapshot methods
134
+ const base = {
135
+ get: (): MaybeUndefined<InferStructState<TFields>, TDefined> => {
136
+ const state = env.getState(operationPath) as InferStructState<TFields> | undefined;
137
+ return (state ?? defaultValue) as MaybeUndefined<InferStructState<TFields>, TDefined>;
138
+ },
139
+ set: (value: InferStructState<TFields>) => {
140
+ env.addOperation(
141
+ Operation.fromDefinition(operationPath, this._opDefinitions.set, value)
142
+ );
143
+ },
144
+ toSnapshot: (): MaybeUndefined<InferStructSnapshot<TFields>, TDefined> => {
145
+ const snapshot = buildSnapshot();
146
+ return snapshot as MaybeUndefined<InferStructSnapshot<TFields>, TDefined>;
147
+ },
148
+ };
149
+
150
+ // Use a JavaScript Proxy to intercept field access
151
+ return new globalThis.Proxy(base as StructProxy<TFields, TDefined>, {
152
+ get: (target, prop, receiver) => {
153
+ // Return base methods (get, set, toSnapshot)
154
+ if (prop === "get") {
155
+ return target.get;
156
+ }
157
+ if (prop === "set") {
158
+ return target.set;
159
+ }
160
+ if (prop === "toSnapshot") {
161
+ return target.toSnapshot;
162
+ }
163
+
164
+ // Handle symbol properties (like Symbol.toStringTag)
165
+ if (typeof prop === "symbol") {
166
+ return undefined;
167
+ }
168
+
169
+ // Check if prop is a field in the schema
170
+ if (prop in fields) {
171
+ const fieldPrimitive = fields[prop as keyof TFields]!;
172
+ const fieldPath = operationPath.append(prop as string);
173
+ return fieldPrimitive._internal.createProxy(env, fieldPath);
174
+ }
175
+
176
+ return undefined;
177
+ },
178
+ has: (target, prop) => {
179
+ if (prop === "get" || prop === "set" || prop === "toSnapshot") return true;
180
+ if (typeof prop === "string" && prop in fields) return true;
181
+ return false;
182
+ },
183
+ });
184
+ },
185
+
186
+ applyOperation: (
187
+ state: InferStructState<TFields> | undefined,
188
+ operation: Operation.Operation<any, any, any>
189
+ ): InferStructState<TFields> => {
190
+ const path = operation.path;
191
+ const tokens = path.toTokens().filter((t: string) => t !== "");
192
+
193
+ let newState: InferStructState<TFields>;
194
+
195
+ // If path is empty or root, this is a struct.set operation
196
+ if (tokens.length === 0) {
197
+ if (operation.kind !== "struct.set") {
198
+ throw new ValidationError(`StructPrimitive 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(`StructPrimitive.set requires an object payload`);
204
+ }
205
+
206
+ newState = payload as InferStructState<TFields>;
207
+ } else {
208
+ // Otherwise, delegate to the appropriate field primitive
209
+ const fieldName = tokens[0] as keyof TFields;
210
+ if (!(fieldName in this._schema.fields)) {
211
+ throw new ValidationError(`Unknown field: ${globalThis.String(fieldName)}`);
212
+ }
213
+
214
+ const fieldPrimitive = this._schema.fields[fieldName]!;
215
+ const remainingPath = path.shift();
216
+ const fieldOperation = {
217
+ ...operation,
218
+ path: remainingPath,
219
+ };
220
+
221
+ // Get the current field state
222
+ const currentState = state ?? ({} as InferStructState<TFields>);
223
+ const currentFieldState = currentState[fieldName] as InferState<typeof fieldPrimitive> | undefined;
224
+
225
+ // Apply the operation to the field
226
+ const newFieldState = fieldPrimitive._internal.applyOperation(currentFieldState, fieldOperation);
227
+
228
+ // Build updated state
229
+ newState = {
230
+ ...currentState,
231
+ [fieldName]: newFieldState,
232
+ };
233
+ }
234
+
235
+ // Run validators on the new state
236
+ runValidators(newState, this._schema.validators);
237
+
238
+ return newState;
239
+ },
240
+
241
+ getInitialState: (): InferStructState<TFields> | undefined => {
242
+ if (this._schema.defaultValue !== undefined) {
243
+ return this._schema.defaultValue;
244
+ }
245
+
246
+ // Build initial state from field defaults
247
+ const fields = this._schema.fields;
248
+ const initialState: Record<string, unknown> = {};
249
+ let hasAnyDefault = false;
250
+
251
+ for (const key in fields) {
252
+ const fieldDefault = fields[key]!._internal.getInitialState();
253
+ if (fieldDefault !== undefined) {
254
+ initialState[key] = fieldDefault;
255
+ hasAnyDefault = true;
256
+ }
257
+ }
258
+
259
+ return hasAnyDefault ? (initialState as InferStructState<TFields>) : undefined;
260
+ },
261
+
262
+ transformOperation: (
263
+ clientOp: Operation.Operation<any, any, any>,
264
+ serverOp: Operation.Operation<any, any, any>
265
+ ): Transform.TransformResult => {
266
+ const clientPath = clientOp.path;
267
+ const serverPath = serverOp.path;
268
+
269
+ // If paths don't overlap at all, no transformation needed
270
+ if (!OperationPath.pathsOverlap(clientPath, serverPath)) {
271
+ return { type: "transformed", operation: clientOp };
272
+ }
273
+
274
+ const clientTokens = clientPath.toTokens().filter((t: string) => t !== "");
275
+ const serverTokens = serverPath.toTokens().filter((t: string) => t !== "");
276
+
277
+ // If both are at root level (struct.set operations)
278
+ if (clientTokens.length === 0 && serverTokens.length === 0) {
279
+ // Client wins (last-write-wins)
280
+ return { type: "transformed", operation: clientOp };
281
+ }
282
+
283
+ // If server set entire struct and client is updating a field
284
+ if (serverTokens.length === 0 && serverOp.kind === "struct.set") {
285
+ // Client's field operation proceeds - optimistic update
286
+ // Server will validate/reject if needed
287
+ return { type: "transformed", operation: clientOp };
288
+ }
289
+
290
+ // If client set entire struct and server is updating a field
291
+ if (clientTokens.length === 0 && clientOp.kind === "struct.set") {
292
+ // Client's struct.set supersedes server's field update
293
+ return { type: "transformed", operation: clientOp };
294
+ }
295
+
296
+ // Both operations target fields
297
+ if (clientTokens.length > 0 && serverTokens.length > 0) {
298
+ const clientField = clientTokens[0] as keyof TFields;
299
+ const serverField = serverTokens[0] as keyof TFields;
300
+
301
+ // Different fields - no conflict
302
+ if (clientField !== serverField) {
303
+ return { type: "transformed", operation: clientOp };
304
+ }
305
+
306
+ // Same field - delegate to field primitive
307
+ const fieldPrimitive = this._schema.fields[clientField];
308
+ if (!fieldPrimitive) {
309
+ return { type: "transformed", operation: clientOp };
310
+ }
311
+
312
+ const clientOpForField = {
313
+ ...clientOp,
314
+ path: clientOp.path.shift(),
315
+ };
316
+ const serverOpForField = {
317
+ ...serverOp,
318
+ path: serverOp.path.shift(),
319
+ };
320
+
321
+ const result = fieldPrimitive._internal.transformOperation(clientOpForField, serverOpForField);
322
+
323
+ if (result.type === "transformed") {
324
+ // Restore the original path
325
+ return {
326
+ type: "transformed",
327
+ operation: {
328
+ ...result.operation,
329
+ path: clientOp.path,
330
+ },
331
+ };
332
+ }
333
+
334
+ return result;
335
+ }
336
+
337
+ // Default: no transformation needed
338
+ return { type: "transformed", operation: clientOp };
339
+ },
340
+ };
341
+ }
342
+
343
+ /** Creates a new StructPrimitive with the given fields */
344
+ export const Struct = <TFields extends Record<string, AnyPrimitive>>(
345
+ fields: TFields
346
+ ): StructPrimitive<TFields, false> =>
347
+ new StructPrimitive({ required: false, defaultValue: undefined, fields, validators: [] });
348
+