@powersync/service-core 0.0.0-dev-20241015210820 → 0.0.0-dev-20241016113053

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 (62) hide show
  1. package/CHANGELOG.md +5 -5
  2. package/dist/api/RouteAPI.d.ts +4 -6
  3. package/dist/api/diagnostics.js +1 -3
  4. package/dist/api/diagnostics.js.map +1 -1
  5. package/dist/api/schema.js +2 -2
  6. package/dist/api/schema.js.map +1 -1
  7. package/dist/replication/AbstractReplicationJob.js +2 -2
  8. package/dist/replication/AbstractReplicationJob.js.map +1 -1
  9. package/dist/replication/ReplicationModule.js +0 -3
  10. package/dist/replication/ReplicationModule.js.map +1 -1
  11. package/dist/routes/configure-fastify.js +12 -12
  12. package/dist/routes/configure-fastify.js.map +1 -1
  13. package/dist/routes/configure-rsocket.js +1 -4
  14. package/dist/routes/configure-rsocket.js.map +1 -1
  15. package/dist/routes/endpoints/admin.js.map +1 -1
  16. package/dist/routes/endpoints/sync-rules.js.map +1 -1
  17. package/dist/routes/router.d.ts +1 -8
  18. package/dist/routes/router.js.map +1 -1
  19. package/dist/storage/BucketStorage.d.ts +16 -16
  20. package/dist/storage/BucketStorage.js +0 -6
  21. package/dist/storage/BucketStorage.js.map +1 -1
  22. package/dist/storage/MongoBucketStorage.d.ts +9 -1
  23. package/dist/storage/MongoBucketStorage.js +21 -5
  24. package/dist/storage/MongoBucketStorage.js.map +1 -1
  25. package/dist/storage/mongo/MongoBucketBatch.js +0 -1
  26. package/dist/storage/mongo/MongoBucketBatch.js.map +1 -1
  27. package/dist/storage/mongo/MongoSyncBucketStorage.d.ts +4 -2
  28. package/dist/storage/mongo/MongoSyncBucketStorage.js +2 -3
  29. package/dist/storage/mongo/MongoSyncBucketStorage.js.map +1 -1
  30. package/dist/storage/mongo/MongoWriteCheckpointAPI.d.ts +3 -1
  31. package/dist/storage/mongo/MongoWriteCheckpointAPI.js +12 -4
  32. package/dist/storage/mongo/MongoWriteCheckpointAPI.js.map +1 -1
  33. package/dist/storage/write-checkpoint.d.ts +1 -0
  34. package/dist/storage/write-checkpoint.js.map +1 -1
  35. package/dist/util/config/compound-config-collector.js +2 -1
  36. package/dist/util/config/compound-config-collector.js.map +1 -1
  37. package/dist/util/config/types.d.ts +1 -0
  38. package/dist/util/protocol-types.d.ts +1 -2
  39. package/package.json +5 -5
  40. package/src/api/RouteAPI.ts +4 -7
  41. package/src/api/diagnostics.ts +1 -3
  42. package/src/api/schema.ts +3 -3
  43. package/src/replication/AbstractReplicationJob.ts +2 -2
  44. package/src/replication/ReplicationModule.ts +0 -4
  45. package/src/routes/configure-fastify.ts +17 -16
  46. package/src/routes/configure-rsocket.ts +2 -7
  47. package/src/routes/endpoints/admin.ts +2 -2
  48. package/src/routes/endpoints/sync-rules.ts +0 -1
  49. package/src/routes/router.ts +1 -7
  50. package/src/storage/BucketStorage.ts +16 -17
  51. package/src/storage/MongoBucketStorage.ts +28 -6
  52. package/src/storage/mongo/MongoBucketBatch.ts +0 -1
  53. package/src/storage/mongo/MongoSyncBucketStorage.ts +3 -5
  54. package/src/storage/mongo/MongoWriteCheckpointAPI.ts +15 -8
  55. package/src/storage/write-checkpoint.ts +2 -0
  56. package/src/util/config/compound-config-collector.ts +2 -1
  57. package/src/util/config/types.ts +1 -0
  58. package/src/util/protocol-types.ts +1 -1
  59. package/test/src/compacting.test.ts +15 -13
  60. package/test/src/data_storage.test.ts +56 -56
  61. package/test/src/sync.test.ts +9 -10
  62. package/tsconfig.tsbuildinfo +1 -1
@@ -90,6 +90,11 @@ export interface BucketStorageFactory
90
90
  */
91
91
  getActiveCheckpoint(): Promise<ActiveCheckpoint>;
92
92
 
93
+ /**
94
+ * Yields the latest sync checkpoint.
95
+ */
96
+ watchActiveCheckpoint(signal: AbortSignal): AsyncIterable<ActiveCheckpoint>;
97
+
93
98
  /**
94
99
  * Yields the latest user write checkpoint whenever the sync checkpoint updates.
95
100
  */
@@ -106,20 +111,20 @@ export interface BucketStorageFactory
106
111
  getPowerSyncInstanceId(): Promise<string>;
107
112
  }
108
113
 
109
- export interface Checkpoint {
114
+ export interface WriteCheckpoint {
115
+ base: ActiveCheckpoint;
116
+ writeCheckpoint: bigint | null;
117
+ }
118
+
119
+ export interface ActiveCheckpoint {
110
120
  readonly checkpoint: util.OpId;
111
121
  readonly lsn: string | null;
112
- }
113
122
 
114
- export interface ActiveCheckpoint extends Checkpoint {
115
123
  hasSyncRules(): boolean;
116
124
 
117
125
  getBucketStorage(): Promise<SyncRulesBucketStorage | null>;
118
- }
119
126
 
120
- export interface WriteCheckpoint {
121
- base: ActiveCheckpoint;
122
- writeCheckpoint: bigint | null;
127
+ syncRules: PersistedSyncRules | null;
123
128
  }
124
129
 
125
130
  export interface StorageMetrics {
@@ -221,7 +226,7 @@ export interface SyncRulesBucketStorage extends DisposableObserverClient<SyncRul
221
226
  callback: (batch: BucketStorageBatch) => Promise<void>
222
227
  ): Promise<FlushedResult | null>;
223
228
 
224
- getCheckpoint(): Promise<Checkpoint>;
229
+ getCheckpoint(): Promise<{ checkpoint: util.OpId }>;
225
230
 
226
231
  getParsedSyncRules(options: ParseSyncRulesOptions): SqlSyncRules;
227
232
 
@@ -389,14 +394,8 @@ export type SaveOp = 'insert' | 'update' | 'delete';
389
394
 
390
395
  export type SaveOptions = SaveInsert | SaveUpdate | SaveDelete;
391
396
 
392
- export enum SaveOperationTag {
393
- INSERT = 'insert',
394
- UPDATE = 'update',
395
- DELETE = 'delete'
396
- }
397
-
398
397
  export interface SaveInsert {
399
- tag: SaveOperationTag.INSERT;
398
+ tag: 'insert';
400
399
  sourceTable: SourceTable;
401
400
  before?: undefined;
402
401
  beforeReplicaId?: undefined;
@@ -405,7 +404,7 @@ export interface SaveInsert {
405
404
  }
406
405
 
407
406
  export interface SaveUpdate {
408
- tag: SaveOperationTag.UPDATE;
407
+ tag: 'update';
409
408
  sourceTable: SourceTable;
410
409
 
411
410
  /**
@@ -424,7 +423,7 @@ export interface SaveUpdate {
424
423
  }
425
424
 
426
425
  export interface SaveDelete {
427
- tag: SaveOperationTag.DELETE;
426
+ tag: 'delete';
428
427
  sourceTable: SourceTable;
429
428
  before?: SqliteRow;
430
429
  beforeReplicaId: ReplicaId;
@@ -78,6 +78,9 @@ export class MongoBucketStorage
78
78
  db: PowerSyncMongo,
79
79
  options: {
80
80
  slot_name_prefix: string;
81
+ /**
82
+ * Initial Write Checkpoint Mode
83
+ */
81
84
  write_checkpoint_mode?: WriteCheckpointMode;
82
85
  }
83
86
  ) {
@@ -303,6 +306,10 @@ export class MongoBucketStorage
303
306
  return this.writeCheckpointAPI.batchCreateCustomWriteCheckpoints(checkpoints);
304
307
  }
305
308
 
309
+ setWriteCheckpointMode(mode: WriteCheckpointMode): void {
310
+ return this.writeCheckpointAPI.setWriteCheckpointMode(mode);
311
+ }
312
+
306
313
  async createCustomWriteCheckpoint(options: CustomWriteCheckpointOptions): Promise<bigint> {
307
314
  return this.writeCheckpointAPI.createCustomWriteCheckpoint(options);
308
315
  }
@@ -408,14 +415,19 @@ export class MongoBucketStorage
408
415
  return null;
409
416
  }
410
417
  return (await this.storageCache.fetch(doc._id)) ?? null;
411
- }
412
- };
418
+ },
419
+ syncRules: doc
420
+ ? new MongoPersistedSyncRulesContent(this.db, doc).parsed({
421
+ defaultSchema: ''
422
+ })
423
+ : null
424
+ } satisfies ActiveCheckpoint;
413
425
  }
414
426
 
415
427
  /**
416
428
  * Instance-wide watch on the latest available checkpoint (op_id + lsn).
417
429
  */
418
- private async *watchActiveCheckpoint(signal: AbortSignal): AsyncIterable<ActiveCheckpoint> {
430
+ private async *_watchActiveCheckpoint(signal: AbortSignal): AsyncIterable<ActiveCheckpoint> {
419
431
  const pipeline: mongo.Document[] = [
420
432
  {
421
433
  $match: {
@@ -428,7 +440,8 @@ export class MongoBucketStorage
428
440
  operationType: 1,
429
441
  'fullDocument._id': 1,
430
442
  'fullDocument.last_checkpoint': 1,
431
- 'fullDocument.last_checkpoint_lsn': 1
443
+ 'fullDocument.last_checkpoint_lsn': 1,
444
+ 'fullDocument.content': 1
432
445
  }
433
446
  }
434
447
  ];
@@ -450,7 +463,8 @@ export class MongoBucketStorage
450
463
  projection: {
451
464
  _id: 1,
452
465
  last_checkpoint: 1,
453
- last_checkpoint_lsn: 1
466
+ last_checkpoint_lsn: 1,
467
+ content: 1
454
468
  }
455
469
  }
456
470
  );
@@ -499,6 +513,7 @@ export class MongoBucketStorage
499
513
  if (doc == null) {
500
514
  continue;
501
515
  }
516
+
502
517
  const op = this.makeActiveCheckpoint(doc);
503
518
  // Check for LSN / checkpoint changes - ignore other metadata changes
504
519
  if (lastOp == null || op.lsn != lastOp.lsn || op.checkpoint != lastOp.checkpoint) {
@@ -510,9 +525,16 @@ export class MongoBucketStorage
510
525
 
511
526
  // Nothing is done here until a subscriber starts to iterate
512
527
  private readonly sharedIter = new sync.BroadcastIterable((signal) => {
513
- return this.watchActiveCheckpoint(signal);
528
+ return this._watchActiveCheckpoint(signal);
514
529
  });
515
530
 
531
+ /**
532
+ * Watch changes to the active sync rules and checkpoint.
533
+ */
534
+ watchActiveCheckpoint(signal: AbortSignal): AsyncIterable<ActiveCheckpoint> {
535
+ return wrapWithAbort(this.sharedIter, signal);
536
+ }
537
+
516
538
  /**
517
539
  * User-specific watch on the latest checkpoint and/or write checkpoint.
518
540
  */
@@ -81,7 +81,6 @@ export class MongoBucketBatch extends DisposableObserver<BucketBatchStorageListe
81
81
  this.session = this.client.startSession();
82
82
  this.slot_name = slot_name;
83
83
  this.sync_rules = sync_rules;
84
- this.batch = new OperationBatch();
85
84
  }
86
85
 
87
86
  addCustomWriteCheckpoint(checkpoint: CustomWriteCheckpointOptions): void {
@@ -8,7 +8,6 @@ import * as util from '../../util/util-index.js';
8
8
  import {
9
9
  BucketDataBatchOptions,
10
10
  BucketStorageBatch,
11
- Checkpoint,
12
11
  CompactOptions,
13
12
  DEFAULT_DOCUMENT_BATCH_LIMIT,
14
13
  DEFAULT_DOCUMENT_CHUNK_LIMIT_BYTES,
@@ -61,16 +60,15 @@ export class MongoSyncBucketStorage
61
60
  return this.parsedSyncRulesCache;
62
61
  }
63
62
 
64
- async getCheckpoint(): Promise<Checkpoint> {
63
+ async getCheckpoint() {
65
64
  const doc = await this.db.sync_rules.findOne(
66
65
  { _id: this.group_id },
67
66
  {
68
- projection: { last_checkpoint: 1, last_checkpoint_lsn: 1 }
67
+ projection: { last_checkpoint: 1 }
69
68
  }
70
69
  );
71
70
  return {
72
- checkpoint: util.timestampToOpId(doc?.last_checkpoint ?? 0n),
73
- lsn: doc?.last_checkpoint_lsn ?? null
71
+ checkpoint: util.timestampToOpId(doc?.last_checkpoint ?? 0n)
74
72
  };
75
73
  }
76
74
 
@@ -17,11 +17,19 @@ export type MongoCheckpointAPIOptions = {
17
17
 
18
18
  export class MongoWriteCheckpointAPI implements WriteCheckpointAPI {
19
19
  readonly db: PowerSyncMongo;
20
- readonly mode: WriteCheckpointMode;
20
+ private _mode: WriteCheckpointMode;
21
21
 
22
22
  constructor(options: MongoCheckpointAPIOptions) {
23
23
  this.db = options.db;
24
- this.mode = options.mode;
24
+ this._mode = options.mode;
25
+ }
26
+
27
+ get mode() {
28
+ return this._mode;
29
+ }
30
+
31
+ setWriteCheckpointMode(mode: WriteCheckpointMode): void {
32
+ this._mode = mode;
25
33
  }
26
34
 
27
35
  async batchCreateCustomWriteCheckpoints(checkpoints: CustomWriteCheckpointOptions[]): Promise<void> {
@@ -29,12 +37,11 @@ export class MongoWriteCheckpointAPI implements WriteCheckpointAPI {
29
37
  }
30
38
 
31
39
  async createCustomWriteCheckpoint(options: CustomWriteCheckpointOptions): Promise<bigint> {
32
- if (this.mode !== WriteCheckpointMode.CUSTOM) {
33
- throw new framework.errors.ValidationError(
34
- `Creating a custom Write Checkpoint when the current Write Checkpoint mode is set to "${this.mode}"`
35
- );
36
- }
37
-
40
+ /**
41
+ * Allow creating custom checkpoints even if the current mode is not `custom`.
42
+ * There might be a state where the next sync rules rely on replicating custom
43
+ * write checkpoints, but the current active sync rules uses managed checkpoints.
44
+ */
38
45
  const { checkpoint, user_id, sync_rules_id } = options;
39
46
  const doc = await this.db.custom_write_checkpoints.findOneAndUpdate(
40
47
  {
@@ -55,6 +55,8 @@ export type ManagedWriteCheckpointOptions = ManagedWriteCheckpointFilters;
55
55
  export type LastWriteCheckpointFilters = CustomWriteCheckpointFilters | ManagedWriteCheckpointFilters;
56
56
 
57
57
  export interface WriteCheckpointAPI {
58
+ setWriteCheckpointMode(mode: WriteCheckpointMode): void;
59
+
58
60
  batchCreateCustomWriteCheckpoints(checkpoints: CustomWriteCheckpointOptions[]): Promise<void>;
59
61
 
60
62
  createCustomWriteCheckpoint(checkpoint: CustomWriteCheckpointOptions): Promise<bigint>;
@@ -122,7 +122,8 @@ export class CompoundConfigCollector {
122
122
  },
123
123
  // TODO maybe move this out of the connection or something
124
124
  // slot_name_prefix: connections[0]?.slot_name_prefix ?? 'powersync_'
125
- slot_name_prefix: 'powersync_'
125
+ slot_name_prefix: 'powersync_',
126
+ parameters: baseConfig.parameters ?? {}
126
127
  };
127
128
 
128
129
  return config;
@@ -64,4 +64,5 @@ export type ResolvedPowerSyncConfig = {
64
64
 
65
65
  /** Prefix for postgres replication slot names. May eventually be connection-specific. */
66
66
  slot_name_prefix: string;
67
+ parameters: Record<string, number | string | boolean | null>;
67
68
  };
@@ -88,7 +88,7 @@ export type StreamingSyncLine =
88
88
  */
89
89
  export type OpId = string;
90
90
 
91
- interface Checkpoint {
91
+ export interface Checkpoint {
92
92
  last_op_id: OpId;
93
93
  write_checkpoint?: OpId;
94
94
  buckets: BucketChecksum[];
@@ -1,9 +1,11 @@
1
- import { SaveOperationTag } from '@/storage/BucketStorage.js';
2
1
  import { MongoCompactOptions } from '@/storage/mongo/MongoCompactor.js';
2
+ import { SqlSyncRules } from '@powersync/service-sync-rules';
3
3
  import { describe, expect, test } from 'vitest';
4
4
  import { validateCompactedBucket } from './bucket_validation.js';
5
5
  import { oneFromAsync } from './stream_utils.js';
6
- import { BATCH_OPTIONS, makeTestTable, MONGO_STORAGE_FACTORY, rid, testRules } from './util.js';
6
+ import { BATCH_OPTIONS, makeTestTable, MONGO_STORAGE_FACTORY, rid, testRules, ZERO_LSN } from './util.js';
7
+ import { ParseSyncRulesOptions, PersistedSyncRulesContent, StartBatchOptions } from '@/storage/BucketStorage.js';
8
+ import { getUuidReplicaIdentityBson } from '@/util/util-index.js';
7
9
 
8
10
  const TEST_TABLE = makeTestTable('test', ['id']);
9
11
 
@@ -29,7 +31,7 @@ bucket_definitions:
29
31
  const result = await storage.startBatch(BATCH_OPTIONS, async (batch) => {
30
32
  await batch.save({
31
33
  sourceTable: TEST_TABLE,
32
- tag: SaveOperationTag.INSERT,
34
+ tag: 'insert',
33
35
  after: {
34
36
  id: 't1'
35
37
  },
@@ -38,7 +40,7 @@ bucket_definitions:
38
40
 
39
41
  await batch.save({
40
42
  sourceTable: TEST_TABLE,
41
- tag: SaveOperationTag.INSERT,
43
+ tag: 'insert',
42
44
  after: {
43
45
  id: 't2'
44
46
  },
@@ -47,7 +49,7 @@ bucket_definitions:
47
49
 
48
50
  await batch.save({
49
51
  sourceTable: TEST_TABLE,
50
- tag: SaveOperationTag.UPDATE,
52
+ tag: 'update',
51
53
  after: {
52
54
  id: 't2'
53
55
  },
@@ -126,7 +128,7 @@ bucket_definitions:
126
128
  const result = await storage.startBatch(BATCH_OPTIONS, async (batch) => {
127
129
  await batch.save({
128
130
  sourceTable: TEST_TABLE,
129
- tag: SaveOperationTag.INSERT,
131
+ tag: 'insert',
130
132
  after: {
131
133
  id: 't1'
132
134
  },
@@ -135,7 +137,7 @@ bucket_definitions:
135
137
 
136
138
  await batch.save({
137
139
  sourceTable: TEST_TABLE,
138
- tag: SaveOperationTag.INSERT,
140
+ tag: 'insert',
139
141
  after: {
140
142
  id: 't2'
141
143
  },
@@ -144,7 +146,7 @@ bucket_definitions:
144
146
 
145
147
  await batch.save({
146
148
  sourceTable: TEST_TABLE,
147
- tag: SaveOperationTag.DELETE,
149
+ tag: 'delete',
148
150
  before: {
149
151
  id: 't1'
150
152
  },
@@ -153,7 +155,7 @@ bucket_definitions:
153
155
 
154
156
  await batch.save({
155
157
  sourceTable: TEST_TABLE,
156
- tag: SaveOperationTag.UPDATE,
158
+ tag: 'update',
157
159
  after: {
158
160
  id: 't2'
159
161
  },
@@ -231,7 +233,7 @@ bucket_definitions:
231
233
  const result = await storage.startBatch(BATCH_OPTIONS, async (batch) => {
232
234
  await batch.save({
233
235
  sourceTable: TEST_TABLE,
234
- tag: SaveOperationTag.INSERT,
236
+ tag: 'insert',
235
237
  after: {
236
238
  id: 't1'
237
239
  },
@@ -240,7 +242,7 @@ bucket_definitions:
240
242
 
241
243
  await batch.save({
242
244
  sourceTable: TEST_TABLE,
243
- tag: SaveOperationTag.INSERT,
245
+ tag: 'insert',
244
246
  after: {
245
247
  id: 't2'
246
248
  },
@@ -249,7 +251,7 @@ bucket_definitions:
249
251
 
250
252
  await batch.save({
251
253
  sourceTable: TEST_TABLE,
252
- tag: SaveOperationTag.DELETE,
254
+ tag: 'delete',
253
255
  before: {
254
256
  id: 't1'
255
257
  },
@@ -263,7 +265,7 @@ bucket_definitions:
263
265
  const result2 = await storage.startBatch(BATCH_OPTIONS, async (batch) => {
264
266
  await batch.save({
265
267
  sourceTable: TEST_TABLE,
266
- tag: SaveOperationTag.DELETE,
268
+ tag: 'delete',
267
269
  before: {
268
270
  id: 't2'
269
271
  },