@adobe/data-sync 0.9.90
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/LICENSE +21 -0
- package/README.md +646 -0
- package/dist/create-sync-server.d.ts +29 -0
- package/dist/create-sync-server.js +156 -0
- package/dist/create-sync-server.js.map +1 -0
- package/dist/create-sync-service.d.ts +120 -0
- package/dist/create-sync-service.js +192 -0
- package/dist/create-sync-service.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/keepalive.test.d.ts +1 -0
- package/dist/keepalive.test.js +161 -0
- package/dist/keepalive.test.js.map +1 -0
- package/dist/loopback-transport.d.ts +20 -0
- package/dist/loopback-transport.js +98 -0
- package/dist/loopback-transport.js.map +1 -0
- package/dist/sync.reconnect.test.d.ts +1 -0
- package/dist/sync.reconnect.test.js +248 -0
- package/dist/sync.reconnect.test.js.map +1 -0
- package/dist/sync.test.d.ts +1 -0
- package/dist/sync.test.js +233 -0
- package/dist/sync.test.js.map +1 -0
- package/dist/transport-onclose.test.d.ts +1 -0
- package/dist/transport-onclose.test.js +74 -0
- package/dist/transport-onclose.test.js.map +1 -0
- package/dist/transport.d.ts +94 -0
- package/dist/transport.js +3 -0
- package/dist/transport.js.map +1 -0
- package/dist/websocket-transport.d.ts +71 -0
- package/dist/websocket-transport.js +148 -0
- package/dist/websocket-transport.js.map +1 -0
- package/package.json +38 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
//
|
|
3
|
+
// Tests for the ping/pong keep-alive added to the sync protocol.
|
|
4
|
+
//
|
|
5
|
+
// SyncService periodically sends `ping`; SyncServer echoes `pong`. Either
|
|
6
|
+
// side closes the transport when the inbound stream goes quiet for longer
|
|
7
|
+
// than its `livenessTimeoutMs`. Closing fires the transport's `onClose`
|
|
8
|
+
// listeners, which higher layers wire to a reconnect flow.
|
|
9
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
10
|
+
import { Database, createRebaseReplayConcurrency } from "@adobe/data/ecs";
|
|
11
|
+
import { createLoopbackTransport } from "./loopback-transport.js";
|
|
12
|
+
import { createSyncServer } from "./create-sync-server.js";
|
|
13
|
+
import { createSyncService } from "./create-sync-service.js";
|
|
14
|
+
const plugin = Database.Plugin.create({
|
|
15
|
+
components: {},
|
|
16
|
+
resources: {},
|
|
17
|
+
archetypes: {},
|
|
18
|
+
transactions: {},
|
|
19
|
+
});
|
|
20
|
+
const makeDb = (userId) => Database.create(plugin, { concurrency: createRebaseReplayConcurrency(userId) });
|
|
21
|
+
describe("sync keep-alive (ping/pong)", () => {
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
vi.useFakeTimers();
|
|
24
|
+
});
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
vi.useRealTimers();
|
|
27
|
+
});
|
|
28
|
+
it("client sends ping at the configured interval", () => {
|
|
29
|
+
const { client, server } = createLoopbackTransport();
|
|
30
|
+
const received = [];
|
|
31
|
+
server.onMessage((msg) => received.push(msg));
|
|
32
|
+
const sync = createSyncService({
|
|
33
|
+
database: makeDb("peer"),
|
|
34
|
+
transport: client,
|
|
35
|
+
pingIntervalMs: 1000,
|
|
36
|
+
livenessTimeoutMs: 0,
|
|
37
|
+
});
|
|
38
|
+
// The first message is the hello handshake — ignore it.
|
|
39
|
+
const before = received.length;
|
|
40
|
+
vi.advanceTimersByTime(1000);
|
|
41
|
+
expect(received.length).toBe(before + 1);
|
|
42
|
+
expect(received[received.length - 1]).toEqual({ kind: "ping" });
|
|
43
|
+
vi.advanceTimersByTime(2500);
|
|
44
|
+
expect(received.filter((m) => m.kind === "ping").length).toBe(3);
|
|
45
|
+
sync.dispose();
|
|
46
|
+
});
|
|
47
|
+
it("server replies to ping with pong", () => {
|
|
48
|
+
const server = createSyncServer({ livenessTimeoutMs: 0 });
|
|
49
|
+
const { client, server: serverTransport } = createLoopbackTransport();
|
|
50
|
+
const inbound = [];
|
|
51
|
+
client.onMessage((msg) => inbound.push(msg));
|
|
52
|
+
server.connect(serverTransport);
|
|
53
|
+
client.send({ kind: "ping" });
|
|
54
|
+
expect(inbound).toEqual([{ kind: "pong" }]);
|
|
55
|
+
server.dispose();
|
|
56
|
+
});
|
|
57
|
+
it("client closes transport when no inbound traffic before liveness timeout", () => {
|
|
58
|
+
const { client, server } = createLoopbackTransport();
|
|
59
|
+
// Drain client outbound so loopback's pending-buffer doesn't grow
|
|
60
|
+
// unboundedly, but never reply.
|
|
61
|
+
server.onMessage(() => undefined);
|
|
62
|
+
let closed = false;
|
|
63
|
+
client.onClose(() => { closed = true; });
|
|
64
|
+
createSyncService({
|
|
65
|
+
database: makeDb("peer"),
|
|
66
|
+
transport: client,
|
|
67
|
+
pingIntervalMs: 1000,
|
|
68
|
+
livenessTimeoutMs: 5000,
|
|
69
|
+
});
|
|
70
|
+
// No pong/welcome ever arrives. Just before the deadline we must
|
|
71
|
+
// still be alive; just after, the transport must have closed itself.
|
|
72
|
+
vi.advanceTimersByTime(4000);
|
|
73
|
+
expect(closed).toBe(false);
|
|
74
|
+
// Advance past the timeout plus a check tick (timeout/4 = 1250ms).
|
|
75
|
+
vi.advanceTimersByTime(2500);
|
|
76
|
+
expect(closed).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
it("inbound traffic resets the liveness deadline", () => {
|
|
79
|
+
const server = createSyncServer({ livenessTimeoutMs: 0 });
|
|
80
|
+
const { client, server: serverTransport } = createLoopbackTransport();
|
|
81
|
+
server.connect(serverTransport);
|
|
82
|
+
let closed = false;
|
|
83
|
+
client.onClose(() => { closed = true; });
|
|
84
|
+
const sync = createSyncService({
|
|
85
|
+
database: makeDb("peer"),
|
|
86
|
+
transport: client,
|
|
87
|
+
pingIntervalMs: 1000,
|
|
88
|
+
livenessTimeoutMs: 5000,
|
|
89
|
+
});
|
|
90
|
+
// Pings keep flowing, server keeps ponging — the deadline resets each
|
|
91
|
+
// tick, so well past the timeout we're still alive.
|
|
92
|
+
vi.advanceTimersByTime(20_000);
|
|
93
|
+
expect(closed).toBe(false);
|
|
94
|
+
sync.dispose();
|
|
95
|
+
server.dispose();
|
|
96
|
+
});
|
|
97
|
+
it("server closes a client transport when its inbound stream goes silent", () => {
|
|
98
|
+
const server = createSyncServer({ livenessTimeoutMs: 5000 });
|
|
99
|
+
const { client, server: serverTransport } = createLoopbackTransport();
|
|
100
|
+
server.connect(serverTransport);
|
|
101
|
+
let clientSawClose = false;
|
|
102
|
+
client.onClose(() => { clientSawClose = true; });
|
|
103
|
+
// We do NOT create a SyncService — no pings, no traffic at all.
|
|
104
|
+
// The server-side liveness sweep should close us on its own.
|
|
105
|
+
vi.advanceTimersByTime(4000);
|
|
106
|
+
expect(clientSawClose).toBe(false);
|
|
107
|
+
vi.advanceTimersByTime(2500);
|
|
108
|
+
expect(clientSawClose).toBe(true);
|
|
109
|
+
server.dispose();
|
|
110
|
+
});
|
|
111
|
+
it("pingIntervalMs: 0 disables ping sending entirely", () => {
|
|
112
|
+
const { client, server } = createLoopbackTransport();
|
|
113
|
+
const received = [];
|
|
114
|
+
server.onMessage((msg) => received.push(msg));
|
|
115
|
+
const sync = createSyncService({
|
|
116
|
+
database: makeDb("peer"),
|
|
117
|
+
transport: client,
|
|
118
|
+
pingIntervalMs: 0,
|
|
119
|
+
livenessTimeoutMs: 0,
|
|
120
|
+
});
|
|
121
|
+
const beforePings = received.filter((m) => m.kind === "ping").length;
|
|
122
|
+
vi.advanceTimersByTime(60_000);
|
|
123
|
+
const afterPings = received.filter((m) => m.kind === "ping").length;
|
|
124
|
+
expect(afterPings).toBe(beforePings);
|
|
125
|
+
sync.dispose();
|
|
126
|
+
});
|
|
127
|
+
it("livenessTimeoutMs: 0 disables liveness checking on the client", () => {
|
|
128
|
+
const { client, server } = createLoopbackTransport();
|
|
129
|
+
server.onMessage(() => undefined);
|
|
130
|
+
let closed = false;
|
|
131
|
+
client.onClose(() => { closed = true; });
|
|
132
|
+
const sync = createSyncService({
|
|
133
|
+
database: makeDb("peer"),
|
|
134
|
+
transport: client,
|
|
135
|
+
pingIntervalMs: 1000,
|
|
136
|
+
livenessTimeoutMs: 0,
|
|
137
|
+
});
|
|
138
|
+
vi.advanceTimersByTime(60_000);
|
|
139
|
+
expect(closed).toBe(false);
|
|
140
|
+
sync.dispose();
|
|
141
|
+
});
|
|
142
|
+
it("dispose stops the keep-alive timers", () => {
|
|
143
|
+
const { client, server } = createLoopbackTransport();
|
|
144
|
+
const received = [];
|
|
145
|
+
server.onMessage((msg) => received.push(msg));
|
|
146
|
+
const sync = createSyncService({
|
|
147
|
+
database: makeDb("peer"),
|
|
148
|
+
transport: client,
|
|
149
|
+
pingIntervalMs: 1000,
|
|
150
|
+
livenessTimeoutMs: 5000,
|
|
151
|
+
});
|
|
152
|
+
vi.advanceTimersByTime(2500);
|
|
153
|
+
const beforeDispose = received.filter((m) => m.kind === "ping").length;
|
|
154
|
+
expect(beforeDispose).toBeGreaterThan(0);
|
|
155
|
+
sync.dispose();
|
|
156
|
+
vi.advanceTimersByTime(10_000);
|
|
157
|
+
const afterDispose = received.filter((m) => m.kind === "ping").length;
|
|
158
|
+
expect(afterDispose).toBe(beforeDispose);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
//# sourceMappingURL=keepalive.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keepalive.test.js","sourceRoot":"","sources":["../src/keepalive.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ClientTransport, ServerTransport } from "./transport.js";
|
|
2
|
+
/**
|
|
3
|
+
* Creates a paired in-process loopback transport.
|
|
4
|
+
*
|
|
5
|
+
* Messages sent on `client` are delivered synchronously to `server` listeners,
|
|
6
|
+
* and vice-versa. Useful for unit tests and single-process demos that need the
|
|
7
|
+
* full sync protocol without network I/O.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* const { client, server } = createLoopbackTransport();
|
|
12
|
+
* const syncServer = createSyncServer();
|
|
13
|
+
* syncServer.connect(server);
|
|
14
|
+
* const sync = createSyncService({ database: db, transport: client });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare const createLoopbackTransport: () => {
|
|
18
|
+
client: ClientTransport;
|
|
19
|
+
server: ServerTransport;
|
|
20
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
/**
|
|
3
|
+
* Creates a paired in-process loopback transport.
|
|
4
|
+
*
|
|
5
|
+
* Messages sent on `client` are delivered synchronously to `server` listeners,
|
|
6
|
+
* and vice-versa. Useful for unit tests and single-process demos that need the
|
|
7
|
+
* full sync protocol without network I/O.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* const { client, server } = createLoopbackTransport();
|
|
12
|
+
* const syncServer = createSyncServer();
|
|
13
|
+
* syncServer.connect(server);
|
|
14
|
+
* const sync = createSyncService({ database: db, transport: client });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export const createLoopbackTransport = () => {
|
|
18
|
+
const clientListeners = new Set();
|
|
19
|
+
const serverListeners = new Set();
|
|
20
|
+
// Messages sent to a side before any listener is registered are buffered
|
|
21
|
+
// and delivered on the first `onMessage` call. This mirrors real async
|
|
22
|
+
// transports (WebSocket, MessagePort) where the sender cannot observe
|
|
23
|
+
// whether the receiver has set up its handler yet.
|
|
24
|
+
const clientPending = [];
|
|
25
|
+
const serverPending = [];
|
|
26
|
+
let closed = false;
|
|
27
|
+
const closeListeners = new Set();
|
|
28
|
+
const fireClose = () => {
|
|
29
|
+
if (!closed)
|
|
30
|
+
return;
|
|
31
|
+
for (const l of closeListeners)
|
|
32
|
+
l();
|
|
33
|
+
closeListeners.clear();
|
|
34
|
+
};
|
|
35
|
+
const client = {
|
|
36
|
+
send(msg) {
|
|
37
|
+
if (closed)
|
|
38
|
+
return;
|
|
39
|
+
if (serverListeners.size === 0) {
|
|
40
|
+
serverPending.push(msg);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
for (const l of serverListeners)
|
|
44
|
+
l(msg);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
onMessage(listener) {
|
|
48
|
+
clientListeners.add(listener);
|
|
49
|
+
// Flush buffered messages delivered before this listener registered.
|
|
50
|
+
for (const msg of clientPending.splice(0))
|
|
51
|
+
listener(msg);
|
|
52
|
+
return () => clientListeners.delete(listener);
|
|
53
|
+
},
|
|
54
|
+
onClose(listener) {
|
|
55
|
+
if (closed) {
|
|
56
|
+
listener();
|
|
57
|
+
return () => undefined;
|
|
58
|
+
}
|
|
59
|
+
closeListeners.add(listener);
|
|
60
|
+
return () => closeListeners.delete(listener);
|
|
61
|
+
},
|
|
62
|
+
close() {
|
|
63
|
+
if (closed)
|
|
64
|
+
return;
|
|
65
|
+
closed = true;
|
|
66
|
+
clientListeners.clear();
|
|
67
|
+
serverListeners.clear();
|
|
68
|
+
fireClose();
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
const server = {
|
|
72
|
+
send(msg) {
|
|
73
|
+
if (closed)
|
|
74
|
+
return;
|
|
75
|
+
if (clientListeners.size === 0) {
|
|
76
|
+
clientPending.push(msg);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
for (const l of clientListeners)
|
|
80
|
+
l(msg);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
onMessage(listener) {
|
|
84
|
+
serverListeners.add(listener);
|
|
85
|
+
for (const msg of serverPending.splice(0))
|
|
86
|
+
listener(msg);
|
|
87
|
+
return () => serverListeners.delete(listener);
|
|
88
|
+
},
|
|
89
|
+
onClose(listener) {
|
|
90
|
+
return client.onClose(listener);
|
|
91
|
+
},
|
|
92
|
+
close() {
|
|
93
|
+
client.close();
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
return { client, server };
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=loopback-transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loopback-transport.js","sourceRoot":"","sources":["../src/loopback-transport.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
//
|
|
3
|
+
// Tests for the hello/welcome reconnect protocol.
|
|
4
|
+
//
|
|
5
|
+
// Scenarios:
|
|
6
|
+
// 1. Fresh client — welcome with resetRequired:false + full replay.
|
|
7
|
+
// 2. Reconnect with matching session + watermark — tail-only replay.
|
|
8
|
+
// 3. Reconnect client ahead of server — resetRequired:true + full replay.
|
|
9
|
+
// 4. Reconnect with mismatched session id — resetRequired:true + full replay.
|
|
10
|
+
// 5. End-to-end: disconnect, reconnect, verify only new envelopes arrive.
|
|
11
|
+
// 6. End-to-end: new server (session mismatch) → reset + full replay.
|
|
12
|
+
import { describe, it, expect, vi } from "vitest";
|
|
13
|
+
import { Database, createRebaseReplayConcurrency } from "@adobe/data/ecs";
|
|
14
|
+
import { createLoopbackTransport } from "./loopback-transport.js";
|
|
15
|
+
import { createSyncServer } from "./create-sync-server.js";
|
|
16
|
+
import { createSyncService } from "./create-sync-service.js";
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Shared plugin
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
const plugin = Database.Plugin.create({
|
|
21
|
+
components: {
|
|
22
|
+
x: { type: "number", default: 0 },
|
|
23
|
+
label: { type: "string" },
|
|
24
|
+
},
|
|
25
|
+
resources: {
|
|
26
|
+
score: { default: 0 },
|
|
27
|
+
},
|
|
28
|
+
archetypes: { Point: ["x", "label"] },
|
|
29
|
+
transactions: {
|
|
30
|
+
addPoint(s, args) {
|
|
31
|
+
return s.archetypes.Point.insert(args);
|
|
32
|
+
},
|
|
33
|
+
bumpScore(s, args) {
|
|
34
|
+
s.resources.score = s.resources.score + args.delta;
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
const makeDb = (userId) => Database.create(plugin, { concurrency: createRebaseReplayConcurrency(userId) });
|
|
39
|
+
const entityCount = (db) => db.select(["x", "label"]).length;
|
|
40
|
+
const labels = (db) => db.select(["label"]).map(e => db.read(e)?.label).sort();
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Hello / welcome protocol unit tests
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
describe("hello/welcome protocol", () => {
|
|
45
|
+
it("fresh client receives welcome with resetRequired:false and full log replay", () => {
|
|
46
|
+
const server = createSyncServer();
|
|
47
|
+
// Pre-populate the server log via a seed client.
|
|
48
|
+
const db0 = makeDb("seed");
|
|
49
|
+
const { client: c0, server: s0 } = createLoopbackTransport();
|
|
50
|
+
const d0 = server.connect(s0);
|
|
51
|
+
const svc0 = createSyncService({ database: db0, transport: c0 });
|
|
52
|
+
db0.transactions.addPoint({ x: 1, label: "a" });
|
|
53
|
+
db0.transactions.addPoint({ x: 2, label: "b" });
|
|
54
|
+
svc0.dispose();
|
|
55
|
+
d0();
|
|
56
|
+
// Fresh client connects AFTER seed commits so it only gets the replay.
|
|
57
|
+
const { client: ct, server: st } = createLoopbackTransport();
|
|
58
|
+
const disconnect = server.connect(st);
|
|
59
|
+
const db1 = makeDb("u1");
|
|
60
|
+
const welcomeMsgs = [];
|
|
61
|
+
const svc1 = createSyncService({
|
|
62
|
+
database: db1,
|
|
63
|
+
transport: ct,
|
|
64
|
+
onWelcome: (msg) => welcomeMsgs.push(msg),
|
|
65
|
+
});
|
|
66
|
+
expect(welcomeMsgs).toHaveLength(1);
|
|
67
|
+
expect(welcomeMsgs[0].resetRequired).toBe(false);
|
|
68
|
+
expect(welcomeMsgs[0].sessionId).toBe(server.sessionId);
|
|
69
|
+
expect(entityCount(db1)).toBe(2);
|
|
70
|
+
svc1.dispose();
|
|
71
|
+
disconnect();
|
|
72
|
+
});
|
|
73
|
+
it("reconnecting client with matching session and watermark receives tail only", () => {
|
|
74
|
+
const server = createSyncServer();
|
|
75
|
+
// Initial connection — commit two transactions.
|
|
76
|
+
const db1 = makeDb("u1");
|
|
77
|
+
const { client: c1, server: s1 } = createLoopbackTransport();
|
|
78
|
+
const d1 = server.connect(s1);
|
|
79
|
+
const svc1 = createSyncService({ database: db1, transport: c1 });
|
|
80
|
+
db1.transactions.addPoint({ x: 1, label: "first" });
|
|
81
|
+
db1.transactions.addPoint({ x: 2, label: "second" });
|
|
82
|
+
const watermarkAfterTwo = svc1.lastAppliedTime();
|
|
83
|
+
const sessionId = svc1.sessionId();
|
|
84
|
+
svc1.dispose();
|
|
85
|
+
d1();
|
|
86
|
+
// Third commit from another peer while client is offline.
|
|
87
|
+
const db2 = makeDb("u2");
|
|
88
|
+
const { client: c2, server: s2 } = createLoopbackTransport();
|
|
89
|
+
const d2 = server.connect(s2);
|
|
90
|
+
const svc2 = createSyncService({ database: db2, transport: c2 });
|
|
91
|
+
db2.transactions.addPoint({ x: 3, label: "third" });
|
|
92
|
+
svc2.dispose();
|
|
93
|
+
d2();
|
|
94
|
+
// Reconnect with watermark — spy on apply() to count replayed envelopes.
|
|
95
|
+
const db1b = makeDb("u1");
|
|
96
|
+
const applyArgs = [];
|
|
97
|
+
const origApply = db1b.apply.bind(db1b);
|
|
98
|
+
db1b.apply = (env) => { applyArgs.push(env); return origApply(env); };
|
|
99
|
+
const welcomeMsgs = [];
|
|
100
|
+
const { client: c1b, server: s1b } = createLoopbackTransport();
|
|
101
|
+
const d1b = server.connect(s1b);
|
|
102
|
+
const svc1b = createSyncService({
|
|
103
|
+
database: db1b,
|
|
104
|
+
transport: c1b,
|
|
105
|
+
priorSessionId: sessionId,
|
|
106
|
+
initialWatermark: watermarkAfterTwo,
|
|
107
|
+
onWelcome: (msg) => welcomeMsgs.push(msg),
|
|
108
|
+
});
|
|
109
|
+
expect(welcomeMsgs[0].resetRequired).toBe(false);
|
|
110
|
+
// Only the third envelope (time > watermarkAfterTwo) should have been applied.
|
|
111
|
+
expect(applyArgs).toHaveLength(1);
|
|
112
|
+
expect(applyArgs[0].time).toBeGreaterThan(watermarkAfterTwo);
|
|
113
|
+
svc1b.dispose();
|
|
114
|
+
d1b();
|
|
115
|
+
});
|
|
116
|
+
it("reconnecting client with time ahead of server triggers resetRequired:true + full replay", () => {
|
|
117
|
+
const server = createSyncServer();
|
|
118
|
+
const { client: ct, server: st } = createLoopbackTransport();
|
|
119
|
+
const disconnect = server.connect(st);
|
|
120
|
+
const db = makeDb("u1");
|
|
121
|
+
const welcomeMsgs = [];
|
|
122
|
+
const svc = createSyncService({
|
|
123
|
+
database: db,
|
|
124
|
+
transport: ct,
|
|
125
|
+
priorSessionId: server.sessionId,
|
|
126
|
+
initialWatermark: 9999,
|
|
127
|
+
onWelcome: (msg) => welcomeMsgs.push(msg),
|
|
128
|
+
});
|
|
129
|
+
expect(welcomeMsgs[0].resetRequired).toBe(true);
|
|
130
|
+
svc.dispose();
|
|
131
|
+
disconnect();
|
|
132
|
+
});
|
|
133
|
+
it("reconnecting client with mismatched session triggers resetRequired:true + full replay", () => {
|
|
134
|
+
const server = createSyncServer();
|
|
135
|
+
// Pre-populate server.
|
|
136
|
+
const db0 = makeDb("seed");
|
|
137
|
+
const { client: c0, server: s0 } = createLoopbackTransport();
|
|
138
|
+
const d0 = server.connect(s0);
|
|
139
|
+
const svc0 = createSyncService({ database: db0, transport: c0 });
|
|
140
|
+
db0.transactions.addPoint({ x: 1, label: "x" });
|
|
141
|
+
svc0.dispose();
|
|
142
|
+
d0();
|
|
143
|
+
const { client: ct, server: st } = createLoopbackTransport();
|
|
144
|
+
const disconnect = server.connect(st);
|
|
145
|
+
const db = makeDb("u1");
|
|
146
|
+
const welcomeMsgs = [];
|
|
147
|
+
const svc = createSyncService({
|
|
148
|
+
database: db,
|
|
149
|
+
transport: ct,
|
|
150
|
+
priorSessionId: "wrong-session-id",
|
|
151
|
+
initialWatermark: 0,
|
|
152
|
+
onWelcome: (msg) => welcomeMsgs.push(msg),
|
|
153
|
+
});
|
|
154
|
+
expect(welcomeMsgs[0].resetRequired).toBe(true);
|
|
155
|
+
expect(entityCount(db)).toBe(1); // full replay applied
|
|
156
|
+
svc.dispose();
|
|
157
|
+
disconnect();
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
// End-to-end reconnect scenarios
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
describe("end-to-end reconnect", () => {
|
|
164
|
+
it("tail-only replay: only new transactions applied after reconnect", () => {
|
|
165
|
+
const server = createSyncServer();
|
|
166
|
+
// --- Initial session ---
|
|
167
|
+
const db = makeDb("u1");
|
|
168
|
+
const { client: c1, server: s1 } = createLoopbackTransport();
|
|
169
|
+
const d1 = server.connect(s1);
|
|
170
|
+
const svc1 = createSyncService({ database: db, transport: c1 });
|
|
171
|
+
db.transactions.addPoint({ x: 1, label: "pre-disconnect-1" });
|
|
172
|
+
db.transactions.addPoint({ x: 2, label: "pre-disconnect-2" });
|
|
173
|
+
const watermark = svc1.lastAppliedTime();
|
|
174
|
+
const session = svc1.sessionId();
|
|
175
|
+
svc1.dispose();
|
|
176
|
+
d1();
|
|
177
|
+
// --- New commit from a second peer while offline ---
|
|
178
|
+
const db2 = makeDb("u2");
|
|
179
|
+
const { client: c2, server: s2 } = createLoopbackTransport();
|
|
180
|
+
const d2 = server.connect(s2);
|
|
181
|
+
const svc2 = createSyncService({ database: db2, transport: c2 });
|
|
182
|
+
db2.transactions.addPoint({ x: 3, label: "while-offline" });
|
|
183
|
+
svc2.dispose();
|
|
184
|
+
d2();
|
|
185
|
+
// --- Reconnect: spy on apply() ---
|
|
186
|
+
const applyTimes = [];
|
|
187
|
+
const origApply = db.apply.bind(db);
|
|
188
|
+
db.apply = (env) => { applyTimes.push(env.time); return origApply(env); };
|
|
189
|
+
const { client: c1b, server: s1b } = createLoopbackTransport();
|
|
190
|
+
const d1b = server.connect(s1b);
|
|
191
|
+
const svc1b = createSyncService({ database: db, transport: c1b, priorSessionId: session, initialWatermark: watermark });
|
|
192
|
+
// Only the one new commit should have been replayed.
|
|
193
|
+
expect(applyTimes).toHaveLength(1);
|
|
194
|
+
expect(applyTimes[0]).toBeGreaterThan(watermark);
|
|
195
|
+
expect(entityCount(db)).toBe(3); // 2 before + 1 after
|
|
196
|
+
svc1b.dispose();
|
|
197
|
+
d1b();
|
|
198
|
+
});
|
|
199
|
+
it("session mismatch: onWelcome with resetRequired:true, db.reset() clears stale state, full replay applied", () => {
|
|
200
|
+
// --- Old server (first session) ---
|
|
201
|
+
const oldServer = createSyncServer();
|
|
202
|
+
const db = makeDb("u1");
|
|
203
|
+
const { client: c1, server: s1 } = createLoopbackTransport();
|
|
204
|
+
const d1 = oldServer.connect(s1);
|
|
205
|
+
const svc1 = createSyncService({ database: db, transport: c1 });
|
|
206
|
+
db.transactions.addPoint({ x: 1, label: "old-session" });
|
|
207
|
+
const watermark = svc1.lastAppliedTime();
|
|
208
|
+
const oldSession = svc1.sessionId();
|
|
209
|
+
svc1.dispose();
|
|
210
|
+
d1();
|
|
211
|
+
oldServer.dispose();
|
|
212
|
+
// --- New server (new session, fresh log) ---
|
|
213
|
+
const newServer = createSyncServer();
|
|
214
|
+
expect(newServer.sessionId).not.toBe(oldSession);
|
|
215
|
+
// Pre-populate new server with different data.
|
|
216
|
+
const dbSeed = makeDb("seed");
|
|
217
|
+
const { client: cs, server: ss } = createLoopbackTransport();
|
|
218
|
+
const ds = newServer.connect(ss);
|
|
219
|
+
const svcs = createSyncService({ database: dbSeed, transport: cs });
|
|
220
|
+
dbSeed.transactions.addPoint({ x: 9, label: "new-session" });
|
|
221
|
+
svcs.dispose();
|
|
222
|
+
ds();
|
|
223
|
+
// --- Reconnect with old session credentials ---
|
|
224
|
+
const resetCalled = vi.fn();
|
|
225
|
+
const { client: c1b, server: s1b } = createLoopbackTransport();
|
|
226
|
+
const d1b = newServer.connect(s1b);
|
|
227
|
+
const svc1b = createSyncService({
|
|
228
|
+
database: db,
|
|
229
|
+
transport: c1b,
|
|
230
|
+
priorSessionId: oldSession,
|
|
231
|
+
initialWatermark: watermark,
|
|
232
|
+
onWelcome: ({ resetRequired }) => {
|
|
233
|
+
if (resetRequired) {
|
|
234
|
+
db.reset();
|
|
235
|
+
resetCalled();
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
expect(resetCalled).toHaveBeenCalledTimes(1);
|
|
240
|
+
// After reset + full replay, db reflects new session's state only.
|
|
241
|
+
expect(entityCount(db)).toBe(1);
|
|
242
|
+
expect(labels(db)).toContain("new-session");
|
|
243
|
+
expect(labels(db)).not.toContain("old-session");
|
|
244
|
+
svc1b.dispose();
|
|
245
|
+
d1b();
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
//# sourceMappingURL=sync.reconnect.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.reconnect.test.js","sourceRoot":"","sources":["../src/sync.reconnect.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|