@vuer-ai/vuer-rtc 0.0.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 (169) hide show
  1. package/dist/client/EditBuffer.d.ts +43 -0
  2. package/dist/client/EditBuffer.d.ts.map +1 -0
  3. package/dist/client/EditBuffer.js +96 -0
  4. package/dist/client/EditBuffer.js.map +1 -0
  5. package/dist/client/actions.d.ts +66 -0
  6. package/dist/client/actions.d.ts.map +1 -0
  7. package/dist/client/actions.js +345 -0
  8. package/dist/client/actions.js.map +1 -0
  9. package/dist/client/createGraph.d.ts +30 -0
  10. package/dist/client/createGraph.d.ts.map +1 -0
  11. package/dist/client/createGraph.js +91 -0
  12. package/dist/client/createGraph.js.map +1 -0
  13. package/dist/client/hooks.d.ts +81 -0
  14. package/dist/client/hooks.d.ts.map +1 -0
  15. package/dist/client/hooks.js +161 -0
  16. package/dist/client/hooks.js.map +1 -0
  17. package/dist/client/index.d.ts +8 -0
  18. package/dist/client/index.d.ts.map +1 -0
  19. package/dist/client/index.js +10 -0
  20. package/dist/client/index.js.map +1 -0
  21. package/dist/client/types.d.ts +74 -0
  22. package/dist/client/types.d.ts.map +1 -0
  23. package/dist/client/types.js +11 -0
  24. package/dist/client/types.js.map +1 -0
  25. package/dist/hooks.d.ts +8 -0
  26. package/dist/hooks.d.ts.map +1 -0
  27. package/dist/hooks.js +7 -0
  28. package/dist/hooks.js.map +1 -0
  29. package/dist/index.d.ts +9 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +12 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/operations/OperationTypes.d.ts +239 -0
  34. package/dist/operations/OperationTypes.d.ts.map +1 -0
  35. package/dist/operations/OperationTypes.js +10 -0
  36. package/dist/operations/OperationTypes.js.map +1 -0
  37. package/dist/operations/OperationValidator.d.ts +32 -0
  38. package/dist/operations/OperationValidator.d.ts.map +1 -0
  39. package/dist/operations/OperationValidator.js +208 -0
  40. package/dist/operations/OperationValidator.js.map +1 -0
  41. package/dist/operations/apply/array.d.ts +22 -0
  42. package/dist/operations/apply/array.d.ts.map +1 -0
  43. package/dist/operations/apply/array.js +64 -0
  44. package/dist/operations/apply/array.js.map +1 -0
  45. package/dist/operations/apply/boolean.d.ts +18 -0
  46. package/dist/operations/apply/boolean.d.ts.map +1 -0
  47. package/dist/operations/apply/boolean.js +34 -0
  48. package/dist/operations/apply/boolean.js.map +1 -0
  49. package/dist/operations/apply/color.d.ts +14 -0
  50. package/dist/operations/apply/color.d.ts.map +1 -0
  51. package/dist/operations/apply/color.js +46 -0
  52. package/dist/operations/apply/color.js.map +1 -0
  53. package/dist/operations/apply/index.d.ts +18 -0
  54. package/dist/operations/apply/index.d.ts.map +1 -0
  55. package/dist/operations/apply/index.js +26 -0
  56. package/dist/operations/apply/index.js.map +1 -0
  57. package/dist/operations/apply/node.d.ts +24 -0
  58. package/dist/operations/apply/node.d.ts.map +1 -0
  59. package/dist/operations/apply/node.js +77 -0
  60. package/dist/operations/apply/node.js.map +1 -0
  61. package/dist/operations/apply/number.d.ts +26 -0
  62. package/dist/operations/apply/number.d.ts.map +1 -0
  63. package/dist/operations/apply/number.js +54 -0
  64. package/dist/operations/apply/number.js.map +1 -0
  65. package/dist/operations/apply/object.d.ts +14 -0
  66. package/dist/operations/apply/object.d.ts.map +1 -0
  67. package/dist/operations/apply/object.js +47 -0
  68. package/dist/operations/apply/object.js.map +1 -0
  69. package/dist/operations/apply/quaternion.d.ts +15 -0
  70. package/dist/operations/apply/quaternion.d.ts.map +1 -0
  71. package/dist/operations/apply/quaternion.js +33 -0
  72. package/dist/operations/apply/quaternion.js.map +1 -0
  73. package/dist/operations/apply/string.d.ts +14 -0
  74. package/dist/operations/apply/string.d.ts.map +1 -0
  75. package/dist/operations/apply/string.js +26 -0
  76. package/dist/operations/apply/string.js.map +1 -0
  77. package/dist/operations/apply/types.d.ts +34 -0
  78. package/dist/operations/apply/types.d.ts.map +1 -0
  79. package/dist/operations/apply/types.js +32 -0
  80. package/dist/operations/apply/types.js.map +1 -0
  81. package/dist/operations/apply/vector3.d.ts +18 -0
  82. package/dist/operations/apply/vector3.d.ts.map +1 -0
  83. package/dist/operations/apply/vector3.js +44 -0
  84. package/dist/operations/apply/vector3.js.map +1 -0
  85. package/dist/operations/dispatcher.d.ts +35 -0
  86. package/dist/operations/dispatcher.d.ts.map +1 -0
  87. package/dist/operations/dispatcher.js +107 -0
  88. package/dist/operations/dispatcher.js.map +1 -0
  89. package/dist/operations/index.d.ts +10 -0
  90. package/dist/operations/index.d.ts.map +1 -0
  91. package/dist/operations/index.js +17 -0
  92. package/dist/operations/index.js.map +1 -0
  93. package/dist/state/ConflictResolver.d.ts +36 -0
  94. package/dist/state/ConflictResolver.d.ts.map +1 -0
  95. package/dist/state/ConflictResolver.js +167 -0
  96. package/dist/state/ConflictResolver.js.map +1 -0
  97. package/dist/state/DType.d.ts +160 -0
  98. package/dist/state/DType.d.ts.map +1 -0
  99. package/dist/state/DType.js +282 -0
  100. package/dist/state/DType.js.map +1 -0
  101. package/dist/state/Schema.d.ts +32 -0
  102. package/dist/state/Schema.d.ts.map +1 -0
  103. package/dist/state/Schema.js +175 -0
  104. package/dist/state/Schema.js.map +1 -0
  105. package/dist/state/VectorClock.d.ts +42 -0
  106. package/dist/state/VectorClock.d.ts.map +1 -0
  107. package/dist/state/VectorClock.js +84 -0
  108. package/dist/state/VectorClock.js.map +1 -0
  109. package/dist/state/index.d.ts +11 -0
  110. package/dist/state/index.d.ts.map +1 -0
  111. package/dist/state/index.js +13 -0
  112. package/dist/state/index.js.map +1 -0
  113. package/docs/OPERATION_HINTS.md +222 -0
  114. package/docs/SCENE_GRAPH.md +373 -0
  115. package/docs/TYPE_BEHAVIORS.md +348 -0
  116. package/examples/01-basic-usage.ts +139 -0
  117. package/examples/02-concurrent-edits.ts +208 -0
  118. package/examples/03-scene-building.ts +258 -0
  119. package/examples/04-conflict-resolution.ts +339 -0
  120. package/examples/README.md +86 -0
  121. package/jest.config.js +19 -0
  122. package/package.json +57 -0
  123. package/src/client/EditBuffer.ts +105 -0
  124. package/src/client/actions.ts +397 -0
  125. package/src/client/createGraph.ts +132 -0
  126. package/src/client/hooks.tsx +249 -0
  127. package/src/client/index.ts +35 -0
  128. package/src/client/types.ts +94 -0
  129. package/src/hooks.ts +20 -0
  130. package/src/index.ts +14 -0
  131. package/src/operations/OperationTypes.ts +340 -0
  132. package/src/operations/OperationValidator.ts +260 -0
  133. package/src/operations/apply/array.ts +84 -0
  134. package/src/operations/apply/boolean.ts +48 -0
  135. package/src/operations/apply/color.ts +65 -0
  136. package/src/operations/apply/index.ts +37 -0
  137. package/src/operations/apply/node.ts +98 -0
  138. package/src/operations/apply/number.ts +76 -0
  139. package/src/operations/apply/object.ts +63 -0
  140. package/src/operations/apply/quaternion.ts +47 -0
  141. package/src/operations/apply/string.ts +36 -0
  142. package/src/operations/apply/types.ts +66 -0
  143. package/src/operations/apply/vector3.ts +60 -0
  144. package/src/operations/dispatcher.ts +127 -0
  145. package/src/operations/index.ts +80 -0
  146. package/src/state/ConflictResolver.ts +205 -0
  147. package/src/state/DType.ts +333 -0
  148. package/src/state/Schema.ts +236 -0
  149. package/src/state/VectorClock.ts +98 -0
  150. package/src/state/index.ts +14 -0
  151. package/tests/client/actions.test.ts +371 -0
  152. package/tests/client/edit-buffer.test.ts +117 -0
  153. package/tests/fixtures/array-ops.jsonl +6 -0
  154. package/tests/fixtures/boolean-ops.jsonl +6 -0
  155. package/tests/fixtures/color-ops.jsonl +4 -0
  156. package/tests/fixtures/edit-buffer.jsonl +3 -0
  157. package/tests/fixtures/node-ops.jsonl +6 -0
  158. package/tests/fixtures/number-ops.jsonl +7 -0
  159. package/tests/fixtures/object-ops.jsonl +4 -0
  160. package/tests/fixtures/operations.jsonl +7 -0
  161. package/tests/fixtures/string-ops.jsonl +4 -0
  162. package/tests/fixtures/undo-redo.jsonl +3 -0
  163. package/tests/fixtures/vector-ops.jsonl +9 -0
  164. package/tests/operations/collections.test.ts +193 -0
  165. package/tests/operations/nodes.test.ts +228 -0
  166. package/tests/operations/primitives.test.ts +222 -0
  167. package/tests/operations/vectors.test.ts +150 -0
  168. package/tsconfig.json +21 -0
  169. package/tsconfig.test.json +9 -0
@@ -0,0 +1,348 @@
1
+ # CRDT Type Behaviors: Comprehensive Reference
2
+
3
+ Different data types have different natural merge semantics. This document defines the behavior of each type when concurrent operations occur.
4
+
5
+ ## Operation Type Format
6
+
7
+ Operations use explicit `otype` that encodes both dtype and operation:
8
+ - `number.set` / `number.add` / `number.multiply` / `number.min` / `number.max`
9
+ - `vector3.set` / `vector3.add` / `vector3.multiply`
10
+ - `color.set` / `color.blend`
11
+ - `array.set` / `array.push` / `array.remove` / `array.union`
12
+ - `node.insert` / `node.remove`
13
+
14
+ ---
15
+
16
+ ## Summary Table
17
+
18
+ | otype | Merge Behavior | Use Case |
19
+ |-------|---------------|----------|
20
+ | `number.set` | LWW | Properties, measurements |
21
+ | `number.add` | Sum | Counters, scores, deltas |
22
+ | `number.multiply` | Product | Scale factors |
23
+ | `number.min` | Minimum | Quality settings |
24
+ | `number.max` | Maximum | High scores |
25
+ | `string.set` | LWW | Names, labels |
26
+ | `string.concat` | Concatenate | Logs (ordered by lamport) |
27
+ | `boolean.set` | LWW | Flags, toggles |
28
+ | `boolean.or` | OR | Feature flags (enable wins) |
29
+ | `boolean.and` | AND | Permissions (disable wins) |
30
+ | `vector3.set` | LWW | Absolute position/scale |
31
+ | `vector3.add` | Component sum | Delta movement |
32
+ | `vector3.multiply` | Component product | Scale gestures |
33
+ | `quaternion.set` | LWW | Rotation |
34
+ | `color.set` | LWW | Material color |
35
+ | `array.set` | LWW | Replace array |
36
+ | `array.push` | Append | Add item |
37
+ | `array.remove` | Remove | Remove item |
38
+ | `array.union` | Union | Merge sets |
39
+ | `object.set` | LWW | Replace object |
40
+ | `object.merge` | Deep merge | Merge properties |
41
+ | `node.insert` | Idempotent | Create node |
42
+ | `node.remove` | Tombstone | Delete node |
43
+
44
+ ## Table of Contents
45
+
46
+ - [Primitive Types](#primitive-types)
47
+ - [Number](#number) - `set`, `add`, `multiply`, `min`, `max`
48
+ - [String](#string) - `set`, `concat`
49
+ - [Boolean](#boolean) - `set`, `or`, `and`
50
+ - [Collection Types](#collection-types)
51
+ - [Array](#array) - `set`, `push`, `remove`, `union`
52
+ - [Object](#object) - `set`, `merge`
53
+ - [Compound Types (3D Graphics)](#compound-types-3d-graphics)
54
+ - [Vector3](#vector3-position-scale) - `set`, `add`, `multiply`
55
+ - [Quaternion](#quaternion-rotation) - `set`, `multiply`
56
+ - [Color](#color) - `set`, `blend`
57
+ - [Scene Graph Operations](#scene-graph-operations)
58
+ - [node.insert](#nodeinsert)
59
+ - [node.remove](#noderemove)
60
+ - [Implementation Strategy](#implementation-strategy)
61
+
62
+ ---
63
+
64
+ ## Primitive Types
65
+
66
+ ### Number
67
+
68
+ | otype | Merge | Example |
69
+ |-------|-------|---------|
70
+ | `number.set` | LWW | `{ otype: 'number.set', path: 'opacity', value: 0.5 }` |
71
+ | `number.add` | Sum | `{ otype: 'number.add', path: 'score', value: 10 }` |
72
+ | `number.multiply` | Product | `{ otype: 'number.multiply', path: 'scale', value: 2 }` |
73
+ | `number.min` | Minimum | `{ otype: 'number.min', path: 'minScore', value: 100 }` |
74
+ | `number.max` | Maximum | `{ otype: 'number.max', path: 'maxScore', value: 100 }` |
75
+
76
+ **Examples:**
77
+ ```typescript
78
+ // Additive counter (concurrent updates sum)
79
+ { key: 'cube-1', otype: 'number.add', path: 'score', value: 10 }
80
+ // Alice: score += 10, Bob: score += 5 → Result: 15 āœ…
81
+
82
+ // LWW property (last write wins)
83
+ { key: 'cube-1', otype: 'number.set', path: 'opacity', value: 0.5 }
84
+ // Alice: opacity = 0.3 (lamport: 100), Bob: opacity = 0.8 (lamport: 101)
85
+ // Result: 0.8 āœ… (Bob wins)
86
+ ```
87
+
88
+ ---
89
+
90
+ ### String
91
+
92
+ | otype | Merge | Example |
93
+ |-------|-------|---------|
94
+ | `string.set` | LWW | `{ otype: 'string.set', path: 'name', value: 'Cube' }` |
95
+ | `string.concat` | Concatenate | `{ otype: 'string.concat', path: 'log', value: 'entry' }` |
96
+
97
+ **Examples:**
98
+ ```typescript
99
+ // LWW name
100
+ { key: 'cube-1', otype: 'string.set', path: 'name', value: 'Red Cube' }
101
+ // Alice: "Red Cube" (lamport: 100), Bob: "Blue Sphere" (lamport: 101)
102
+ // Result: "Blue Sphere" āœ…
103
+ ```
104
+
105
+ ---
106
+
107
+ ### Boolean
108
+
109
+ | otype | Merge | Example |
110
+ |-------|-------|---------|
111
+ | `boolean.set` | LWW | `{ otype: 'boolean.set', path: 'visible', value: true }` |
112
+ | `boolean.or` | OR (enable wins) | `{ otype: 'boolean.or', path: 'enabled', value: true }` |
113
+ | `boolean.and` | AND (disable wins) | `{ otype: 'boolean.and', path: 'allowed', value: false }` |
114
+
115
+ **Examples:**
116
+ ```typescript
117
+ // LWW visibility
118
+ { key: 'cube-1', otype: 'boolean.set', path: 'visible', value: false }
119
+
120
+ // OR bias (enable wins regardless of lamport)
121
+ { key: 'cube-1', otype: 'boolean.or', path: 'enabled', value: true }
122
+ // Alice: true, Bob: false → Result: true āœ…
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Collection Types
128
+
129
+ ### Array
130
+
131
+ | otype | Merge | Example |
132
+ |-------|-------|---------|
133
+ | `array.set` | LWW (replace) | `{ otype: 'array.set', path: 'children', value: ['a', 'b'] }` |
134
+ | `array.push` | Append | `{ otype: 'array.push', path: 'children', value: 'c' }` |
135
+ | `array.remove` | Remove item | `{ otype: 'array.remove', path: 'children', value: 'a' }` |
136
+ | `array.union` | Union | `{ otype: 'array.union', path: 'tags', value: ['new'] }` |
137
+
138
+ **Examples:**
139
+ ```typescript
140
+ // Replace entire array
141
+ { key: 'scene', otype: 'array.set', path: 'children', value: ['cube-1', 'sphere-1'] }
142
+
143
+ // Append to array
144
+ { key: 'scene', otype: 'array.push', path: 'children', value: 'light-1' }
145
+
146
+ // Remove from array
147
+ { key: 'scene', otype: 'array.remove', path: 'children', value: 'cube-1' }
148
+ ```
149
+
150
+ ---
151
+
152
+ ### Object
153
+
154
+ | otype | Merge | Example |
155
+ |-------|-------|---------|
156
+ | `object.set` | LWW (replace) | `{ otype: 'object.set', path: 'metadata', value: {...} }` |
157
+ | `object.merge` | Deep merge | `{ otype: 'object.merge', path: 'metadata', value: {...} }` |
158
+
159
+ ---
160
+
161
+ ## Compound Types (3D Graphics)
162
+
163
+ ### Vector3 (Position, Scale)
164
+
165
+ | otype | Merge | Example |
166
+ |-------|-------|---------|
167
+ | `vector3.set` | LWW | `{ otype: 'vector3.set', path: 'transform.position', value: [0,5,0] }` |
168
+ | `vector3.add` | Component sum | `{ otype: 'vector3.add', path: 'transform.position', value: [5,0,0] }` |
169
+ | `vector3.multiply` | Component product | `{ otype: 'vector3.multiply', path: 'transform.scale', value: [2,2,2] }` |
170
+
171
+ **Examples:**
172
+ ```typescript
173
+ // Absolute position (inspector input)
174
+ { key: 'cube-1', otype: 'vector3.set', path: 'transform.position', value: [10, 5, 0] }
175
+ // Alice: [10, 5, 0] (lamport: 100), Bob: [0, 10, 0] (lamport: 101)
176
+ // Result: [0, 10, 0] āœ… (Bob wins)
177
+
178
+ // Additive movement (drag)
179
+ { key: 'cube-1', otype: 'vector3.add', path: 'transform.position', value: [5, 0, 0] }
180
+ // Alice: += [5, 0, 0], Bob: += [0, 3, 0]
181
+ // Result: += [5, 3, 0] āœ… (both movements apply)
182
+ ```
183
+
184
+ ---
185
+
186
+ ### Quaternion (Rotation)
187
+
188
+ | otype | Merge | Example |
189
+ |-------|-------|---------|
190
+ | `quaternion.set` | LWW | `{ otype: 'quaternion.set', path: 'transform.rotation', value: [0,0,0,1] }` |
191
+ | `quaternion.multiply` | Composition | `{ otype: 'quaternion.multiply', path: 'transform.rotation', value: [...] }` |
192
+
193
+ **Note:** Use `quaternion.set` (LWW) for predictable UX. Quaternion composition is unpredictable for users.
194
+
195
+ ---
196
+
197
+ ### Color
198
+
199
+ | otype | Merge | Example |
200
+ |-------|-------|---------|
201
+ | `color.set` | LWW | `{ otype: 'color.set', path: 'color', value: '#ff0000' }` |
202
+ | `color.blend` | Average | `{ otype: 'color.blend', path: 'color', value: '#00ff00' }` |
203
+
204
+ ---
205
+
206
+ ## Scene Graph Operations
207
+
208
+ ### node.insert
209
+
210
+ Create a new node in the scene graph.
211
+
212
+ ```typescript
213
+ {
214
+ key: 'cube-1',
215
+ otype: 'node.insert',
216
+ path: 'cube-1',
217
+ value: {
218
+ id: 'uuid-cube-001',
219
+ tag: 'Mesh',
220
+ name: 'Red Cube',
221
+ color: '#ff0000',
222
+ 'transform.position': [0, 0, 0],
223
+ 'transform.rotation': [0, 0, 0, 1],
224
+ 'transform.scale': [1, 1, 1]
225
+ }
226
+ }
227
+ ```
228
+
229
+ **Behavior:**
230
+ - Adds node to `nodes` map: `nodes[key] = node`
231
+ - If key exists → idempotent (no-op)
232
+
233
+ ---
234
+
235
+ ### node.remove
236
+
237
+ Delete node (tombstone).
238
+
239
+ ```typescript
240
+ {
241
+ key: 'cube-1',
242
+ otype: 'node.remove',
243
+ path: 'cube-1'
244
+ }
245
+ ```
246
+
247
+ **Behavior:**
248
+ 1. Set `node.deletedAt = timestamp` (tombstone)
249
+ 2. Remove key from all parents' `children` arrays
250
+
251
+ ---
252
+
253
+ ## Implementation Strategy
254
+
255
+ ### Operation Registry Pattern
256
+
257
+ ```typescript
258
+ // operations/registry.ts
259
+ export { applyNumberSet } from './number.js';
260
+ export { applyNumberAdd } from './number.js';
261
+ export { applyVector3Set } from './vector3.js';
262
+ export { applyVector3Add } from './vector3.js';
263
+ export { applyArraySet } from './array.js';
264
+ export { applyArrayPush } from './array.js';
265
+ export { applyArrayRemove } from './array.js';
266
+ export { applyNodeInsert } from './node.js';
267
+ export { applyNodeRemove } from './node.js';
268
+ // ...
269
+
270
+ // index.ts
271
+ import * as registry from './operations/registry.js';
272
+
273
+ export type NumberSetOp = { otype: 'number.set'; ... };
274
+ export type NumberAddOp = { otype: 'number.add'; ... };
275
+ // ...
276
+ ```
277
+
278
+ ### Apply Function Signature
279
+
280
+ ```typescript
281
+ import { produce } from 'immer';
282
+
283
+ // Each apply function uses immer for immutable updates
284
+ export function applyNumberSet(
285
+ draft: SceneGraph,
286
+ op: NumberSetOp,
287
+ meta: OpMeta
288
+ ): void {
289
+ const node = draft.nodes[op.key];
290
+ if (!node || node.deletedAt) return;
291
+ node[op.path] = op.value;
292
+ node.lamportTime = meta.lamportTime;
293
+ node.updatedAt = meta.timestamp;
294
+ }
295
+
296
+ export function applyNumberAdd(
297
+ draft: SceneGraph,
298
+ op: NumberAddOp,
299
+ meta: OpMeta
300
+ ): void {
301
+ const node = draft.nodes[op.key];
302
+ if (!node || node.deletedAt) return;
303
+ const current = (node[op.path] as number) ?? 0;
304
+ node[op.path] = current + op.value;
305
+ node.lamportTime = meta.lamportTime;
306
+ node.updatedAt = meta.timestamp;
307
+ }
308
+ ```
309
+
310
+ ### Dispatcher
311
+
312
+ ```typescript
313
+ import { produce } from 'immer';
314
+ import * as registry from './operations/registry.js';
315
+
316
+ const handlers: Record<string, (draft: SceneGraph, op: Operation, meta: OpMeta) => void> = {
317
+ 'number.set': registry.applyNumberSet,
318
+ 'number.add': registry.applyNumberAdd,
319
+ 'vector3.set': registry.applyVector3Set,
320
+ 'vector3.add': registry.applyVector3Add,
321
+ 'array.set': registry.applyArraySet,
322
+ 'array.push': registry.applyArrayPush,
323
+ 'array.remove': registry.applyArrayRemove,
324
+ 'node.insert': registry.applyNodeInsert,
325
+ 'node.remove': registry.applyNodeRemove,
326
+ // ...
327
+ };
328
+
329
+ export function applyMessage(graph: SceneGraph, msg: CRDTMessage): SceneGraph {
330
+ return produce(graph, (draft) => {
331
+ const meta = {
332
+ sessionId: msg.sessionId,
333
+ clock: msg.clock,
334
+ lamportTime: msg.lamportTime,
335
+ timestamp: msg.timestamp,
336
+ };
337
+
338
+ for (const op of msg.ops) {
339
+ const handler = handlers[op.otype];
340
+ if (handler) {
341
+ handler(draft, op as any, meta);
342
+ } else {
343
+ console.warn(`Unknown otype: ${op.otype}`);
344
+ }
345
+ }
346
+ });
347
+ }
348
+ ```
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Example 01: Basic Usage
3
+ *
4
+ * Demonstrates how to create a scene and apply operations.
5
+ * Run with: npx tsx examples/01-basic-usage.ts
6
+ */
7
+
8
+ import { createEmptyGraph, applyMessage } from '../src/operations/index.js';
9
+ import type { CRDTMessage } from '../src/operations/index.js';
10
+
11
+ console.log('šŸŽ¬ Example 01: Basic Usage\n');
12
+
13
+ // Start with an empty scene graph
14
+ let graph = createEmptyGraph();
15
+ console.log('Initial graph:', graph);
16
+
17
+ // ============================================
18
+ // Step 1: Create a scene node
19
+ // ============================================
20
+
21
+ const msg1: CRDTMessage = {
22
+ id: 'msg-001',
23
+ sessionId: 'session-server',
24
+ clock: { 'session-server': 1 },
25
+ lamportTime: 1,
26
+ timestamp: Date.now(),
27
+ ops: [
28
+ {
29
+ key: 'scene',
30
+ otype: 'node.insert',
31
+ path: 'scene',
32
+ value: {
33
+ id: 'uuid-scene-001',
34
+ tag: 'Scene',
35
+ name: 'My Scene',
36
+ background: '#87CEEB',
37
+ 'transform.position': [0, 0, 0],
38
+ 'transform.rotation': [0, 0, 0, 1],
39
+ 'transform.scale': [1, 1, 1],
40
+ },
41
+ },
42
+ ],
43
+ };
44
+
45
+ graph = applyMessage(graph, msg1);
46
+ console.log('\nāœ… After inserting scene:');
47
+ console.log(' Root key:', graph.rootKey);
48
+ console.log(' Scene node:', graph.nodes['scene']);
49
+
50
+ // ============================================
51
+ // Step 2: Add a cube to the scene
52
+ // ============================================
53
+
54
+ const msg2: CRDTMessage = {
55
+ id: 'msg-002',
56
+ sessionId: 'session-alice',
57
+ clock: { 'session-alice': 1 },
58
+ lamportTime: 2,
59
+ timestamp: Date.now(),
60
+ ops: [
61
+ // Insert the cube node and add to scene's children in one operation
62
+ {
63
+ key: 'cube-1',
64
+ otype: 'node.insert',
65
+ path: 'cube-1',
66
+ parent: 'scene', // Automatically adds to scene's children
67
+ value: {
68
+ id: 'uuid-cube-001',
69
+ tag: 'Mesh',
70
+ name: 'Red Cube',
71
+ color: '#ff0000',
72
+ 'transform.position': [2, 1, 0],
73
+ 'transform.rotation': [0, 0, 0, 1],
74
+ 'transform.scale': [1, 1, 1],
75
+ },
76
+ },
77
+ ],
78
+ };
79
+
80
+ graph = applyMessage(graph, msg2);
81
+ console.log('\nāœ… After adding cube:');
82
+ console.log(' Cube node:', graph.nodes['cube-1']);
83
+ console.log(' Scene children:', graph.nodes['scene'].children);
84
+
85
+ // ============================================
86
+ // Step 3: Move the cube (additive)
87
+ // ============================================
88
+
89
+ const msg3: CRDTMessage = {
90
+ id: 'msg-003',
91
+ sessionId: 'session-alice',
92
+ clock: { 'session-alice': 2 },
93
+ lamportTime: 3,
94
+ timestamp: Date.now(),
95
+ ops: [
96
+ {
97
+ key: 'cube-1',
98
+ otype: 'vector3.add',
99
+ path: 'transform.position',
100
+ value: [5, 0, 0], // Move right by 5
101
+ },
102
+ ],
103
+ };
104
+
105
+ graph = applyMessage(graph, msg3);
106
+ console.log('\nāœ… After moving cube (additive):');
107
+ console.log(' Cube position:', graph.nodes['cube-1']['transform.position']);
108
+ // Expected: [7, 1, 0] (original [2,1,0] + delta [5,0,0])
109
+
110
+ // ============================================
111
+ // Step 4: Change cube color
112
+ // ============================================
113
+
114
+ const msg4: CRDTMessage = {
115
+ id: 'msg-004',
116
+ sessionId: 'session-alice',
117
+ clock: { 'session-alice': 3 },
118
+ lamportTime: 4,
119
+ timestamp: Date.now(),
120
+ ops: [
121
+ {
122
+ key: 'cube-1',
123
+ otype: 'color.set',
124
+ path: 'color',
125
+ value: '#00ff00', // Change to green
126
+ },
127
+ ],
128
+ };
129
+
130
+ graph = applyMessage(graph, msg4);
131
+ console.log('\nāœ… After changing color:');
132
+ console.log(' Cube color:', graph.nodes['cube-1'].color);
133
+
134
+ // ============================================
135
+ // Final state
136
+ // ============================================
137
+
138
+ console.log('\nšŸ“Š Final Scene Graph:');
139
+ console.log(JSON.stringify(graph, null, 2));
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Example 02: Concurrent Edits
3
+ *
4
+ * Demonstrates how CRDT handles concurrent edits from multiple users.
5
+ * Run with: npx tsx examples/02-concurrent-edits.ts
6
+ */
7
+
8
+ import { createEmptyGraph, applyMessage } from '../src/operations/index.js';
9
+ import type { CRDTMessage } from '../src/operations/index.js';
10
+
11
+ console.log('šŸŽ¬ Example 02: Concurrent Edits\n');
12
+ console.log('Scenario: Alice and Bob both edit the same cube at the same time.\n');
13
+
14
+ // Setup: Create a scene with a cube
15
+ let graph = createEmptyGraph();
16
+
17
+ const setupMsg: CRDTMessage = {
18
+ id: 'setup',
19
+ sessionId: 'server',
20
+ clock: { server: 1 },
21
+ lamportTime: 0,
22
+ timestamp: Date.now(),
23
+ ops: [
24
+ {
25
+ key: 'cube',
26
+ otype: 'node.insert',
27
+ path: 'cube',
28
+ value: {
29
+ key: 'uuid-cube',
30
+ tag: 'Mesh',
31
+ name: 'Shared Cube',
32
+ color: '#ffffff',
33
+ score: 0,
34
+ 'transform.position': [0, 0, 0],
35
+ },
36
+ },
37
+ ],
38
+ };
39
+
40
+ graph = applyMessage(graph, setupMsg);
41
+ console.log('Initial cube position:', graph.nodes['cube']['transform.position']);
42
+ console.log('Initial cube score:', graph.nodes['cube'].score);
43
+
44
+ // ============================================
45
+ // Scenario 1: Concurrent Drag (Additive)
46
+ // ============================================
47
+
48
+ console.log('\n--- Scenario 1: Concurrent Drag (vector3.add) ---\n');
49
+
50
+ // Alice drags the cube right by [5, 0, 0]
51
+ const aliceDrag: CRDTMessage = {
52
+ id: 'alice-drag',
53
+ sessionId: 'alice',
54
+ clock: { alice: 1 },
55
+ lamportTime: 10,
56
+ timestamp: Date.now(),
57
+ ops: [
58
+ {
59
+ key: 'cube',
60
+ otype: 'vector3.add', // Additive!
61
+ path: 'transform.position',
62
+ value: [5, 0, 0],
63
+ },
64
+ ],
65
+ };
66
+
67
+ // Bob drags the cube up by [0, 3, 0] (concurrent with Alice)
68
+ const bobDrag: CRDTMessage = {
69
+ id: 'bob-drag',
70
+ sessionId: 'bob',
71
+ clock: { bob: 1 },
72
+ lamportTime: 11,
73
+ timestamp: Date.now(),
74
+ ops: [
75
+ {
76
+ key: 'cube',
77
+ otype: 'vector3.add', // Additive!
78
+ path: 'transform.position',
79
+ value: [0, 3, 0],
80
+ },
81
+ ],
82
+ };
83
+
84
+ console.log('Alice drags: += [5, 0, 0]');
85
+ console.log('Bob drags: += [0, 3, 0]');
86
+
87
+ // Apply both (order doesn't matter for additive!)
88
+ graph = applyMessage(graph, aliceDrag);
89
+ graph = applyMessage(graph, bobDrag);
90
+
91
+ console.log('\nāœ… Result: Both movements applied!');
92
+ console.log('Final position:', graph.nodes['cube']['transform.position']);
93
+ console.log('Expected: [5, 3, 0]');
94
+
95
+ // ============================================
96
+ // Scenario 2: Concurrent Score Update (Additive)
97
+ // ============================================
98
+
99
+ console.log('\n--- Scenario 2: Concurrent Score Update (number.add) ---\n');
100
+
101
+ // Alice adds 10 points
102
+ const aliceScore: CRDTMessage = {
103
+ id: 'alice-score',
104
+ sessionId: 'alice',
105
+ clock: { alice: 2 },
106
+ lamportTime: 20,
107
+ timestamp: Date.now(),
108
+ ops: [
109
+ {
110
+ key: 'cube',
111
+ otype: 'number.add',
112
+ path: 'score',
113
+ value: 10,
114
+ },
115
+ ],
116
+ };
117
+
118
+ // Bob adds 5 points (concurrent)
119
+ const bobScore: CRDTMessage = {
120
+ id: 'bob-score',
121
+ sessionId: 'bob',
122
+ clock: { bob: 2 },
123
+ lamportTime: 21,
124
+ timestamp: Date.now(),
125
+ ops: [
126
+ {
127
+ key: 'cube',
128
+ otype: 'number.add',
129
+ path: 'score',
130
+ value: 5,
131
+ },
132
+ ],
133
+ };
134
+
135
+ console.log('Alice: score += 10');
136
+ console.log('Bob: score += 5');
137
+
138
+ graph = applyMessage(graph, aliceScore);
139
+ graph = applyMessage(graph, bobScore);
140
+
141
+ console.log('\nāœ… Result: Both scores accumulated!');
142
+ console.log('Final score:', graph.nodes['cube'].score);
143
+ console.log('Expected: 15');
144
+
145
+ // ============================================
146
+ // Scenario 3: Concurrent Color Change (LWW)
147
+ // ============================================
148
+
149
+ console.log('\n--- Scenario 3: Concurrent Color Change (color.set - LWW) ---\n');
150
+
151
+ // Alice sets color to red (lamport: 30)
152
+ const aliceColor: CRDTMessage = {
153
+ id: 'alice-color',
154
+ sessionId: 'alice',
155
+ clock: { alice: 3 },
156
+ lamportTime: 30,
157
+ timestamp: Date.now(),
158
+ ops: [
159
+ {
160
+ key: 'cube',
161
+ otype: 'color.set', // LWW!
162
+ path: 'color',
163
+ value: '#ff0000',
164
+ },
165
+ ],
166
+ };
167
+
168
+ // Bob sets color to blue (lamport: 31 - higher, so Bob wins)
169
+ const bobColor: CRDTMessage = {
170
+ id: 'bob-color',
171
+ sessionId: 'bob',
172
+ clock: { bob: 3 },
173
+ lamportTime: 31, // Higher lamport time
174
+ timestamp: Date.now(),
175
+ ops: [
176
+ {
177
+ key: 'cube',
178
+ otype: 'color.set', // LWW!
179
+ path: 'color',
180
+ value: '#0000ff',
181
+ },
182
+ ],
183
+ };
184
+
185
+ console.log('Alice: color = #ff0000 (red) [lamport: 30]');
186
+ console.log('Bob: color = #0000ff (blue) [lamport: 31]');
187
+
188
+ graph = applyMessage(graph, aliceColor);
189
+ graph = applyMessage(graph, bobColor);
190
+
191
+ console.log('\nāœ… Result: Bob wins (higher lamport time)!');
192
+ console.log('Final color:', graph.nodes['cube'].color);
193
+ console.log('Expected: #0000ff (blue)');
194
+
195
+ // ============================================
196
+ // Summary
197
+ // ============================================
198
+
199
+ console.log('\nšŸ“Š Summary:');
200
+ console.log('─'.repeat(50));
201
+ console.log('Additive operations (*.add): Values are SUMMED');
202
+ console.log(' → Great for: drag, counters, deltas');
203
+ console.log(' → Order doesn\'t matter!');
204
+ console.log('');
205
+ console.log('LWW operations (*.set): Last write wins');
206
+ console.log(' → Great for: colors, absolute positions, names');
207
+ console.log(' → Higher lamportTime wins');
208
+ console.log('─'.repeat(50));