@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,48 @@
1
+ /**
2
+ * Boolean operations: set, or, and
3
+ */
4
+
5
+ import type { SceneGraph, BooleanSetOp, BooleanOrOp, BooleanAndOp } from '../OperationTypes.js';
6
+ import type { OpMeta } from './types.js';
7
+ import { getNodeProperty, setNodeProperty, setNodePropertyLWW } from './types.js';
8
+
9
+ /**
10
+ * boolean.set - Last-Write-Wins
11
+ */
12
+ export function BooleanSet(
13
+ draft: SceneGraph,
14
+ op: BooleanSetOp,
15
+ meta: OpMeta
16
+ ): void {
17
+ const node = draft.nodes[op.key];
18
+ if (!node || node.deletedAt) return;
19
+ setNodePropertyLWW(node, op.path, op.value, meta);
20
+ }
21
+
22
+ /**
23
+ * boolean.or - Logical OR (enable wins)
24
+ */
25
+ export function BooleanOr(
26
+ draft: SceneGraph,
27
+ op: BooleanOrOp,
28
+ meta: OpMeta
29
+ ): void {
30
+ const node = draft.nodes[op.key];
31
+ if (!node || node.deletedAt) return;
32
+ const current = getNodeProperty(node, op.path, false);
33
+ setNodeProperty(node, op.path, current || op.value, meta);
34
+ }
35
+
36
+ /**
37
+ * boolean.and - Logical AND (disable wins)
38
+ */
39
+ export function BooleanAnd(
40
+ draft: SceneGraph,
41
+ op: BooleanAndOp,
42
+ meta: OpMeta
43
+ ): void {
44
+ const node = draft.nodes[op.key];
45
+ if (!node || node.deletedAt) return;
46
+ const current = getNodeProperty(node, op.path, true);
47
+ setNodeProperty(node, op.path, current && op.value, meta);
48
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Color operations: set, blend
3
+ */
4
+
5
+ import type { SceneGraph, ColorSetOp, ColorBlendOp } from '../OperationTypes.js';
6
+ import type { OpMeta } from './types.js';
7
+ import { getNodeProperty, setNodeProperty, setNodePropertyLWW } from './types.js';
8
+
9
+ /**
10
+ * color.set - Last-Write-Wins
11
+ */
12
+ export function ColorSet(
13
+ draft: SceneGraph,
14
+ op: ColorSetOp,
15
+ meta: OpMeta
16
+ ): void {
17
+ const node = draft.nodes[op.key];
18
+ if (!node || node.deletedAt) return;
19
+ setNodePropertyLWW(node, op.path, op.value, meta);
20
+ }
21
+
22
+ /**
23
+ * Parse hex color to RGB components
24
+ */
25
+ function hexToRgb(hex: string): [number, number, number] {
26
+ const h = hex.replace('#', '');
27
+ return [
28
+ parseInt(h.substring(0, 2), 16),
29
+ parseInt(h.substring(2, 4), 16),
30
+ parseInt(h.substring(4, 6), 16),
31
+ ];
32
+ }
33
+
34
+ /**
35
+ * Convert RGB to hex color
36
+ */
37
+ function rgbToHex(r: number, g: number, b: number): string {
38
+ const toHex = (n: number) => Math.round(Math.max(0, Math.min(255, n))).toString(16).padStart(2, '0');
39
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
40
+ }
41
+
42
+ /**
43
+ * color.blend - Average/blend colors
44
+ */
45
+ export function ColorBlend(
46
+ draft: SceneGraph,
47
+ op: ColorBlendOp,
48
+ meta: OpMeta
49
+ ): void {
50
+ const node = draft.nodes[op.key];
51
+ if (!node || node.deletedAt) return;
52
+
53
+ const currentHex = getNodeProperty(node, op.path, '#000000');
54
+ const [r1, g1, b1] = hexToRgb(currentHex);
55
+ const [r2, g2, b2] = hexToRgb(op.value);
56
+
57
+ // Simple average blend
58
+ const result = rgbToHex(
59
+ (r1 + r2) / 2,
60
+ (g1 + g2) / 2,
61
+ (b1 + b2) / 2
62
+ );
63
+
64
+ setNodeProperty(node, op.path, result, meta);
65
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Operation Apply Registry
3
+ *
4
+ * Exports all apply functions as a registry.
5
+ * Usage: import * as registry from './apply/index.js'
6
+ */
7
+
8
+ // Types
9
+ export type { OpMeta, ApplyFn } from './types.js';
10
+ export { getNodeProperty, setNodeProperty } from './types.js';
11
+
12
+ // Number operations
13
+ export { NumberSet, NumberAdd, NumberMultiply, NumberMin, NumberMax } from './number.js';
14
+
15
+ // String operations
16
+ export { StringSet, StringConcat } from './string.js';
17
+
18
+ // Boolean operations
19
+ export { BooleanSet, BooleanOr, BooleanAnd } from './boolean.js';
20
+
21
+ // Vector3 operations
22
+ export { Vector3Set, Vector3Add, Vector3Multiply } from './vector3.js';
23
+
24
+ // Quaternion operations
25
+ export { QuaternionSet, QuaternionMultiply } from './quaternion.js';
26
+
27
+ // Color operations
28
+ export { ColorSet, ColorBlend } from './color.js';
29
+
30
+ // Array operations
31
+ export { ArraySet, ArrayPush, ArrayRemove, ArrayUnion } from './array.js';
32
+
33
+ // Object operations
34
+ export { ObjectSet, ObjectMerge } from './object.js';
35
+
36
+ // Node operations
37
+ export { NodeInsert, NodeRemove } from './node.js';
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Node operations: insert, remove
3
+ */
4
+
5
+ import type { SceneGraph, SceneNode, NodeInsertOp, NodeRemoveOp } from '../OperationTypes.js';
6
+ import type { OpMeta } from './types.js';
7
+
8
+ /**
9
+ * node.insert - Create new node as child of parent
10
+ *
11
+ * op.key = parent node's key
12
+ * op.path = 'children'
13
+ * op.value.key = new node's key
14
+ *
15
+ * Idempotent: if node key exists, no-op
16
+ */
17
+ export function NodeInsert(
18
+ draft: SceneGraph,
19
+ op: NodeInsertOp,
20
+ meta: OpMeta
21
+ ): void {
22
+ const nodeKey = op.value.key;
23
+
24
+ // Idempotent: skip if key already exists
25
+ if (draft.nodes[nodeKey]) {
26
+ return;
27
+ }
28
+
29
+ // Extract core fields from value
30
+ const { key, id, tag, name, ...properties } = op.value;
31
+
32
+ // Create new node
33
+ const newNode: SceneNode = {
34
+ id,
35
+ key: nodeKey,
36
+ tag,
37
+ name,
38
+ children: [],
39
+ clock: { ...meta.clock },
40
+ lamportTime: meta.lamportTime,
41
+ createdAt: meta.timestamp,
42
+ updatedAt: meta.timestamp,
43
+ };
44
+
45
+ // Copy all remaining properties (flat structure with dot-notation)
46
+ for (const [propKey, propValue] of Object.entries(properties)) {
47
+ if (propKey === 'children') {
48
+ newNode.children = Array.isArray(propValue) ? [...propValue] : [];
49
+ } else {
50
+ newNode[propKey] = propValue;
51
+ }
52
+ }
53
+
54
+ // Add to graph
55
+ draft.nodes[nodeKey] = newNode;
56
+
57
+ // Set as root if first node
58
+ if (!draft.rootKey) {
59
+ draft.rootKey = nodeKey;
60
+ }
61
+
62
+ // Add to parent's children
63
+ const parentNode = draft.nodes[op.key];
64
+ if (parentNode && !parentNode.children.includes(nodeKey)) {
65
+ parentNode.children.push(nodeKey);
66
+ }
67
+ }
68
+
69
+ /**
70
+ * node.remove - Remove node from parent and soft delete (tombstone)
71
+ *
72
+ * op.key = parent node's key
73
+ * op.path = 'children'
74
+ * op.value = node key to remove
75
+ */
76
+ export function NodeRemove(
77
+ draft: SceneGraph,
78
+ op: NodeRemoveOp,
79
+ meta: OpMeta
80
+ ): void {
81
+ const nodeKey = op.value;
82
+ const node = draft.nodes[nodeKey];
83
+
84
+ if (!node) {
85
+ return; // Node doesn't exist, ignore
86
+ }
87
+
88
+ // Tombstone: mark as deleted
89
+ node.deletedAt = meta.timestamp;
90
+ node.lamportTime = meta.lamportTime;
91
+ node.updatedAt = meta.timestamp;
92
+
93
+ // Remove from parent's children
94
+ const parentNode = draft.nodes[op.key];
95
+ if (parentNode) {
96
+ parentNode.children = parentNode.children.filter(childKey => childKey !== nodeKey);
97
+ }
98
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Number operations: set, add, multiply, min, max
3
+ */
4
+
5
+ import type { SceneGraph, NumberSetOp, NumberAddOp, NumberMultiplyOp, NumberMinOp, NumberMaxOp } from '../OperationTypes.js';
6
+ import type { OpMeta } from './types.js';
7
+ import { getNodeProperty, setNodeProperty, setNodePropertyLWW } from './types.js';
8
+
9
+ /**
10
+ * number.set - Last-Write-Wins
11
+ */
12
+ export function NumberSet(
13
+ draft: SceneGraph,
14
+ op: NumberSetOp,
15
+ meta: OpMeta
16
+ ): void {
17
+ const node = draft.nodes[op.key];
18
+ if (!node || node.deletedAt) return;
19
+ setNodePropertyLWW(node, op.path, op.value, meta);
20
+ }
21
+
22
+ /**
23
+ * number.add - Additive (sum concurrent values)
24
+ */
25
+ export function NumberAdd(
26
+ draft: SceneGraph,
27
+ op: NumberAddOp,
28
+ meta: OpMeta
29
+ ): void {
30
+ const node = draft.nodes[op.key];
31
+ if (!node || node.deletedAt) return;
32
+ const current = getNodeProperty(node, op.path, 0);
33
+ setNodeProperty(node, op.path, current + op.value, meta);
34
+ }
35
+
36
+ /**
37
+ * number.multiply - Multiplicative (product of concurrent values)
38
+ */
39
+ export function NumberMultiply(
40
+ draft: SceneGraph,
41
+ op: NumberMultiplyOp,
42
+ meta: OpMeta
43
+ ): void {
44
+ const node = draft.nodes[op.key];
45
+ if (!node || node.deletedAt) return;
46
+ const current = getNodeProperty(node, op.path, 1);
47
+ setNodeProperty(node, op.path, current * op.value, meta);
48
+ }
49
+
50
+ /**
51
+ * number.min - Take minimum value
52
+ */
53
+ export function NumberMin(
54
+ draft: SceneGraph,
55
+ op: NumberMinOp,
56
+ meta: OpMeta
57
+ ): void {
58
+ const node = draft.nodes[op.key];
59
+ if (!node || node.deletedAt) return;
60
+ const current = getNodeProperty(node, op.path, Infinity);
61
+ setNodeProperty(node, op.path, Math.min(current, op.value), meta);
62
+ }
63
+
64
+ /**
65
+ * number.max - Take maximum value
66
+ */
67
+ export function NumberMax(
68
+ draft: SceneGraph,
69
+ op: NumberMaxOp,
70
+ meta: OpMeta
71
+ ): void {
72
+ const node = draft.nodes[op.key];
73
+ if (!node || node.deletedAt) return;
74
+ const current = getNodeProperty(node, op.path, -Infinity);
75
+ setNodeProperty(node, op.path, Math.max(current, op.value), meta);
76
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Object operations: set, merge
3
+ */
4
+
5
+ import type { SceneGraph, ObjectSetOp, ObjectMergeOp } from '../OperationTypes.js';
6
+ import type { OpMeta } from './types.js';
7
+ import { getNodeProperty, setNodeProperty, setNodePropertyLWW } from './types.js';
8
+
9
+ /**
10
+ * object.set - Last-Write-Wins (replace entire object)
11
+ */
12
+ export function ObjectSet(
13
+ draft: SceneGraph,
14
+ op: ObjectSetOp,
15
+ meta: OpMeta
16
+ ): void {
17
+ const node = draft.nodes[op.key];
18
+ if (!node || node.deletedAt) return;
19
+ setNodePropertyLWW(node, op.path, { ...op.value }, meta);
20
+ }
21
+
22
+ /**
23
+ * Deep merge helper
24
+ */
25
+ function deepMerge(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown> {
26
+ const result = { ...target };
27
+ for (const key of Object.keys(source)) {
28
+ const sourceVal = source[key];
29
+ const targetVal = result[key];
30
+
31
+ if (
32
+ sourceVal !== null &&
33
+ typeof sourceVal === 'object' &&
34
+ !Array.isArray(sourceVal) &&
35
+ targetVal !== null &&
36
+ typeof targetVal === 'object' &&
37
+ !Array.isArray(targetVal)
38
+ ) {
39
+ result[key] = deepMerge(
40
+ targetVal as Record<string, unknown>,
41
+ sourceVal as Record<string, unknown>
42
+ );
43
+ } else {
44
+ result[key] = sourceVal;
45
+ }
46
+ }
47
+ return result;
48
+ }
49
+
50
+ /**
51
+ * object.merge - Deep merge objects
52
+ */
53
+ export function ObjectMerge(
54
+ draft: SceneGraph,
55
+ op: ObjectMergeOp,
56
+ meta: OpMeta
57
+ ): void {
58
+ const node = draft.nodes[op.key];
59
+ if (!node || node.deletedAt) return;
60
+ const current = getNodeProperty<Record<string, unknown>>(node, op.path, {});
61
+ const merged = deepMerge(current, op.value);
62
+ setNodeProperty(node, op.path, merged, meta);
63
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Quaternion operations: set, multiply
3
+ */
4
+
5
+ import type { SceneGraph, QuaternionSetOp, QuaternionMultiplyOp } from '../OperationTypes.js';
6
+ import type { OpMeta } from './types.js';
7
+ import { getNodeProperty, setNodeProperty, setNodePropertyLWW } from './types.js';
8
+
9
+ type Quaternion = [number, number, number, number];
10
+
11
+ /**
12
+ * quaternion.set - Last-Write-Wins
13
+ */
14
+ export function QuaternionSet(
15
+ draft: SceneGraph,
16
+ op: QuaternionSetOp,
17
+ meta: OpMeta
18
+ ): void {
19
+ const node = draft.nodes[op.key];
20
+ if (!node || node.deletedAt) return;
21
+ setNodePropertyLWW(node, op.path, [...op.value] as Quaternion, meta);
22
+ }
23
+
24
+ /**
25
+ * quaternion.multiply - Quaternion multiplication (composition)
26
+ * Result = current * op.value (Hamilton product)
27
+ */
28
+ export function QuaternionMultiply(
29
+ draft: SceneGraph,
30
+ op: QuaternionMultiplyOp,
31
+ meta: OpMeta
32
+ ): void {
33
+ const node = draft.nodes[op.key];
34
+ if (!node || node.deletedAt) return;
35
+ const [ax, ay, az, aw] = getNodeProperty<Quaternion>(node, op.path, [0, 0, 0, 1]);
36
+ const [bx, by, bz, bw] = op.value;
37
+
38
+ // Hamilton product: a * b
39
+ const result: Quaternion = [
40
+ aw * bx + ax * bw + ay * bz - az * by,
41
+ aw * by - ax * bz + ay * bw + az * bx,
42
+ aw * bz + ax * by - ay * bx + az * bw,
43
+ aw * bw - ax * bx - ay * by - az * bz,
44
+ ];
45
+
46
+ setNodeProperty(node, op.path, result, meta);
47
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * String operations: set, concat
3
+ */
4
+
5
+ import type { SceneGraph, StringSetOp, StringConcatOp } from '../OperationTypes.js';
6
+ import type { OpMeta } from './types.js';
7
+ import { getNodeProperty, setNodeProperty, setNodePropertyLWW } from './types.js';
8
+
9
+ /**
10
+ * string.set - Last-Write-Wins
11
+ */
12
+ export function StringSet(
13
+ draft: SceneGraph,
14
+ op: StringSetOp,
15
+ meta: OpMeta
16
+ ): void {
17
+ const node = draft.nodes[op.key];
18
+ if (!node || node.deletedAt) return;
19
+ setNodePropertyLWW(node, op.path, op.value, meta);
20
+ }
21
+
22
+ /**
23
+ * string.concat - Concatenate (append)
24
+ */
25
+ export function StringConcat(
26
+ draft: SceneGraph,
27
+ op: StringConcatOp,
28
+ meta: OpMeta
29
+ ): void {
30
+ const node = draft.nodes[op.key];
31
+ if (!node || node.deletedAt) return;
32
+ const current = getNodeProperty(node, op.path, '');
33
+ const separator = op.separator ?? '';
34
+ const newValue = current ? `${current}${separator}${op.value}` : op.value;
35
+ setNodeProperty(node, op.path, newValue, meta);
36
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Shared types for apply functions
3
+ */
4
+
5
+ import type { VectorClock } from '../../state/VectorClock.js';
6
+ import type { SceneGraph, SceneNode } from '../OperationTypes.js';
7
+
8
+ /**
9
+ * Operation metadata from the CRDTMessage envelope
10
+ */
11
+ export interface OpMeta {
12
+ sessionId: string;
13
+ clock: VectorClock;
14
+ lamportTime: number;
15
+ timestamp: number;
16
+ }
17
+
18
+ /**
19
+ * Apply function signature - mutates draft in place (immer)
20
+ */
21
+ export type ApplyFn<TOp> = (
22
+ draft: SceneGraph,
23
+ op: TOp,
24
+ meta: OpMeta
25
+ ) => void;
26
+
27
+ /**
28
+ * Helper to get or initialize a node property with a default value
29
+ */
30
+ export function getNodeProperty<T>(node: SceneNode, path: string, defaultValue: T): T {
31
+ const value = node[path as keyof SceneNode];
32
+ return value !== undefined ? (value as T) : defaultValue;
33
+ }
34
+
35
+ /**
36
+ * Helper to set a node property (for additive operations)
37
+ * Does NOT update lamportTime - only LWW operations should do that
38
+ */
39
+ export function setNodeProperty(
40
+ node: SceneNode,
41
+ path: string,
42
+ value: unknown,
43
+ meta: OpMeta
44
+ ): void {
45
+ node[path] = value;
46
+ node.updatedAt = meta.timestamp;
47
+ }
48
+
49
+ /**
50
+ * Helper to set a node property with LWW semantics
51
+ * Only updates if incoming lamportTime >= current lamportTime
52
+ * (>= allows multiple ops in the same message to apply)
53
+ */
54
+ export function setNodePropertyLWW(
55
+ node: SceneNode,
56
+ path: string,
57
+ value: unknown,
58
+ meta: OpMeta
59
+ ): void {
60
+ // LWW: only update if incoming lamport time is higher or equal
61
+ if (meta.lamportTime >= node.lamportTime) {
62
+ node[path] = value;
63
+ node.lamportTime = meta.lamportTime;
64
+ node.updatedAt = meta.timestamp;
65
+ }
66
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Vector3 operations: set, add, multiply
3
+ */
4
+
5
+ import type { SceneGraph, Vector3SetOp, Vector3AddOp, Vector3MultiplyOp } from '../OperationTypes.js';
6
+ import type { OpMeta } from './types.js';
7
+ import { getNodeProperty, setNodeProperty, setNodePropertyLWW } from './types.js';
8
+
9
+ type Vector3 = [number, number, number];
10
+
11
+ /**
12
+ * vector3.set - Last-Write-Wins
13
+ */
14
+ export function Vector3Set(
15
+ draft: SceneGraph,
16
+ op: Vector3SetOp,
17
+ meta: OpMeta
18
+ ): void {
19
+ const node = draft.nodes[op.key];
20
+ if (!node || node.deletedAt) return;
21
+ setNodePropertyLWW(node, op.path, [...op.value] as Vector3, meta);
22
+ }
23
+
24
+ /**
25
+ * vector3.add - Component-wise addition (additive deltas)
26
+ */
27
+ export function Vector3Add(
28
+ draft: SceneGraph,
29
+ op: Vector3AddOp,
30
+ meta: OpMeta
31
+ ): void {
32
+ const node = draft.nodes[op.key];
33
+ if (!node || node.deletedAt) return;
34
+ const current = getNodeProperty<Vector3>(node, op.path, [0, 0, 0]);
35
+ const result: Vector3 = [
36
+ current[0] + op.value[0],
37
+ current[1] + op.value[1],
38
+ current[2] + op.value[2],
39
+ ];
40
+ setNodeProperty(node, op.path, result, meta);
41
+ }
42
+
43
+ /**
44
+ * vector3.multiply - Component-wise multiplication (scale)
45
+ */
46
+ export function Vector3Multiply(
47
+ draft: SceneGraph,
48
+ op: Vector3MultiplyOp,
49
+ meta: OpMeta
50
+ ): void {
51
+ const node = draft.nodes[op.key];
52
+ if (!node || node.deletedAt) return;
53
+ const current = getNodeProperty<Vector3>(node, op.path, [1, 1, 1]);
54
+ const result: Vector3 = [
55
+ current[0] * op.value[0],
56
+ current[1] * op.value[1],
57
+ current[2] * op.value[2],
58
+ ];
59
+ setNodeProperty(node, op.path, result, meta);
60
+ }