@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,333 @@
1
+ /**
2
+ * DType - Data type system with merge operations
3
+ *
4
+ * Based on test-data/datatypes.ts
5
+ *
6
+ * Each dtype has supported operations that define merge behavior
7
+ * for concurrent updates.
8
+ */
9
+
10
+ /**
11
+ * PropertyValue with metadata
12
+ */
13
+ export interface ValueWithMeta<T = any> {
14
+ value: T;
15
+ lamportTime: number;
16
+ sessionId?: string;
17
+ }
18
+
19
+ /**
20
+ * Merge function signature
21
+ */
22
+ export type MergeFn<T = any> = (values: ValueWithMeta<T>[]) => T;
23
+
24
+ /**
25
+ * DType definitions
26
+ */
27
+ export const DType = {
28
+ // ============================================
29
+ // PRIMITIVES
30
+ // ============================================
31
+
32
+ /**
33
+ * Number operations
34
+ */
35
+ number: {
36
+ /**
37
+ * SET - Last-Write-Wins
38
+ */
39
+ set: ((values: ValueWithMeta<number>[]) => {
40
+ const sorted = [...values].sort((a, b) => b.lamportTime - a.lamportTime);
41
+ return sorted[0].value;
42
+ }) as MergeFn<number>,
43
+
44
+ /**
45
+ * ADD - Sum all values
46
+ */
47
+ add: ((values: ValueWithMeta<number>[]) => {
48
+ return values.reduce((sum, v) => sum + v.value, 0);
49
+ }) as MergeFn<number>,
50
+
51
+ /**
52
+ * MULTIPLY - Multiply all values
53
+ */
54
+ multiply: ((values: ValueWithMeta<number>[]) => {
55
+ return values.reduce((product, v) => product * v.value, 1);
56
+ }) as MergeFn<number>,
57
+
58
+ /**
59
+ * MIN - Take minimum
60
+ */
61
+ min: ((values: ValueWithMeta<number>[]) => {
62
+ return Math.min(...values.map((v) => v.value));
63
+ }) as MergeFn<number>,
64
+
65
+ /**
66
+ * MAX - Take maximum
67
+ */
68
+ max: ((values: ValueWithMeta<number>[]) => {
69
+ return Math.max(...values.map((v) => v.value));
70
+ }) as MergeFn<number>,
71
+ },
72
+
73
+ /**
74
+ * String operations
75
+ */
76
+ string: {
77
+ /**
78
+ * SET - Last-Write-Wins
79
+ */
80
+ set: ((values: ValueWithMeta<string>[]) => {
81
+ const sorted = [...values].sort((a, b) => b.lamportTime - a.lamportTime);
82
+ return sorted[0].value;
83
+ }) as MergeFn<string>,
84
+
85
+ /**
86
+ * CONCAT - Concatenate (ordered by lamport)
87
+ */
88
+ concat: ((values: ValueWithMeta<string>[], separator = '\n') => {
89
+ const sorted = [...values].sort((a, b) => a.lamportTime - b.lamportTime);
90
+ return sorted.map((v) => v.value).join(separator);
91
+ }) as MergeFn<string>,
92
+ },
93
+
94
+ /**
95
+ * Boolean operations
96
+ */
97
+ boolean: {
98
+ /**
99
+ * SET - Last-Write-Wins
100
+ */
101
+ set: ((values: ValueWithMeta<boolean>[]) => {
102
+ const sorted = [...values].sort((a, b) => b.lamportTime - a.lamportTime);
103
+ return sorted[0].value;
104
+ }) as MergeFn<boolean>,
105
+
106
+ /**
107
+ * OR - Logical OR (enable wins)
108
+ */
109
+ or: ((values: ValueWithMeta<boolean>[]) => {
110
+ return values.some((v) => v.value === true);
111
+ }) as MergeFn<boolean>,
112
+
113
+ /**
114
+ * AND - Logical AND (disable wins)
115
+ */
116
+ and: ((values: ValueWithMeta<boolean>[]) => {
117
+ return values.every((v) => v.value === true);
118
+ }) as MergeFn<boolean>,
119
+ },
120
+
121
+ // ============================================
122
+ // COMPOUND TYPES (3D GRAPHICS)
123
+ // ============================================
124
+
125
+ /**
126
+ * Vector3 operations - [x, y, z]
127
+ */
128
+ vector3: {
129
+ /**
130
+ * SET - Replace entire vector (preserves user intent)
131
+ */
132
+ set: ((values: ValueWithMeta<[number, number, number]>[]) => {
133
+ const sorted = [...values].sort((a, b) => b.lamportTime - a.lamportTime);
134
+ return sorted[0].value;
135
+ }) as MergeFn<[number, number, number]>,
136
+
137
+ /**
138
+ * ADD - Component-wise addition
139
+ */
140
+ add: ((values: ValueWithMeta<[number, number, number]>[]) => {
141
+ const sum: [number, number, number] = [0, 0, 0];
142
+ for (const v of values) {
143
+ sum[0] += v.value[0];
144
+ sum[1] += v.value[1];
145
+ sum[2] += v.value[2];
146
+ }
147
+ return sum;
148
+ }) as MergeFn<[number, number, number]>,
149
+
150
+ /**
151
+ * MULTIPLY - Component-wise multiplication
152
+ */
153
+ multiply: ((values: ValueWithMeta<[number, number, number]>[]) => {
154
+ const product: [number, number, number] = [1, 1, 1];
155
+ for (const v of values) {
156
+ product[0] *= v.value[0];
157
+ product[1] *= v.value[1];
158
+ product[2] *= v.value[2];
159
+ }
160
+ return product;
161
+ }) as MergeFn<[number, number, number]>,
162
+ },
163
+
164
+ /**
165
+ * Quaternion operations - [x, y, z, w]
166
+ */
167
+ quaternion: {
168
+ /**
169
+ * SET - Replace entire quaternion
170
+ */
171
+ set: ((values: ValueWithMeta<[number, number, number, number]>[]) => {
172
+ const sorted = [...values].sort((a, b) => b.lamportTime - a.lamportTime);
173
+ return sorted[0].value;
174
+ }) as MergeFn<[number, number, number, number]>,
175
+
176
+ /**
177
+ * MULTIPLY - Quaternion multiplication (composition)
178
+ */
179
+ multiply: ((values: ValueWithMeta<[number, number, number, number]>[]) => {
180
+ const sorted = [...values].sort((a, b) => a.lamportTime - b.lamportTime);
181
+ let result = sorted[0].value;
182
+
183
+ for (let i = 1; i < sorted.length; i++) {
184
+ result = multiplyQuaternions(result, sorted[i].value);
185
+ }
186
+
187
+ return result;
188
+ }) as MergeFn<[number, number, number, number]>,
189
+ },
190
+
191
+ /**
192
+ * Color operations
193
+ */
194
+ color: {
195
+ /**
196
+ * SET - Replace color
197
+ */
198
+ set: ((values: ValueWithMeta<string>[]) => {
199
+ const sorted = [...values].sort((a, b) => b.lamportTime - a.lamportTime);
200
+ return sorted[0].value;
201
+ }) as MergeFn<string>,
202
+
203
+ /**
204
+ * BLEND - Average colors
205
+ */
206
+ blend: ((values: ValueWithMeta<string>[]) => {
207
+ const rgbs = values.map((v) => hexToRgb(v.value));
208
+ const avgR = Math.round(rgbs.reduce((sum, rgb) => sum + rgb.r, 0) / rgbs.length);
209
+ const avgG = Math.round(rgbs.reduce((sum, rgb) => sum + rgb.g, 0) / rgbs.length);
210
+ const avgB = Math.round(rgbs.reduce((sum, rgb) => sum + rgb.b, 0) / rgbs.length);
211
+ return rgbToHex(avgR, avgG, avgB);
212
+ }) as MergeFn<string>,
213
+ },
214
+
215
+ // ============================================
216
+ // COLLECTIONS
217
+ // ============================================
218
+
219
+ /**
220
+ * Array operations
221
+ */
222
+ array: {
223
+ /**
224
+ * SET - Replace entire array
225
+ */
226
+ set: ((values: ValueWithMeta<any[]>[]) => {
227
+ const sorted = [...values].sort((a, b) => b.lamportTime - a.lamportTime);
228
+ return sorted[0].value;
229
+ }) as MergeFn<any[]>,
230
+
231
+ /**
232
+ * UNION - Merge unique elements
233
+ */
234
+ union: ((values: ValueWithMeta<any[]>[]) => {
235
+ const allElements = values.flatMap((v) => v.value);
236
+ return [...new Set(allElements)];
237
+ }) as MergeFn<any[]>,
238
+
239
+ /**
240
+ * APPEND - Append all (ordered by lamport)
241
+ */
242
+ append: ((values: ValueWithMeta<any[]>[]) => {
243
+ const sorted = [...values].sort((a, b) => a.lamportTime - b.lamportTime);
244
+ return sorted.flatMap((v) => v.value);
245
+ }) as MergeFn<any[]>,
246
+ },
247
+
248
+ /**
249
+ * Object operations
250
+ */
251
+ object: {
252
+ /**
253
+ * SET - Replace entire object
254
+ */
255
+ set: ((values: ValueWithMeta<Record<string, any>>[]) => {
256
+ const sorted = [...values].sort((a, b) => b.lamportTime - a.lamportTime);
257
+ return sorted[0].value;
258
+ }) as MergeFn<Record<string, any>>,
259
+
260
+ /**
261
+ * LWW_PER_KEY - Last-Write-Wins per key
262
+ */
263
+ lwwPerKey: ((values: ValueWithMeta<Record<string, any>>[]) => {
264
+ const result: Record<string, any> = {};
265
+ const latestTimes: Record<string, number> = {};
266
+
267
+ for (const v of values) {
268
+ for (const [key, value] of Object.entries(v.value)) {
269
+ if (!latestTimes[key] || v.lamportTime > latestTimes[key]) {
270
+ result[key] = value;
271
+ latestTimes[key] = v.lamportTime;
272
+ }
273
+ }
274
+ }
275
+
276
+ return result;
277
+ }) as MergeFn<Record<string, any>>,
278
+ },
279
+
280
+ // ============================================
281
+ // SPECIAL
282
+ // ============================================
283
+
284
+ /**
285
+ * Immutable - Cannot be changed
286
+ */
287
+ immutable: {
288
+ set: ((values: ValueWithMeta[]) => {
289
+ // Return first value (creation value)
290
+ const sorted = [...values].sort((a, b) => a.lamportTime - b.lamportTime);
291
+ return sorted[0].value;
292
+ }) as MergeFn,
293
+ },
294
+ };
295
+
296
+ /**
297
+ * Helper: Multiply quaternions
298
+ */
299
+ function multiplyQuaternions(
300
+ a: [number, number, number, number],
301
+ b: [number, number, number, number]
302
+ ): [number, number, number, number] {
303
+ const [ax, ay, az, aw] = a;
304
+ const [bx, by, bz, bw] = b;
305
+
306
+ return [
307
+ aw * bx + ax * bw + ay * bz - az * by,
308
+ aw * by - ax * bz + ay * bw + az * bx,
309
+ aw * bz + ax * by - ay * bx + az * bw,
310
+ aw * bw - ax * bx - ay * by - az * bz,
311
+ ];
312
+ }
313
+
314
+ /**
315
+ * Helper: Hex to RGB
316
+ */
317
+ function hexToRgb(hex: string): { r: number; g: number; b: number } {
318
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
319
+ return result
320
+ ? {
321
+ r: parseInt(result[1], 16),
322
+ g: parseInt(result[2], 16),
323
+ b: parseInt(result[3], 16),
324
+ }
325
+ : { r: 0, g: 0, b: 0 };
326
+ }
327
+
328
+ /**
329
+ * Helper: RGB to Hex
330
+ */
331
+ function rgbToHex(r: number, g: number, b: number): string {
332
+ return '#' + [r, g, b].map((x) => x.toString(16).padStart(2, '0')).join('');
333
+ }
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Schema - Property schema definitions
3
+ *
4
+ * Defines dtype and operation for each property,
5
+ * determining how concurrent updates are merged.
6
+ *
7
+ * Based on test-data/schema-example.ts
8
+ */
9
+
10
+ /**
11
+ * Property Schema Definition
12
+ */
13
+ export interface PropertySchema {
14
+ dtype:
15
+ | 'string'
16
+ | 'number'
17
+ | 'boolean'
18
+ | 'vector3'
19
+ | 'quaternion'
20
+ | 'color'
21
+ | 'array'
22
+ | 'object'
23
+ | 'any';
24
+
25
+ operation:
26
+ | 'set' // LWW
27
+ | 'add' // Additive
28
+ | 'multiply' // Multiplicative
29
+ | 'min' // Minimum
30
+ | 'max' // Maximum
31
+ | 'or' // Logical OR
32
+ | 'and' // Logical AND
33
+ | 'union' // Set union
34
+ | 'append' // Append (ordered)
35
+ | 'rga' // Replicated Growable Array
36
+ | 'lww-per-key' // LWW per object key
37
+ | 'lww-per-component' // LWW per component
38
+ | 'blend' // Blend/average
39
+ | 'immutable'; // Cannot change
40
+
41
+ default?: any;
42
+ }
43
+
44
+ /**
45
+ * Scene Node Schema
46
+ *
47
+ * Defines merge behavior for all scene node properties.
48
+ */
49
+ export const sceneNodeSchema: Record<string, PropertySchema> = {
50
+ // ========================================
51
+ // Identity (immutable)
52
+ // ========================================
53
+ id: {
54
+ dtype: 'string',
55
+ operation: 'immutable',
56
+ },
57
+
58
+ tag: {
59
+ dtype: 'string',
60
+ operation: 'immutable',
61
+ },
62
+
63
+ key: {
64
+ dtype: 'string',
65
+ operation: 'set',
66
+ default: '',
67
+ },
68
+
69
+ name: {
70
+ dtype: 'string',
71
+ operation: 'set',
72
+ default: '',
73
+ },
74
+
75
+ // ========================================
76
+ // Tree Structure
77
+ // ========================================
78
+ children: {
79
+ dtype: 'array',
80
+ operation: 'set', // Phase 2: Simple LWW
81
+ default: [],
82
+ },
83
+
84
+ // ========================================
85
+ // Transform (LWW)
86
+ // ========================================
87
+ 'transform.position': {
88
+ dtype: 'vector3',
89
+ operation: 'set',
90
+ default: [0, 0, 0],
91
+ },
92
+
93
+ 'transform.rotation': {
94
+ dtype: 'quaternion',
95
+ operation: 'set',
96
+ default: [0, 0, 0, 1],
97
+ },
98
+
99
+ 'transform.scale': {
100
+ dtype: 'vector3',
101
+ operation: 'set',
102
+ default: [1, 1, 1],
103
+ },
104
+
105
+ // ========================================
106
+ // Common Properties (LWW)
107
+ // ========================================
108
+ visible: {
109
+ dtype: 'boolean',
110
+ operation: 'set',
111
+ default: true,
112
+ },
113
+
114
+ color: {
115
+ dtype: 'color',
116
+ operation: 'set',
117
+ default: '#ffffff',
118
+ },
119
+
120
+ opacity: {
121
+ dtype: 'number',
122
+ operation: 'set',
123
+ default: 1.0,
124
+ },
125
+
126
+ // ========================================
127
+ // Material Properties (LWW)
128
+ // ========================================
129
+ material: {
130
+ dtype: 'string',
131
+ operation: 'set',
132
+ default: '',
133
+ },
134
+
135
+ geometry: {
136
+ dtype: 'string',
137
+ operation: 'set',
138
+ default: '',
139
+ },
140
+
141
+ metalness: {
142
+ dtype: 'number',
143
+ operation: 'set',
144
+ default: 0,
145
+ },
146
+
147
+ roughness: {
148
+ dtype: 'number',
149
+ operation: 'set',
150
+ default: 1,
151
+ },
152
+
153
+ // ========================================
154
+ // Additive Properties
155
+ // ========================================
156
+ score: {
157
+ dtype: 'number',
158
+ operation: 'add',
159
+ default: 0,
160
+ },
161
+
162
+ damage: {
163
+ dtype: 'number',
164
+ operation: 'add',
165
+ default: 0,
166
+ },
167
+
168
+ // ========================================
169
+ // Collection Properties
170
+ // ========================================
171
+ tags: {
172
+ dtype: 'array',
173
+ operation: 'union',
174
+ default: [],
175
+ },
176
+
177
+ // ========================================
178
+ // Object Properties (LWW per key)
179
+ // ========================================
180
+ properties: {
181
+ dtype: 'object',
182
+ operation: 'lww-per-key',
183
+ default: {},
184
+ },
185
+
186
+ // ========================================
187
+ // CRDT Metadata
188
+ // ========================================
189
+ clock: {
190
+ dtype: 'object',
191
+ operation: 'lww-per-key',
192
+ default: {},
193
+ },
194
+
195
+ lamportTime: {
196
+ dtype: 'number',
197
+ operation: 'set',
198
+ default: 0,
199
+ },
200
+
201
+ createdAt: {
202
+ dtype: 'number',
203
+ operation: 'immutable',
204
+ },
205
+
206
+ updatedAt: {
207
+ dtype: 'number',
208
+ operation: 'set',
209
+ },
210
+
211
+ deletedAt: {
212
+ dtype: 'number',
213
+ operation: 'set',
214
+ },
215
+ };
216
+
217
+ /**
218
+ * Get schema for a property
219
+ * Returns default schema if not found
220
+ */
221
+ export function getPropertySchema(propertyName: string): PropertySchema {
222
+ return (
223
+ sceneNodeSchema[propertyName] || {
224
+ dtype: 'any',
225
+ operation: 'set', // Default to LWW
226
+ }
227
+ );
228
+ }
229
+
230
+ /**
231
+ * Check if a property is immutable
232
+ */
233
+ export function isImmutable(propertyName: string): boolean {
234
+ const schema = getPropertySchema(propertyName);
235
+ return schema.operation === 'immutable';
236
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * VectorClock - CRDT-inspired vector clock implementation
3
+ *
4
+ * Vector clocks provide causal ordering of operations in a distributed system.
5
+ * Each session maintains a counter, and clocks are compared to detect:
6
+ * - Causal ordering (A happened before B)
7
+ * - Concurrent operations (A and B are independent)
8
+ */
9
+
10
+ export type VectorClock = Record<string, number>;
11
+
12
+ export class VectorClockManager {
13
+ /**
14
+ * Create a new vector clock for a session
15
+ * Initializes the session's counter to 0
16
+ */
17
+ create(sessionId: string): VectorClock {
18
+ return { [sessionId]: 0 };
19
+ }
20
+
21
+ /**
22
+ * Increment the counter for a session
23
+ * Returns a new clock (immutable)
24
+ */
25
+ increment(clock: VectorClock, sessionId: string): VectorClock {
26
+ const currentValue = clock[sessionId] || 0;
27
+ return {
28
+ ...clock,
29
+ [sessionId]: currentValue + 1,
30
+ };
31
+ }
32
+
33
+ /**
34
+ * Merge two vector clocks
35
+ * Takes the maximum value for each session
36
+ * Used when receiving remote operations
37
+ */
38
+ merge(clock1: VectorClock, clock2: VectorClock): VectorClock {
39
+ const merged: VectorClock = { ...clock1 };
40
+
41
+ Object.entries(clock2).forEach(([sessionId, count]) => {
42
+ merged[sessionId] = Math.max(merged[sessionId] || 0, count);
43
+ });
44
+
45
+ return merged;
46
+ }
47
+
48
+ /**
49
+ * Compare two vector clocks
50
+ *
51
+ * Returns:
52
+ * 1 if clock1 > clock2 (clock1 causally after clock2)
53
+ * -1 if clock1 < clock2 (clock1 causally before clock2)
54
+ * 0 if concurrent (neither causally precedes the other)
55
+ */
56
+ compare(clock1: VectorClock, clock2: VectorClock): number {
57
+ const allSessionIds = new Set([
58
+ ...Object.keys(clock1),
59
+ ...Object.keys(clock2),
60
+ ]);
61
+
62
+ let clock1Greater = false;
63
+ let clock2Greater = false;
64
+
65
+ allSessionIds.forEach((sessionId) => {
66
+ const val1 = clock1[sessionId] || 0;
67
+ const val2 = clock2[sessionId] || 0;
68
+
69
+ if (val1 > val2) {
70
+ clock1Greater = true;
71
+ }
72
+ if (val2 > val1) {
73
+ clock2Greater = true;
74
+ }
75
+ });
76
+
77
+ // If clock1 is greater in all dimensions, it causally follows clock2
78
+ if (clock1Greater && !clock2Greater) {
79
+ return 1;
80
+ }
81
+
82
+ // If clock2 is greater in all dimensions, it causally follows clock1
83
+ if (clock2Greater && !clock1Greater) {
84
+ return -1;
85
+ }
86
+
87
+ // Otherwise, they are concurrent (or identical)
88
+ return 0;
89
+ }
90
+
91
+ /**
92
+ * Check if two operations are concurrent
93
+ * (neither causally precedes the other)
94
+ */
95
+ areConcurrent(clock1: VectorClock, clock2: VectorClock): boolean {
96
+ return this.compare(clock1, clock2) === 0;
97
+ }
98
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * State module exports
3
+ *
4
+ * - VectorClock: Used by client for causal ordering
5
+ * - DType, Schema, ConflictResolver: Server-side conflict resolution for concurrent updates
6
+ */
7
+
8
+ // Vector clock (used by client and server)
9
+ export { VectorClockManager, type VectorClock } from './VectorClock.js';
10
+
11
+ // Server-side conflict resolution utilities
12
+ export { DType, type ValueWithMeta, type MergeFn } from './DType.js';
13
+ export { sceneNodeSchema, getPropertySchema, isImmutable, type PropertySchema } from './Schema.js';
14
+ export { ConflictResolver } from './ConflictResolver.js';