@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
package/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # Voidsync structures
2
+
3
+ ## OperationDefinition
4
+ Definice operace je struktura, která popisuje, jakou podobu má operace, jaké jsou její parametry a jakým způsobem se má aplikovat.
5
+
6
+ ## OperationPath
7
+ Cesta k elementu, na který se operace aplikuje.
8
+
9
+
10
+ ## Operation
11
+ Operace je jedna atomická změna ve strukture.
12
+
13
+ ## Transaction
14
+ Transakce je kolekce jedné nebo více operací, které jsou aplikovány najednou. Každá transakce má své ID a jedná se jednotku sloužící k synchronizaci mezi klienty.
15
+
16
+
17
+
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@voidhash/mimic",
3
+ "version": "0.0.1-alpha.1",
4
+ "type": "module",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/voidhashcom/voidhash",
8
+ "directory": "packages/mimic"
9
+ },
10
+ "main": "./src/index.ts",
11
+ "exports": {
12
+ ".": "./src/index.ts",
13
+ "./server": "./src/server/index.ts",
14
+ "./client": "./src/client/index.ts"
15
+ },
16
+ "scripts": {
17
+ "build": "tsdown",
18
+ "lint": "biome check .",
19
+ "typecheck": "tsc --noEmit",
20
+ "test": "vitest run -c vitest.mts"
21
+ },
22
+ "devDependencies": {
23
+ "@effect/vitest": "^0.27.0",
24
+ "@voidhash/tsconfig": "workspace:*",
25
+ "tsdown": "^0.18.2",
26
+ "typescript": "5.8.3",
27
+ "vite-tsconfig-paths": "^5.1.4",
28
+ "vitest": "^3.2.4"
29
+ },
30
+ "peerDependencies": {
31
+ "effect": "catalog:"
32
+ }
33
+ }
@@ -0,0 +1,256 @@
1
+ import * as Operation from "./Operation";
2
+ import * as OperationPath from "./OperationPath";
3
+ import * as ProxyEnvironment from "./ProxyEnvironment";
4
+ import * as Transaction from "./Transaction";
5
+ import type * as Primitive from "./Primitive";
6
+
7
+ // =============================================================================
8
+ // Document Errors
9
+ // =============================================================================
10
+
11
+ /**
12
+ * Error thrown when attempting to start a nested transaction.
13
+ */
14
+ export class NestedTransactionError extends Error {
15
+ readonly _tag = "NestedTransactionError";
16
+ constructor() {
17
+ super("Nested transactions are not supported");
18
+ this.name = "NestedTransactionError";
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Error thrown when an operation fails to apply.
24
+ */
25
+ export class OperationError extends Error {
26
+ readonly _tag = "OperationError";
27
+ constructor(message: string) {
28
+ super(message);
29
+ this.name = "OperationError";
30
+ }
31
+ }
32
+
33
+ // =============================================================================
34
+ // Document Interface
35
+ // =============================================================================
36
+
37
+ /**
38
+ * A Document manages state for a primitive-based schema with transaction support.
39
+ */
40
+ export interface Document<TSchema extends Primitive.AnyPrimitive> {
41
+ /** The schema defining this document's structure */
42
+ readonly schema: TSchema;
43
+
44
+ /** Root proxy for accessing and modifying document data */
45
+ readonly root: Primitive.InferProxy<TSchema>;
46
+
47
+ /** Returns the current document state */
48
+ get(): Primitive.InferState<TSchema> | undefined;
49
+
50
+ /**
51
+ * Returns a readonly snapshot of the entire document state for rendering.
52
+ * The snapshot is a type-safe, readonly structure where:
53
+ * - Required fields and fields with defaults are guaranteed to be defined
54
+ * - Optional fields may be undefined
55
+ */
56
+ toSnapshot(): Primitive.InferSnapshot<TSchema>;
57
+
58
+ /**
59
+ * Runs a function within a transaction.
60
+ * All operations are collected and applied atomically.
61
+ * If the function throws, all changes are rolled back.
62
+ * @returns The return value of the function
63
+ */
64
+ transaction<R>(fn: (root: Primitive.InferProxy<TSchema>) => R): R;
65
+
66
+ /**
67
+ * Applies external operations (e.g., from server/peers) to the document.
68
+ * These operations are NOT added to pending operations.
69
+ */
70
+ apply(ops: ReadonlyArray<Operation.Operation<any, any, any>>): void;
71
+
72
+ /**
73
+ * Returns pending local operations as a Transaction and clears the buffer.
74
+ */
75
+ flush(): Transaction.Transaction;
76
+ }
77
+
78
+ // =============================================================================
79
+ // Document Options
80
+ // =============================================================================
81
+
82
+ export interface DocumentOptions<TSchema extends Primitive.AnyPrimitive> {
83
+ /** Initial state for the document */
84
+ readonly initial?: Primitive.InferState<TSchema>;
85
+ }
86
+
87
+ // =============================================================================
88
+ // Document Implementation
89
+ // =============================================================================
90
+
91
+ /**
92
+ * Creates a new Document for the given schema.
93
+ */
94
+ export const make = <TSchema extends Primitive.AnyPrimitive>(
95
+ schema: TSchema,
96
+ options?: DocumentOptions<TSchema>
97
+ ): Document<TSchema> => {
98
+ // Internal state
99
+ let _state: Primitive.InferState<TSchema> | undefined =
100
+ options?.initial ?? schema._internal.getInitialState();
101
+
102
+ // Pending operations buffer (local changes not yet flushed)
103
+ let _pending: Operation.Operation<any, any, any>[] = [];
104
+
105
+ // Transaction state
106
+ let _inTransaction = false;
107
+ let _txOps: Operation.Operation<any, any, any>[] = [];
108
+ let _txBaseState: Primitive.InferState<TSchema> | undefined = undefined;
109
+
110
+ /**
111
+ * Gets state at the given path.
112
+ */
113
+ const getStateAtPath = (path: OperationPath.OperationPath): unknown => {
114
+ const tokens = path.toTokens().filter(t => t !== "");
115
+
116
+ if (tokens.length === 0) {
117
+ return _state;
118
+ }
119
+
120
+ let current: unknown = _state;
121
+ for (const token of tokens) {
122
+ if (current === null || current === undefined) {
123
+ return undefined;
124
+ }
125
+
126
+ if (typeof current === "object") {
127
+ // Handle array entries (which have { id, pos, value } structure)
128
+ if (Array.isArray(current)) {
129
+ // Try to find by ID in array entries
130
+ const entry = current.find((e: any) => e.id === token);
131
+ if (entry) {
132
+ current = entry.value;
133
+ continue;
134
+ }
135
+ }
136
+
137
+ // Handle regular object property access
138
+ current = (current as Record<string, unknown>)[token];
139
+ } else {
140
+ return undefined;
141
+ }
142
+ }
143
+
144
+ return current;
145
+ };
146
+
147
+ /**
148
+ * Applies a single operation to the current state.
149
+ */
150
+ const applyOperation = (op: Operation.Operation<any, any, any>): void => {
151
+ try {
152
+ _state = schema._internal.applyOperation(_state, op);
153
+ } catch (error) {
154
+ if (error instanceof Error) {
155
+ throw new OperationError(error.message);
156
+ }
157
+ throw new OperationError(String(error));
158
+ }
159
+ };
160
+
161
+ /**
162
+ * Handles an operation from a proxy.
163
+ * In transaction mode: collects operations, applies to state immediately for subsequent reads.
164
+ * Outside transaction mode: auto-wraps in a single-operation transaction.
165
+ */
166
+ const handleOperation = (op: Operation.Operation<any, any, any>): void => {
167
+ if (_inTransaction) {
168
+ // In transaction: collect op and apply immediately for subsequent reads
169
+ _txOps.push(op);
170
+ applyOperation(op);
171
+ } else {
172
+ // Not in transaction: auto-wrap in single-operation transaction
173
+ const baseState = _state;
174
+ try {
175
+ applyOperation(op);
176
+ _pending.push(op);
177
+ } catch (error) {
178
+ // Rollback on error
179
+ _state = baseState;
180
+ throw error;
181
+ }
182
+ }
183
+ };
184
+
185
+ /**
186
+ * Creates a ProxyEnvironment for the document.
187
+ */
188
+ const createEnv = (): ProxyEnvironment.ProxyEnvironment => {
189
+ return ProxyEnvironment.make({
190
+ onOperation: handleOperation,
191
+ getState: getStateAtPath,
192
+ });
193
+ };
194
+
195
+ // Create the root proxy
196
+ const env = createEnv();
197
+ const rootProxy = schema._internal.createProxy(env, OperationPath.make("")) as Primitive.InferProxy<TSchema>;
198
+
199
+ // Document implementation
200
+ const document: Document<TSchema> = {
201
+ schema,
202
+ root: rootProxy,
203
+
204
+ get: (): Primitive.InferState<TSchema> | undefined => {
205
+ return _state;
206
+ },
207
+
208
+ toSnapshot: (): Primitive.InferSnapshot<TSchema> => {
209
+ return (rootProxy as { toSnapshot(): Primitive.InferSnapshot<TSchema> }).toSnapshot();
210
+ },
211
+
212
+ transaction: <R,>(fn: (root: Primitive.InferProxy<TSchema>) => R): R => {
213
+ if (_inTransaction) {
214
+ throw new NestedTransactionError();
215
+ }
216
+
217
+ // Start transaction
218
+ _inTransaction = true;
219
+ _txOps = [];
220
+ _txBaseState = _state;
221
+
222
+ try {
223
+ // Execute the transaction function
224
+ const result = fn(rootProxy);
225
+
226
+ // Commit: add transaction ops to pending
227
+ _pending.push(..._txOps);
228
+
229
+ return result;
230
+ } catch (error) {
231
+ // Rollback: restore base state
232
+ _state = _txBaseState;
233
+ throw error;
234
+ } finally {
235
+ // Clean up transaction state
236
+ _inTransaction = false;
237
+ _txOps = [];
238
+ _txBaseState = undefined;
239
+ }
240
+ },
241
+
242
+ apply: (ops: ReadonlyArray<Operation.Operation<any, any, any>>): void => {
243
+ for (const op of ops) {
244
+ applyOperation(op);
245
+ }
246
+ },
247
+
248
+ flush: (): Transaction.Transaction => {
249
+ const tx = Transaction.make(_pending);
250
+ _pending = [];
251
+ return tx;
252
+ },
253
+ };
254
+
255
+ return document;
256
+ };