@powersync/service-core-tests 0.0.0-dev-20260225160713 → 0.0.0-dev-20260511080634

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +95 -3
  2. package/dist/test-utils/MetricsHelper.js +1 -1
  3. package/dist/test-utils/MetricsHelper.js.map +1 -1
  4. package/dist/test-utils/StorageDataHelpers.d.ts +8 -0
  5. package/dist/test-utils/StorageDataHelpers.js +33 -0
  6. package/dist/test-utils/StorageDataHelpers.js.map +1 -0
  7. package/dist/test-utils/general-utils.d.ts +22 -3
  8. package/dist/test-utils/general-utils.js +56 -3
  9. package/dist/test-utils/general-utils.js.map +1 -1
  10. package/dist/test-utils/stream_utils.js +2 -2
  11. package/dist/test-utils/stream_utils.js.map +1 -1
  12. package/dist/test-utils/test-utils-index.d.ts +1 -0
  13. package/dist/test-utils/test-utils-index.js +1 -0
  14. package/dist/test-utils/test-utils-index.js.map +1 -1
  15. package/dist/tests/register-compacting-tests.d.ts +1 -1
  16. package/dist/tests/register-compacting-tests.js +514 -587
  17. package/dist/tests/register-compacting-tests.js.map +1 -1
  18. package/dist/tests/register-data-storage-checkpoint-tests.d.ts +1 -1
  19. package/dist/tests/register-data-storage-checkpoint-tests.js +200 -301
  20. package/dist/tests/register-data-storage-checkpoint-tests.js.map +1 -1
  21. package/dist/tests/register-data-storage-data-tests.d.ts +2 -2
  22. package/dist/tests/register-data-storage-data-tests.js +1220 -1041
  23. package/dist/tests/register-data-storage-data-tests.js.map +1 -1
  24. package/dist/tests/register-data-storage-parameter-tests.d.ts +1 -1
  25. package/dist/tests/register-data-storage-parameter-tests.js +681 -632
  26. package/dist/tests/register-data-storage-parameter-tests.js.map +1 -1
  27. package/dist/tests/register-migration-tests.js +63 -139
  28. package/dist/tests/register-migration-tests.js.map +1 -1
  29. package/dist/tests/register-parameter-compacting-tests.d.ts +1 -1
  30. package/dist/tests/register-parameter-compacting-tests.js +121 -201
  31. package/dist/tests/register-parameter-compacting-tests.js.map +1 -1
  32. package/dist/tests/register-sync-tests.d.ts +2 -1
  33. package/dist/tests/register-sync-tests.js +974 -1136
  34. package/dist/tests/register-sync-tests.js.map +1 -1
  35. package/dist/tests/tests-index.d.ts +4 -4
  36. package/dist/tests/tests-index.js +4 -4
  37. package/dist/tests/tests-index.js.map +1 -1
  38. package/dist/tests/util.d.ts +5 -4
  39. package/dist/tests/util.js +27 -12
  40. package/dist/tests/util.js.map +1 -1
  41. package/package.json +6 -6
  42. package/src/test-utils/MetricsHelper.ts +1 -1
  43. package/src/test-utils/StorageDataHelpers.ts +44 -0
  44. package/src/test-utils/general-utils.ts +81 -4
  45. package/src/test-utils/stream_utils.ts +2 -2
  46. package/src/test-utils/test-utils-index.ts +1 -0
  47. package/src/tests/register-compacting-tests.ts +376 -322
  48. package/src/tests/register-data-storage-checkpoint-tests.ts +85 -53
  49. package/src/tests/register-data-storage-data-tests.ts +1076 -563
  50. package/src/tests/register-data-storage-parameter-tests.ts +631 -327
  51. package/src/tests/register-parameter-compacting-tests.ts +92 -96
  52. package/src/tests/register-sync-tests.ts +463 -379
  53. package/src/tests/tests-index.ts +4 -4
  54. package/src/tests/util.ts +46 -17
  55. package/tsconfig.tsbuildinfo +1 -1
@@ -1,60 +1,8 @@
1
- var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
- if (value !== null && value !== void 0) {
3
- if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
- var dispose, inner;
5
- if (async) {
6
- if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
- dispose = value[Symbol.asyncDispose];
8
- }
9
- if (dispose === void 0) {
10
- if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
- dispose = value[Symbol.dispose];
12
- if (async) inner = dispose;
13
- }
14
- if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
- if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
- env.stack.push({ value: value, dispose: dispose, async: async });
17
- }
18
- else if (async) {
19
- env.stack.push({ async: true });
20
- }
21
- return value;
22
- };
23
- var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
- return function (env) {
25
- function fail(e) {
26
- env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
- env.hasError = true;
28
- }
29
- var r, s = 0;
30
- function next() {
31
- while (r = env.stack.pop()) {
32
- try {
33
- if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
- if (r.dispose) {
35
- var result = r.dispose.call(r.value);
36
- if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
- }
38
- else s |= 1;
39
- }
40
- catch (e) {
41
- fail(e);
42
- }
43
- }
44
- if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
- if (env.hasError) throw env.error;
46
- }
47
- return next();
48
- };
49
- })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
- var e = new Error(message);
51
- return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
- });
53
- import { JwtPayload, storage, updateSyncRulesFromYaml } from '@powersync/service-core';
54
- import { RequestParameters, ScopedParameterLookup } from '@powersync/service-sync-rules';
1
+ import { CURRENT_STORAGE_VERSION, JwtPayload, storage, updateSyncRulesFromYaml } from '@powersync/service-core';
2
+ import { RequestParameters } from '@powersync/service-sync-rules';
55
3
  import { expect, test } from 'vitest';
56
4
  import * as test_utils from '../test-utils/test-utils-index.js';
57
- import { bucketRequest, TEST_TABLE } from './util.js';
5
+ import { bucketRequest } from '../test-utils/test-utils-index.js';
58
6
  /**
59
7
  * @example
60
8
  * ```TypeScript
@@ -65,130 +13,123 @@ import { bucketRequest, TEST_TABLE } from './util.js';
65
13
  *
66
14
  * ```
67
15
  */
68
- export function registerDataStorageParameterTests(generateStorageFactory) {
69
- const MYBUCKET_1 = { lookupName: 'mybucket', queryId: '1' };
16
+ export function registerDataStorageParameterTests(config) {
17
+ const generateStorageFactory = config.factory;
18
+ const storageVersion = config.storageVersion ?? CURRENT_STORAGE_VERSION;
70
19
  test('save and load parameters', async () => {
71
- const env_1 = { stack: [], error: void 0, hasError: false };
72
- try {
73
- const factory = __addDisposableResource(env_1, await generateStorageFactory(), true);
74
- const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
20
+ await using factory = await generateStorageFactory();
21
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
75
22
  bucket_definitions:
76
23
  mybucket:
77
24
  parameters:
78
25
  - SELECT group_id FROM test WHERE id1 = token_parameters.user_id OR id2 = token_parameters.user_id
79
26
  data: []
80
- `));
81
- const bucketStorage = factory.getInstance(syncRules);
82
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
83
- await batch.save({
84
- sourceTable: TEST_TABLE,
85
- tag: storage.SaveOperationTag.INSERT,
86
- after: {
87
- id: 't2',
88
- id1: 'user3',
89
- id2: 'user4',
90
- group_id: 'group2a'
91
- },
92
- afterReplicaId: test_utils.rid('t2')
93
- });
94
- await batch.save({
95
- sourceTable: TEST_TABLE,
96
- tag: storage.SaveOperationTag.INSERT,
97
- after: {
98
- id: 't1',
99
- id1: 'user1',
100
- id2: 'user2',
101
- group_id: 'group1a'
102
- },
103
- afterReplicaId: test_utils.rid('t1')
104
- });
105
- await batch.commit('1/1');
106
- });
107
- const checkpoint = await bucketStorage.getCheckpoint();
108
- const parameters = await checkpoint.getParameterSets([ScopedParameterLookup.direct(MYBUCKET_1, ['user1'])]);
109
- expect(parameters).toEqual([
110
- {
111
- group_id: 'group1a'
112
- }
113
- ]);
114
- }
115
- catch (e_1) {
116
- env_1.error = e_1;
117
- env_1.hasError = true;
118
- }
119
- finally {
120
- const result_1 = __disposeResources(env_1);
121
- if (result_1)
122
- await result_1;
123
- }
27
+ `, {
28
+ storageVersion
29
+ }));
30
+ const bucketStorage = factory.getInstance(syncRules);
31
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncRules();
32
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
33
+ const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
34
+ await writer.markAllSnapshotDone('1/1');
35
+ await writer.save({
36
+ sourceTable: testTable,
37
+ tag: storage.SaveOperationTag.INSERT,
38
+ after: {
39
+ id: 't2',
40
+ id1: 'user3',
41
+ id2: 'user4',
42
+ group_id: 'group2a'
43
+ },
44
+ afterReplicaId: test_utils.rid('t2')
45
+ });
46
+ await writer.save({
47
+ sourceTable: testTable,
48
+ tag: storage.SaveOperationTag.INSERT,
49
+ after: {
50
+ id: 't1',
51
+ id1: 'user1',
52
+ id2: 'user2',
53
+ group_id: 'group1a'
54
+ },
55
+ afterReplicaId: test_utils.rid('t1')
56
+ });
57
+ await writer.commit('1/1');
58
+ const checkpoint = await bucketStorage.getCheckpoint();
59
+ const parameters = new RequestParameters(new JwtPayload({ sub: 'user1' }), {});
60
+ const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
61
+ const buckets = await querier.queryDynamicBucketDescriptions({
62
+ async getParameterSets(lookups) {
63
+ expect(lookups.map((l) => l.indexKey)).toEqual([['user1']]);
64
+ const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
65
+ expect(parameter_sets).toEqual([{ group_id: 'group1a' }]);
66
+ return parameter_sets;
67
+ }
68
+ });
69
+ expect(buckets.map((b) => b.bucket)).toEqual([bucketRequest(syncRules, 'mybucket["group1a"]').bucket]);
124
70
  });
125
71
  test('it should use the latest version', async () => {
126
- const env_2 = { stack: [], error: void 0, hasError: false };
127
- try {
128
- const factory = __addDisposableResource(env_2, await generateStorageFactory(), true);
129
- const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
72
+ await using factory = await generateStorageFactory();
73
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
130
74
  bucket_definitions:
131
75
  mybucket:
132
76
  parameters:
133
77
  - SELECT group_id FROM test WHERE id = token_parameters.user_id
134
78
  data: []
135
- `));
136
- const bucketStorage = factory.getInstance(syncRules);
137
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
138
- await batch.save({
139
- sourceTable: TEST_TABLE,
140
- tag: storage.SaveOperationTag.INSERT,
141
- after: {
142
- id: 'user1',
143
- group_id: 'group1'
144
- },
145
- afterReplicaId: test_utils.rid('user1')
146
- });
147
- await batch.commit('1/1');
148
- });
149
- const checkpoint1 = await bucketStorage.getCheckpoint();
150
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
151
- await batch.save({
152
- sourceTable: TEST_TABLE,
153
- tag: storage.SaveOperationTag.INSERT,
154
- after: {
155
- id: 'user1',
156
- group_id: 'group2'
157
- },
158
- afterReplicaId: test_utils.rid('user1')
159
- });
160
- await batch.commit('1/2');
161
- });
162
- const checkpoint2 = await bucketStorage.getCheckpoint();
163
- const parameters = await checkpoint2.getParameterSets([ScopedParameterLookup.direct(MYBUCKET_1, ['user1'])]);
164
- expect(parameters).toEqual([
165
- {
166
- group_id: 'group2'
167
- }
168
- ]);
169
- // Use the checkpoint to get older data if relevant
170
- const parameters2 = await checkpoint1.getParameterSets([ScopedParameterLookup.direct(MYBUCKET_1, ['user1'])]);
171
- expect(parameters2).toEqual([
172
- {
173
- group_id: 'group1'
174
- }
175
- ]);
176
- }
177
- catch (e_2) {
178
- env_2.error = e_2;
179
- env_2.hasError = true;
180
- }
181
- finally {
182
- const result_2 = __disposeResources(env_2);
183
- if (result_2)
184
- await result_2;
185
- }
79
+ `, {
80
+ storageVersion
81
+ }));
82
+ const bucketStorage = factory.getInstance(syncRules);
83
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncRules();
84
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
85
+ const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
86
+ await writer.markAllSnapshotDone('1/1');
87
+ await writer.save({
88
+ sourceTable: testTable,
89
+ tag: storage.SaveOperationTag.INSERT,
90
+ after: {
91
+ id: 'user1',
92
+ group_id: 'group1'
93
+ },
94
+ afterReplicaId: test_utils.rid('user1')
95
+ });
96
+ await writer.commit('1/1');
97
+ const checkpoint1 = await bucketStorage.getCheckpoint();
98
+ await writer.save({
99
+ sourceTable: testTable,
100
+ tag: storage.SaveOperationTag.INSERT,
101
+ after: {
102
+ id: 'user1',
103
+ group_id: 'group2'
104
+ },
105
+ afterReplicaId: test_utils.rid('user1')
106
+ });
107
+ await writer.commit('1/2');
108
+ const checkpoint2 = await bucketStorage.getCheckpoint();
109
+ const parameters = new RequestParameters(new JwtPayload({ sub: 'user1' }), {});
110
+ const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
111
+ const buckets1 = await querier.queryDynamicBucketDescriptions({
112
+ async getParameterSets(lookups) {
113
+ expect(lookups.map((l) => l.indexKey)).toEqual([['user1']]);
114
+ const parameter_sets = await checkpoint1.getParameterSets(lookups, 1000);
115
+ expect(parameter_sets).toEqual([{ group_id: 'group1' }]);
116
+ return parameter_sets;
117
+ }
118
+ });
119
+ expect(buckets1.map((b) => b.bucket)).toEqual([bucketRequest(syncRules, 'mybucket["group1"]').bucket]);
120
+ const buckets2 = await querier.queryDynamicBucketDescriptions({
121
+ async getParameterSets(lookups) {
122
+ expect(lookups.map((l) => l.indexKey)).toEqual([['user1']]);
123
+ const parameter_sets = await checkpoint2.getParameterSets(lookups, 1000);
124
+ expect(parameter_sets).toEqual([{ group_id: 'group2' }]);
125
+ return parameter_sets;
126
+ }
127
+ });
128
+ expect(buckets2.map((b) => b.bucket)).toEqual([bucketRequest(syncRules, 'mybucket["group2"]').bucket]);
186
129
  });
187
130
  test('it should use the latest version after updates', async () => {
188
- const env_3 = { stack: [], error: void 0, hasError: false };
189
- try {
190
- const factory = __addDisposableResource(env_3, await generateStorageFactory(), true);
191
- const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
131
+ await using factory = await generateStorageFactory();
132
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
192
133
  bucket_definitions:
193
134
  mybucket:
194
135
  parameters:
@@ -196,326 +137,293 @@ bucket_definitions:
196
137
  FROM todos
197
138
  WHERE list_id IN token_parameters.list_id
198
139
  data: []
199
- `));
200
- const bucketStorage = factory.getInstance(syncRules);
201
- const table = test_utils.makeTestTable('todos', ['id', 'list_id']);
202
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
203
- // Create two todos which initially belong to different lists
204
- await batch.save({
205
- sourceTable: table,
206
- tag: storage.SaveOperationTag.INSERT,
207
- after: {
208
- id: 'todo1',
209
- list_id: 'list1'
210
- },
211
- afterReplicaId: test_utils.rid('todo1')
212
- });
213
- await batch.save({
214
- sourceTable: table,
215
- tag: storage.SaveOperationTag.INSERT,
216
- after: {
217
- id: 'todo2',
218
- list_id: 'list2'
219
- },
220
- afterReplicaId: test_utils.rid('todo2')
221
- });
222
- await batch.commit('1/1');
223
- });
224
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
225
- // Update the second todo item to now belong to list 1
226
- await batch.save({
227
- sourceTable: table,
228
- tag: storage.SaveOperationTag.UPDATE,
229
- after: {
230
- id: 'todo2',
231
- list_id: 'list1'
232
- },
233
- afterReplicaId: test_utils.rid('todo2')
234
- });
235
- await batch.commit('1/1');
236
- });
237
- // We specifically request the todo_ids for both lists.
238
- // There removal operation for the association of `list2`::`todo2` should not interfere with the new
239
- // association of `list1`::`todo2`
240
- const checkpoint = await bucketStorage.getCheckpoint();
241
- const parameters = await checkpoint.getParameterSets([
242
- ScopedParameterLookup.direct(MYBUCKET_1, ['list1']),
243
- ScopedParameterLookup.direct(MYBUCKET_1, ['list2'])
244
- ]);
245
- expect(parameters.sort((a, b) => a.todo_id.localeCompare(b.todo_id))).toEqual([
246
- {
247
- todo_id: 'todo1'
248
- },
249
- {
250
- todo_id: 'todo2'
251
- }
252
- ]);
253
- }
254
- catch (e_3) {
255
- env_3.error = e_3;
256
- env_3.hasError = true;
257
- }
258
- finally {
259
- const result_3 = __disposeResources(env_3);
260
- if (result_3)
261
- await result_3;
262
- }
140
+ `, { storageVersion }));
141
+ const bucketStorage = factory.getInstance(syncRules);
142
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncRules();
143
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
144
+ const table = await test_utils.resolveTestTable(writer, 'todos', ['id', 'list_id'], config);
145
+ await writer.markAllSnapshotDone('1/1');
146
+ // Create two todos which initially belong to different lists
147
+ await writer.save({
148
+ sourceTable: table,
149
+ tag: storage.SaveOperationTag.INSERT,
150
+ after: {
151
+ id: 'todo1',
152
+ list_id: 'list1'
153
+ },
154
+ afterReplicaId: test_utils.rid('todo1')
155
+ });
156
+ await writer.save({
157
+ sourceTable: table,
158
+ tag: storage.SaveOperationTag.INSERT,
159
+ after: {
160
+ id: 'todo2',
161
+ list_id: 'list2'
162
+ },
163
+ afterReplicaId: test_utils.rid('todo2')
164
+ });
165
+ await writer.commit('1/1');
166
+ // Update the second todo item to now belong to list 1
167
+ await writer.save({
168
+ sourceTable: table,
169
+ tag: storage.SaveOperationTag.UPDATE,
170
+ after: {
171
+ id: 'todo2',
172
+ list_id: 'list1'
173
+ },
174
+ afterReplicaId: test_utils.rid('todo2')
175
+ });
176
+ await writer.commit('1/1');
177
+ // We specifically request the todo_ids for both lists.
178
+ // There removal operation for the association of `list2`::`todo2` should not interfere with the new
179
+ // association of `list1`::`todo2`
180
+ const checkpoint = await bucketStorage.getCheckpoint();
181
+ const parameters = new RequestParameters(new JwtPayload({ sub: 'u1', parameters: { list_id: ['list1', 'list2'] } }), {});
182
+ const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
183
+ const buckets = await querier.queryDynamicBucketDescriptions({
184
+ async getParameterSets(lookups) {
185
+ expect(lookups.map((l) => JSON.stringify(l.indexKey)).sort()).toEqual(['["list1"]', '["list2"]']);
186
+ const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
187
+ expect(parameter_sets.sort((a, b) => a.todo_id.localeCompare(b.todo_id))).toEqual([
188
+ { todo_id: 'todo1' },
189
+ { todo_id: 'todo2' }
190
+ ]);
191
+ return parameter_sets;
192
+ }
193
+ });
194
+ expect(buckets.map((b) => b.bucket).sort()).toEqual([
195
+ bucketRequest(syncRules, 'mybucket["todo1"]').bucket,
196
+ bucketRequest(syncRules, 'mybucket["todo2"]').bucket
197
+ ]);
263
198
  });
264
199
  test('save and load parameters with different number types', async () => {
265
- const env_4 = { stack: [], error: void 0, hasError: false };
266
- try {
267
- const factory = __addDisposableResource(env_4, await generateStorageFactory(), true);
268
- const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
200
+ await using factory = await generateStorageFactory();
201
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
269
202
  bucket_definitions:
270
203
  mybucket:
271
204
  parameters:
272
205
  - SELECT group_id FROM test WHERE n1 = token_parameters.n1 and f2 = token_parameters.f2 and f3 = token_parameters.f3
273
206
  data: []
274
- `));
275
- const bucketStorage = factory.getInstance(syncRules);
276
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
277
- await batch.save({
278
- sourceTable: TEST_TABLE,
279
- tag: storage.SaveOperationTag.INSERT,
280
- after: {
281
- id: 't1',
282
- group_id: 'group1',
283
- n1: 314n,
284
- f2: 314,
285
- f3: 3.14
286
- },
287
- afterReplicaId: test_utils.rid('t1')
288
- });
289
- await batch.commit('1/1');
207
+ `, {
208
+ storageVersion
209
+ }));
210
+ const bucketStorage = factory.getInstance(syncRules);
211
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncRules();
212
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
213
+ const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
214
+ await writer.markAllSnapshotDone('1/1');
215
+ await writer.save({
216
+ sourceTable: testTable,
217
+ tag: storage.SaveOperationTag.INSERT,
218
+ after: {
219
+ id: 't1',
220
+ group_id: 'group1',
221
+ n1: 314n,
222
+ f2: 314,
223
+ f3: 3.14
224
+ },
225
+ afterReplicaId: test_utils.rid('t1')
226
+ });
227
+ await writer.commit('1/1');
228
+ const checkpoint = await bucketStorage.getCheckpoint();
229
+ const testQuery = async (jwtParameters, expectedParameterSets) => {
230
+ const parameters = new RequestParameters(new JwtPayload({ sub: 'u1', parameters: jwtParameters }), {});
231
+ const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
232
+ return await querier.queryDynamicBucketDescriptions({
233
+ async getParameterSets(lookups) {
234
+ const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
235
+ expect(parameter_sets).toEqual(expectedParameterSets);
236
+ return parameter_sets;
237
+ }
290
238
  });
291
- const TEST_PARAMS = { group_id: 'group1' };
292
- const checkpoint = await bucketStorage.getCheckpoint();
293
- const parameters1 = await checkpoint.getParameterSets([
294
- ScopedParameterLookup.direct(MYBUCKET_1, [314n, 314, 3.14])
295
- ]);
296
- expect(parameters1).toEqual([TEST_PARAMS]);
297
- const parameters2 = await checkpoint.getParameterSets([
298
- ScopedParameterLookup.direct(MYBUCKET_1, [314, 314n, 3.14])
299
- ]);
300
- expect(parameters2).toEqual([TEST_PARAMS]);
301
- const parameters3 = await checkpoint.getParameterSets([ScopedParameterLookup.direct(MYBUCKET_1, [314n, 314, 3])]);
302
- expect(parameters3).toEqual([]);
303
- }
304
- catch (e_4) {
305
- env_4.error = e_4;
306
- env_4.hasError = true;
307
- }
308
- finally {
309
- const result_4 = __disposeResources(env_4);
310
- if (result_4)
311
- await result_4;
312
- }
239
+ };
240
+ expect(await testQuery({ n1: 314n, f2: 314, f3: 3.14 }, [{ group_id: 'group1' }])).toMatchObject([
241
+ { bucket: bucketRequest(syncRules, 'mybucket["group1"]').bucket }
242
+ ]);
243
+ expect(await testQuery({ n1: 314, f2: 314n, f3: 3.14 }, [{ group_id: 'group1' }])).toMatchObject([
244
+ { bucket: bucketRequest(syncRules, 'mybucket["group1"]').bucket }
245
+ ]);
246
+ expect(await testQuery({ n1: 314n, f2: 314, f3: 3 }, [])).toEqual([]);
313
247
  });
314
248
  test('save and load parameters with large numbers', async () => {
315
- const env_5 = { stack: [], error: void 0, hasError: false };
316
- try {
317
- // This ensures serialization / deserialization of "current_data" is done correctly.
318
- // This specific case tested here cannot happen with postgres in practice, but we still
319
- // test this to ensure correct deserialization.
320
- const factory = __addDisposableResource(env_5, await generateStorageFactory(), true);
321
- const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
249
+ // This ensures serialization / deserialization of "current_data" is done correctly.
250
+ // This specific case tested here cannot happen with postgres in practice, but we still
251
+ // test this to ensure correct deserialization.
252
+ await using factory = await generateStorageFactory();
253
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
322
254
  bucket_definitions:
323
255
  mybucket:
324
256
  parameters:
325
257
  - SELECT group_id FROM test WHERE n1 = token_parameters.n1
326
258
  data: []
327
- `));
328
- const bucketStorage = factory.getInstance(syncRules);
329
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
330
- await batch.save({
331
- sourceTable: TEST_TABLE,
332
- tag: storage.SaveOperationTag.INSERT,
333
- after: {
334
- id: 't1',
335
- group_id: 'group1',
336
- n1: 1152921504606846976n // 2^60
337
- },
338
- afterReplicaId: test_utils.rid('t1')
339
- });
340
- await batch.save({
341
- sourceTable: TEST_TABLE,
342
- tag: storage.SaveOperationTag.UPDATE,
343
- after: {
344
- id: 't1',
345
- group_id: 'group1',
346
- // Simulate a TOAST value, even though it can't happen for values like this
347
- // in practice.
348
- n1: undefined
349
- },
350
- afterReplicaId: test_utils.rid('t1')
351
- });
352
- await batch.commit('1/1');
353
- });
354
- const TEST_PARAMS = { group_id: 'group1' };
355
- const checkpoint = await bucketStorage.getCheckpoint();
356
- const parameters1 = await checkpoint.getParameterSets([
357
- ScopedParameterLookup.direct(MYBUCKET_1, [1152921504606846976n])
358
- ]);
359
- expect(parameters1).toEqual([TEST_PARAMS]);
360
- }
361
- catch (e_5) {
362
- env_5.error = e_5;
363
- env_5.hasError = true;
364
- }
365
- finally {
366
- const result_5 = __disposeResources(env_5);
367
- if (result_5)
368
- await result_5;
369
- }
259
+ `, {
260
+ storageVersion
261
+ }));
262
+ const bucketStorage = factory.getInstance(syncRules);
263
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncRules();
264
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
265
+ const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
266
+ await writer.markAllSnapshotDone('1/1');
267
+ await writer.save({
268
+ sourceTable: testTable,
269
+ tag: storage.SaveOperationTag.INSERT,
270
+ after: {
271
+ id: 't1',
272
+ group_id: 'group1',
273
+ n1: 1152921504606846976n // 2^60
274
+ },
275
+ afterReplicaId: test_utils.rid('t1')
276
+ });
277
+ await writer.save({
278
+ sourceTable: testTable,
279
+ tag: storage.SaveOperationTag.UPDATE,
280
+ after: {
281
+ id: 't1',
282
+ group_id: 'group1',
283
+ // Simulate a TOAST value, even though it can't happen for values like this
284
+ // in practice.
285
+ n1: undefined
286
+ },
287
+ afterReplicaId: test_utils.rid('t1')
288
+ });
289
+ await writer.commit('1/1');
290
+ const checkpoint = await bucketStorage.getCheckpoint();
291
+ const n1 = 1152921504606846976n;
292
+ const parameters = new RequestParameters(new JwtPayload({ sub: 'u1', parameters: { n1 } }), {});
293
+ const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
294
+ const buckets = await querier.queryDynamicBucketDescriptions({
295
+ getParameterSets: async (lookups) => {
296
+ expect(lookups.map((l) => l.indexKey)).toEqual([[n1]]);
297
+ const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
298
+ expect(parameter_sets).toEqual([{ group_id: 'group1' }]);
299
+ return parameter_sets;
300
+ }
301
+ });
302
+ expect(buckets.map((b) => b.bucket)).toEqual([bucketRequest(syncRules, 'mybucket["group1"]').bucket]);
370
303
  });
371
304
  test('save and load parameters with workspaceId', async () => {
372
- const env_6 = { stack: [], error: void 0, hasError: false };
373
- try {
374
- const WORKSPACE_TABLE = test_utils.makeTestTable('workspace', ['id']);
375
- const factory = __addDisposableResource(env_6, await generateStorageFactory(), true);
376
- const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
305
+ await using factory = await generateStorageFactory();
306
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
377
307
  bucket_definitions:
378
308
  by_workspace:
379
309
  parameters:
380
310
  - SELECT id as workspace_id FROM workspace WHERE
381
311
  workspace."userId" = token_parameters.user_id
382
312
  data: []
383
- `));
384
- const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncRules();
385
- const bucketStorage = factory.getInstance(syncRules);
386
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
387
- await batch.save({
388
- sourceTable: WORKSPACE_TABLE,
389
- tag: storage.SaveOperationTag.INSERT,
390
- after: {
391
- id: 'workspace1',
392
- userId: 'u1'
393
- },
394
- afterReplicaId: test_utils.rid('workspace1')
395
- });
396
- await batch.commit('1/1');
397
- });
398
- const checkpoint = await bucketStorage.getCheckpoint();
399
- const parameters = new RequestParameters(new JwtPayload({ sub: 'u1' }), {});
400
- const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
401
- const buckets = await querier.queryDynamicBucketDescriptions({
402
- async getParameterSets(lookups) {
403
- expect(lookups).toEqual([ScopedParameterLookup.direct({ lookupName: 'by_workspace', queryId: '1' }, ['u1'])]);
404
- const parameter_sets = await checkpoint.getParameterSets(lookups);
405
- expect(parameter_sets).toEqual([{ workspace_id: 'workspace1' }]);
406
- return parameter_sets;
407
- }
408
- });
409
- expect(buckets).toEqual([
410
- {
411
- bucket: bucketRequest(syncRules, 'by_workspace["workspace1"]'),
412
- priority: 3,
413
- definition: 'by_workspace',
414
- inclusion_reasons: ['default']
415
- }
416
- ]);
417
- }
418
- catch (e_6) {
419
- env_6.error = e_6;
420
- env_6.hasError = true;
421
- }
422
- finally {
423
- const result_6 = __disposeResources(env_6);
424
- if (result_6)
425
- await result_6;
426
- }
313
+ `, {
314
+ storageVersion
315
+ }));
316
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncRules();
317
+ const bucketStorage = factory.getInstance(syncRules);
318
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
319
+ const workspaceTable = await test_utils.resolveTestTable(writer, 'workspace', ['id'], config);
320
+ await writer.markAllSnapshotDone('1/1');
321
+ await writer.save({
322
+ sourceTable: workspaceTable,
323
+ tag: storage.SaveOperationTag.INSERT,
324
+ after: {
325
+ id: 'workspace1',
326
+ userId: 'u1'
327
+ },
328
+ afterReplicaId: test_utils.rid('workspace1')
329
+ });
330
+ await writer.commit('1/1');
331
+ const checkpoint = await bucketStorage.getCheckpoint();
332
+ const parameters = new RequestParameters(new JwtPayload({ sub: 'u1' }), {});
333
+ const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
334
+ const buckets = await querier.queryDynamicBucketDescriptions({
335
+ async getParameterSets(lookups) {
336
+ expect(lookups.map((l) => l.indexKey)).toEqual([['u1']]);
337
+ const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
338
+ expect(parameter_sets).toEqual([{ workspace_id: 'workspace1' }]);
339
+ return parameter_sets;
340
+ }
341
+ });
342
+ expect(buckets).toEqual([
343
+ {
344
+ bucket: bucketRequest(syncRules, 'by_workspace["workspace1"]').bucket,
345
+ priority: 3,
346
+ definition: 'by_workspace',
347
+ inclusion_reasons: ['default']
348
+ }
349
+ ]);
427
350
  });
428
351
  test('save and load parameters with dynamic global buckets', async () => {
429
- const env_7 = { stack: [], error: void 0, hasError: false };
430
- try {
431
- const WORKSPACE_TABLE = test_utils.makeTestTable('workspace');
432
- const factory = __addDisposableResource(env_7, await generateStorageFactory(), true);
433
- const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
352
+ await using factory = await generateStorageFactory();
353
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
434
354
  bucket_definitions:
435
355
  by_public_workspace:
436
356
  parameters:
437
357
  - SELECT id as workspace_id FROM workspace WHERE
438
358
  workspace.visibility = 'public'
439
359
  data: []
440
- `));
441
- const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncRules();
442
- const bucketStorage = factory.getInstance(syncRules);
443
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
444
- await batch.save({
445
- sourceTable: WORKSPACE_TABLE,
446
- tag: storage.SaveOperationTag.INSERT,
447
- after: {
448
- id: 'workspace1',
449
- visibility: 'public'
450
- },
451
- afterReplicaId: test_utils.rid('workspace1')
452
- });
453
- await batch.save({
454
- sourceTable: WORKSPACE_TABLE,
455
- tag: storage.SaveOperationTag.INSERT,
456
- after: {
457
- id: 'workspace2',
458
- visibility: 'private'
459
- },
460
- afterReplicaId: test_utils.rid('workspace2')
461
- });
462
- await batch.save({
463
- sourceTable: WORKSPACE_TABLE,
464
- tag: storage.SaveOperationTag.INSERT,
465
- after: {
466
- id: 'workspace3',
467
- visibility: 'public'
468
- },
469
- afterReplicaId: test_utils.rid('workspace3')
470
- });
471
- await batch.commit('1/1');
472
- });
473
- const checkpoint = await bucketStorage.getCheckpoint();
474
- const parameters = new RequestParameters(new JwtPayload({ sub: 'unknown' }), {});
475
- const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
476
- const buckets = await querier.queryDynamicBucketDescriptions({
477
- async getParameterSets(lookups) {
478
- expect(lookups).toEqual([
479
- ScopedParameterLookup.direct({ lookupName: 'by_public_workspace', queryId: '1' }, [])
480
- ]);
481
- const parameter_sets = await checkpoint.getParameterSets(lookups);
482
- parameter_sets.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
483
- expect(parameter_sets).toEqual([{ workspace_id: 'workspace1' }, { workspace_id: 'workspace3' }]);
484
- return parameter_sets;
485
- }
486
- });
487
- buckets.sort((a, b) => a.bucket.localeCompare(b.bucket));
488
- expect(buckets).toEqual([
489
- {
490
- bucket: bucketRequest(syncRules, 'by_public_workspace["workspace1"]'),
491
- priority: 3,
492
- definition: 'by_public_workspace',
493
- inclusion_reasons: ['default']
494
- },
495
- {
496
- bucket: bucketRequest(syncRules, 'by_public_workspace["workspace3"]'),
497
- priority: 3,
498
- definition: 'by_public_workspace',
499
- inclusion_reasons: ['default']
500
- }
501
- ]);
502
- }
503
- catch (e_7) {
504
- env_7.error = e_7;
505
- env_7.hasError = true;
506
- }
507
- finally {
508
- const result_7 = __disposeResources(env_7);
509
- if (result_7)
510
- await result_7;
511
- }
360
+ `, {
361
+ storageVersion
362
+ }));
363
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncRules();
364
+ const bucketStorage = factory.getInstance(syncRules);
365
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
366
+ const workspaceTable = await test_utils.resolveTestTable(writer, 'workspace', undefined, config);
367
+ await writer.markAllSnapshotDone('1/1');
368
+ await writer.save({
369
+ sourceTable: workspaceTable,
370
+ tag: storage.SaveOperationTag.INSERT,
371
+ after: {
372
+ id: 'workspace1',
373
+ visibility: 'public'
374
+ },
375
+ afterReplicaId: test_utils.rid('workspace1')
376
+ });
377
+ await writer.save({
378
+ sourceTable: workspaceTable,
379
+ tag: storage.SaveOperationTag.INSERT,
380
+ after: {
381
+ id: 'workspace2',
382
+ visibility: 'private'
383
+ },
384
+ afterReplicaId: test_utils.rid('workspace2')
385
+ });
386
+ await writer.save({
387
+ sourceTable: workspaceTable,
388
+ tag: storage.SaveOperationTag.INSERT,
389
+ after: {
390
+ id: 'workspace3',
391
+ visibility: 'public'
392
+ },
393
+ afterReplicaId: test_utils.rid('workspace3')
394
+ });
395
+ await writer.commit('1/1');
396
+ const checkpoint = await bucketStorage.getCheckpoint();
397
+ const parameters = new RequestParameters(new JwtPayload({ sub: 'unknown' }), {});
398
+ const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
399
+ const buckets = await querier.queryDynamicBucketDescriptions({
400
+ async getParameterSets(lookups) {
401
+ expect(lookups.map((l) => l.indexKey)).toEqual([[]]);
402
+ const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
403
+ parameter_sets.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
404
+ expect(parameter_sets).toEqual([{ workspace_id: 'workspace1' }, { workspace_id: 'workspace3' }]);
405
+ return parameter_sets;
406
+ }
407
+ });
408
+ buckets.sort((a, b) => a.bucket.localeCompare(b.bucket));
409
+ expect(buckets).toEqual([
410
+ {
411
+ bucket: bucketRequest(syncRules, 'by_public_workspace["workspace1"]').bucket,
412
+ priority: 3,
413
+ definition: 'by_public_workspace',
414
+ inclusion_reasons: ['default']
415
+ },
416
+ {
417
+ bucket: bucketRequest(syncRules, 'by_public_workspace["workspace3"]').bucket,
418
+ priority: 3,
419
+ definition: 'by_public_workspace',
420
+ inclusion_reasons: ['default']
421
+ }
422
+ ]);
512
423
  });
513
424
  test('multiple parameter queries', async () => {
514
- const env_8 = { stack: [], error: void 0, hasError: false };
515
- try {
516
- const WORKSPACE_TABLE = test_utils.makeTestTable('workspace');
517
- const factory = __addDisposableResource(env_8, await generateStorageFactory(), true);
518
- const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
425
+ await using factory = await generateStorageFactory();
426
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
519
427
  bucket_definitions:
520
428
  by_workspace:
521
429
  parameters:
@@ -524,171 +432,152 @@ bucket_definitions:
524
432
  - SELECT id as workspace_id FROM workspace WHERE
525
433
  workspace.user_id = token_parameters.user_id
526
434
  data: []
527
- `));
528
- const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncRules();
529
- const bucketStorage = factory.getInstance(syncRules);
530
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
531
- await batch.save({
532
- sourceTable: WORKSPACE_TABLE,
533
- tag: storage.SaveOperationTag.INSERT,
534
- after: {
535
- id: 'workspace1',
536
- visibility: 'public'
537
- },
538
- afterReplicaId: test_utils.rid('workspace1')
539
- });
540
- await batch.save({
541
- sourceTable: WORKSPACE_TABLE,
542
- tag: storage.SaveOperationTag.INSERT,
543
- after: {
544
- id: 'workspace2',
545
- visibility: 'private'
546
- },
547
- afterReplicaId: test_utils.rid('workspace2')
548
- });
549
- await batch.save({
550
- sourceTable: WORKSPACE_TABLE,
551
- tag: storage.SaveOperationTag.INSERT,
552
- after: {
553
- id: 'workspace3',
554
- user_id: 'u1',
555
- visibility: 'private'
556
- },
557
- afterReplicaId: test_utils.rid('workspace3')
558
- });
559
- await batch.save({
560
- sourceTable: WORKSPACE_TABLE,
561
- tag: storage.SaveOperationTag.INSERT,
562
- after: {
563
- id: 'workspace4',
564
- user_id: 'u2',
565
- visibility: 'private'
566
- },
567
- afterReplicaId: test_utils.rid('workspace4')
568
- });
569
- await batch.commit('1/1');
570
- });
571
- const checkpoint = await bucketStorage.getCheckpoint();
572
- const parameters = new RequestParameters(new JwtPayload({ sub: 'u1' }), {});
573
- // Test intermediate values - could be moved to sync_rules.test.ts
574
- const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
575
- // Test final values - the important part
576
- const foundLookups = [];
577
- const parameter_sets = [];
578
- const buckets = (await querier.queryDynamicBucketDescriptions({
579
- async getParameterSets(lookups) {
580
- foundLookups.push(...lookups);
581
- const output = await checkpoint.getParameterSets(lookups);
582
- parameter_sets.push(...output);
583
- return output;
584
- }
585
- })).map((e) => e.bucket);
586
- expect(foundLookups).toEqual([
587
- ScopedParameterLookup.direct({ lookupName: 'by_workspace', queryId: '1' }, []),
588
- ScopedParameterLookup.direct({ lookupName: 'by_workspace', queryId: '2' }, ['u1'])
589
- ]);
590
- parameter_sets.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
591
- expect(parameter_sets).toEqual([{ workspace_id: 'workspace1' }, { workspace_id: 'workspace3' }]);
592
- buckets.sort();
593
- expect(buckets).toEqual([
594
- bucketRequest(syncRules, 'by_workspace["workspace1"]'),
595
- bucketRequest(syncRules, 'by_workspace["workspace3"]')
596
- ]);
597
- }
598
- catch (e_8) {
599
- env_8.error = e_8;
600
- env_8.hasError = true;
601
- }
602
- finally {
603
- const result_8 = __disposeResources(env_8);
604
- if (result_8)
605
- await result_8;
606
- }
435
+ `, {
436
+ storageVersion
437
+ }));
438
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncRules();
439
+ const bucketStorage = factory.getInstance(syncRules);
440
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
441
+ const workspaceTable = await test_utils.resolveTestTable(writer, 'workspace', undefined, config);
442
+ await writer.markAllSnapshotDone('1/1');
443
+ await writer.save({
444
+ sourceTable: workspaceTable,
445
+ tag: storage.SaveOperationTag.INSERT,
446
+ after: {
447
+ id: 'workspace1',
448
+ visibility: 'public'
449
+ },
450
+ afterReplicaId: test_utils.rid('workspace1')
451
+ });
452
+ await writer.save({
453
+ sourceTable: workspaceTable,
454
+ tag: storage.SaveOperationTag.INSERT,
455
+ after: {
456
+ id: 'workspace2',
457
+ visibility: 'private'
458
+ },
459
+ afterReplicaId: test_utils.rid('workspace2')
460
+ });
461
+ await writer.save({
462
+ sourceTable: workspaceTable,
463
+ tag: storage.SaveOperationTag.INSERT,
464
+ after: {
465
+ id: 'workspace3',
466
+ user_id: 'u1',
467
+ visibility: 'private'
468
+ },
469
+ afterReplicaId: test_utils.rid('workspace3')
470
+ });
471
+ await writer.save({
472
+ sourceTable: workspaceTable,
473
+ tag: storage.SaveOperationTag.INSERT,
474
+ after: {
475
+ id: 'workspace4',
476
+ user_id: 'u2',
477
+ visibility: 'private'
478
+ },
479
+ afterReplicaId: test_utils.rid('workspace4')
480
+ });
481
+ await writer.commit('1/1');
482
+ const checkpoint = await bucketStorage.getCheckpoint();
483
+ const parameters = new RequestParameters(new JwtPayload({ sub: 'u1' }), {});
484
+ // Test intermediate values - could be moved to sync_rules.test.ts
485
+ const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
486
+ // Test final values - the important part
487
+ const foundLookups = [];
488
+ const parameter_sets = [];
489
+ const buckets = (await querier.queryDynamicBucketDescriptions({
490
+ async getParameterSets(lookups) {
491
+ foundLookups.push(...lookups);
492
+ const output = await checkpoint.getParameterSets(lookups, 1000);
493
+ parameter_sets.push(...output);
494
+ return output;
495
+ }
496
+ })).map((e) => e.bucket);
497
+ // Not testing the scope anymore - the exact format depends on storage version
498
+ expect(foundLookups.map((l) => l.indexKey)).toEqual([[], ['u1']]);
499
+ parameter_sets.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
500
+ expect(parameter_sets).toEqual([{ workspace_id: 'workspace1' }, { workspace_id: 'workspace3' }]);
501
+ buckets.sort();
502
+ expect(buckets).toEqual([
503
+ bucketRequest(syncRules, 'by_workspace["workspace1"]').bucket,
504
+ bucketRequest(syncRules, 'by_workspace["workspace3"]').bucket
505
+ ]);
607
506
  });
608
507
  test('truncate parameters', async () => {
609
- const env_9 = { stack: [], error: void 0, hasError: false };
610
- try {
611
- const factory = __addDisposableResource(env_9, await generateStorageFactory(), true);
612
- const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
508
+ await using factory = await generateStorageFactory();
509
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
613
510
  bucket_definitions:
614
511
  mybucket:
615
512
  parameters:
616
513
  - SELECT group_id FROM test WHERE id1 = token_parameters.user_id OR id2 = token_parameters.user_id
617
514
  data: []
618
- `));
619
- const bucketStorage = factory.getInstance(syncRules);
620
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
621
- await batch.save({
622
- sourceTable: TEST_TABLE,
623
- tag: storage.SaveOperationTag.INSERT,
624
- after: {
625
- id: 't2',
626
- id1: 'user3',
627
- id2: 'user4',
628
- group_id: 'group2a'
629
- },
630
- afterReplicaId: test_utils.rid('t2')
631
- });
632
- await batch.truncate([TEST_TABLE]);
633
- });
634
- const checkpoint = await bucketStorage.getCheckpoint();
635
- const parameters = await checkpoint.getParameterSets([ScopedParameterLookup.direct(MYBUCKET_1, ['user1'])]);
636
- expect(parameters).toEqual([]);
637
- }
638
- catch (e_9) {
639
- env_9.error = e_9;
640
- env_9.hasError = true;
641
- }
642
- finally {
643
- const result_9 = __disposeResources(env_9);
644
- if (result_9)
645
- await result_9;
646
- }
515
+ `, {
516
+ storageVersion
517
+ }));
518
+ const bucketStorage = factory.getInstance(syncRules);
519
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncRules();
520
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
521
+ const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
522
+ await writer.markAllSnapshotDone('1/1');
523
+ await writer.save({
524
+ sourceTable: testTable,
525
+ tag: storage.SaveOperationTag.INSERT,
526
+ after: {
527
+ id: 't2',
528
+ id1: 'user3',
529
+ id2: 'user4',
530
+ group_id: 'group2a'
531
+ },
532
+ afterReplicaId: test_utils.rid('t2')
533
+ });
534
+ await writer.truncate([testTable]);
535
+ await writer.flush();
536
+ const checkpoint = await bucketStorage.getCheckpoint();
537
+ const parameters = new RequestParameters(new JwtPayload({ sub: 'user1' }), {});
538
+ const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
539
+ const buckets = await querier.queryDynamicBucketDescriptions({
540
+ async getParameterSets(lookups) {
541
+ expect(lookups.map((l) => l.indexKey)).toEqual([['user1']]);
542
+ const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
543
+ expect(parameter_sets).toEqual([]);
544
+ return parameter_sets;
545
+ }
546
+ });
547
+ expect(buckets).toEqual([]);
647
548
  });
648
549
  test('invalidate cached parsed sync rules', async () => {
649
- const env_10 = { stack: [], error: void 0, hasError: false };
650
- try {
651
- const bucketStorageFactory = __addDisposableResource(env_10, await generateStorageFactory(), true);
652
- const syncRules = await bucketStorageFactory.updateSyncRules(updateSyncRulesFromYaml(`
550
+ await using bucketStorageFactory = await generateStorageFactory();
551
+ const syncRules = await bucketStorageFactory.updateSyncRules(updateSyncRulesFromYaml(`
653
552
  bucket_definitions:
654
553
  by_workspace:
655
554
  parameters:
656
555
  - SELECT id as workspace_id FROM workspace WHERE
657
556
  workspace."userId" = token_parameters.user_id
658
557
  data: []
659
- `));
660
- const syncBucketStorage = bucketStorageFactory.getInstance(syncRules);
661
- const parsedSchema1 = syncBucketStorage.getParsedSyncRules({
662
- defaultSchema: 'public'
663
- });
664
- const parsedSchema2 = syncBucketStorage.getParsedSyncRules({
665
- defaultSchema: 'public'
666
- });
667
- // These should be cached, this will be the same instance
668
- expect(parsedSchema2).equals(parsedSchema1);
669
- expect(parsedSchema1.getSourceTables()[0].schema).equals('public');
670
- const parsedSchema3 = syncBucketStorage.getParsedSyncRules({
671
- defaultSchema: 'databasename'
672
- });
673
- // The cache should not be used
674
- expect(parsedSchema3).not.equals(parsedSchema2);
675
- expect(parsedSchema3.getSourceTables()[0].schema).equals('databasename');
676
- }
677
- catch (e_10) {
678
- env_10.error = e_10;
679
- env_10.hasError = true;
680
- }
681
- finally {
682
- const result_10 = __disposeResources(env_10);
683
- if (result_10)
684
- await result_10;
685
- }
558
+ `, {
559
+ storageVersion
560
+ }));
561
+ const syncBucketStorage = bucketStorageFactory.getInstance(syncRules);
562
+ const parsedSchema1 = syncBucketStorage.getParsedSyncRules({
563
+ defaultSchema: 'public'
564
+ });
565
+ const parsedSchema2 = syncBucketStorage.getParsedSyncRules({
566
+ defaultSchema: 'public'
567
+ });
568
+ // These should be cached, this will be the same instance
569
+ expect(parsedSchema2).equals(parsedSchema1);
570
+ expect(parsedSchema1.getSourceTables()[0].schema).equals('public');
571
+ const parsedSchema3 = syncBucketStorage.getParsedSyncRules({
572
+ defaultSchema: 'databasename'
573
+ });
574
+ // The cache should not be used
575
+ expect(parsedSchema3).not.equals(parsedSchema2);
576
+ expect(parsedSchema3.getSourceTables()[0].schema).equals('databasename');
686
577
  });
687
578
  test('sync streams smoke test', async () => {
688
- const env_11 = { stack: [], error: void 0, hasError: false };
689
- try {
690
- const factory = __addDisposableResource(env_11, await generateStorageFactory(), true);
691
- const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
579
+ await using factory = await generateStorageFactory();
580
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
692
581
  config:
693
582
  edition: 3
694
583
 
@@ -698,41 +587,201 @@ streams:
698
587
  SELECT data.* FROM test AS data, test AS param
699
588
  WHERE data.foo = param.bar AND param.baz = auth.user_id()
700
589
  `));
701
- const bucketStorage = factory.getInstance(syncRules);
702
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
703
- await batch.save({
704
- sourceTable: TEST_TABLE,
705
- tag: storage.SaveOperationTag.INSERT,
706
- after: {
707
- baz: 'baz',
708
- bar: 'bar'
709
- },
710
- afterReplicaId: test_utils.rid('t1')
711
- });
712
- await batch.commit('1/1');
590
+ const bucketStorage = factory.getInstance(syncRules);
591
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncRules();
592
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
593
+ const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
594
+ await writer.markAllSnapshotDone('1/1');
595
+ await writer.save({
596
+ sourceTable: testTable,
597
+ tag: storage.SaveOperationTag.INSERT,
598
+ after: {
599
+ baz: 'baz',
600
+ bar: 'bar'
601
+ },
602
+ afterReplicaId: test_utils.rid('t1')
603
+ });
604
+ await writer.commit('1/1');
605
+ const checkpoint = await bucketStorage.getCheckpoint();
606
+ const parameters = new RequestParameters(new JwtPayload({ sub: 'baz' }), {});
607
+ const querier = sync_rules.getBucketParameterQuerier({
608
+ ...test_utils.querierOptions(parameters),
609
+ streams: {
610
+ stream: [
611
+ {
612
+ priorityOverride: null,
613
+ parameters: null,
614
+ opaque_id: 123
615
+ }
616
+ ]
617
+ }
618
+ }).querier;
619
+ const buckets = await querier.queryDynamicBucketDescriptions({
620
+ async getParameterSets(lookups) {
621
+ expect(lookups.map((l) => l.indexKey)).toEqual([['baz']]);
622
+ const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
623
+ expect(parameter_sets).toEqual([{ '0': 'bar' }]);
624
+ return parameter_sets;
625
+ }
626
+ });
627
+ console.log('whatabuckets', buckets);
628
+ expect(buckets).toHaveLength(1);
629
+ expect(buckets).toMatchObject([
630
+ {
631
+ bucket: expect.stringMatching(/stream.*\["bar"\]$/),
632
+ definition: 'stream',
633
+ inclusion_reasons: [{ subscription: 123 }],
634
+ priority: 3
635
+ }
636
+ ]);
637
+ });
638
+ test('respects parameter limit', async () => {
639
+ await using factory = await generateStorageFactory();
640
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
641
+ config:
642
+ edition: 3
643
+
644
+ streams:
645
+ a:
646
+ auto_subscribe: true
647
+ query: SELECT * FROM a WHERE id IN (SELECT id FROM b)
648
+ `, {
649
+ storageVersion
650
+ }));
651
+ const bucketStorage = factory.getInstance(syncRules);
652
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncRules();
653
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
654
+ const testTable = await test_utils.resolveTestTable(writer, 'b', ['id'], config);
655
+ await writer.markAllSnapshotDone('1/1');
656
+ for (let i = 0; i < 10; i++) {
657
+ await writer.save({
658
+ sourceTable: testTable,
659
+ tag: storage.SaveOperationTag.INSERT,
660
+ after: {
661
+ id: `t${i}`
662
+ },
663
+ afterReplicaId: test_utils.rid(`t${i}`)
713
664
  });
714
- const checkpoint = await bucketStorage.getCheckpoint();
715
- const parameters = await checkpoint.getParameterSets([
716
- ScopedParameterLookup.direct({
717
- lookupName: 'lookup',
718
- queryId: '0'
719
- }, ['baz'])
720
- ]);
721
- expect(parameters).toEqual([
722
- {
723
- '0': 'bar'
724
- }
725
- ]);
726
- }
727
- catch (e_11) {
728
- env_11.error = e_11;
729
- env_11.hasError = true;
730
- }
731
- finally {
732
- const result_11 = __disposeResources(env_11);
733
- if (result_11)
734
- await result_11;
735
665
  }
666
+ await writer.commit('1/1');
667
+ const checkpoint = await bucketStorage.getCheckpoint();
668
+ const parameters = new RequestParameters(new JwtPayload({ sub: 'u' }), {});
669
+ const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
670
+ await expect(querier.queryDynamicBucketDescriptions({
671
+ async getParameterSets(lookups) {
672
+ const parameter_sets = await checkpoint.getParameterSets(lookups, 5);
673
+ return parameter_sets;
674
+ }
675
+ })).rejects.toThrow('Too many parameter results (limit was 5)');
676
+ });
677
+ test('sync streams store multiple parameter outputs for a single source row and lookup', async () => {
678
+ await using factory = await generateStorageFactory();
679
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
680
+ config:
681
+ edition: 3
682
+ streams:
683
+ chat:
684
+ auto_subscribe: true
685
+ query: |
686
+ SELECT a.*
687
+ FROM a, b, json_each(b.x) x, json_each(b.y) y
688
+ WHERE a.x = x.value AND y.value = auth.user_id()
689
+ `));
690
+ const bucketStorage = factory.getInstance(syncRules);
691
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncRules();
692
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
693
+ const tableB = await test_utils.resolveTestTable(writer, 'b', ['id'], config);
694
+ const firstState = {
695
+ id: 'id0',
696
+ x: JSON.stringify(['x1', 'x2']),
697
+ y: JSON.stringify(['y1', 'y2'])
698
+ };
699
+ const secondState = {
700
+ id: 'id0',
701
+ x: JSON.stringify(['x2']),
702
+ y: JSON.stringify(['y1', 'y2'])
703
+ };
704
+ const thirdState = {
705
+ id: 'id0',
706
+ x: JSON.stringify(['x2']),
707
+ y: JSON.stringify(['y2'])
708
+ };
709
+ const replicaId = test_utils.rid('id0');
710
+ await writer.markAllSnapshotDone('1/1');
711
+ await writer.save({
712
+ sourceTable: tableB,
713
+ tag: storage.SaveOperationTag.INSERT,
714
+ after: firstState,
715
+ afterReplicaId: replicaId
716
+ });
717
+ await writer.commit('1/1');
718
+ let checkpoint = await bucketStorage.getCheckpoint();
719
+ const parameters = new RequestParameters(new JwtPayload({ sub: 'y1' }), {});
720
+ const querier = sync_rules.getBucketParameterQuerier({
721
+ ...test_utils.querierOptions(parameters)
722
+ }).querier;
723
+ let buckets = await querier.queryDynamicBucketDescriptions({
724
+ async getParameterSets(lookups) {
725
+ expect(lookups.map((l) => l.indexKey)).toEqual([['y1']]);
726
+ const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
727
+ expect(parameter_sets).toEqual([{ '0': 'x1' }, { '0': 'x2' }]);
728
+ return parameter_sets;
729
+ }
730
+ });
731
+ expect(buckets).toMatchObject([
732
+ {
733
+ bucket: expect.stringMatching(/chat.*\["x1"\]$/),
734
+ definition: 'chat',
735
+ inclusion_reasons: ['default'],
736
+ priority: 3
737
+ },
738
+ {
739
+ bucket: expect.stringMatching(/chat.*\["x2"\]$/),
740
+ definition: 'chat',
741
+ inclusion_reasons: ['default'],
742
+ priority: 3
743
+ }
744
+ ]);
745
+ // Make the x2 bucket inaccessible
746
+ await writer.save({
747
+ sourceTable: tableB,
748
+ tag: storage.SaveOperationTag.UPDATE,
749
+ before: firstState,
750
+ after: secondState,
751
+ beforeReplicaId: replicaId,
752
+ afterReplicaId: replicaId
753
+ });
754
+ await writer.commit('1/2');
755
+ checkpoint = await bucketStorage.getCheckpoint();
756
+ buckets = await querier.queryDynamicBucketDescriptions({
757
+ async getParameterSets(lookups) {
758
+ expect(lookups.map((l) => l.indexKey)).toEqual([['y1']]);
759
+ const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
760
+ expect(parameter_sets).toEqual([{ '0': 'x2' }]);
761
+ return parameter_sets;
762
+ }
763
+ });
764
+ expect(buckets.map((bkt) => bkt.bucket)).toStrictEqual([expect.stringContaining('x2')]);
765
+ // Second update, remove user from inputs
766
+ await writer.save({
767
+ sourceTable: tableB,
768
+ tag: storage.SaveOperationTag.UPDATE,
769
+ before: secondState,
770
+ after: thirdState,
771
+ beforeReplicaId: replicaId,
772
+ afterReplicaId: replicaId
773
+ });
774
+ await writer.commit('1/3');
775
+ checkpoint = await bucketStorage.getCheckpoint();
776
+ buckets = await querier.queryDynamicBucketDescriptions({
777
+ async getParameterSets(lookups) {
778
+ expect(lookups.map((l) => l.indexKey)).toEqual([['y1']]);
779
+ const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
780
+ expect(parameter_sets).toHaveLength(0);
781
+ return parameter_sets;
782
+ }
783
+ });
784
+ expect(buckets).toHaveLength(0);
736
785
  });
737
786
  }
738
787
  //# sourceMappingURL=register-data-storage-parameter-tests.js.map