on-zero 0.4.26 → 0.4.28

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 (123) hide show
  1. package/dist/cjs/createZeroClient.cjs +14 -1
  2. package/dist/cjs/createZeroClient.native.js +15 -1
  3. package/dist/cjs/createZeroClient.native.js.map +1 -1
  4. package/dist/cjs/httpPull/auth.test.cjs +197 -0
  5. package/dist/cjs/httpPull/auth.test.native.js +279 -0
  6. package/dist/cjs/httpPull/auth.test.native.js.map +1 -0
  7. package/dist/cjs/httpPull/churn.test.cjs +132 -0
  8. package/dist/cjs/httpPull/churn.test.native.js +155 -0
  9. package/dist/cjs/httpPull/churn.test.native.js.map +1 -0
  10. package/dist/cjs/httpPull/fixtureSchema.cjs +76 -0
  11. package/dist/cjs/httpPull/fixtureSchema.native.js +82 -0
  12. package/dist/cjs/httpPull/fixtureSchema.native.js.map +1 -0
  13. package/dist/cjs/httpPull/fixtureServer.cjs +340 -0
  14. package/dist/cjs/httpPull/fixtureServer.native.js +534 -0
  15. package/dist/cjs/httpPull/fixtureServer.native.js.map +1 -0
  16. package/dist/cjs/httpPull/integration.test.cjs +53 -0
  17. package/dist/cjs/httpPull/integration.test.native.js +60 -0
  18. package/dist/cjs/httpPull/integration.test.native.js.map +1 -0
  19. package/dist/cjs/httpPull/rebase.test.cjs +360 -0
  20. package/dist/cjs/httpPull/rebase.test.native.js +420 -0
  21. package/dist/cjs/httpPull/rebase.test.native.js.map +1 -0
  22. package/dist/cjs/httpPull/relations.test.cjs +107 -0
  23. package/dist/cjs/httpPull/relations.test.native.js +119 -0
  24. package/dist/cjs/httpPull/relations.test.native.js.map +1 -0
  25. package/dist/cjs/httpPull/testHarness.cjs +100 -0
  26. package/dist/cjs/httpPull/testHarness.native.js +112 -0
  27. package/dist/cjs/httpPull/testHarness.native.js.map +1 -0
  28. package/dist/cjs/httpPull/transport.test.cjs +588 -0
  29. package/dist/cjs/httpPull/transport.test.native.js +677 -0
  30. package/dist/cjs/httpPull/transport.test.native.js.map +1 -0
  31. package/dist/cjs/httpPullTransport.cjs +447 -0
  32. package/dist/cjs/httpPullTransport.native.js +710 -0
  33. package/dist/cjs/httpPullTransport.native.js.map +1 -0
  34. package/dist/cjs/index.cjs +1 -0
  35. package/dist/cjs/index.native.js +1 -0
  36. package/dist/cjs/index.native.js.map +1 -1
  37. package/dist/esm/createZeroClient.mjs +14 -1
  38. package/dist/esm/createZeroClient.mjs.map +1 -1
  39. package/dist/esm/createZeroClient.native.js +15 -1
  40. package/dist/esm/createZeroClient.native.js.map +1 -1
  41. package/dist/esm/httpPull/auth.test.mjs +198 -0
  42. package/dist/esm/httpPull/auth.test.mjs.map +1 -0
  43. package/dist/esm/httpPull/auth.test.native.js +277 -0
  44. package/dist/esm/httpPull/auth.test.native.js.map +1 -0
  45. package/dist/esm/httpPull/churn.test.mjs +133 -0
  46. package/dist/esm/httpPull/churn.test.mjs.map +1 -0
  47. package/dist/esm/httpPull/churn.test.native.js +153 -0
  48. package/dist/esm/httpPull/churn.test.native.js.map +1 -0
  49. package/dist/esm/httpPull/fixtureSchema.mjs +50 -0
  50. package/dist/esm/httpPull/fixtureSchema.mjs.map +1 -0
  51. package/dist/esm/httpPull/fixtureSchema.native.js +53 -0
  52. package/dist/esm/httpPull/fixtureSchema.native.js.map +1 -0
  53. package/dist/esm/httpPull/fixtureServer.mjs +315 -0
  54. package/dist/esm/httpPull/fixtureServer.mjs.map +1 -0
  55. package/dist/esm/httpPull/fixtureServer.native.js +506 -0
  56. package/dist/esm/httpPull/fixtureServer.native.js.map +1 -0
  57. package/dist/esm/httpPull/integration.test.mjs +54 -0
  58. package/dist/esm/httpPull/integration.test.mjs.map +1 -0
  59. package/dist/esm/httpPull/integration.test.native.js +58 -0
  60. package/dist/esm/httpPull/integration.test.native.js.map +1 -0
  61. package/dist/esm/httpPull/rebase.test.mjs +361 -0
  62. package/dist/esm/httpPull/rebase.test.mjs.map +1 -0
  63. package/dist/esm/httpPull/rebase.test.native.js +418 -0
  64. package/dist/esm/httpPull/rebase.test.native.js.map +1 -0
  65. package/dist/esm/httpPull/relations.test.mjs +108 -0
  66. package/dist/esm/httpPull/relations.test.mjs.map +1 -0
  67. package/dist/esm/httpPull/relations.test.native.js +117 -0
  68. package/dist/esm/httpPull/relations.test.native.js.map +1 -0
  69. package/dist/esm/httpPull/testHarness.mjs +72 -0
  70. package/dist/esm/httpPull/testHarness.mjs.map +1 -0
  71. package/dist/esm/httpPull/testHarness.native.js +81 -0
  72. package/dist/esm/httpPull/testHarness.native.js.map +1 -0
  73. package/dist/esm/httpPull/transport.test.mjs +589 -0
  74. package/dist/esm/httpPull/transport.test.mjs.map +1 -0
  75. package/dist/esm/httpPull/transport.test.native.js +675 -0
  76. package/dist/esm/httpPull/transport.test.native.js.map +1 -0
  77. package/dist/esm/httpPullTransport.mjs +421 -0
  78. package/dist/esm/httpPullTransport.mjs.map +1 -0
  79. package/dist/esm/httpPullTransport.native.js +681 -0
  80. package/dist/esm/httpPullTransport.native.js.map +1 -0
  81. package/dist/esm/index.js +1 -0
  82. package/dist/esm/index.js.map +1 -1
  83. package/dist/esm/index.mjs +1 -0
  84. package/dist/esm/index.mjs.map +1 -1
  85. package/dist/esm/index.native.js +1 -0
  86. package/dist/esm/index.native.js.map +1 -1
  87. package/package.json +2 -2
  88. package/src/createZeroClient.tsx +22 -0
  89. package/src/httpPull/auth.test.ts +208 -0
  90. package/src/httpPull/churn.test.ts +147 -0
  91. package/src/httpPull/fixtureSchema.ts +82 -0
  92. package/src/httpPull/fixtureServer.ts +391 -0
  93. package/src/httpPull/integration.test.ts +57 -0
  94. package/src/httpPull/rebase.test.ts +368 -0
  95. package/src/httpPull/relations.test.ts +135 -0
  96. package/src/httpPull/testHarness.ts +95 -0
  97. package/src/httpPull/transport.test.ts +603 -0
  98. package/src/httpPullTransport.ts +587 -0
  99. package/src/index.ts +1 -0
  100. package/types/createZeroClient.d.ts +3 -1
  101. package/types/createZeroClient.d.ts.map +1 -1
  102. package/types/httpPull/auth.test.d.ts +2 -0
  103. package/types/httpPull/auth.test.d.ts.map +1 -0
  104. package/types/httpPull/churn.test.d.ts +2 -0
  105. package/types/httpPull/churn.test.d.ts.map +1 -0
  106. package/types/httpPull/fixtureSchema.d.ts +111 -0
  107. package/types/httpPull/fixtureSchema.d.ts.map +1 -0
  108. package/types/httpPull/fixtureServer.d.ts +14 -0
  109. package/types/httpPull/fixtureServer.d.ts.map +1 -0
  110. package/types/httpPull/integration.test.d.ts +2 -0
  111. package/types/httpPull/integration.test.d.ts.map +1 -0
  112. package/types/httpPull/rebase.test.d.ts +2 -0
  113. package/types/httpPull/rebase.test.d.ts.map +1 -0
  114. package/types/httpPull/relations.test.d.ts +2 -0
  115. package/types/httpPull/relations.test.d.ts.map +1 -0
  116. package/types/httpPull/testHarness.d.ts +32 -0
  117. package/types/httpPull/testHarness.d.ts.map +1 -0
  118. package/types/httpPull/transport.test.d.ts +2 -0
  119. package/types/httpPull/transport.test.d.ts.map +1 -0
  120. package/types/httpPullTransport.d.ts +13 -0
  121. package/types/httpPullTransport.d.ts.map +1 -0
  122. package/types/index.d.ts +1 -0
  123. package/types/index.d.ts.map +1 -1
@@ -0,0 +1,360 @@
1
+ var import_vitest = require("vitest");
2
+ var import_testHarness = require("./testHarness.cjs");
3
+ let harness;
4
+ (0, import_vitest.afterEach)(async () => {
5
+ await harness?.close();
6
+ harness = void 0;
7
+ });
8
+ (0, import_vitest.test)("ack-then-pull keeps optimistic rows visible until the authoritative snapshot lands", async () => {
9
+ const postPushPull = deferred();
10
+ const postPushPullStarted = deferred();
11
+ let shouldHoldPostPushPull = false;
12
+ harness = await (0, import_testHarness.startZeroHttpHarness)({
13
+ seed: {
14
+ user: [{
15
+ id: "u1",
16
+ name: "ada"
17
+ }],
18
+ project: [{
19
+ id: "p1",
20
+ ownerId: "u1",
21
+ name: "first"
22
+ }],
23
+ member: []
24
+ },
25
+ interceptFetch: next => async (input, init) => {
26
+ const path = new URL(String(input)).pathname;
27
+ if (path === "/push") {
28
+ const response = await next(input, init);
29
+ shouldHoldPostPushPull = true;
30
+ return response;
31
+ }
32
+ if (path === "/pull" && shouldHoldPostPushPull) {
33
+ shouldHoldPostPushPull = false;
34
+ postPushPullStarted.resolve();
35
+ await postPushPull.promise;
36
+ }
37
+ return next(input, init);
38
+ }
39
+ });
40
+ const zero = harness.createZero("u1");
41
+ const view = zero.query.project.materialize();
42
+ await (0, import_testHarness.waitForComplete)(view);
43
+ const emissions = recordEmissions(view);
44
+ const mutation = zero.mutate.project.create({
45
+ id: "p2",
46
+ ownerId: "u1",
47
+ name: "second"
48
+ });
49
+ await mutation.client;
50
+ await (0, import_testHarness.eventually)(() => (0, import_vitest.expect)(projectIDs(view.data)).toContain("p2"));
51
+ await mutation.server;
52
+ await postPushPullStarted.promise;
53
+ (0, import_vitest.expect)(projectIDs(view.data)).toContain("p2");
54
+ postPushPull.resolve();
55
+ await harness.transport.pull();
56
+ await (0, import_testHarness.eventually)(() => (0, import_vitest.expect)(projectIDs(view.data).sort()).toEqual(["p1", "p2"]));
57
+ expectNeverDisappearsAfterFirstSeen(emissions, "p2");
58
+ (0, import_vitest.expect)(harness.server.rows("project").sort(byID)).toEqual([{
59
+ id: "p1",
60
+ ownerId: "u1",
61
+ name: "first"
62
+ }, {
63
+ id: "p2",
64
+ ownerId: "u1",
65
+ name: "second"
66
+ }]);
67
+ emissions.cleanup();
68
+ view.destroy();
69
+ });
70
+ (0, import_vitest.test)("pull-then-ack rebases optimistic rows over a newer snapshot", async () => {
71
+ const pushGate = deferred();
72
+ const pushStarted = deferred();
73
+ harness = await (0, import_testHarness.startZeroHttpHarness)({
74
+ seed: {
75
+ user: [{
76
+ id: "u1",
77
+ name: "ada"
78
+ }],
79
+ project: [{
80
+ id: "p1",
81
+ ownerId: "u1",
82
+ name: "first"
83
+ }],
84
+ member: []
85
+ },
86
+ interceptFetch: next => async (input, init) => {
87
+ const path = new URL(String(input)).pathname;
88
+ if (path === "/push") {
89
+ const body = JSON.parse(String(init?.body));
90
+ pushStarted.resolve(body);
91
+ await pushGate.promise;
92
+ }
93
+ return next(input, init);
94
+ }
95
+ });
96
+ const zero = harness.createZero("u1");
97
+ const view = zero.query.project.materialize();
98
+ await (0, import_testHarness.waitForComplete)(view);
99
+ const emissions = recordEmissions(view);
100
+ const mutation = zero.mutate.project.create({
101
+ id: "p2",
102
+ ownerId: "u1",
103
+ name: "optimistic"
104
+ });
105
+ await mutation.client;
106
+ await (0, import_testHarness.eventually)(() => (0, import_vitest.expect)(projectIDs(view.data)).toContain("p2"));
107
+ const heldPush = await pushStarted.promise;
108
+ await rawPush(harness, {
109
+ clientGroupID: heldPush.clientGroupID,
110
+ clientID: "server-side",
111
+ id: 1,
112
+ name: "project|create",
113
+ args: {
114
+ id: "p-server",
115
+ ownerId: "u1",
116
+ name: "server change"
117
+ }
118
+ });
119
+ await harness.transport.pull();
120
+ await (0, import_testHarness.eventually)(() => (0, import_vitest.expect)(projectIDs(view.data).sort()).toEqual(["p-server", "p1", "p2"]));
121
+ (0, import_vitest.expect)(harness.server.rows("project").map(row => row.id).sort()).toEqual(["p-server", "p1"]);
122
+ pushGate.resolve();
123
+ await mutation.server;
124
+ await harness.transport.pull();
125
+ await (0, import_testHarness.eventually)(() => (0, import_vitest.expect)(projectIDs(view.data).sort()).toEqual(["p-server", "p1", "p2"]));
126
+ expectNeverDisappearsAfterFirstSeen(emissions, "p2");
127
+ (0, import_vitest.expect)(harness.server.rows("project").sort(byID)).toEqual([{
128
+ id: "p-server",
129
+ ownerId: "u1",
130
+ name: "server change"
131
+ }, {
132
+ id: "p1",
133
+ ownerId: "u1",
134
+ name: "first"
135
+ }, {
136
+ id: "p2",
137
+ ownerId: "u1",
138
+ name: "optimistic"
139
+ }]);
140
+ emissions.cleanup();
141
+ view.destroy();
142
+ });
143
+ (0, import_vitest.test)("app-error rollback advances LMID and reverts optimistic state after pull", async () => {
144
+ const pushGate = deferred();
145
+ const pushStarted = deferred();
146
+ let clientGroupID = "";
147
+ harness = await (0, import_testHarness.startZeroHttpHarness)({
148
+ seed: {
149
+ user: [{
150
+ id: "u1",
151
+ name: "ada"
152
+ }, {
153
+ id: "u2",
154
+ name: "ben"
155
+ }],
156
+ project: [{
157
+ id: "p1",
158
+ ownerId: "u2",
159
+ name: "shared"
160
+ }],
161
+ member: [{
162
+ id: "m1",
163
+ projectId: "p1",
164
+ userId: "u1"
165
+ }]
166
+ },
167
+ interceptFetch: next => async (input, init) => {
168
+ if (new URL(String(input)).pathname === "/push") {
169
+ const body = JSON.parse(String(init?.body));
170
+ clientGroupID = body.clientGroupID;
171
+ pushStarted.resolve();
172
+ await pushGate.promise;
173
+ }
174
+ return next(input, init);
175
+ }
176
+ });
177
+ const zero = harness.createZero("u1");
178
+ const view = zero.query.project.materialize();
179
+ await (0, import_testHarness.waitForComplete)(view);
180
+ const emissions = recordEmissions(view);
181
+ const mutation = zero.mutate.project.rename({
182
+ id: "p1",
183
+ name: "stolen"
184
+ });
185
+ await mutation.client;
186
+ await pushStarted.promise;
187
+ (0, import_vitest.expect)(projectName(view.data, "p1")).toBe("stolen");
188
+ (0, import_vitest.expect)(harness.server.rows("project")).toEqual([{
189
+ id: "p1",
190
+ ownerId: "u2",
191
+ name: "shared"
192
+ }]);
193
+ pushGate.resolve();
194
+ await (0, import_vitest.expect)(mutation.server).resolves.toMatchObject({
195
+ type: "error",
196
+ error: {
197
+ type: "app",
198
+ details: "forbidden"
199
+ }
200
+ });
201
+ await harness.transport.pull();
202
+ await (0, import_testHarness.eventually)(() => (0, import_vitest.expect)(projectName(view.data, "p1")).toBe("shared"));
203
+ (0, import_vitest.expect)(emissions.values.map(rows => projectName(rows, "p1"))).toContain("stolen");
204
+ (0, import_vitest.expect)(emissions.values.at(-1)?.[0]).toEqual({
205
+ id: "p1",
206
+ ownerId: "u2",
207
+ name: "shared"
208
+ });
209
+ (0, import_vitest.expect)(harness.server.rows("project")).toEqual([{
210
+ id: "p1",
211
+ ownerId: "u2",
212
+ name: "shared"
213
+ }]);
214
+ const pull = await rawPull(harness, "u1", {
215
+ clientGroupID
216
+ });
217
+ (0, import_vitest.expect)(Object.values(pull.lastMutationIDChanges)).toContain(1);
218
+ emissions.cleanup();
219
+ view.destroy();
220
+ });
221
+ (0, import_vitest.test)("app-error rollback removes phantom optimistic create after pull", async () => {
222
+ const pushGate = deferred();
223
+ const pushStarted = deferred();
224
+ let clientGroupID = "";
225
+ harness = await (0, import_testHarness.startZeroHttpHarness)({
226
+ seed: {
227
+ user: [{
228
+ id: "u1",
229
+ name: "ada"
230
+ }, {
231
+ id: "u2",
232
+ name: "ben"
233
+ }],
234
+ project: [],
235
+ member: []
236
+ },
237
+ interceptFetch: next => async (input, init) => {
238
+ if (new URL(String(input)).pathname === "/push") {
239
+ const body = JSON.parse(String(init?.body));
240
+ clientGroupID = body.clientGroupID;
241
+ pushStarted.resolve();
242
+ await pushGate.promise;
243
+ }
244
+ return next(input, init);
245
+ }
246
+ });
247
+ const zero = harness.createZero("u1");
248
+ const view = zero.query.project.materialize();
249
+ await (0, import_testHarness.waitForComplete)(view);
250
+ const emissions = recordEmissions(view);
251
+ const mutation = zero.mutate.project.create({
252
+ id: "p-phantom",
253
+ ownerId: "u2",
254
+ name: "forbidden"
255
+ });
256
+ await mutation.client;
257
+ await pushStarted.promise;
258
+ await (0, import_testHarness.eventually)(() => (0, import_vitest.expect)(projectIDs(view.data)).toContain("p-phantom"));
259
+ (0, import_vitest.expect)(harness.server.rows("project")).toEqual([]);
260
+ pushGate.resolve();
261
+ await (0, import_vitest.expect)(mutation.server).resolves.toMatchObject({
262
+ type: "error",
263
+ error: {
264
+ type: "app",
265
+ details: "forbidden"
266
+ }
267
+ });
268
+ await harness.transport.pull();
269
+ await (0, import_testHarness.eventually)(() => (0, import_vitest.expect)(projectIDs(view.data)).not.toContain("p-phantom"));
270
+ (0, import_vitest.expect)(emissions.values.some(rows => projectIDs(rows).includes("p-phantom"))).toBe(true);
271
+ (0, import_vitest.expect)(emissions.values.at(-1)).toEqual([]);
272
+ (0, import_vitest.expect)(harness.server.rows("project")).toEqual([]);
273
+ const pull = await rawPull(harness, "u1", {
274
+ clientGroupID
275
+ });
276
+ (0, import_vitest.expect)(Object.values(pull.lastMutationIDChanges)).toContain(1);
277
+ emissions.cleanup();
278
+ view.destroy();
279
+ });
280
+ async function rawPush(harness2, mutation) {
281
+ const response = await fetch(`${harness2.server.url}/push`, {
282
+ method: "POST",
283
+ headers: {
284
+ authorization: "Bearer token-u1",
285
+ "content-type": "application/json"
286
+ },
287
+ body: JSON.stringify({
288
+ timestamp: Date.now(),
289
+ clientGroupID: mutation.clientGroupID,
290
+ pushVersion: 1,
291
+ requestID: `raw-${mutation.clientID}-${mutation.id}`,
292
+ mutations: [{
293
+ type: "custom",
294
+ name: mutation.name,
295
+ id: mutation.id,
296
+ clientID: mutation.clientID,
297
+ args: [mutation.args]
298
+ }]
299
+ })
300
+ });
301
+ (0, import_vitest.expect)(response.status).toBe(200);
302
+ return response.json();
303
+ }
304
+ async function rawPull(harness2, userID, body) {
305
+ const response = await fetch(`${harness2.server.url}/pull`, {
306
+ method: "POST",
307
+ headers: {
308
+ authorization: `Bearer token-${userID}`,
309
+ "content-type": "application/json"
310
+ },
311
+ body: JSON.stringify({
312
+ clientID: "raw-pull",
313
+ clientGroupID: body.clientGroupID,
314
+ cookie: null
315
+ })
316
+ });
317
+ (0, import_vitest.expect)(response.status).toBe(200);
318
+ return response.json();
319
+ }
320
+ function recordEmissions(view) {
321
+ const values = [];
322
+ const cleanup = view.addListener(data => values.push(snapshot(data)));
323
+ return {
324
+ values,
325
+ cleanup
326
+ };
327
+ }
328
+ function snapshot(rows) {
329
+ return JSON.parse(JSON.stringify(rows));
330
+ }
331
+ function projectIDs(rows) {
332
+ return rows.map(row => row.id);
333
+ }
334
+ function projectName(rows, id) {
335
+ return rows.find(row => row.id === id)?.name;
336
+ }
337
+ function expectNeverDisappearsAfterFirstSeen(emissions, id) {
338
+ let seen = false;
339
+ for (const rows of emissions.values) {
340
+ if (projectIDs(rows).includes(id)) seen = true;
341
+ if (seen) (0, import_vitest.expect)(projectIDs(rows)).toContain(id);
342
+ }
343
+ (0, import_vitest.expect)(seen).toBe(true);
344
+ }
345
+ function byID(a, b) {
346
+ return a.id.localeCompare(b.id);
347
+ }
348
+ function deferred() {
349
+ let resolve;
350
+ let reject;
351
+ const promise = new Promise((res, rej) => {
352
+ resolve = res;
353
+ reject = rej;
354
+ });
355
+ return {
356
+ promise,
357
+ resolve,
358
+ reject
359
+ };
360
+ }