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