on-zero 0.4.26 → 0.4.27

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 +13 -1
  2. package/dist/cjs/createZeroClient.native.js +13 -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 +568 -0
  29. package/dist/cjs/httpPull/transport.test.native.js +655 -0
  30. package/dist/cjs/httpPull/transport.test.native.js.map +1 -0
  31. package/dist/cjs/httpPullTransport.cjs +432 -0
  32. package/dist/cjs/httpPullTransport.native.js +695 -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 +13 -1
  38. package/dist/esm/createZeroClient.mjs.map +1 -1
  39. package/dist/esm/createZeroClient.native.js +13 -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 +569 -0
  74. package/dist/esm/httpPull/transport.test.mjs.map +1 -0
  75. package/dist/esm/httpPull/transport.test.native.js +653 -0
  76. package/dist/esm/httpPull/transport.test.native.js.map +1 -0
  77. package/dist/esm/httpPullTransport.mjs +406 -0
  78. package/dist/esm/httpPullTransport.mjs.map +1 -0
  79. package/dist/esm/httpPullTransport.native.js +666 -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 +19 -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 +577 -0
  98. package/src/httpPullTransport.ts +559 -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,568 @@
1
+ var import_zero = require("@rocicorp/zero");
2
+ var import_vitest = require("vitest");
3
+ var import_fixtureSchema = require("./fixtureSchema.cjs");
4
+ var import_httpPullTransport = require("../httpPullTransport.cjs");
5
+ const ORIGIN = "https://zero-http.local";
6
+ const zeros = [];
7
+ const transports = [];
8
+ let storageID = 0;
9
+ (0, import_vitest.afterEach)(async () => {
10
+ while (zeros.length) await zeros.pop()?.close();
11
+ while (transports.length) transports.pop()?.uninstall();
12
+ import_vitest.vi.useRealTimers();
13
+ });
14
+ (0, import_vitest.describe)("zero-http transport", () => {
15
+ (0, import_vitest.test)("connect + complete hydrates a stock Zero materialized query", async () => {
16
+ const requests = [];
17
+ let cookie = 0;
18
+ const fetch = import_vitest.vi.fn(async (input, init) => {
19
+ const request = recordRequest(input, init);
20
+ requests.push(request);
21
+ (0, import_vitest.expect)(request.path).toBe("/pull");
22
+ (0, import_vitest.expect)(request.headers.authorization).toBe("Bearer token-u1");
23
+ return jsonResponse({
24
+ cookie: ++cookie,
25
+ lastMutationIDChanges: {},
26
+ rowsPatch: [{
27
+ op: "clear"
28
+ }, {
29
+ op: "put",
30
+ tableName: "user",
31
+ value: {
32
+ id: "u1",
33
+ name: "ada"
34
+ }
35
+ }, {
36
+ op: "put",
37
+ tableName: "project",
38
+ value: {
39
+ id: "p1",
40
+ ownerId: "u1",
41
+ name: "control"
42
+ }
43
+ }, {
44
+ op: "put",
45
+ tableName: "member",
46
+ value: {
47
+ id: "m1",
48
+ projectId: "p1",
49
+ userId: "u1"
50
+ }
51
+ }]
52
+ });
53
+ });
54
+ const transport = install(fetch);
55
+ const zero = createZero();
56
+ const view = zero.query.project.related("members").materialize();
57
+ const data = await waitForComplete(view);
58
+ view.destroy();
59
+ (0, import_vitest.expect)(transport.connections).toBe(1);
60
+ (0, import_vitest.expect)(data).toEqual([{
61
+ id: "p1",
62
+ ownerId: "u1",
63
+ name: "control",
64
+ members: [{
65
+ id: "m1",
66
+ projectId: "p1",
67
+ userId: "u1"
68
+ }]
69
+ }]);
70
+ (0, import_vitest.expect)(requests[0].body.cookie).toBeNull();
71
+ (0, import_vitest.expect)(requests[0].body.clientID).toEqual(import_vitest.expect.any(String));
72
+ (0, import_vitest.expect)(requests[0].body.clientGroupID).toEqual(import_vitest.expect.any(String));
73
+ });
74
+ (0, import_vitest.test)("push frames POST to /push, resolve server promises, and schedule a follow-up pull", async () => {
75
+ const requests = [];
76
+ const fetch = import_vitest.vi.fn(async (input, init) => {
77
+ const request = recordRequest(input, init);
78
+ requests.push(request);
79
+ if (request.path === "/pull") {
80
+ return jsonResponse({
81
+ cookie: request.body.cookie,
82
+ unchanged: true
83
+ });
84
+ }
85
+ (0, import_vitest.expect)(request.path).toBe("/push");
86
+ const mutation2 = request.body.mutations[0];
87
+ return jsonResponse({
88
+ pushResponse: {
89
+ mutations: [{
90
+ id: {
91
+ clientID: mutation2.clientID,
92
+ id: mutation2.id
93
+ },
94
+ result: {}
95
+ }]
96
+ }
97
+ });
98
+ });
99
+ install(fetch);
100
+ const zero = createZero();
101
+ await eventually(() => (0, import_vitest.expect)(requests.filter(request => request.path === "/pull").length).toBe(1));
102
+ const pullsBeforePush = requests.filter(request => request.path === "/pull").length;
103
+ const mutation = zero.mutate.project.create({
104
+ id: "p1",
105
+ ownerId: "u1",
106
+ name: "created"
107
+ });
108
+ await mutation.client;
109
+ await mutation.server;
110
+ const push = requests.find(request => request.path === "/push");
111
+ (0, import_vitest.expect)(push?.headers.authorization).toBe("Bearer token-u1");
112
+ (0, import_vitest.expect)(push?.body).toMatchObject({
113
+ clientGroupID: import_vitest.expect.any(String),
114
+ pushVersion: 1,
115
+ requestID: import_vitest.expect.any(String),
116
+ mutations: [{
117
+ type: "custom",
118
+ name: "project|create",
119
+ id: 1,
120
+ clientID: import_vitest.expect.any(String),
121
+ args: [{
122
+ id: "p1",
123
+ ownerId: "u1",
124
+ name: "created"
125
+ }]
126
+ }]
127
+ });
128
+ await eventually(() => (0, import_vitest.expect)(requests.filter(request => request.path === "/pull").length).toBe(pullsBeforePush + 1));
129
+ });
130
+ (0, import_vitest.test)("updateAuth frame updates bearer headers for later requests", async () => {
131
+ const requests = [];
132
+ const fetch = import_vitest.vi.fn(async (input, init) => {
133
+ const request = recordRequest(input, init);
134
+ requests.push(request);
135
+ (0, import_vitest.expect)(request.path).toBe("/pull");
136
+ return jsonResponse({
137
+ cookie: request.body.cookie,
138
+ unchanged: true
139
+ });
140
+ });
141
+ const transport = install(fetch);
142
+ const {
143
+ socket
144
+ } = openRawSocketWithMessages({
145
+ authToken: "token-old"
146
+ });
147
+ await eventually(() => (0, import_vitest.expect)(requests.length).toBe(1));
148
+ (0, import_vitest.expect)(requests[0].headers.authorization).toBe("Bearer token-old");
149
+ socket.send(JSON.stringify(["updateAuth", {
150
+ auth: "token-new"
151
+ }]));
152
+ await transport.pull();
153
+ (0, import_vitest.expect)(requests.at(-1)?.headers.authorization).toBe("Bearer token-new");
154
+ });
155
+ (0, import_vitest.test)("push frames are serialized per socket", async () => {
156
+ const firstPushStarted = defer();
157
+ const releaseFirstPush = defer();
158
+ const pushIDs = [];
159
+ const fetch = import_vitest.vi.fn(async (input, init) => {
160
+ const request = recordRequest(input, init);
161
+ if (request.path === "/pull") {
162
+ return jsonResponse({
163
+ cookie: request.body.cookie,
164
+ unchanged: true
165
+ });
166
+ }
167
+ (0, import_vitest.expect)(request.path).toBe("/push");
168
+ const mutationID = request.body.mutations[0].id;
169
+ pushIDs.push(mutationID);
170
+ if (mutationID === 1) {
171
+ firstPushStarted.resolve();
172
+ await releaseFirstPush.promise;
173
+ }
174
+ return jsonResponse({
175
+ pushResponse: {
176
+ mutations: request.body.mutations.map(mutation => ({
177
+ id: {
178
+ clientID: mutation.clientID,
179
+ id: mutation.id
180
+ },
181
+ result: {}
182
+ }))
183
+ }
184
+ });
185
+ });
186
+ install(fetch);
187
+ const {
188
+ messages,
189
+ socket
190
+ } = openRawSocketWithMessages();
191
+ await eventually(() => (0, import_vitest.expect)(messages.some(message => message[0] === "connected")).toBe(true));
192
+ socket.send(JSON.stringify(["push", pushBody(1)]));
193
+ await firstPushStarted.promise;
194
+ socket.send(JSON.stringify(["push", pushBody(2)]));
195
+ await sleep(25);
196
+ (0, import_vitest.expect)(pushIDs).toEqual([1]);
197
+ releaseFirstPush.resolve();
198
+ await eventually(() => (0, import_vitest.expect)(pushIDs).toEqual([1, 2]));
199
+ await eventually(() => (0, import_vitest.expect)(messages.filter(message => message[0] === "pushResponse")).toHaveLength(2));
200
+ (0, import_vitest.expect)(messages.filter(message => message[0] === "pushResponse").map(message => message[1].mutations[0].id.id)).toEqual([1, 2]);
201
+ });
202
+ (0, import_vitest.test)("cookie discipline skips unchanged pokes, chains changed pokes, and coalesces concurrent pulls", async () => {
203
+ const requests = [];
204
+ const responses = [{
205
+ cookie: 1,
206
+ lastMutationIDChanges: {},
207
+ rowsPatch: [{
208
+ op: "clear"
209
+ }]
210
+ }, {
211
+ cookie: 1,
212
+ unchanged: true
213
+ }, {
214
+ cookie: 2,
215
+ lastMutationIDChanges: {},
216
+ rowsPatch: [{
217
+ op: "clear"
218
+ }, {
219
+ op: "put",
220
+ tableName: "project",
221
+ value: {
222
+ id: "p2",
223
+ ownerId: "u1",
224
+ name: "second"
225
+ }
226
+ }]
227
+ }, {
228
+ cookie: 3,
229
+ lastMutationIDChanges: {},
230
+ rowsPatch: [{
231
+ op: "clear"
232
+ }, {
233
+ op: "put",
234
+ tableName: "project",
235
+ value: {
236
+ id: "p3",
237
+ ownerId: "u1",
238
+ name: "third"
239
+ }
240
+ }]
241
+ }];
242
+ const deferred = defer();
243
+ responses.push(deferred.promise);
244
+ let inFlight = 0;
245
+ let maxInFlight = 0;
246
+ const fetch = import_vitest.vi.fn(async (input, init) => {
247
+ inFlight++;
248
+ maxInFlight = Math.max(maxInFlight, inFlight);
249
+ try {
250
+ const request = recordRequest(input, init);
251
+ requests.push(request);
252
+ const response = responses.shift();
253
+ if (!response) throw new Error("missing canned response");
254
+ return jsonResponse(await response);
255
+ } finally {
256
+ inFlight--;
257
+ }
258
+ });
259
+ const transport = install(fetch);
260
+ const messages = openRawSocket();
261
+ await eventually(() => (0, import_vitest.expect)(messages.some(message => message[0] === "pokeEnd")).toBe(true));
262
+ (0, import_vitest.expect)(requests[0].body.cookie).toBeNull();
263
+ (0, import_vitest.expect)(findMessage(messages, "pokeStart")[1].baseCookie).toBeNull();
264
+ (0, import_vitest.expect)(findMessage(messages, "pokeEnd")[1].cookie).toBe("00000000000000000001");
265
+ messages.length = 0;
266
+ await transport.pull();
267
+ (0, import_vitest.expect)(messages.filter(message => message[0].startsWith("poke"))).toEqual([]);
268
+ (0, import_vitest.expect)(requests[1].body.cookie).toBe(1);
269
+ await transport.pull();
270
+ (0, import_vitest.expect)(findMessage(messages, "pokeStart")[1].baseCookie).toBe("00000000000000000001");
271
+ (0, import_vitest.expect)(findMessage(messages, "pokeEnd")[1].cookie).toBe("00000000000000000002");
272
+ (0, import_vitest.expect)(requests[2].body.cookie).toBe(1);
273
+ messages.length = 0;
274
+ await transport.pull();
275
+ (0, import_vitest.expect)(findMessage(messages, "pokeStart")[1].baseCookie).toBe("00000000000000000002");
276
+ (0, import_vitest.expect)(findMessage(messages, "pokeEnd")[1].cookie).toBe("00000000000000000003");
277
+ (0, import_vitest.expect)(requests[3].body.cookie).toBe(2);
278
+ messages.length = 0;
279
+ const concurrentA = transport.pull();
280
+ const concurrentB = transport.pull();
281
+ await eventually(() => (0, import_vitest.expect)(requests.length).toBe(5));
282
+ deferred.resolve({
283
+ cookie: 4,
284
+ lastMutationIDChanges: {},
285
+ rowsPatch: [{
286
+ op: "clear"
287
+ }]
288
+ });
289
+ await Promise.all([concurrentA, concurrentB]);
290
+ (0, import_vitest.expect)(requests.length).toBe(5);
291
+ (0, import_vitest.expect)(maxInFlight).toBe(1);
292
+ (0, import_vitest.expect)(findMessage(messages, "pokeStart")[1].baseCookie).toBe("00000000000000000003");
293
+ (0, import_vitest.expect)(findMessage(messages, "pokeEnd")[1].cookie).toBe("00000000000000000004");
294
+ });
295
+ (0, import_vitest.test)("unchanged pull flushes late query registration to complete", async () => {
296
+ const requests = [];
297
+ const fetch = import_vitest.vi.fn(async (input, init) => {
298
+ const request = recordRequest(input, init);
299
+ requests.push(request);
300
+ (0, import_vitest.expect)(request.path).toBe("/pull");
301
+ if (request.body.cookie === null) {
302
+ return jsonResponse({
303
+ cookie: 1,
304
+ lastMutationIDChanges: {},
305
+ rowsPatch: [{
306
+ op: "clear"
307
+ }, {
308
+ op: "put",
309
+ tableName: "user",
310
+ value: {
311
+ id: "u1",
312
+ name: "ada"
313
+ }
314
+ }, {
315
+ op: "put",
316
+ tableName: "project",
317
+ value: {
318
+ id: "p1",
319
+ ownerId: "u1",
320
+ name: "first"
321
+ }
322
+ }]
323
+ });
324
+ }
325
+ return jsonResponse({
326
+ cookie: request.body.cookie,
327
+ unchanged: true
328
+ });
329
+ });
330
+ install(fetch);
331
+ const zero = createZero();
332
+ const projectView = zero.query.project.materialize();
333
+ await waitForComplete(projectView);
334
+ const userView = zero.query.user.materialize();
335
+ const users = await waitForComplete(userView);
336
+ (0, import_vitest.expect)(users).toEqual([{
337
+ id: "u1",
338
+ name: "ada"
339
+ }]);
340
+ (0, import_vitest.expect)(requests.at(-1)?.body.cookie).toBe(1);
341
+ projectView.destroy();
342
+ userView.destroy();
343
+ });
344
+ (0, import_vitest.test)("ping is answered locally and the stock Zero connection survives idle ping", async () => {
345
+ const fetch = import_vitest.vi.fn(async (input, init) => {
346
+ const request = recordRequest(input, init);
347
+ (0, import_vitest.expect)(request.path).toBe("/pull");
348
+ return jsonResponse({
349
+ cookie: request.body.cookie,
350
+ unchanged: true
351
+ });
352
+ });
353
+ const transport = install(fetch);
354
+ const zero = createZero({
355
+ pingTimeoutMs: 10
356
+ });
357
+ await eventually(() => (0, import_vitest.expect)(zero.connection.state.current.name).toBe("connected"));
358
+ await sleep(40);
359
+ (0, import_vitest.expect)(zero.connection.state.current.name).toBe("connected");
360
+ (0, import_vitest.expect)(transport.connections).toBe(1);
361
+ });
362
+ (0, import_vitest.test)("401 pull failure closes the fake socket without materializing data", async () => {
363
+ const requests = [];
364
+ const fetch = import_vitest.vi.fn(async (input, init) => {
365
+ const request = recordRequest(input, init);
366
+ requests.push(request);
367
+ (0, import_vitest.expect)(request.path).toBe("/pull");
368
+ return jsonResponse({
369
+ error: "unauthorized"
370
+ }, {
371
+ status: 401
372
+ });
373
+ });
374
+ const transport = install(fetch);
375
+ const zero = createZero();
376
+ const view = zero.query.project.materialize();
377
+ const emissions = [];
378
+ const cleanup = view.addListener((data, resultType) => {
379
+ emissions.push({
380
+ data: JSON.parse(JSON.stringify(data)),
381
+ resultType
382
+ });
383
+ });
384
+ await eventually(() => (0, import_vitest.expect)(requests.length).toBeGreaterThan(0));
385
+ await eventually(() => (0, import_vitest.expect)(zero.connection.state.current.name).toBe("needs-auth"));
386
+ await sleep(25);
387
+ (0, import_vitest.expect)(transport.connections).toBe(0);
388
+ (0, import_vitest.expect)(emissions.flatMap(emission => emission.data)).toEqual([]);
389
+ (0, import_vitest.expect)(view.data).toEqual([]);
390
+ cleanup();
391
+ view.destroy();
392
+ });
393
+ (0, import_vitest.test)("non-origin WebSockets pass through to the native implementation", () => {
394
+ const previous = globalThis.WebSocket;
395
+ class NativeWebSocket {
396
+ constructor(url, protocols) {
397
+ this.url = url;
398
+ this.protocols = protocols;
399
+ }
400
+ static CONNECTING = 0;
401
+ static OPEN = 1;
402
+ static CLOSING = 2;
403
+ static CLOSED = 3;
404
+ }
405
+ globalThis.WebSocket = NativeWebSocket;
406
+ const transport = (0, import_httpPullTransport.installHttpPullTransport)({
407
+ origin: ORIGIN,
408
+ fetch: import_vitest.vi.fn()
409
+ });
410
+ const socket = new WebSocket("wss://elsewhere.local/socket", "native");
411
+ (0, import_vitest.expect)(socket).toBeInstanceOf(NativeWebSocket);
412
+ (0, import_vitest.expect)(socket.url).toBe("wss://elsewhere.local/socket");
413
+ transport.uninstall();
414
+ (0, import_vitest.expect)(globalThis.WebSocket).toBe(NativeWebSocket);
415
+ globalThis.WebSocket = previous;
416
+ });
417
+ (0, import_vitest.test)("ensureHttpPullTransport installs once per origin", () => {
418
+ const origin = "http://127.0.0.1:65501";
419
+ const first = (0, import_httpPullTransport.ensureHttpPullTransport)({
420
+ origin,
421
+ fetch: import_vitest.vi.fn()
422
+ });
423
+ const second = (0, import_httpPullTransport.ensureHttpPullTransport)({
424
+ origin,
425
+ fetch: import_vitest.vi.fn()
426
+ });
427
+ (0, import_vitest.expect)(second).toBe(first);
428
+ first.uninstall();
429
+ });
430
+ });
431
+ function install(fetch) {
432
+ const transport = (0, import_httpPullTransport.installHttpPullTransport)({
433
+ origin: ORIGIN,
434
+ fetch
435
+ });
436
+ transports.push(transport);
437
+ return transport;
438
+ }
439
+ function createZero(options = {}) {
440
+ const zero = new import_zero.Zero({
441
+ server: ORIGIN,
442
+ userID: "u1",
443
+ auth: "token-u1",
444
+ schema: import_fixtureSchema.zeroHttpFixtureSchema,
445
+ kvStore: "mem",
446
+ storageKey: `zero-http-test-${++storageID}`,
447
+ mutators: import_fixtureSchema.zeroHttpFixtureMutators,
448
+ pingTimeoutMs: options.pingTimeoutMs
449
+ });
450
+ zeros.push(zero);
451
+ return zero;
452
+ }
453
+ function recordRequest(input, init) {
454
+ const url = new URL(String(input));
455
+ const headers = Object.fromEntries(Object.entries(init?.headers ?? {}).map(([key, value]) => [key.toLowerCase(), value]));
456
+ return {
457
+ url: url.toString(),
458
+ path: url.pathname,
459
+ headers,
460
+ body: init?.body ? JSON.parse(String(init.body)) : void 0
461
+ };
462
+ }
463
+ function jsonResponse(body, init) {
464
+ return new Response(JSON.stringify(body), {
465
+ status: init?.status ?? 200,
466
+ statusText: init?.statusText,
467
+ headers: {
468
+ "content-type": "application/json",
469
+ ...init?.headers
470
+ }
471
+ });
472
+ }
473
+ async function waitForComplete(view) {
474
+ return new Promise((resolve, reject) => {
475
+ const timeout = setTimeout(() => reject(new Error("timed out waiting for complete query")), 5e3);
476
+ let cleanup = () => {};
477
+ cleanup = view.addListener((data, resultType) => {
478
+ if (resultType !== "complete") return;
479
+ clearTimeout(timeout);
480
+ cleanup();
481
+ resolve(JSON.parse(JSON.stringify(data)));
482
+ });
483
+ });
484
+ }
485
+ async function eventually(assertion, timeout = 1e3) {
486
+ const started = Date.now();
487
+ let lastError;
488
+ while (Date.now() - started < timeout) {
489
+ try {
490
+ await assertion();
491
+ return;
492
+ } catch (error) {
493
+ lastError = error;
494
+ await sleep(10);
495
+ }
496
+ }
497
+ throw lastError;
498
+ }
499
+ function sleep(ms) {
500
+ return new Promise(resolve => setTimeout(resolve, ms));
501
+ }
502
+ function openRawSocket() {
503
+ return openRawSocketWithMessages().messages;
504
+ }
505
+ function openRawSocketWithMessages(opts) {
506
+ const url = new URL(`${ORIGIN}/sync/v51/connect`);
507
+ url.protocol = "wss:";
508
+ url.searchParams.set("clientID", "c1");
509
+ url.searchParams.set("clientGroupID", "cg1");
510
+ url.searchParams.set("userID", "u1");
511
+ url.searchParams.set("baseCookie", "");
512
+ url.searchParams.set("lmid", "0");
513
+ url.searchParams.set("wsid", "ws-test");
514
+ const messages = [];
515
+ const socket = new WebSocket(url, encodeSecProtocol(["initConnection", {
516
+ desiredQueriesPatch: opts?.desiredQueriesPatch ?? []
517
+ }], opts?.authToken ?? "token-u1"));
518
+ socket.addEventListener("message", event => {
519
+ messages.push(JSON.parse(String(event.data)));
520
+ });
521
+ return {
522
+ messages,
523
+ socket
524
+ };
525
+ }
526
+ function pushBody(id) {
527
+ return {
528
+ clientGroupID: "cg1",
529
+ pushVersion: 1,
530
+ requestID: `push-${id}`,
531
+ timestamp: Date.now(),
532
+ mutations: [{
533
+ type: "custom",
534
+ name: "project|create",
535
+ id,
536
+ clientID: "c1",
537
+ args: [{
538
+ id: `p${id}`,
539
+ ownerId: "u1",
540
+ name: `project ${id}`
541
+ }]
542
+ }]
543
+ };
544
+ }
545
+ function findMessage(messages, type) {
546
+ const message = messages.find(item => item[0] === type);
547
+ (0, import_vitest.expect)(message).toBeDefined();
548
+ return message;
549
+ }
550
+ function encodeSecProtocol(initConnectionMessage, authToken) {
551
+ return encodeURIComponent(Buffer.from(JSON.stringify({
552
+ initConnectionMessage,
553
+ authToken
554
+ })).toString("base64"));
555
+ }
556
+ function defer() {
557
+ let resolve;
558
+ let reject;
559
+ const promise = new Promise((res, rej) => {
560
+ resolve = res;
561
+ reject = rej;
562
+ });
563
+ return {
564
+ promise,
565
+ resolve,
566
+ reject
567
+ };
568
+ }