@powersync/service-core 0.0.0-dev-20241111122558 → 0.0.0-dev-20241119082750
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/CHANGELOG.md +12 -5
- package/dist/routes/configure-fastify.d.ts +16 -0
- package/dist/routes/configure-fastify.js +2 -1
- package/dist/routes/configure-fastify.js.map +1 -1
- package/dist/routes/endpoints/probes.d.ts +74 -0
- package/dist/routes/endpoints/probes.js +51 -0
- package/dist/routes/endpoints/probes.js.map +1 -0
- package/dist/routes/router.d.ts +2 -2
- package/dist/storage/BucketStorage.d.ts +10 -0
- package/dist/storage/BucketStorage.js.map +1 -1
- package/dist/storage/mongo/MongoBucketBatch.d.ts +11 -1
- package/dist/storage/mongo/MongoBucketBatch.js +55 -39
- package/dist/storage/mongo/MongoBucketBatch.js.map +1 -1
- package/dist/storage/mongo/MongoSyncBucketStorage.js +9 -1
- package/dist/storage/mongo/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/mongo/MongoWriteCheckpointAPI.js +9 -2
- package/dist/storage/mongo/MongoWriteCheckpointAPI.js.map +1 -1
- package/dist/storage/mongo/OperationBatch.d.ts +6 -1
- package/dist/storage/mongo/OperationBatch.js +9 -0
- package/dist/storage/mongo/OperationBatch.js.map +1 -1
- package/package.json +5 -5
- package/src/routes/configure-fastify.ts +2 -1
- package/src/routes/endpoints/probes.ts +58 -0
- package/src/routes/router.ts +2 -2
- package/src/storage/BucketStorage.ts +10 -0
- package/src/storage/mongo/MongoBucketBatch.ts +74 -54
- package/src/storage/mongo/MongoSyncBucketStorage.ts +13 -12
- package/src/storage/mongo/MongoWriteCheckpointAPI.ts +9 -2
- package/src/storage/mongo/OperationBatch.ts +10 -1
- package/test/src/routes/probes.integration.test.ts +235 -0
- package/test/src/routes/probes.test.ts +153 -0
- package/test/src/sync.test.ts +62 -1
- package/test/src/util.ts +2 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import Fastify, { FastifyInstance } from 'fastify';
|
|
3
|
+
import { container } from '@powersync/lib-services-framework';
|
|
4
|
+
import * as auth from '../../../src/routes/auth.js';
|
|
5
|
+
import * as system from '../../../src/system/system-index.js';
|
|
6
|
+
import { configureFastifyServer } from '../../../src/index.js';
|
|
7
|
+
import { ProbeRoutes } from '../../../src/routes/endpoints/probes.js';
|
|
8
|
+
|
|
9
|
+
vi.mock('@powersync/lib-services-framework', async () => {
|
|
10
|
+
const actual = (await vi.importActual('@powersync/lib-services-framework')) as any;
|
|
11
|
+
return {
|
|
12
|
+
...actual,
|
|
13
|
+
container: {
|
|
14
|
+
...actual.container,
|
|
15
|
+
probes: {
|
|
16
|
+
state: vi.fn()
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('Probe Routes Integration', () => {
|
|
23
|
+
let app: FastifyInstance;
|
|
24
|
+
let mockSystem: system.ServiceContext;
|
|
25
|
+
|
|
26
|
+
beforeEach(async () => {
|
|
27
|
+
app = Fastify();
|
|
28
|
+
mockSystem = { routerEngine: {} } as system.ServiceContext;
|
|
29
|
+
await configureFastifyServer(app, { service_context: mockSystem });
|
|
30
|
+
await app.ready();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(async () => {
|
|
34
|
+
await app.close();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('Startup Probe', () => {
|
|
38
|
+
it('returns 200 when system is started', async () => {
|
|
39
|
+
const mockState = {
|
|
40
|
+
started: true,
|
|
41
|
+
ready: true,
|
|
42
|
+
touched_at: new Date()
|
|
43
|
+
};
|
|
44
|
+
vi.spyOn(auth, 'authUser').mockResolvedValue({ authorized: true });
|
|
45
|
+
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
46
|
+
|
|
47
|
+
const response = await app.inject({
|
|
48
|
+
method: 'GET',
|
|
49
|
+
url: ProbeRoutes.STARTUP
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(response.statusCode).toBe(200);
|
|
53
|
+
expect(JSON.parse(response.payload)).toEqual({
|
|
54
|
+
...mockState,
|
|
55
|
+
touched_at: mockState.touched_at.toISOString()
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('returns 400 when system is not started', async () => {
|
|
60
|
+
const mockState = {
|
|
61
|
+
started: false,
|
|
62
|
+
ready: false,
|
|
63
|
+
touched_at: new Date()
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
67
|
+
|
|
68
|
+
const response = await app.inject({
|
|
69
|
+
method: 'GET',
|
|
70
|
+
url: ProbeRoutes.STARTUP
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(response.statusCode).toBe(400);
|
|
74
|
+
expect(JSON.parse(response.payload)).toEqual({
|
|
75
|
+
...mockState,
|
|
76
|
+
touched_at: mockState.touched_at.toISOString()
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('Liveness Probe', () => {
|
|
82
|
+
it('returns 200 when system was touched recently', async () => {
|
|
83
|
+
const mockState = {
|
|
84
|
+
started: true,
|
|
85
|
+
ready: true,
|
|
86
|
+
touched_at: new Date()
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
90
|
+
|
|
91
|
+
const response = await app.inject({
|
|
92
|
+
method: 'GET',
|
|
93
|
+
url: ProbeRoutes.LIVENESS
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
expect(response.statusCode).toBe(200);
|
|
97
|
+
expect(JSON.parse(response.payload)).toEqual({
|
|
98
|
+
...mockState,
|
|
99
|
+
touched_at: mockState.touched_at.toISOString()
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('returns 400 when system has not been touched recently', async () => {
|
|
104
|
+
const mockState = {
|
|
105
|
+
started: true,
|
|
106
|
+
ready: true,
|
|
107
|
+
touched_at: new Date(Date.now() - 15000) // 15 seconds ago
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
111
|
+
|
|
112
|
+
const response = await app.inject({
|
|
113
|
+
method: 'GET',
|
|
114
|
+
url: ProbeRoutes.LIVENESS
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(response.statusCode).toBe(400);
|
|
118
|
+
expect(JSON.parse(response.payload)).toEqual({
|
|
119
|
+
...mockState,
|
|
120
|
+
touched_at: mockState.touched_at.toISOString()
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('Readiness Probe', () => {
|
|
126
|
+
it('returns 200 when system is ready', async () => {
|
|
127
|
+
const mockState = {
|
|
128
|
+
started: true,
|
|
129
|
+
ready: true,
|
|
130
|
+
touched_at: new Date()
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
134
|
+
|
|
135
|
+
const response = await app.inject({
|
|
136
|
+
method: 'GET',
|
|
137
|
+
url: ProbeRoutes.READINESS
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(response.statusCode).toBe(200);
|
|
141
|
+
expect(JSON.parse(response.payload)).toEqual({
|
|
142
|
+
...mockState,
|
|
143
|
+
touched_at: mockState.touched_at.toISOString()
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('returns 400 when system is not ready', async () => {
|
|
148
|
+
const mockState = {
|
|
149
|
+
started: true,
|
|
150
|
+
ready: false,
|
|
151
|
+
touched_at: new Date()
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
155
|
+
|
|
156
|
+
const response = await app.inject({
|
|
157
|
+
method: 'GET',
|
|
158
|
+
url: ProbeRoutes.READINESS
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
expect(response.statusCode).toBe(400);
|
|
162
|
+
expect(JSON.parse(response.payload)).toEqual({
|
|
163
|
+
...mockState,
|
|
164
|
+
touched_at: mockState.touched_at.toISOString()
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('Request Queue Behavior', () => {
|
|
170
|
+
it('handles concurrent requests within limits', async () => {
|
|
171
|
+
const mockState = { started: true, ready: true, touched_at: new Date() };
|
|
172
|
+
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
173
|
+
|
|
174
|
+
// Create array of 15 concurrent requests (default concurrency is 10)
|
|
175
|
+
const requests = Array(15)
|
|
176
|
+
.fill(null)
|
|
177
|
+
.map(() =>
|
|
178
|
+
app.inject({
|
|
179
|
+
method: 'GET',
|
|
180
|
+
url: ProbeRoutes.STARTUP
|
|
181
|
+
})
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const responses = await Promise.all(requests);
|
|
185
|
+
|
|
186
|
+
// All requests should complete successfully
|
|
187
|
+
responses.forEach((response) => {
|
|
188
|
+
expect(response.statusCode).toBe(200);
|
|
189
|
+
expect(JSON.parse(response.payload)).toEqual({
|
|
190
|
+
...mockState,
|
|
191
|
+
touched_at: mockState.touched_at.toISOString()
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('respects max queue depth', async () => {
|
|
197
|
+
const mockState = { started: true, ready: true, touched_at: new Date() };
|
|
198
|
+
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
199
|
+
|
|
200
|
+
// Create array of 35 concurrent requests (default max_queue_depth is 20)
|
|
201
|
+
const requests = Array(35)
|
|
202
|
+
.fill(null)
|
|
203
|
+
.map(() =>
|
|
204
|
+
app.inject({
|
|
205
|
+
method: 'GET',
|
|
206
|
+
url: ProbeRoutes.STARTUP
|
|
207
|
+
})
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const responses = await Promise.all(requests);
|
|
211
|
+
|
|
212
|
+
// Some requests should succeed and some should fail with 429
|
|
213
|
+
const successCount = responses.filter((r) => r.statusCode === 200).length;
|
|
214
|
+
const queueFullCount = responses.filter((r) => r.statusCode === 429).length;
|
|
215
|
+
|
|
216
|
+
expect(successCount).toBeGreaterThan(0);
|
|
217
|
+
expect(queueFullCount).toBeGreaterThan(0);
|
|
218
|
+
expect(successCount + queueFullCount).toBe(35);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('Content Types', () => {
|
|
223
|
+
it('returns correct content type headers', async () => {
|
|
224
|
+
const mockState = { started: true, ready: true, touched_at: new Date() };
|
|
225
|
+
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
226
|
+
|
|
227
|
+
const response = await app.inject({
|
|
228
|
+
method: 'GET',
|
|
229
|
+
url: ProbeRoutes.STARTUP
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
expect(response.headers['content-type']).toMatch(/application\/json/);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
});
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { container } from '@powersync/lib-services-framework';
|
|
3
|
+
import { startupCheck, livenessCheck, readinessCheck } from '../../../src/routes/endpoints/probes.js';
|
|
4
|
+
|
|
5
|
+
// Mock the container
|
|
6
|
+
vi.mock('@powersync/lib-services-framework', () => ({
|
|
7
|
+
container: {
|
|
8
|
+
probes: {
|
|
9
|
+
state: vi.fn()
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
router: {
|
|
13
|
+
HTTPMethod: {
|
|
14
|
+
GET: 'GET'
|
|
15
|
+
},
|
|
16
|
+
RouterResponse: class RouterResponse {
|
|
17
|
+
status: number;
|
|
18
|
+
data: any;
|
|
19
|
+
headers: Record<string, string>;
|
|
20
|
+
afterSend: () => Promise<void>;
|
|
21
|
+
__micro_router_response = true;
|
|
22
|
+
|
|
23
|
+
constructor({ status, data, headers, afterSend }: {
|
|
24
|
+
status?: number;
|
|
25
|
+
data: any;
|
|
26
|
+
headers?: Record<string, string>;
|
|
27
|
+
afterSend?: () => Promise<void>;
|
|
28
|
+
}) {
|
|
29
|
+
this.status = status || 200;
|
|
30
|
+
this.data = data;
|
|
31
|
+
this.headers = headers || { 'Content-Type': 'application/json' };
|
|
32
|
+
this.afterSend = afterSend ?? (() => Promise.resolve());
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
describe('Probe Routes', () => {
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
vi.clearAllMocks();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('startupCheck', () => {
|
|
44
|
+
it('has the correct route definitions', () => {
|
|
45
|
+
expect(startupCheck.path).toBe('/probes/startup');
|
|
46
|
+
expect(startupCheck.method).toBe('GET');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('returns 200 when started is true', async () => {
|
|
50
|
+
const mockState = {
|
|
51
|
+
started: true,
|
|
52
|
+
ready: true,
|
|
53
|
+
touched_at: new Date()
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
57
|
+
|
|
58
|
+
const response = await startupCheck.handler();
|
|
59
|
+
|
|
60
|
+
expect(response.status).toEqual(200);
|
|
61
|
+
expect(response.data).toEqual(mockState);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('returns 400 when started is false', async () => {
|
|
65
|
+
const mockState = {
|
|
66
|
+
started: false,
|
|
67
|
+
ready: false,
|
|
68
|
+
touched_at: new Date()
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
72
|
+
|
|
73
|
+
const response = await startupCheck.handler();
|
|
74
|
+
|
|
75
|
+
expect(response.status).toBe(400);
|
|
76
|
+
expect(response.data).toEqual(mockState);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('livenessCheck', () => {
|
|
81
|
+
it('has the correct route definitions', () => {
|
|
82
|
+
expect(livenessCheck.path).toBe('/probes/liveness');
|
|
83
|
+
expect(livenessCheck.method).toBe('GET');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('returns 200 when last touch was less than 10 seconds ago', async () => {
|
|
87
|
+
const mockState = {
|
|
88
|
+
started: true,
|
|
89
|
+
ready: true,
|
|
90
|
+
touched_at: new Date(Date.now() - 9000) // 11 seconds ago
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
94
|
+
|
|
95
|
+
const response = await livenessCheck.handler();
|
|
96
|
+
|
|
97
|
+
expect(response.status).toBe(200);
|
|
98
|
+
expect(response.data).toEqual(mockState);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('returns 400 when last touch was more than 10 seconds ago', async () => {
|
|
102
|
+
const mockState = {
|
|
103
|
+
started: true,
|
|
104
|
+
ready: true,
|
|
105
|
+
touched_at: new Date(Date.now() - 11000)
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
109
|
+
|
|
110
|
+
const response = await livenessCheck.handler();
|
|
111
|
+
|
|
112
|
+
expect(response.status).toBe(400);
|
|
113
|
+
expect(response.data).toEqual(mockState);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('readinessCheck', () => {
|
|
118
|
+
it('has the correct route definitions', () => {
|
|
119
|
+
expect(readinessCheck.path).toBe('/probes/readiness');
|
|
120
|
+
expect(readinessCheck.method).toBe('GET');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('returns 200 when ready is true', async () => {
|
|
124
|
+
const mockState = {
|
|
125
|
+
started: true,
|
|
126
|
+
ready: true,
|
|
127
|
+
touched_at: new Date()
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
131
|
+
|
|
132
|
+
const response = await readinessCheck.handler();
|
|
133
|
+
|
|
134
|
+
expect(response.status).toBe(200);
|
|
135
|
+
expect(response.data).toEqual(mockState);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('returns 400 when ready is false', async () => {
|
|
139
|
+
const mockState = {
|
|
140
|
+
started: true,
|
|
141
|
+
ready: false,
|
|
142
|
+
touched_at: new Date()
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
146
|
+
|
|
147
|
+
const response = await readinessCheck.handler();
|
|
148
|
+
|
|
149
|
+
expect(response.status).toBe(400);
|
|
150
|
+
expect(response.data).toEqual(mockState);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|
package/test/src/sync.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SaveOperationTag } from '@/storage/storage-index.js';
|
|
2
2
|
import { RequestTracker } from '@/sync/RequestTracker.js';
|
|
3
|
-
import { streamResponse } from '@/sync/sync.js';
|
|
3
|
+
import { streamResponse, SyncStreamParameters } from '@/sync/sync.js';
|
|
4
4
|
import { StreamingSyncLine } from '@/util/protocol-types.js';
|
|
5
5
|
import { JSONBig } from '@powersync/service-jsonbig';
|
|
6
6
|
import { RequestParameters } from '@powersync/service-sync-rules';
|
|
@@ -381,6 +381,67 @@ function defineTests(factory: StorageFactory) {
|
|
|
381
381
|
})
|
|
382
382
|
});
|
|
383
383
|
});
|
|
384
|
+
|
|
385
|
+
test('write checkpoint', async () => {
|
|
386
|
+
const f = await factory();
|
|
387
|
+
|
|
388
|
+
const syncRules = await f.updateSyncRules({
|
|
389
|
+
content: BASIC_SYNC_RULES
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
const storage = f.getInstance(syncRules);
|
|
393
|
+
await storage.autoActivate();
|
|
394
|
+
|
|
395
|
+
await storage.startBatch(BATCH_OPTIONS, async (batch) => {
|
|
396
|
+
// <= the managed write checkpoint LSN below
|
|
397
|
+
await batch.commit('0/1');
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
const checkpoint = await storage.createManagedWriteCheckpoint({
|
|
401
|
+
user_id: 'test',
|
|
402
|
+
heads: { '1': '1/0' }
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
const params: SyncStreamParameters = {
|
|
406
|
+
storage: f,
|
|
407
|
+
params: {
|
|
408
|
+
buckets: [],
|
|
409
|
+
include_checksum: true,
|
|
410
|
+
raw_data: true
|
|
411
|
+
},
|
|
412
|
+
parseOptions: PARSE_OPTIONS,
|
|
413
|
+
tracker,
|
|
414
|
+
syncParams: new RequestParameters({ sub: 'test' }, {}),
|
|
415
|
+
token: { sub: 'test', exp: Date.now() / 1000 + 10 } as any
|
|
416
|
+
};
|
|
417
|
+
const stream1 = streamResponse(params);
|
|
418
|
+
const lines1 = await consumeCheckpointLines(stream1);
|
|
419
|
+
|
|
420
|
+
// If write checkpoints are not correctly filtered, this may already
|
|
421
|
+
// contain the write checkpoint.
|
|
422
|
+
expect(lines1[0]).toMatchObject({
|
|
423
|
+
checkpoint: expect.objectContaining({
|
|
424
|
+
last_op_id: '0',
|
|
425
|
+
write_checkpoint: undefined
|
|
426
|
+
})
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
await storage.startBatch(BATCH_OPTIONS, async (batch) => {
|
|
430
|
+
// must be >= the managed write checkpoint LSN
|
|
431
|
+
await batch.commit('1/0');
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
// At this point the LSN has advanced, so the write checkpoint should be
|
|
435
|
+
// included in the next checkpoint message.
|
|
436
|
+
const stream2 = streamResponse(params);
|
|
437
|
+
const lines2 = await consumeCheckpointLines(stream2);
|
|
438
|
+
expect(lines2[0]).toMatchObject({
|
|
439
|
+
checkpoint: expect.objectContaining({
|
|
440
|
+
last_op_id: '0',
|
|
441
|
+
write_checkpoint: `${checkpoint}`
|
|
442
|
+
})
|
|
443
|
+
});
|
|
444
|
+
});
|
|
384
445
|
}
|
|
385
446
|
|
|
386
447
|
/**
|
package/test/src/util.ts
CHANGED
|
@@ -51,7 +51,8 @@ export const PARSE_OPTIONS: ParseSyncRulesOptions = {
|
|
|
51
51
|
|
|
52
52
|
export const BATCH_OPTIONS: StartBatchOptions = {
|
|
53
53
|
...PARSE_OPTIONS,
|
|
54
|
-
zeroLSN: ZERO_LSN
|
|
54
|
+
zeroLSN: ZERO_LSN,
|
|
55
|
+
storeCurrentData: true
|
|
55
56
|
};
|
|
56
57
|
|
|
57
58
|
export function testRules(content: string): PersistedSyncRulesContent {
|