@powersync/service-core 1.9.0 → 1.10.0
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 +14 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/routes/configure-fastify.d.ts +1 -1
- package/dist/routes/endpoints/probes.d.ts +2 -2
- package/dist/routes/endpoints/probes.js +16 -2
- package/dist/routes/endpoints/probes.js.map +1 -1
- package/dist/storage/SyncRulesBucketStorage.d.ts +16 -5
- package/dist/storage/SyncRulesBucketStorage.js +2 -2
- package/dist/storage/SyncRulesBucketStorage.js.map +1 -1
- package/dist/storage/WriteCheckpointAPI.d.ts +24 -2
- package/dist/storage/WriteCheckpointAPI.js.map +1 -1
- package/dist/storage/bson.d.ts +4 -3
- package/dist/storage/bson.js +6 -10
- package/dist/storage/bson.js.map +1 -1
- package/dist/{sync → streams}/BroadcastIterable.js +2 -2
- package/dist/streams/BroadcastIterable.js.map +1 -0
- package/dist/streams/Demultiplexer.d.ts +52 -0
- package/dist/streams/Demultiplexer.js +128 -0
- package/dist/streams/Demultiplexer.js.map +1 -0
- package/dist/{sync → streams}/LastValueSink.d.ts +2 -2
- package/dist/{sync → streams}/LastValueSink.js +2 -2
- package/dist/streams/LastValueSink.js.map +1 -0
- package/dist/{sync → streams}/merge.js +1 -1
- package/dist/streams/merge.js.map +1 -0
- package/dist/streams/streams-index.d.ts +4 -0
- package/dist/streams/streams-index.js +5 -0
- package/dist/streams/streams-index.js.map +1 -0
- package/dist/sync/BucketChecksumState.d.ts +7 -2
- package/dist/sync/BucketChecksumState.js +61 -40
- package/dist/sync/BucketChecksumState.js.map +1 -1
- package/dist/sync/sync-index.d.ts +0 -3
- package/dist/sync/sync-index.js +0 -3
- package/dist/sync/sync-index.js.map +1 -1
- package/dist/sync/sync.js +2 -2
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/util.d.ts +10 -1
- package/dist/sync/util.js +30 -0
- package/dist/sync/util.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +3 -0
- package/src/routes/endpoints/probes.ts +18 -2
- package/src/storage/SyncRulesBucketStorage.ts +18 -7
- package/src/storage/WriteCheckpointAPI.ts +28 -2
- package/src/storage/bson.ts +10 -12
- package/src/{sync → streams}/BroadcastIterable.ts +2 -2
- package/src/streams/Demultiplexer.ts +165 -0
- package/src/{sync → streams}/LastValueSink.ts +2 -2
- package/src/{sync → streams}/merge.ts +1 -1
- package/src/streams/streams-index.ts +4 -0
- package/src/sync/BucketChecksumState.ts +71 -55
- package/src/sync/sync-index.ts +0 -3
- package/src/sync/sync.ts +2 -2
- package/src/sync/util.ts +34 -1
- package/test/src/broadcast_iterable.test.ts +8 -8
- package/test/src/demultiplexer.test.ts +205 -0
- package/test/src/merge_iterable.test.ts +1 -1
- package/test/src/routes/probes.integration.test.ts +5 -5
- package/test/src/routes/probes.test.ts +5 -4
- package/test/src/sync/BucketChecksumState.test.ts +14 -13
- package/test/src/util.test.ts +48 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/sync/BroadcastIterable.js.map +0 -1
- package/dist/sync/LastValueSink.js.map +0 -1
- package/dist/sync/merge.js.map +0 -1
- /package/dist/{sync → streams}/BroadcastIterable.d.ts +0 -0
- /package/dist/{sync → streams}/merge.d.ts +0 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// Vitest Unit Tests
|
|
2
|
+
import { Demultiplexer, DemultiplexerSource, DemultiplexerSourceFactory, DemultiplexerValue } from '@/index.js';
|
|
3
|
+
import { delayEach } from 'ix/asynciterable/operators/delayeach.js';
|
|
4
|
+
import { take } from 'ix/asynciterable/operators/take.js';
|
|
5
|
+
import { toArray } from 'ix/asynciterable/toarray.js';
|
|
6
|
+
import * as timers from 'node:timers/promises';
|
|
7
|
+
import { describe, expect, it } from 'vitest';
|
|
8
|
+
|
|
9
|
+
describe('Demultiplexer', () => {
|
|
10
|
+
it('should start subscription lazily and provide first value', async () => {
|
|
11
|
+
const mockSource: DemultiplexerSourceFactory<string> = (signal: AbortSignal) => {
|
|
12
|
+
const iterator = (async function* (): AsyncIterable<DemultiplexerValue<string>> {})();
|
|
13
|
+
return {
|
|
14
|
+
iterator,
|
|
15
|
+
getFirstValue: async (key: string) => `first-${key}`
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const demux = new Demultiplexer(mockSource);
|
|
20
|
+
const signal = new AbortController().signal;
|
|
21
|
+
|
|
22
|
+
const iter = demux.subscribe('user1', signal)[Symbol.asyncIterator]();
|
|
23
|
+
const result = await iter.next();
|
|
24
|
+
expect(result.value).toBe('first-user1');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should handle multiple subscribers to the same key', async () => {
|
|
28
|
+
const iter = (async function* () {
|
|
29
|
+
yield { key: 'user1', value: 'value1' };
|
|
30
|
+
yield { key: 'user1', value: 'value2' };
|
|
31
|
+
})();
|
|
32
|
+
const source: DemultiplexerSource<string> = {
|
|
33
|
+
iterator: iter,
|
|
34
|
+
getFirstValue: async (key: string) => `first-${key}`
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const demux = new Demultiplexer(() => source);
|
|
38
|
+
const signal = new AbortController().signal;
|
|
39
|
+
|
|
40
|
+
const iter1 = demux.subscribe('user1', signal)[Symbol.asyncIterator]();
|
|
41
|
+
const iter2 = demux.subscribe('user1', signal)[Symbol.asyncIterator]();
|
|
42
|
+
|
|
43
|
+
// Due to only keeping the last value, some values are skipped
|
|
44
|
+
expect(await iter1.next()).toEqual({ value: 'first-user1', done: false });
|
|
45
|
+
expect(await iter1.next()).toEqual({ value: 'value1', done: false });
|
|
46
|
+
expect(await iter1.next()).toEqual({ value: undefined, done: true });
|
|
47
|
+
|
|
48
|
+
expect(await iter2.next()).toEqual({ value: 'first-user1', done: false });
|
|
49
|
+
expect(await iter2.next()).toEqual({ value: undefined, done: true });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should handle multiple subscribers to the same key (2)', async () => {
|
|
53
|
+
const p1 = Promise.withResolvers<void>();
|
|
54
|
+
const p2 = Promise.withResolvers<void>();
|
|
55
|
+
const p3 = Promise.withResolvers<void>();
|
|
56
|
+
|
|
57
|
+
const iter = (async function* () {
|
|
58
|
+
await p1.promise;
|
|
59
|
+
yield { key: 'user1', value: 'value1' };
|
|
60
|
+
await p2.promise;
|
|
61
|
+
yield { key: 'user1', value: 'value2' };
|
|
62
|
+
await p3.promise;
|
|
63
|
+
})();
|
|
64
|
+
|
|
65
|
+
const source: DemultiplexerSource<string> = {
|
|
66
|
+
iterator: iter,
|
|
67
|
+
getFirstValue: async (key: string) => `first-${key}`
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const demux = new Demultiplexer(() => source);
|
|
71
|
+
const signal = new AbortController().signal;
|
|
72
|
+
|
|
73
|
+
const iter1 = demux.subscribe('user1', signal)[Symbol.asyncIterator]();
|
|
74
|
+
const iter2 = demux.subscribe('user1', signal)[Symbol.asyncIterator]();
|
|
75
|
+
|
|
76
|
+
// Due to only keeping the last value, some values are skilled
|
|
77
|
+
expect(await iter1.next()).toEqual({ value: 'first-user1', done: false });
|
|
78
|
+
expect(await iter2.next()).toEqual({ value: 'first-user1', done: false });
|
|
79
|
+
p1.resolve();
|
|
80
|
+
|
|
81
|
+
expect(await iter1.next()).toEqual({ value: 'value1', done: false });
|
|
82
|
+
expect(await iter2.next()).toEqual({ value: 'value1', done: false });
|
|
83
|
+
p2.resolve();
|
|
84
|
+
|
|
85
|
+
expect(await iter1.next()).toEqual({ value: 'value2', done: false });
|
|
86
|
+
p3.resolve();
|
|
87
|
+
|
|
88
|
+
expect(await iter1.next()).toEqual({ value: undefined, done: true });
|
|
89
|
+
expect(await iter2.next()).toEqual({ value: undefined, done: true });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should handle multiple subscribers to different keys', async () => {
|
|
93
|
+
const p1 = Promise.withResolvers<void>();
|
|
94
|
+
const p2 = Promise.withResolvers<void>();
|
|
95
|
+
const p3 = Promise.withResolvers<void>();
|
|
96
|
+
|
|
97
|
+
const iter = (async function* () {
|
|
98
|
+
await p1.promise;
|
|
99
|
+
yield { key: 'user1', value: 'value1' };
|
|
100
|
+
await p2.promise;
|
|
101
|
+
yield { key: 'user2', value: 'value2' };
|
|
102
|
+
await p3.promise;
|
|
103
|
+
})();
|
|
104
|
+
|
|
105
|
+
const source: DemultiplexerSource<string> = {
|
|
106
|
+
iterator: iter,
|
|
107
|
+
getFirstValue: async (key: string) => `first-${key}`
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const demux = new Demultiplexer(() => source);
|
|
111
|
+
const signal = new AbortController().signal;
|
|
112
|
+
|
|
113
|
+
const iter1 = demux.subscribe('user1', signal)[Symbol.asyncIterator]();
|
|
114
|
+
const iter2 = demux.subscribe('user2', signal)[Symbol.asyncIterator]();
|
|
115
|
+
|
|
116
|
+
// Due to only keeping the last value, some values are skilled
|
|
117
|
+
expect(await iter1.next()).toEqual({ value: 'first-user1', done: false });
|
|
118
|
+
expect(await iter2.next()).toEqual({ value: 'first-user2', done: false });
|
|
119
|
+
p1.resolve();
|
|
120
|
+
|
|
121
|
+
expect(await iter1.next()).toEqual({ value: 'value1', done: false });
|
|
122
|
+
p2.resolve();
|
|
123
|
+
|
|
124
|
+
expect(await iter2.next()).toEqual({ value: 'value2', done: false });
|
|
125
|
+
p3.resolve();
|
|
126
|
+
|
|
127
|
+
expect(await iter1.next()).toEqual({ value: undefined, done: true });
|
|
128
|
+
expect(await iter2.next()).toEqual({ value: undefined, done: true });
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should abort', async () => {
|
|
132
|
+
const iter = (async function* () {
|
|
133
|
+
yield { key: 'user1', value: 'value1' };
|
|
134
|
+
yield { key: 'user1', value: 'value2' };
|
|
135
|
+
})();
|
|
136
|
+
|
|
137
|
+
const source: DemultiplexerSource<string> = {
|
|
138
|
+
iterator: iter,
|
|
139
|
+
getFirstValue: async (key: string) => `first-${key}`
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const demux = new Demultiplexer(() => source);
|
|
143
|
+
const controller = new AbortController();
|
|
144
|
+
|
|
145
|
+
const iter1 = demux.subscribe('user1', controller.signal)[Symbol.asyncIterator]();
|
|
146
|
+
|
|
147
|
+
expect(await iter1.next()).toEqual({ value: 'first-user1', done: false });
|
|
148
|
+
controller.abort();
|
|
149
|
+
|
|
150
|
+
await expect(iter1.next()).rejects.toThrow('The operation has been aborted');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should handle errors on multiple subscribers', async () => {
|
|
154
|
+
let sourceIndex = 0;
|
|
155
|
+
const sourceFn = async function* (signal: AbortSignal): AsyncIterable<DemultiplexerValue<number>> {
|
|
156
|
+
// Test value out by 1000 means it may have used the wrong iteration of the source
|
|
157
|
+
const base = (sourceIndex += 1000);
|
|
158
|
+
const abortedPromise = new Promise((resolve) => {
|
|
159
|
+
signal.addEventListener('abort', resolve, { once: true });
|
|
160
|
+
});
|
|
161
|
+
for (let i = 0; !signal.aborted; i++) {
|
|
162
|
+
if (base + i == 1005) {
|
|
163
|
+
throw new Error('simulated failure');
|
|
164
|
+
}
|
|
165
|
+
yield { key: 'u1', value: base + i };
|
|
166
|
+
await Promise.race([abortedPromise, timers.setTimeout(1)]);
|
|
167
|
+
}
|
|
168
|
+
// Test value out by 100 means this wasn't reached
|
|
169
|
+
sourceIndex += 100;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const sourceFactory: DemultiplexerSourceFactory<number> = (signal) => {
|
|
173
|
+
const source: DemultiplexerSource<number> = {
|
|
174
|
+
iterator: sourceFn(signal),
|
|
175
|
+
getFirstValue: async (key: string) => -1
|
|
176
|
+
};
|
|
177
|
+
return source;
|
|
178
|
+
};
|
|
179
|
+
const demux = new Demultiplexer(sourceFactory);
|
|
180
|
+
|
|
181
|
+
const controller = new AbortController();
|
|
182
|
+
|
|
183
|
+
const delayed1 = delayEach(9)(demux.subscribe('u1', controller.signal));
|
|
184
|
+
const delayed2 = delayEach(10)(demux.subscribe('u1', controller.signal));
|
|
185
|
+
expect(demux.active).toBe(false);
|
|
186
|
+
const results1Promise = toArray(take(5)(delayed1)) as Promise<number[]>;
|
|
187
|
+
const results2Promise = toArray(take(5)(delayed2)) as Promise<number[]>;
|
|
188
|
+
|
|
189
|
+
const [r1, r2] = await Promise.allSettled([results1Promise, results2Promise]);
|
|
190
|
+
|
|
191
|
+
expect(r1).toEqual({ status: 'rejected', reason: new Error('simulated failure') });
|
|
192
|
+
expect(r2).toEqual({ status: 'rejected', reason: new Error('simulated failure') });
|
|
193
|
+
|
|
194
|
+
expect(demux.active).toBe(false);
|
|
195
|
+
|
|
196
|
+
// This starts a new source
|
|
197
|
+
const delayed3 = delayEach(10)(demux.subscribe('u1', controller.signal));
|
|
198
|
+
const results3 = await toArray(take(6)(delayed3));
|
|
199
|
+
expect(results3.length).toEqual(6);
|
|
200
|
+
expect(results3[0]).toEqual(-1); // Initial value
|
|
201
|
+
// There should be approximately 10ms between each value, but we allow for some slack
|
|
202
|
+
expect(results3[5]).toBeGreaterThan(2005);
|
|
203
|
+
expect(results3[5]).toBeLessThan(2200);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import Fastify, { FastifyInstance } from 'fastify';
|
|
3
1
|
import { container } from '@powersync/lib-services-framework';
|
|
4
|
-
import
|
|
5
|
-
import
|
|
2
|
+
import Fastify, { FastifyInstance } from 'fastify';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
4
|
import { configureFastifyServer } from '../../../src/index.js';
|
|
5
|
+
import * as auth from '../../../src/routes/auth.js';
|
|
7
6
|
import { ProbeRoutes } from '../../../src/routes/endpoints/probes.js';
|
|
7
|
+
import * as system from '../../../src/system/system-index.js';
|
|
8
8
|
|
|
9
9
|
vi.mock('@powersync/lib-services-framework', async () => {
|
|
10
10
|
const actual = (await vi.importActual('@powersync/lib-services-framework')) as any;
|
|
@@ -25,7 +25,7 @@ describe('Probe Routes Integration', () => {
|
|
|
25
25
|
|
|
26
26
|
beforeEach(async () => {
|
|
27
27
|
app = Fastify();
|
|
28
|
-
mockSystem = { routerEngine: {} } as system.ServiceContext;
|
|
28
|
+
mockSystem = { routerEngine: {}, replicationEngine: {} } as system.ServiceContext;
|
|
29
29
|
await configureFastifyServer(app, { service_context: mockSystem });
|
|
30
30
|
await app.ready();
|
|
31
31
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
1
|
import { container } from '@powersync/lib-services-framework';
|
|
3
|
-
import {
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { livenessCheck, readinessCheck, startupCheck } from '../../../src/routes/endpoints/probes.js';
|
|
4
4
|
|
|
5
5
|
// Mock the container
|
|
6
6
|
vi.mock('@powersync/lib-services-framework', () => ({
|
|
@@ -83,6 +83,7 @@ describe('Probe Routes', () => {
|
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
describe('livenessCheck', () => {
|
|
86
|
+
const mockedContext = { context: { service_context: { replicationEngine: {} } } } as any;
|
|
86
87
|
it('has the correct route definitions', () => {
|
|
87
88
|
expect(livenessCheck.path).toBe('/probes/liveness');
|
|
88
89
|
expect(livenessCheck.method).toBe('GET');
|
|
@@ -97,7 +98,7 @@ describe('Probe Routes', () => {
|
|
|
97
98
|
|
|
98
99
|
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
99
100
|
|
|
100
|
-
const response = await livenessCheck.handler();
|
|
101
|
+
const response = await livenessCheck.handler(mockedContext);
|
|
101
102
|
|
|
102
103
|
expect(response.status).toBe(200);
|
|
103
104
|
expect(response.data).toEqual(mockState);
|
|
@@ -112,7 +113,7 @@ describe('Probe Routes', () => {
|
|
|
112
113
|
|
|
113
114
|
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
114
115
|
|
|
115
|
-
const response = await livenessCheck.handler();
|
|
116
|
+
const response = await livenessCheck.handler(mockedContext);
|
|
116
117
|
|
|
117
118
|
expect(response.status).toBe(400);
|
|
118
119
|
expect(response.data).toEqual(mockState);
|
|
@@ -8,7 +8,8 @@ import {
|
|
|
8
8
|
SyncContext,
|
|
9
9
|
WatchFilterEvent
|
|
10
10
|
} from '@/index.js';
|
|
11
|
-
import {
|
|
11
|
+
import { JSONBig } from '@powersync/service-jsonbig';
|
|
12
|
+
import { RequestParameters, SqliteJsonRow, ParameterLookup, SqlSyncRules } from '@powersync/service-sync-rules';
|
|
12
13
|
import { describe, expect, test } from 'vitest';
|
|
13
14
|
|
|
14
15
|
describe('BucketChecksumState', () => {
|
|
@@ -97,9 +98,9 @@ bucket_definitions:
|
|
|
97
98
|
base: { checkpoint: 2n, lsn: '2' },
|
|
98
99
|
writeCheckpoint: null,
|
|
99
100
|
update: {
|
|
100
|
-
updatedDataBuckets: ['global[]'],
|
|
101
|
+
updatedDataBuckets: new Set(['global[]']),
|
|
101
102
|
invalidateDataBuckets: false,
|
|
102
|
-
|
|
103
|
+
updatedParameterLookups: new Set(),
|
|
103
104
|
invalidateParameterBuckets: false
|
|
104
105
|
}
|
|
105
106
|
}))!;
|
|
@@ -200,7 +201,7 @@ bucket_definitions:
|
|
|
200
201
|
writeCheckpoint: null,
|
|
201
202
|
update: {
|
|
202
203
|
...CHECKPOINT_INVALIDATE_ALL,
|
|
203
|
-
updatedDataBuckets: ['global[1]', 'global[2]'],
|
|
204
|
+
updatedDataBuckets: new Set(['global[1]', 'global[2]']),
|
|
204
205
|
invalidateDataBuckets: false
|
|
205
206
|
}
|
|
206
207
|
}))!;
|
|
@@ -293,7 +294,7 @@ bucket_definitions:
|
|
|
293
294
|
// Invalidate the state for global[1] - will only re-check the single bucket.
|
|
294
295
|
// This is essentially inconsistent state, but is the simplest way to test that
|
|
295
296
|
// the filter is working.
|
|
296
|
-
updatedDataBuckets: ['global[1]'],
|
|
297
|
+
updatedDataBuckets: new Set(['global[1]']),
|
|
297
298
|
invalidateDataBuckets: false
|
|
298
299
|
}
|
|
299
300
|
}))!;
|
|
@@ -420,7 +421,7 @@ bucket_definitions:
|
|
|
420
421
|
update: {
|
|
421
422
|
...CHECKPOINT_INVALIDATE_ALL,
|
|
422
423
|
invalidateDataBuckets: false,
|
|
423
|
-
updatedDataBuckets: ['global[1]']
|
|
424
|
+
updatedDataBuckets: new Set(['global[1]'])
|
|
424
425
|
}
|
|
425
426
|
}))!;
|
|
426
427
|
expect(line2.checkpointLine).toEqual({
|
|
@@ -474,10 +475,10 @@ bucket_definitions:
|
|
|
474
475
|
|
|
475
476
|
storage.getParameterSets = async (
|
|
476
477
|
checkpoint: InternalOpId,
|
|
477
|
-
lookups:
|
|
478
|
+
lookups: ParameterLookup[]
|
|
478
479
|
): Promise<SqliteJsonRow[]> => {
|
|
479
480
|
expect(checkpoint).toEqual(1n);
|
|
480
|
-
expect(lookups).toEqual([
|
|
481
|
+
expect(lookups).toEqual([ParameterLookup.normalized('by_project', '1', ['u1'])]);
|
|
481
482
|
return [{ id: 1 }, { id: 2 }];
|
|
482
483
|
};
|
|
483
484
|
|
|
@@ -519,10 +520,10 @@ bucket_definitions:
|
|
|
519
520
|
|
|
520
521
|
storage.getParameterSets = async (
|
|
521
522
|
checkpoint: InternalOpId,
|
|
522
|
-
lookups:
|
|
523
|
+
lookups: ParameterLookup[]
|
|
523
524
|
): Promise<SqliteJsonRow[]> => {
|
|
524
525
|
expect(checkpoint).toEqual(2n);
|
|
525
|
-
expect(lookups).toEqual([
|
|
526
|
+
expect(lookups).toEqual([ParameterLookup.normalized('by_project', '1', ['u1'])]);
|
|
526
527
|
return [{ id: 1 }, { id: 2 }, { id: 3 }];
|
|
527
528
|
};
|
|
528
529
|
|
|
@@ -532,8 +533,8 @@ bucket_definitions:
|
|
|
532
533
|
writeCheckpoint: null,
|
|
533
534
|
update: {
|
|
534
535
|
invalidateDataBuckets: false,
|
|
535
|
-
updatedDataBuckets:
|
|
536
|
-
|
|
536
|
+
updatedDataBuckets: new Set(),
|
|
537
|
+
updatedParameterLookups: new Set([JSONBig.stringify(['by_project', '1', 'u1'])]),
|
|
537
538
|
invalidateParameterBuckets: false
|
|
538
539
|
}
|
|
539
540
|
}))!;
|
|
@@ -580,7 +581,7 @@ class MockBucketChecksumStateStorage implements BucketChecksumStateStorage {
|
|
|
580
581
|
);
|
|
581
582
|
}
|
|
582
583
|
|
|
583
|
-
async getParameterSets(checkpoint: InternalOpId, lookups:
|
|
584
|
+
async getParameterSets(checkpoint: InternalOpId, lookups: ParameterLookup[]): Promise<SqliteJsonRow[]> {
|
|
584
585
|
throw new Error('Method not implemented.');
|
|
585
586
|
}
|
|
586
587
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { getIntersection, hasIntersection } from '@/index.js';
|
|
2
|
+
import { describe, expect, test } from 'vitest';
|
|
3
|
+
|
|
4
|
+
describe('utils', () => {
|
|
5
|
+
function testInstersection(a: Set<any>, b: Set<any>, expected: boolean) {
|
|
6
|
+
expect(hasIntersection(a, b)).toBe(expected);
|
|
7
|
+
expect(hasIntersection(b, a)).toBe(expected);
|
|
8
|
+
const mapA = new Map([...a].map((v) => [v, 1]));
|
|
9
|
+
const mapB = new Map([...b].map((v) => [v, 1]));
|
|
10
|
+
expect(hasIntersection(mapA, b)).toBe(expected);
|
|
11
|
+
expect(hasIntersection(mapB, a)).toBe(expected);
|
|
12
|
+
expect(hasIntersection(mapA, mapB)).toBe(expected);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
test('hasIntersection', async () => {
|
|
16
|
+
testInstersection(new Set(['a']), new Set(['a']), true);
|
|
17
|
+
testInstersection(new Set(['a', 'b', 'c']), new Set(['a', 'b', 'c']), true);
|
|
18
|
+
testInstersection(new Set(['a', 'b', 'c']), new Set(['d', 'e']), false);
|
|
19
|
+
testInstersection(new Set(['a', 'b', 'c']), new Set(['d', 'c', 'e']), true);
|
|
20
|
+
testInstersection(new Set(['a', 'b', 'c']), new Set(['c', 'e']), true);
|
|
21
|
+
testInstersection(new Set(['a', 'b', 'c', 2]), new Set([1, 2, 3]), true);
|
|
22
|
+
testInstersection(new Set(['a', 'b', 'c', 4]), new Set([1, 2, 3]), false);
|
|
23
|
+
testInstersection(new Set([]), new Set([1, 2, 3]), false);
|
|
24
|
+
testInstersection(new Set([]), new Set([]), false);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
function testGetIntersection(a: Set<any>, b: Set<any>, expected: any[]) {
|
|
28
|
+
expect([...getIntersection(a, b)]).toEqual(expected);
|
|
29
|
+
expect([...getIntersection(b, a)]).toEqual(expected);
|
|
30
|
+
const mapA = new Map([...a].map((v) => [v, 1]));
|
|
31
|
+
const mapB = new Map([...b].map((v) => [v, 1]));
|
|
32
|
+
expect([...getIntersection(mapA, b)]).toEqual(expected);
|
|
33
|
+
expect([...getIntersection(mapB, a)]).toEqual(expected);
|
|
34
|
+
expect([...getIntersection(mapA, mapB)]).toEqual(expected);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
test('getIntersection', async () => {
|
|
38
|
+
testGetIntersection(new Set(['a']), new Set(['a']), ['a']);
|
|
39
|
+
testGetIntersection(new Set(['a', 'b', 'c']), new Set(['a', 'b', 'c']), ['a', 'b', 'c']);
|
|
40
|
+
testGetIntersection(new Set(['a', 'b', 'c']), new Set(['d', 'e']), []);
|
|
41
|
+
testGetIntersection(new Set(['a', 'b', 'c']), new Set(['d', 'c', 'e']), ['c']);
|
|
42
|
+
testGetIntersection(new Set(['a', 'b', 'c']), new Set(['c', 'e']), ['c']);
|
|
43
|
+
testGetIntersection(new Set(['a', 'b', 'c', 2]), new Set([1, 2, 3]), [2]);
|
|
44
|
+
testGetIntersection(new Set(['a', 'b', 'c', 4]), new Set([1, 2, 3]), []);
|
|
45
|
+
testGetIntersection(new Set([]), new Set([1, 2, 3]), []);
|
|
46
|
+
testGetIntersection(new Set([]), new Set([]), []);
|
|
47
|
+
});
|
|
48
|
+
});
|