@verdant-web/common 1.11.0

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 (241) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cjs/EventSubscriber.d.ts +17 -0
  3. package/dist/cjs/EventSubscriber.js +67 -0
  4. package/dist/cjs/EventSubscriber.js.map +1 -0
  5. package/dist/cjs/baseline.d.ts +5 -0
  6. package/dist/cjs/baseline.js +3 -0
  7. package/dist/cjs/baseline.js.map +1 -0
  8. package/dist/cjs/batching.d.ts +41 -0
  9. package/dist/cjs/batching.js +89 -0
  10. package/dist/cjs/batching.js.map +1 -0
  11. package/dist/cjs/files.d.ts +19 -0
  12. package/dist/cjs/files.js +19 -0
  13. package/dist/cjs/files.js.map +1 -0
  14. package/dist/cjs/files.test.d.ts +1 -0
  15. package/dist/cjs/files.test.js +81 -0
  16. package/dist/cjs/files.test.js.map +1 -0
  17. package/dist/cjs/index.d.ts +18 -0
  18. package/dist/cjs/index.js +36 -0
  19. package/dist/cjs/index.js.map +1 -0
  20. package/dist/cjs/indexes.d.ts +14 -0
  21. package/dist/cjs/indexes.js +116 -0
  22. package/dist/cjs/indexes.js.map +1 -0
  23. package/dist/cjs/indexes.test.d.ts +1 -0
  24. package/dist/cjs/indexes.test.js +128 -0
  25. package/dist/cjs/indexes.test.js.map +1 -0
  26. package/dist/cjs/migration.d.ts +98 -0
  27. package/dist/cjs/migration.js +128 -0
  28. package/dist/cjs/migration.js.map +1 -0
  29. package/dist/cjs/oids.d.ts +87 -0
  30. package/dist/cjs/oids.js +375 -0
  31. package/dist/cjs/oids.js.map +1 -0
  32. package/dist/cjs/oids.test.d.ts +1 -0
  33. package/dist/cjs/oids.test.js +282 -0
  34. package/dist/cjs/oids.test.js.map +1 -0
  35. package/dist/cjs/operation.d.ts +139 -0
  36. package/dist/cjs/operation.js +533 -0
  37. package/dist/cjs/operation.js.map +1 -0
  38. package/dist/cjs/operation.test.d.ts +1 -0
  39. package/dist/cjs/operation.test.js +827 -0
  40. package/dist/cjs/operation.test.js.map +1 -0
  41. package/dist/cjs/patch.d.ts +26 -0
  42. package/dist/cjs/patch.js +227 -0
  43. package/dist/cjs/patch.js.map +1 -0
  44. package/dist/cjs/presence.d.ts +27 -0
  45. package/dist/cjs/presence.js +3 -0
  46. package/dist/cjs/presence.js.map +1 -0
  47. package/dist/cjs/protocol.d.ts +145 -0
  48. package/dist/cjs/protocol.js +4 -0
  49. package/dist/cjs/protocol.js.map +1 -0
  50. package/dist/cjs/refs.d.ts +5 -0
  51. package/dist/cjs/refs.js +22 -0
  52. package/dist/cjs/refs.js.map +1 -0
  53. package/dist/cjs/replica.d.ts +28 -0
  54. package/dist/cjs/replica.js +29 -0
  55. package/dist/cjs/replica.js.map +1 -0
  56. package/dist/cjs/schema/defaults.d.ts +4 -0
  57. package/dist/cjs/schema/defaults.js +72 -0
  58. package/dist/cjs/schema/defaults.js.map +1 -0
  59. package/dist/cjs/schema/defaults.test.d.ts +1 -0
  60. package/dist/cjs/schema/defaults.test.js +21 -0
  61. package/dist/cjs/schema/defaults.test.js.map +1 -0
  62. package/dist/cjs/schema/index.d.ts +8 -0
  63. package/dist/cjs/schema/index.js +29 -0
  64. package/dist/cjs/schema/index.js.map +1 -0
  65. package/dist/cjs/schema/indexFilters.d.ts +6 -0
  66. package/dist/cjs/schema/indexFilters.js +31 -0
  67. package/dist/cjs/schema/indexFilters.js.map +1 -0
  68. package/dist/cjs/schema/types/collection.d.ts +51 -0
  69. package/dist/cjs/schema/types/collection.js +3 -0
  70. package/dist/cjs/schema/types/collection.js.map +1 -0
  71. package/dist/cjs/schema/types/compounds.d.ts +12 -0
  72. package/dist/cjs/schema/types/compounds.js +3 -0
  73. package/dist/cjs/schema/types/compounds.js.map +1 -0
  74. package/dist/cjs/schema/types/fields.d.ts +59 -0
  75. package/dist/cjs/schema/types/fields.js +3 -0
  76. package/dist/cjs/schema/types/fields.js.map +1 -0
  77. package/dist/cjs/schema/types/filters.d.ts +29 -0
  78. package/dist/cjs/schema/types/filters.js +3 -0
  79. package/dist/cjs/schema/types/filters.js.map +1 -0
  80. package/dist/cjs/schema/types/shapes.d.ts +21 -0
  81. package/dist/cjs/schema/types/shapes.js +3 -0
  82. package/dist/cjs/schema/types/shapes.js.map +1 -0
  83. package/dist/cjs/schema/types/synthetics.d.ts +28 -0
  84. package/dist/cjs/schema/types/synthetics.js +3 -0
  85. package/dist/cjs/schema/types/synthetics.js.map +1 -0
  86. package/dist/cjs/schema/types.d.ts +18 -0
  87. package/dist/cjs/schema/types.js +23 -0
  88. package/dist/cjs/schema/types.js.map +1 -0
  89. package/dist/cjs/timestamp.d.ts +48 -0
  90. package/dist/cjs/timestamp.js +244 -0
  91. package/dist/cjs/timestamp.js.map +1 -0
  92. package/dist/cjs/timestamp.test.d.ts +1 -0
  93. package/dist/cjs/timestamp.test.js +94 -0
  94. package/dist/cjs/timestamp.test.js.map +1 -0
  95. package/dist/cjs/undo.d.ts +3 -0
  96. package/dist/cjs/undo.js +199 -0
  97. package/dist/cjs/undo.js.map +1 -0
  98. package/dist/cjs/utils.d.ts +15 -0
  99. package/dist/cjs/utils.js +128 -0
  100. package/dist/cjs/utils.js.map +1 -0
  101. package/dist/cjs/utils.test.d.ts +1 -0
  102. package/dist/cjs/utils.test.js +37 -0
  103. package/dist/cjs/utils.test.js.map +1 -0
  104. package/dist/esm/EventSubscriber.d.ts +17 -0
  105. package/dist/esm/EventSubscriber.js +63 -0
  106. package/dist/esm/EventSubscriber.js.map +1 -0
  107. package/dist/esm/baseline.d.ts +5 -0
  108. package/dist/esm/baseline.js +2 -0
  109. package/dist/esm/baseline.js.map +1 -0
  110. package/dist/esm/batching.d.ts +41 -0
  111. package/dist/esm/batching.js +84 -0
  112. package/dist/esm/batching.js.map +1 -0
  113. package/dist/esm/files.d.ts +19 -0
  114. package/dist/esm/files.js +13 -0
  115. package/dist/esm/files.js.map +1 -0
  116. package/dist/esm/files.test.d.ts +1 -0
  117. package/dist/esm/files.test.js +79 -0
  118. package/dist/esm/files.test.js.map +1 -0
  119. package/dist/esm/index.d.ts +18 -0
  120. package/dist/esm/index.js +16 -0
  121. package/dist/esm/index.js.map +1 -0
  122. package/dist/esm/indexes.d.ts +14 -0
  123. package/dist/esm/indexes.js +106 -0
  124. package/dist/esm/indexes.js.map +1 -0
  125. package/dist/esm/indexes.test.d.ts +1 -0
  126. package/dist/esm/indexes.test.js +126 -0
  127. package/dist/esm/indexes.test.js.map +1 -0
  128. package/dist/esm/migration.d.ts +98 -0
  129. package/dist/esm/migration.js +122 -0
  130. package/dist/esm/migration.js.map +1 -0
  131. package/dist/esm/oids.d.ts +87 -0
  132. package/dist/esm/oids.js +348 -0
  133. package/dist/esm/oids.js.map +1 -0
  134. package/dist/esm/oids.test.d.ts +1 -0
  135. package/dist/esm/oids.test.js +280 -0
  136. package/dist/esm/oids.test.js.map +1 -0
  137. package/dist/esm/operation.d.ts +139 -0
  138. package/dist/esm/operation.js +518 -0
  139. package/dist/esm/operation.js.map +1 -0
  140. package/dist/esm/operation.test.d.ts +1 -0
  141. package/dist/esm/operation.test.js +825 -0
  142. package/dist/esm/operation.test.js.map +1 -0
  143. package/dist/esm/patch.d.ts +26 -0
  144. package/dist/esm/patch.js +223 -0
  145. package/dist/esm/patch.js.map +1 -0
  146. package/dist/esm/presence.d.ts +27 -0
  147. package/dist/esm/presence.js +2 -0
  148. package/dist/esm/presence.js.map +1 -0
  149. package/dist/esm/protocol.d.ts +145 -0
  150. package/dist/esm/protocol.js +3 -0
  151. package/dist/esm/protocol.js.map +1 -0
  152. package/dist/esm/refs.d.ts +5 -0
  153. package/dist/esm/refs.js +17 -0
  154. package/dist/esm/refs.js.map +1 -0
  155. package/dist/esm/replica.d.ts +28 -0
  156. package/dist/esm/replica.js +26 -0
  157. package/dist/esm/replica.js.map +1 -0
  158. package/dist/esm/schema/defaults.d.ts +4 -0
  159. package/dist/esm/schema/defaults.js +66 -0
  160. package/dist/esm/schema/defaults.js.map +1 -0
  161. package/dist/esm/schema/defaults.test.d.ts +1 -0
  162. package/dist/esm/schema/defaults.test.js +19 -0
  163. package/dist/esm/schema/defaults.test.js.map +1 -0
  164. package/dist/esm/schema/index.d.ts +8 -0
  165. package/dist/esm/schema/index.js +10 -0
  166. package/dist/esm/schema/index.js.map +1 -0
  167. package/dist/esm/schema/indexFilters.d.ts +6 -0
  168. package/dist/esm/schema/indexFilters.js +23 -0
  169. package/dist/esm/schema/indexFilters.js.map +1 -0
  170. package/dist/esm/schema/types/collection.d.ts +51 -0
  171. package/dist/esm/schema/types/collection.js +2 -0
  172. package/dist/esm/schema/types/collection.js.map +1 -0
  173. package/dist/esm/schema/types/compounds.d.ts +12 -0
  174. package/dist/esm/schema/types/compounds.js +2 -0
  175. package/dist/esm/schema/types/compounds.js.map +1 -0
  176. package/dist/esm/schema/types/fields.d.ts +59 -0
  177. package/dist/esm/schema/types/fields.js +2 -0
  178. package/dist/esm/schema/types/fields.js.map +1 -0
  179. package/dist/esm/schema/types/filters.d.ts +29 -0
  180. package/dist/esm/schema/types/filters.js +2 -0
  181. package/dist/esm/schema/types/filters.js.map +1 -0
  182. package/dist/esm/schema/types/shapes.d.ts +21 -0
  183. package/dist/esm/schema/types/shapes.js +2 -0
  184. package/dist/esm/schema/types/shapes.js.map +1 -0
  185. package/dist/esm/schema/types/synthetics.d.ts +28 -0
  186. package/dist/esm/schema/types/synthetics.js +2 -0
  187. package/dist/esm/schema/types/synthetics.js.map +1 -0
  188. package/dist/esm/schema/types.d.ts +18 -0
  189. package/dist/esm/schema/types.js +7 -0
  190. package/dist/esm/schema/types.js.map +1 -0
  191. package/dist/esm/timestamp.d.ts +48 -0
  192. package/dist/esm/timestamp.js +229 -0
  193. package/dist/esm/timestamp.js.map +1 -0
  194. package/dist/esm/timestamp.test.d.ts +1 -0
  195. package/dist/esm/timestamp.test.js +92 -0
  196. package/dist/esm/timestamp.test.js.map +1 -0
  197. package/dist/esm/undo.d.ts +3 -0
  198. package/dist/esm/undo.js +195 -0
  199. package/dist/esm/undo.js.map +1 -0
  200. package/dist/esm/utils.d.ts +15 -0
  201. package/dist/esm/utils.js +114 -0
  202. package/dist/esm/utils.js.map +1 -0
  203. package/dist/esm/utils.test.d.ts +1 -0
  204. package/dist/esm/utils.test.js +35 -0
  205. package/dist/esm/utils.test.js.map +1 -0
  206. package/dist/tsconfig-cjs.tsbuildinfo +1 -0
  207. package/dist/tsconfig.tsbuildinfo +1 -0
  208. package/package.json +38 -0
  209. package/src/EventSubscriber.ts +81 -0
  210. package/src/baseline.ts +9 -0
  211. package/src/batching.ts +135 -0
  212. package/src/files.ts +34 -0
  213. package/src/index.ts +26 -0
  214. package/src/indexes.test.ts +158 -0
  215. package/src/indexes.ts +151 -0
  216. package/src/migration.ts +391 -0
  217. package/src/oids.test.ts +395 -0
  218. package/src/oids.ts +442 -0
  219. package/src/operation.test.ts +966 -0
  220. package/src/operation.ts +755 -0
  221. package/src/patch.ts +277 -0
  222. package/src/presence.ts +29 -0
  223. package/src/protocol.ts +184 -0
  224. package/src/refs.ts +16 -0
  225. package/src/replica.ts +29 -0
  226. package/src/schema/defaults.test.ts +19 -0
  227. package/src/schema/defaults.ts +72 -0
  228. package/src/schema/index.ts +28 -0
  229. package/src/schema/indexFilters.ts +49 -0
  230. package/src/schema/types/collection.ts +133 -0
  231. package/src/schema/types/compounds.ts +33 -0
  232. package/src/schema/types/fields.ts +85 -0
  233. package/src/schema/types/filters.ts +39 -0
  234. package/src/schema/types/shapes.ts +83 -0
  235. package/src/schema/types/synthetics.ts +45 -0
  236. package/src/schema/types.ts +28 -0
  237. package/src/timestamp.test.ts +113 -0
  238. package/src/timestamp.ts +284 -0
  239. package/src/undo.ts +205 -0
  240. package/src/utils.test.ts +38 -0
  241. package/src/utils.ts +137 -0
@@ -0,0 +1,825 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createFileRef } from './files.js';
3
+ import { assignOid, assignOidsToAllSubObjects, createRef, getOid, } from './oids.js';
4
+ import { applyPatch, applyOperations, diffToPatches, initialToPatches, substituteRefsWithObjects, } from './operation.js';
5
+ function createClock(init = 0) {
6
+ let i = init;
7
+ return () => (i++).toString();
8
+ }
9
+ describe('creating diff patch operations', () => {
10
+ describe('on flat objects', () => {
11
+ it('generates and applies set and remove operations', () => {
12
+ const from = { foo: 'bar', baz: 'qux', zing: 1 };
13
+ assignOid(from, 'test/a');
14
+ const to = { foo: 'bar', baz: 'pop' };
15
+ assignOid(to, 'test/a');
16
+ const ops = diffToPatches(from, to, createClock(), []);
17
+ expect(ops).toMatchInlineSnapshot(`
18
+ [
19
+ {
20
+ "data": {
21
+ "name": "baz",
22
+ "op": "set",
23
+ "value": "pop",
24
+ },
25
+ "oid": "test/a",
26
+ "timestamp": "0",
27
+ },
28
+ {
29
+ "data": {
30
+ "name": "zing",
31
+ "op": "remove",
32
+ },
33
+ "oid": "test/a",
34
+ "timestamp": "1",
35
+ },
36
+ ]
37
+ `);
38
+ const result = applyOperations(from, ops);
39
+ expect(result).toEqual(to);
40
+ });
41
+ });
42
+ describe('on nested objects', () => {
43
+ it('replaces whole nested objects', () => {
44
+ const from = assignOid({
45
+ foo: 'bar',
46
+ baz: assignOid({ qux: 'corge' }, 'test/a:0'),
47
+ }, 'test/a');
48
+ const to = assignOid({
49
+ foo: 'bar',
50
+ baz: assignOid({ qux: 'grault' }, 'test/a:1'),
51
+ }, 'test/a');
52
+ const patches = diffToPatches(from, to, createClock(), []);
53
+ expect(patches).toMatchInlineSnapshot(`
54
+ [
55
+ {
56
+ "data": {
57
+ "op": "initialize",
58
+ "value": {
59
+ "qux": "grault",
60
+ },
61
+ },
62
+ "oid": "test/a:1",
63
+ "timestamp": "0",
64
+ },
65
+ {
66
+ "data": {
67
+ "name": "baz",
68
+ "op": "set",
69
+ "value": {
70
+ "@@type": "ref",
71
+ "id": "test/a:1",
72
+ },
73
+ },
74
+ "oid": "test/a",
75
+ "timestamp": "1",
76
+ },
77
+ {
78
+ "data": {
79
+ "op": "delete",
80
+ },
81
+ "oid": "test/a:0",
82
+ "timestamp": "2",
83
+ },
84
+ ]
85
+ `);
86
+ });
87
+ it('replaces whole nested objects with different ids even if fields are the same', () => {
88
+ const from = {
89
+ foo: 'bar',
90
+ baz: { qux: 'corge' },
91
+ };
92
+ assignOid(from, 'test/a');
93
+ assignOid(from.baz, 'test/a:0');
94
+ const to = {
95
+ foo: 'bar',
96
+ baz: { qux: 'corge' },
97
+ };
98
+ assignOid(to, 'test/a');
99
+ assignOid(to.baz, 'test/a:1');
100
+ const patches = diffToPatches(from, to, createClock(), []);
101
+ expect(patches).toMatchInlineSnapshot(`
102
+ [
103
+ {
104
+ "data": {
105
+ "op": "initialize",
106
+ "value": {
107
+ "qux": "corge",
108
+ },
109
+ },
110
+ "oid": "test/a:1",
111
+ "timestamp": "0",
112
+ },
113
+ {
114
+ "data": {
115
+ "name": "baz",
116
+ "op": "set",
117
+ "value": {
118
+ "@@type": "ref",
119
+ "id": "test/a:1",
120
+ },
121
+ },
122
+ "oid": "test/a",
123
+ "timestamp": "1",
124
+ },
125
+ {
126
+ "data": {
127
+ "op": "delete",
128
+ },
129
+ "oid": "test/a:0",
130
+ "timestamp": "2",
131
+ },
132
+ ]
133
+ `);
134
+ });
135
+ it('does not replace objects with the same identity and same fields', () => {
136
+ const from = {
137
+ foo: 'bar',
138
+ baz: { qux: 'corge' },
139
+ };
140
+ assignOid(from, 'test/a');
141
+ assignOid(from.baz, 'test/a:0');
142
+ const to = {
143
+ foo: 'bar',
144
+ baz: { qux: 'corge' },
145
+ };
146
+ assignOid(to, 'test/a');
147
+ assignOid(to.baz, 'test/a:0');
148
+ const patches = diffToPatches(from, to, createClock(), []);
149
+ expect(patches).toEqual([]);
150
+ });
151
+ it('patches sub-objects with same identity', () => {
152
+ const from = {
153
+ foo: 'bar',
154
+ baz: { qux: 'corge' },
155
+ };
156
+ assignOid(from, 'test/a');
157
+ assignOid(from.baz, 'test/a:0');
158
+ const to = {
159
+ foo: 'bar',
160
+ baz: { qux: 'fig' },
161
+ };
162
+ assignOid(to, 'test/a');
163
+ assignOid(to.baz, 'test/a:0');
164
+ const patches = diffToPatches(from, to, createClock(), []);
165
+ expect(patches).toMatchInlineSnapshot(`
166
+ [
167
+ {
168
+ "data": {
169
+ "name": "qux",
170
+ "op": "set",
171
+ "value": "fig",
172
+ },
173
+ "oid": "test/a:0",
174
+ "timestamp": "0",
175
+ },
176
+ ]
177
+ `);
178
+ });
179
+ it('retains keys which are undefined in new object if defaultUndefined is set', () => {
180
+ const from = {
181
+ foo: 'bar',
182
+ baz: { qux: 'corge' },
183
+ };
184
+ assignOid(from, 'test/a');
185
+ assignOid(from.baz, 'test/a:0');
186
+ const to = {
187
+ foo: 'bip',
188
+ };
189
+ assignOid(to, 'test/a');
190
+ const patches = diffToPatches(from, to, createClock(), [], undefined, [], {
191
+ defaultUndefined: true,
192
+ mergeUnknownObjects: true,
193
+ });
194
+ expect(patches).toMatchInlineSnapshot(`
195
+ [
196
+ {
197
+ "data": {
198
+ "name": "foo",
199
+ "op": "set",
200
+ "value": "bip",
201
+ },
202
+ "oid": "test/a",
203
+ "timestamp": "0",
204
+ },
205
+ ]
206
+ `);
207
+ });
208
+ });
209
+ describe('on lists of primitives', () => {
210
+ it('pushes items', async () => {
211
+ expect(applyOperations(assignOid(['foo', 'bar'], 'test/a'), [
212
+ {
213
+ oid: 'test/a',
214
+ timestamp: 'meh',
215
+ data: {
216
+ op: 'list-push',
217
+ value: 'baz',
218
+ },
219
+ },
220
+ ])).toEqual(assignOid(['foo', 'bar', 'baz'], 'test/a'));
221
+ });
222
+ it.todo('sets items');
223
+ it.todo('inserts items');
224
+ it.todo('removes items by index');
225
+ it.todo('removes items by value');
226
+ it.todo('moves items by index');
227
+ it.todo('moves items by value');
228
+ });
229
+ describe.todo('on lists of objects', () => {
230
+ it.todo('pushes items');
231
+ it.todo('moves objects by identity');
232
+ it.todo('removes objects by identity');
233
+ });
234
+ describe.todo('on lists of lists', () => {
235
+ it.todo('rejects operations by value or identity');
236
+ });
237
+ it('should work for complex nested arrays and objects', () => {
238
+ const from = {
239
+ foo: {
240
+ bar: [1, 2, 3],
241
+ },
242
+ baz: [
243
+ {
244
+ corge: true,
245
+ },
246
+ ],
247
+ };
248
+ assignOid(from, 'test/a');
249
+ assignOid(from.foo, 'test/a:1');
250
+ assignOid(from.foo.bar, 'test/a:2');
251
+ assignOid(from.baz, 'test/a:3');
252
+ assignOid(from.baz[0], 'test/a:4');
253
+ const to = {
254
+ foo: {
255
+ bar: [1, 2],
256
+ bop: [0],
257
+ },
258
+ baz: [
259
+ {
260
+ corge: false,
261
+ },
262
+ {
263
+ corge: false,
264
+ },
265
+ ],
266
+ };
267
+ assignOid(to, 'test/a');
268
+ assignOid(to.foo, 'test/a:1');
269
+ assignOid(to.foo.bar, 'test/a:2');
270
+ assignOid(to.foo.bop, 'test/a:6');
271
+ assignOid(to.baz, 'test/a:3');
272
+ assignOid(to.baz[0], 'test/a:4');
273
+ assignOid(to.baz[1], 'test/a:5');
274
+ expect(diffToPatches(from, to, createClock(), [])).toMatchInlineSnapshot(`
275
+ [
276
+ {
277
+ "data": {
278
+ "count": 1,
279
+ "index": 2,
280
+ "op": "list-delete",
281
+ },
282
+ "oid": "test/a:2",
283
+ "timestamp": "0",
284
+ },
285
+ {
286
+ "data": {
287
+ "op": "initialize",
288
+ "value": [
289
+ 0,
290
+ ],
291
+ },
292
+ "oid": "test/a:6",
293
+ "timestamp": "1",
294
+ },
295
+ {
296
+ "data": {
297
+ "name": "bop",
298
+ "op": "set",
299
+ "value": {
300
+ "@@type": "ref",
301
+ "id": "test/a:6",
302
+ },
303
+ },
304
+ "oid": "test/a:1",
305
+ "timestamp": "2",
306
+ },
307
+ {
308
+ "data": {
309
+ "name": "corge",
310
+ "op": "set",
311
+ "value": false,
312
+ },
313
+ "oid": "test/a:4",
314
+ "timestamp": "3",
315
+ },
316
+ {
317
+ "data": {
318
+ "op": "initialize",
319
+ "value": {
320
+ "corge": false,
321
+ },
322
+ },
323
+ "oid": "test/a:5",
324
+ "timestamp": "4",
325
+ },
326
+ {
327
+ "data": {
328
+ "name": 1,
329
+ "op": "set",
330
+ "value": {
331
+ "@@type": "ref",
332
+ "id": "test/a:5",
333
+ },
334
+ },
335
+ "oid": "test/a:3",
336
+ "timestamp": "5",
337
+ },
338
+ ]
339
+ `);
340
+ });
341
+ it('should try to merge unknown objects if specified to do so', () => {
342
+ const from = {
343
+ foo: {
344
+ bar: [1, 2, 3],
345
+ },
346
+ baz: [
347
+ {
348
+ corge: true,
349
+ },
350
+ ],
351
+ };
352
+ assignOid(from, 'test/a');
353
+ assignOidsToAllSubObjects(from, createClock());
354
+ const to = {
355
+ foo: {
356
+ bar: [1, 2],
357
+ bop: [0],
358
+ },
359
+ baz: [
360
+ {
361
+ corge: false,
362
+ },
363
+ {
364
+ corge: false,
365
+ },
366
+ ],
367
+ };
368
+ expect(diffToPatches(from, to, createClock(), [], createClock(5), [], {
369
+ mergeUnknownObjects: true,
370
+ })).toMatchInlineSnapshot(`
371
+ [
372
+ {
373
+ "data": {
374
+ "count": 1,
375
+ "index": 2,
376
+ "op": "list-delete",
377
+ },
378
+ "oid": "test/a.foo.bar:1",
379
+ "timestamp": "0",
380
+ },
381
+ {
382
+ "data": {
383
+ "op": "initialize",
384
+ "value": [
385
+ 0,
386
+ ],
387
+ },
388
+ "oid": "test/a.foo.bop:5",
389
+ "timestamp": "1",
390
+ },
391
+ {
392
+ "data": {
393
+ "name": "bop",
394
+ "op": "set",
395
+ "value": {
396
+ "@@type": "ref",
397
+ "id": "test/a.foo.bop:5",
398
+ },
399
+ },
400
+ "oid": "test/a.foo:0",
401
+ "timestamp": "2",
402
+ },
403
+ {
404
+ "data": {
405
+ "name": "corge",
406
+ "op": "set",
407
+ "value": false,
408
+ },
409
+ "oid": "test/a.baz.#:3",
410
+ "timestamp": "3",
411
+ },
412
+ {
413
+ "data": {
414
+ "op": "initialize",
415
+ "value": {
416
+ "corge": false,
417
+ },
418
+ },
419
+ "oid": "test/a.baz.#:6",
420
+ "timestamp": "4",
421
+ },
422
+ {
423
+ "data": {
424
+ "name": 1,
425
+ "op": "set",
426
+ "value": {
427
+ "@@type": "ref",
428
+ "id": "test/a.baz.#:6",
429
+ },
430
+ },
431
+ "oid": "test/a.baz:2",
432
+ "timestamp": "5",
433
+ },
434
+ ]
435
+ `);
436
+ });
437
+ it('should not diff file refs', () => {
438
+ const from = {
439
+ foo: {
440
+ file: createFileRef('abc123'),
441
+ },
442
+ bar: [createFileRef('def456'), createFileRef('ghi789')],
443
+ };
444
+ assignOid(from, 'test/a');
445
+ assignOidsToAllSubObjects(from, createClock());
446
+ const to = {
447
+ foo: {
448
+ file: createFileRef('abc456'),
449
+ },
450
+ bar: [createFileRef('def456')],
451
+ };
452
+ expect(diffToPatches(from, to, createClock(), [], createClock(5), [], {
453
+ mergeUnknownObjects: true,
454
+ })).toMatchInlineSnapshot(`
455
+ [
456
+ {
457
+ "data": {
458
+ "name": "file",
459
+ "op": "set",
460
+ "value": {
461
+ "@@type": "file",
462
+ "id": "abc456",
463
+ },
464
+ },
465
+ "oid": "test/a.foo:0",
466
+ "timestamp": "0",
467
+ },
468
+ {
469
+ "data": {
470
+ "count": 1,
471
+ "index": 1,
472
+ "op": "list-delete",
473
+ },
474
+ "oid": "test/a.bar:1",
475
+ "timestamp": "1",
476
+ },
477
+ ]
478
+ `);
479
+ });
480
+ });
481
+ describe('substituting refs with objects', () => {
482
+ it('does nothing when no refs exist', () => {
483
+ const root = {
484
+ foo: 'bar',
485
+ };
486
+ assignOid(root, 'test/a');
487
+ const substituted = substituteRefsWithObjects(root, new Map());
488
+ expect(root).toEqual(assignOid({
489
+ foo: 'bar',
490
+ }, 'test/a'));
491
+ expect(substituted).toEqual([]);
492
+ });
493
+ it('inserts top level objects with oids', () => {
494
+ const root = {
495
+ foo: {
496
+ '@@type': 'ref',
497
+ id: 'test/1:a',
498
+ },
499
+ qux: 1,
500
+ };
501
+ assignOid(root, 'test/1');
502
+ const substituted = substituteRefsWithObjects(root, new Map([['test/1:a', { foo: 'bar' }]]));
503
+ expect(root).toEqual(assignOid({
504
+ foo: assignOid({
505
+ foo: 'bar',
506
+ }, 'test/1:a'),
507
+ qux: 1,
508
+ }, 'test/1'));
509
+ expect(substituted).toEqual(['test/1:a']);
510
+ expect(getOid(root)).toEqual('test/1');
511
+ expect(getOid(root.foo)).toEqual('test/1:a');
512
+ });
513
+ it('inserts nested objects with oids', () => {
514
+ const root = {
515
+ foo: {
516
+ '@@type': 'ref',
517
+ id: 'test/1:a',
518
+ },
519
+ };
520
+ assignOid(root, 'test/1');
521
+ const substituted = substituteRefsWithObjects(root, new Map([
522
+ [
523
+ 'test/1:a',
524
+ {
525
+ foo: 'bar',
526
+ baz: {
527
+ '@@type': 'ref',
528
+ id: 'test/1:b',
529
+ },
530
+ },
531
+ ],
532
+ [
533
+ 'test/1:b',
534
+ {
535
+ qux: 'corge',
536
+ },
537
+ ],
538
+ ]));
539
+ expect(root).toEqual(assignOid({
540
+ foo: assignOid({
541
+ foo: 'bar',
542
+ baz: assignOid({
543
+ qux: 'corge',
544
+ }, 'test/1:b'),
545
+ }, 'test/1:a'),
546
+ }, 'test/1'));
547
+ expect(substituted).toEqual(['test/1:a', 'test/1:b']);
548
+ expect(getOid(root)).toEqual('test/1');
549
+ expect(getOid(root.foo)).toEqual('test/1:a');
550
+ expect(getOid(root.foo.baz)).toEqual('test/1:b');
551
+ });
552
+ it('substitutes arrays of objects', () => {
553
+ const root = {
554
+ foo: {
555
+ '@@type': 'ref',
556
+ id: 'test/1:c',
557
+ },
558
+ };
559
+ assignOid(root, 'test/1');
560
+ const substituted = substituteRefsWithObjects(root, new Map([
561
+ [
562
+ 'test/1:c',
563
+ [
564
+ {
565
+ '@@type': 'ref',
566
+ id: 'test/1:a',
567
+ },
568
+ {
569
+ '@@type': 'ref',
570
+ id: 'test/1:b',
571
+ },
572
+ ],
573
+ ],
574
+ [
575
+ 'test/1:a',
576
+ {
577
+ foo: 'bar',
578
+ },
579
+ ],
580
+ [
581
+ 'test/1:b',
582
+ {
583
+ qux: 'corge',
584
+ },
585
+ ],
586
+ ]));
587
+ expect(root).toEqual({
588
+ foo: assignOid([
589
+ assignOid({
590
+ foo: 'bar',
591
+ }, 'test/1:a'),
592
+ assignOid({
593
+ qux: 'corge',
594
+ }, 'test/1:b'),
595
+ ], 'test/1:c'),
596
+ });
597
+ expect(substituted).toEqual(['test/1:c', 'test/1:a', 'test/1:b']);
598
+ expect(getOid(root)).toEqual('test/1');
599
+ expect(getOid(root.foo)).toEqual('test/1:c');
600
+ expect(getOid(root.foo[0])).toEqual('test/1:a');
601
+ expect(getOid(root.foo[1])).toEqual('test/1:b');
602
+ });
603
+ it.todo('substitutes arrays of arrays of objects');
604
+ });
605
+ describe('creating patches from initial state', () => {
606
+ it('adds oids to all sub-objects', async () => {
607
+ let i = 0;
608
+ function createSubId() {
609
+ return (i++).toString();
610
+ }
611
+ const result = initialToPatches({
612
+ foo: {
613
+ bar: 'baz',
614
+ },
615
+ qux: [
616
+ {
617
+ corge: 'grault',
618
+ },
619
+ {
620
+ bin: {
621
+ oof: 1,
622
+ },
623
+ },
624
+ ],
625
+ }, 'test/a', createClock(), createSubId);
626
+ /**
627
+ * Patches should not include assigned OID property
628
+ */
629
+ expect(result).toMatchInlineSnapshot(`
630
+ [
631
+ {
632
+ "data": {
633
+ "op": "initialize",
634
+ "value": {
635
+ "bar": "baz",
636
+ },
637
+ },
638
+ "oid": "test/a.foo:0",
639
+ "timestamp": "0",
640
+ },
641
+ {
642
+ "data": {
643
+ "op": "initialize",
644
+ "value": {
645
+ "corge": "grault",
646
+ },
647
+ },
648
+ "oid": "test/a.qux.#:2",
649
+ "timestamp": "1",
650
+ },
651
+ {
652
+ "data": {
653
+ "op": "initialize",
654
+ "value": {
655
+ "oof": 1,
656
+ },
657
+ },
658
+ "oid": "test/a.qux.#.bin:4",
659
+ "timestamp": "2",
660
+ },
661
+ {
662
+ "data": {
663
+ "op": "initialize",
664
+ "value": {
665
+ "bin": {
666
+ "@@type": "ref",
667
+ "id": "test/a.qux.#.bin:4",
668
+ },
669
+ },
670
+ },
671
+ "oid": "test/a.qux.#:3",
672
+ "timestamp": "3",
673
+ },
674
+ {
675
+ "data": {
676
+ "op": "initialize",
677
+ "value": [
678
+ {
679
+ "@@type": "ref",
680
+ "id": "test/a.qux.#:2",
681
+ },
682
+ {
683
+ "@@type": "ref",
684
+ "id": "test/a.qux.#:3",
685
+ },
686
+ ],
687
+ },
688
+ "oid": "test/a.qux:1",
689
+ "timestamp": "4",
690
+ },
691
+ {
692
+ "data": {
693
+ "op": "initialize",
694
+ "value": {
695
+ "foo": {
696
+ "@@type": "ref",
697
+ "id": "test/a.foo:0",
698
+ },
699
+ "qux": {
700
+ "@@type": "ref",
701
+ "id": "test/a.qux:1",
702
+ },
703
+ },
704
+ },
705
+ "oid": "test/a",
706
+ "timestamp": "5",
707
+ },
708
+ ]
709
+ `);
710
+ });
711
+ });
712
+ describe('applying individual operations', () => {
713
+ it('applies a list-remove of only the last instance', () => {
714
+ expect(applyPatch(['a', 'b', 'c', 'a', 'b'], {
715
+ op: 'list-remove',
716
+ value: 'a',
717
+ only: 'last',
718
+ })).toEqual(['a', 'b', 'c', 'b']);
719
+ });
720
+ it('applies a list-remove of only the first instance', () => {
721
+ expect(applyPatch(['a', 'b', 'c', 'a', 'b'], {
722
+ op: 'list-remove',
723
+ value: 'a',
724
+ only: 'first',
725
+ })).toEqual(['b', 'c', 'a', 'b']);
726
+ });
727
+ it('applies a list-remove of all instances', () => {
728
+ expect(applyPatch(['a', 'b', 'c', 'a', 'b'], {
729
+ op: 'list-remove',
730
+ value: 'a',
731
+ })).toEqual(['b', 'c', 'b']);
732
+ });
733
+ it('applies a list-remove of an object ref', () => {
734
+ expect(applyPatch([
735
+ createRef('a'),
736
+ createRef('b'),
737
+ createRef('c'),
738
+ createRef('a'),
739
+ createRef('b'),
740
+ ], {
741
+ op: 'list-remove',
742
+ value: createRef('a'),
743
+ only: 'last',
744
+ })).toEqual([createRef('a'), createRef('b'), createRef('c'), createRef('b')]);
745
+ });
746
+ it('applies a list-add of a nonexistent item', () => {
747
+ expect(applyPatch(['a', 'b', 'c'], {
748
+ op: 'list-add',
749
+ value: 'd',
750
+ })).toEqual(['a', 'b', 'c', 'd']);
751
+ });
752
+ it('does not apply a list-add if the item is already present', () => {
753
+ expect(applyPatch(['a', 'b', 'c'], {
754
+ op: 'list-add',
755
+ value: 'b',
756
+ })).toEqual(['a', 'b', 'c']);
757
+ });
758
+ });
759
+ describe('collecting deleted refs during patch application', () => {
760
+ it('collects refs that are removed from a list', () => {
761
+ const refs = [];
762
+ expect(applyPatch([
763
+ createRef('a'),
764
+ createRef('b'),
765
+ createRef('c'),
766
+ createRef('d'),
767
+ createRef('e'),
768
+ ], {
769
+ op: 'list-remove',
770
+ value: createRef('c'),
771
+ }, refs)).toEqual([createRef('a'), createRef('b'), createRef('d'), createRef('e')]);
772
+ expect(refs).toEqual([createRef('c')]);
773
+ });
774
+ it('collects a deleted ref field from an object', () => {
775
+ const refs = [];
776
+ expect(applyPatch({
777
+ a: createRef('a'),
778
+ b: createRef('b'),
779
+ c: createRef('c'),
780
+ }, {
781
+ op: 'remove',
782
+ name: 'b',
783
+ }, refs)).toEqual({
784
+ a: createRef('a'),
785
+ c: createRef('c'),
786
+ });
787
+ expect(refs).toEqual([createRef('b')]);
788
+ });
789
+ it('collects all refs in a deleted object', () => {
790
+ const refs = [];
791
+ expect(applyPatch({
792
+ a: createRef('a'),
793
+ b: createRef('b'),
794
+ c: createRef('c'),
795
+ }, {
796
+ op: 'delete',
797
+ }, refs)).toEqual(undefined);
798
+ expect(refs).toEqual([createRef('a'), createRef('b'), createRef('c')]);
799
+ });
800
+ it('collects all refs in a deleted list', () => {
801
+ const refs = [];
802
+ expect(applyPatch([createRef('a'), createRef('b'), createRef('c')], {
803
+ op: 'delete',
804
+ }, refs)).toEqual(undefined);
805
+ expect(refs).toEqual([createRef('a'), createRef('b'), createRef('c')]);
806
+ });
807
+ it('collects a ref in a replaced field', () => {
808
+ const refs = [];
809
+ expect(applyPatch({
810
+ a: createRef('a'),
811
+ b: createRef('b'),
812
+ c: createRef('c'),
813
+ }, {
814
+ op: 'set',
815
+ name: 'b',
816
+ value: createRef('d'),
817
+ }, refs)).toEqual({
818
+ a: createRef('a'),
819
+ b: createRef('d'),
820
+ c: createRef('c'),
821
+ });
822
+ expect(refs).toEqual([createRef('b')]);
823
+ });
824
+ });
825
+ //# sourceMappingURL=operation.test.js.map