@powersync/service-core 0.0.0-dev-20241022094219 → 0.0.0-dev-20241022111143

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 (82) hide show
  1. package/CHANGELOG.md +6 -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.d.ts +1 -13
  10. package/dist/replication/ReplicationModule.js +4 -7
  11. package/dist/replication/ReplicationModule.js.map +1 -1
  12. package/dist/routes/configure-fastify.js +12 -12
  13. package/dist/routes/configure-fastify.js.map +1 -1
  14. package/dist/routes/configure-rsocket.js +1 -4
  15. package/dist/routes/configure-rsocket.js.map +1 -1
  16. package/dist/routes/endpoints/admin.js.map +1 -1
  17. package/dist/routes/endpoints/checkpointing.js +82 -14
  18. package/dist/routes/endpoints/checkpointing.js.map +1 -1
  19. package/dist/routes/endpoints/sync-rules.js.map +1 -1
  20. package/dist/routes/router.d.ts +1 -8
  21. package/dist/routes/router.js.map +1 -1
  22. package/dist/storage/BucketStorage.d.ts +15 -19
  23. package/dist/storage/BucketStorage.js +0 -6
  24. package/dist/storage/BucketStorage.js.map +1 -1
  25. package/dist/storage/MongoBucketStorage.d.ts +3 -9
  26. package/dist/storage/MongoBucketStorage.js +4 -21
  27. package/dist/storage/MongoBucketStorage.js.map +1 -1
  28. package/dist/storage/StorageEngine.d.ts +1 -5
  29. package/dist/storage/StorageEngine.js +1 -19
  30. package/dist/storage/StorageEngine.js.map +1 -1
  31. package/dist/storage/StorageProvider.d.ts +1 -8
  32. package/dist/storage/{write-checkpoint.d.ts → WriteCheckpointAPI.d.ts} +29 -10
  33. package/dist/storage/{write-checkpoint.js → WriteCheckpointAPI.js} +2 -2
  34. package/dist/storage/WriteCheckpointAPI.js.map +1 -0
  35. package/dist/storage/mongo/MongoBucketBatch.d.ts +2 -2
  36. package/dist/storage/mongo/MongoBucketBatch.js +0 -1
  37. package/dist/storage/mongo/MongoBucketBatch.js.map +1 -1
  38. package/dist/storage/mongo/MongoStorageProvider.js +1 -2
  39. package/dist/storage/mongo/MongoStorageProvider.js.map +1 -1
  40. package/dist/storage/mongo/MongoSyncBucketStorage.d.ts +13 -3
  41. package/dist/storage/mongo/MongoSyncBucketStorage.js +35 -7
  42. package/dist/storage/mongo/MongoSyncBucketStorage.js.map +1 -1
  43. package/dist/storage/mongo/MongoWriteCheckpointAPI.d.ts +4 -2
  44. package/dist/storage/mongo/MongoWriteCheckpointAPI.js +13 -7
  45. package/dist/storage/mongo/MongoWriteCheckpointAPI.js.map +1 -1
  46. package/dist/storage/storage-index.d.ts +2 -2
  47. package/dist/storage/storage-index.js +2 -2
  48. package/dist/storage/storage-index.js.map +1 -1
  49. package/dist/util/config/compound-config-collector.js +2 -1
  50. package/dist/util/config/compound-config-collector.js.map +1 -1
  51. package/dist/util/config/types.d.ts +1 -0
  52. package/dist/util/protocol-types.d.ts +1 -2
  53. package/package.json +5 -5
  54. package/src/api/RouteAPI.ts +4 -7
  55. package/src/api/diagnostics.ts +1 -3
  56. package/src/api/schema.ts +3 -3
  57. package/src/replication/AbstractReplicationJob.ts +2 -2
  58. package/src/replication/ReplicationModule.ts +5 -26
  59. package/src/routes/configure-fastify.ts +17 -16
  60. package/src/routes/configure-rsocket.ts +2 -7
  61. package/src/routes/endpoints/admin.ts +2 -2
  62. package/src/routes/endpoints/checkpointing.ts +8 -1
  63. package/src/routes/endpoints/sync-rules.ts +0 -1
  64. package/src/routes/router.ts +1 -7
  65. package/src/storage/BucketStorage.ts +16 -23
  66. package/src/storage/MongoBucketStorage.ts +9 -39
  67. package/src/storage/StorageEngine.ts +2 -24
  68. package/src/storage/StorageProvider.ts +1 -9
  69. package/src/storage/{write-checkpoint.ts → WriteCheckpointAPI.ts} +31 -13
  70. package/src/storage/mongo/MongoBucketBatch.ts +2 -3
  71. package/src/storage/mongo/MongoStorageProvider.ts +1 -2
  72. package/src/storage/mongo/MongoSyncBucketStorage.ts +52 -9
  73. package/src/storage/mongo/MongoWriteCheckpointAPI.ts +16 -8
  74. package/src/storage/storage-index.ts +2 -2
  75. package/src/util/config/compound-config-collector.ts +2 -1
  76. package/src/util/config/types.ts +1 -0
  77. package/src/util/protocol-types.ts +1 -1
  78. package/test/src/compacting.test.ts +15 -13
  79. package/test/src/data_storage.test.ts +56 -56
  80. package/test/src/sync.test.ts +9 -10
  81. package/tsconfig.tsbuildinfo +1 -1
  82. package/dist/storage/write-checkpoint.js.map +0 -1
@@ -137,8 +137,8 @@ export const reprocess = routeDefinition({
137
137
  connections: [
138
138
  {
139
139
  // Previously the connection was asserted with `!`
140
- tag: baseConfig.tag,
141
- id: baseConfig.id,
140
+ tag: baseConfig!.tag!,
141
+ id: baseConfig!.id,
142
142
  slot_name: new_rules.slot_name
143
143
  }
144
144
  ]
@@ -1,6 +1,7 @@
1
1
  import { logger, router, schema } from '@powersync/lib-services-framework';
2
2
  import * as t from 'ts-codec';
3
3
 
4
+ import * as framework from '@powersync/lib-services-framework';
4
5
  import * as util from '../../util/util-index.js';
5
6
  import { authUser } from '../auth.js';
6
7
  import { routeDefinition } from '../router.js';
@@ -63,7 +64,13 @@ export const writeCheckpoint2 = routeDefinition({
63
64
  storageEngine: { activeBucketStorage }
64
65
  } = service_context;
65
66
 
66
- const writeCheckpoint = await activeBucketStorage.createManagedWriteCheckpoint({
67
+ const activeSyncRules = await activeBucketStorage.getActiveSyncRulesContent();
68
+ if (!activeSyncRules) {
69
+ throw new framework.errors.ValidationError(`Cannot create Write Checkpoint since no sync rules are active.`);
70
+ }
71
+
72
+ using syncBucketStorage = activeBucketStorage.getInstance(activeSyncRules);
73
+ const writeCheckpoint = await syncBucketStorage.createManagedWriteCheckpoint({
67
74
  user_id: full_user_id,
68
75
  heads: { '1': currentCheckpoint }
69
76
  });
@@ -3,7 +3,6 @@ import { SqlSyncRules, SyncRulesErrors } from '@powersync/service-sync-rules';
3
3
  import type { FastifyPluginAsync } from 'fastify';
4
4
  import * as t from 'ts-codec';
5
5
 
6
- import * as system from '../../system/system-index.js';
7
6
  import { authApi } from '../auth.js';
8
7
  import { routeDefinition } from '../router.js';
9
8
  import { RouteAPI } from '../../api/RouteAPI.js';
@@ -1,19 +1,13 @@
1
1
  import { router } from '@powersync/lib-services-framework';
2
2
  import * as auth from '../auth/auth-index.js';
3
3
  import { ServiceContext } from '../system/ServiceContext.js';
4
- import { RouterEngine } from './RouterEngine.js';
5
4
 
6
- /**
7
- * The {@link RouterEngine} must be provided for these routes
8
- */
9
- export type RouterServiceContext = ServiceContext & { routerEngine: RouterEngine };
10
5
  /**
11
6
  * Common context for routes
12
7
  */
13
8
  export type Context = {
14
9
  user_id?: string;
15
-
16
- service_context: RouterServiceContext;
10
+ service_context: ServiceContext;
17
11
 
18
12
  token_payload?: auth.JwtPayload;
19
13
  token_errors?: string[];
@@ -12,16 +12,15 @@ import * as util from '../util/util-index.js';
12
12
  import { ReplicationEventPayload } from './ReplicationEventPayload.js';
13
13
  import { SourceEntityDescriptor } from './SourceEntity.js';
14
14
  import { SourceTable } from './SourceTable.js';
15
- import { BatchedCustomWriteCheckpointOptions, ReplicaId, WriteCheckpointAPI } from './storage-index.js';
15
+ import { BatchedCustomWriteCheckpointOptions, ReplicaId } from './storage-index.js';
16
+ import { SyncStorageWriteCheckpointAPI } from './WriteCheckpointAPI.js';
16
17
 
17
18
  export interface BucketStorageFactoryListener extends DisposableListener {
18
19
  syncStorageCreated: (storage: SyncRulesBucketStorage) => void;
19
20
  replicationEvent: (event: ReplicationEventPayload) => void;
20
21
  }
21
22
 
22
- export interface BucketStorageFactory
23
- extends DisposableObserverClient<BucketStorageFactoryListener>,
24
- WriteCheckpointAPI {
23
+ export interface BucketStorageFactory extends DisposableObserverClient<BucketStorageFactoryListener> {
25
24
  /**
26
25
  * Update sync rules from configuration, if changed.
27
26
  */
@@ -106,22 +105,20 @@ export interface BucketStorageFactory
106
105
  getPowerSyncInstanceId(): Promise<string>;
107
106
  }
108
107
 
109
- export interface Checkpoint {
108
+ export interface WriteCheckpoint {
109
+ base: ActiveCheckpoint;
110
+ writeCheckpoint: bigint | null;
111
+ }
112
+
113
+ export interface ActiveCheckpoint {
110
114
  readonly checkpoint: util.OpId;
111
115
  readonly lsn: string | null;
112
- }
113
116
 
114
- export interface ActiveCheckpoint extends Checkpoint {
115
117
  hasSyncRules(): boolean;
116
118
 
117
119
  getBucketStorage(): Promise<SyncRulesBucketStorage | null>;
118
120
  }
119
121
 
120
- export interface WriteCheckpoint {
121
- base: ActiveCheckpoint;
122
- writeCheckpoint: bigint | null;
123
- }
124
-
125
122
  export interface StorageMetrics {
126
123
  /**
127
124
  * Size of operations (bucket_data)
@@ -208,7 +205,9 @@ export interface SyncRulesBucketStorageListener extends DisposableListener {
208
205
  batchStarted: (batch: BucketStorageBatch) => void;
209
206
  }
210
207
 
211
- export interface SyncRulesBucketStorage extends DisposableObserverClient<SyncRulesBucketStorageListener> {
208
+ export interface SyncRulesBucketStorage
209
+ extends DisposableObserverClient<SyncRulesBucketStorageListener>,
210
+ SyncStorageWriteCheckpointAPI {
212
211
  readonly group_id: number;
213
212
  readonly slot_name: string;
214
213
 
@@ -221,7 +220,7 @@ export interface SyncRulesBucketStorage extends DisposableObserverClient<SyncRul
221
220
  callback: (batch: BucketStorageBatch) => Promise<void>
222
221
  ): Promise<FlushedResult | null>;
223
222
 
224
- getCheckpoint(): Promise<Checkpoint>;
223
+ getCheckpoint(): Promise<{ checkpoint: util.OpId }>;
225
224
 
226
225
  getParsedSyncRules(options: ParseSyncRulesOptions): SqlSyncRules;
227
226
 
@@ -387,14 +386,8 @@ export type SaveOp = 'insert' | 'update' | 'delete';
387
386
 
388
387
  export type SaveOptions = SaveInsert | SaveUpdate | SaveDelete;
389
388
 
390
- export enum SaveOperationTag {
391
- INSERT = 'insert',
392
- UPDATE = 'update',
393
- DELETE = 'delete'
394
- }
395
-
396
389
  export interface SaveInsert {
397
- tag: SaveOperationTag.INSERT;
390
+ tag: 'insert';
398
391
  sourceTable: SourceTable;
399
392
  before?: undefined;
400
393
  beforeReplicaId?: undefined;
@@ -403,7 +396,7 @@ export interface SaveInsert {
403
396
  }
404
397
 
405
398
  export interface SaveUpdate {
406
- tag: SaveOperationTag.UPDATE;
399
+ tag: 'update';
407
400
  sourceTable: SourceTable;
408
401
 
409
402
  /**
@@ -422,7 +415,7 @@ export interface SaveUpdate {
422
415
  }
423
416
 
424
417
  export interface SaveDelete {
425
- tag: SaveOperationTag.DELETE;
418
+ tag: 'delete';
426
419
  sourceTable: SourceTable;
427
420
  before?: SqliteRow;
428
421
  beforeReplicaId: ReplicaId;
@@ -21,20 +21,13 @@ import {
21
21
  UpdateSyncRulesOptions,
22
22
  WriteCheckpoint
23
23
  } from './BucketStorage.js';
24
- import { PowerSyncMongo } from './mongo/db.js';
24
+ import { PowerSyncMongo, PowerSyncMongoOptions } from './mongo/db.js';
25
25
  import { SyncRuleDocument, SyncRuleState } from './mongo/models.js';
26
26
  import { MongoPersistedSyncRulesContent } from './mongo/MongoPersistedSyncRulesContent.js';
27
27
  import { MongoSyncBucketStorage } from './mongo/MongoSyncBucketStorage.js';
28
- import { MongoWriteCheckpointAPI } from './mongo/MongoWriteCheckpointAPI.js';
29
28
  import { generateSlotName } from './mongo/util.js';
30
- import {
31
- CustomWriteCheckpointOptions,
32
- DEFAULT_WRITE_CHECKPOINT_MODE,
33
- LastWriteCheckpointFilters,
34
- ManagedWriteCheckpointOptions,
35
- WriteCheckpointAPI,
36
- WriteCheckpointMode
37
- } from './write-checkpoint.js';
29
+
30
+ export interface MongoBucketStorageOptions extends PowerSyncMongoOptions {}
38
31
 
39
32
  export class MongoBucketStorage
40
33
  extends DisposableObserver<BucketStorageFactoryListener>
@@ -45,10 +38,6 @@ export class MongoBucketStorage
45
38
  // TODO: This is still Postgres specific and needs to be reworked
46
39
  public readonly slot_name_prefix: string;
47
40
 
48
- readonly write_checkpoint_mode: WriteCheckpointMode;
49
-
50
- protected readonly writeCheckpointAPI: WriteCheckpointAPI;
51
-
52
41
  private readonly storageCache = new LRUCache<number, MongoSyncBucketStorage>({
53
42
  max: 3,
54
43
  fetchMethod: async (id) => {
@@ -76,7 +65,6 @@ export class MongoBucketStorage
76
65
  db: PowerSyncMongo,
77
66
  options: {
78
67
  slot_name_prefix: string;
79
- write_checkpoint_mode?: WriteCheckpointMode;
80
68
  }
81
69
  ) {
82
70
  super();
@@ -84,11 +72,6 @@ export class MongoBucketStorage
84
72
  this.db = db;
85
73
  this.session = this.client.startSession();
86
74
  this.slot_name_prefix = options.slot_name_prefix;
87
- this.write_checkpoint_mode = options.write_checkpoint_mode ?? DEFAULT_WRITE_CHECKPOINT_MODE;
88
- this.writeCheckpointAPI = new MongoWriteCheckpointAPI({
89
- db,
90
- mode: this.write_checkpoint_mode
91
- });
92
75
  }
93
76
 
94
77
  getInstance(options: PersistedSyncRulesContent): MongoSyncBucketStorage {
@@ -297,22 +280,6 @@ export class MongoBucketStorage
297
280
  });
298
281
  }
299
282
 
300
- async batchCreateCustomWriteCheckpoints(checkpoints: CustomWriteCheckpointOptions[]): Promise<void> {
301
- return this.writeCheckpointAPI.batchCreateCustomWriteCheckpoints(checkpoints);
302
- }
303
-
304
- async createCustomWriteCheckpoint(options: CustomWriteCheckpointOptions): Promise<bigint> {
305
- return this.writeCheckpointAPI.createCustomWriteCheckpoint(options);
306
- }
307
-
308
- async createManagedWriteCheckpoint(options: ManagedWriteCheckpointOptions): Promise<bigint> {
309
- return this.writeCheckpointAPI.createManagedWriteCheckpoint(options);
310
- }
311
-
312
- async lastWriteCheckpoint(filters: LastWriteCheckpointFilters): Promise<bigint | null> {
313
- return this.writeCheckpointAPI.lastWriteCheckpoint(filters);
314
- }
315
-
316
283
  async getActiveCheckpoint(): Promise<ActiveCheckpoint> {
317
284
  const doc = await this.db.sync_rules.findOne(
318
285
  {
@@ -424,7 +391,7 @@ export class MongoBucketStorage
424
391
  }
425
392
  return (await this.storageCache.fetch(doc._id)) ?? null;
426
393
  }
427
- };
394
+ } satisfies ActiveCheckpoint;
428
395
  }
429
396
 
430
397
  /**
@@ -514,6 +481,7 @@ export class MongoBucketStorage
514
481
  if (doc == null) {
515
482
  continue;
516
483
  }
484
+
517
485
  const op = this.makeActiveCheckpoint(doc);
518
486
  // Check for LSN / checkpoint changes - ignore other metadata changes
519
487
  if (lastOp == null || op.lsn != lastOp.lsn || op.checkpoint != lastOp.checkpoint) {
@@ -544,12 +512,14 @@ export class MongoBucketStorage
544
512
  // 1. checkpoint (op_id) changes.
545
513
  // 2. write checkpoint changes for the specific user
546
514
  const bucketStorage = await cp.getBucketStorage();
515
+ if (!bucketStorage) {
516
+ continue;
517
+ }
547
518
 
548
519
  const lsnFilters: Record<string, string> = lsn ? { 1: lsn } : {};
549
520
 
550
- const currentWriteCheckpoint = await this.lastWriteCheckpoint({
521
+ const currentWriteCheckpoint = await bucketStorage.lastWriteCheckpoint({
551
522
  user_id,
552
- sync_rules_id: bucketStorage?.group_id,
553
523
  heads: {
554
524
  ...lsnFilters
555
525
  }
@@ -1,17 +1,12 @@
1
1
  import { DisposableListener, DisposableObserver, logger } from '@powersync/lib-services-framework';
2
2
  import { ResolvedPowerSyncConfig } from '../util/util-index.js';
3
3
  import { BucketStorageFactory } from './BucketStorage.js';
4
- import { ActiveStorage, BucketStorageProvider, StorageSettings } from './StorageProvider.js';
5
- import { DEFAULT_WRITE_CHECKPOINT_MODE } from './write-checkpoint.js';
4
+ import { ActiveStorage, BucketStorageProvider } from './StorageProvider.js';
6
5
 
7
6
  export type StorageEngineOptions = {
8
7
  configuration: ResolvedPowerSyncConfig;
9
8
  };
10
9
 
11
- export const DEFAULT_STORAGE_SETTINGS: StorageSettings = {
12
- writeCheckpointMode: DEFAULT_WRITE_CHECKPOINT_MODE
13
- };
14
-
15
10
  export interface StorageEngineListener extends DisposableListener {
16
11
  storageActivated: (storage: BucketStorageFactory) => void;
17
12
  }
@@ -20,11 +15,9 @@ export class StorageEngine extends DisposableObserver<StorageEngineListener> {
20
15
  // TODO: This will need to revisited when we actually support multiple storage providers.
21
16
  private storageProviders: Map<string, BucketStorageProvider> = new Map();
22
17
  private currentActiveStorage: ActiveStorage | null = null;
23
- private _activeSettings: StorageSettings;
24
18
 
25
19
  constructor(private options: StorageEngineOptions) {
26
20
  super();
27
- this._activeSettings = DEFAULT_STORAGE_SETTINGS;
28
21
  }
29
22
 
30
23
  get activeBucketStorage(): BucketStorageFactory {
@@ -39,20 +32,6 @@ export class StorageEngine extends DisposableObserver<StorageEngineListener> {
39
32
  return this.currentActiveStorage;
40
33
  }
41
34
 
42
- get activeSettings(): StorageSettings {
43
- return { ...this._activeSettings };
44
- }
45
-
46
- updateSettings(settings: Partial<StorageSettings>) {
47
- if (this.currentActiveStorage) {
48
- throw new Error(`Storage is already active, settings cannot be modified.`);
49
- }
50
- this._activeSettings = {
51
- ...this._activeSettings,
52
- ...settings
53
- };
54
- }
55
-
56
35
  /**
57
36
  * Register a provider which generates a {@link BucketStorageFactory}
58
37
  * given the matching config specified in the loaded {@link ResolvedPowerSyncConfig}
@@ -65,8 +44,7 @@ export class StorageEngine extends DisposableObserver<StorageEngineListener> {
65
44
  logger.info('Starting Storage Engine...');
66
45
  const { configuration } = this.options;
67
46
  this.currentActiveStorage = await this.storageProviders.get(configuration.storage.type)!.getStorage({
68
- resolvedConfig: configuration,
69
- ...this.activeSettings
47
+ resolvedConfig: configuration
70
48
  });
71
49
  this.iterateListeners((cb) => cb.storageActivated?.(this.activeBucketStorage));
72
50
  logger.info(`Successfully activated storage: ${configuration.storage.type}.`);
@@ -1,6 +1,5 @@
1
1
  import * as util from '../util/util-index.js';
2
2
  import { BucketStorageFactory } from './BucketStorage.js';
3
- import { WriteCheckpointMode } from './write-checkpoint.js';
4
3
 
5
4
  export interface ActiveStorage {
6
5
  storage: BucketStorageFactory;
@@ -12,14 +11,7 @@ export interface ActiveStorage {
12
11
  tearDown(): Promise<boolean>;
13
12
  }
14
13
 
15
- /**
16
- * Settings which can be modified by various modules in their initialization.
17
- */
18
- export interface StorageSettings {
19
- writeCheckpointMode: WriteCheckpointMode;
20
- }
21
-
22
- export interface GetStorageOptions extends StorageSettings {
14
+ export interface GetStorageOptions {
23
15
  // TODO: This should just be the storage config. Update once the slot name prefix coupling has been removed from the storage
24
16
  resolvedConfig: util.ResolvedPowerSyncConfig;
25
17
  }
@@ -3,7 +3,7 @@ export enum WriteCheckpointMode {
3
3
  * Raw mappings of `user_id` to `write_checkpoint`s should
4
4
  * be supplied for each set of sync rules.
5
5
  */
6
- CUSTOM = 'manual',
6
+ CUSTOM = 'custom',
7
7
  /**
8
8
  * Write checkpoints are stored as a mapping of `user_id` plus
9
9
  * replication HEAD (lsn in Postgres) to an automatically generated
@@ -26,19 +26,19 @@ export interface CustomWriteCheckpointFilters extends BaseWriteCheckpointIdentif
26
26
  sync_rules_id: number;
27
27
  }
28
28
 
29
- export interface CustomWriteCheckpointOptions extends CustomWriteCheckpointFilters {
29
+ export interface BatchedCustomWriteCheckpointOptions extends BaseWriteCheckpointIdentifier {
30
30
  /**
31
31
  * A supplied incrementing Write Checkpoint number
32
32
  */
33
33
  checkpoint: bigint;
34
34
  }
35
35
 
36
- /**
37
- * Options for creating a custom Write Checkpoint in a batch.
38
- * A {@link BucketStorageBatch} is already associated with a Sync Rules instance.
39
- * The `sync_rules_id` is not required here.
40
- */
41
- export type BatchedCustomWriteCheckpointOptions = Omit<CustomWriteCheckpointOptions, 'sync_rules_id'>;
36
+ export interface CustomWriteCheckpointOptions extends BatchedCustomWriteCheckpointOptions {
37
+ /**
38
+ * Sync rules which were active when this checkpoint was created.
39
+ */
40
+ sync_rules_id: number;
41
+ }
42
42
 
43
43
  /**
44
44
  * Managed Write Checkpoints are a mapping of User ID to replication HEAD
@@ -52,15 +52,33 @@ export interface ManagedWriteCheckpointFilters extends BaseWriteCheckpointIdenti
52
52
 
53
53
  export type ManagedWriteCheckpointOptions = ManagedWriteCheckpointFilters;
54
54
 
55
+ export type SyncStorageLastWriteCheckpointFilters = BaseWriteCheckpointIdentifier | ManagedWriteCheckpointFilters;
55
56
  export type LastWriteCheckpointFilters = CustomWriteCheckpointFilters | ManagedWriteCheckpointFilters;
56
57
 
57
- export interface WriteCheckpointAPI {
58
- batchCreateCustomWriteCheckpoints(checkpoints: CustomWriteCheckpointOptions[]): Promise<void>;
59
-
60
- createCustomWriteCheckpoint(checkpoint: CustomWriteCheckpointOptions): Promise<bigint>;
61
-
58
+ export interface BaseWriteCheckpointAPI {
59
+ readonly writeCheckpointMode: WriteCheckpointMode;
60
+ setWriteCheckpointMode(mode: WriteCheckpointMode): void;
62
61
  createManagedWriteCheckpoint(checkpoint: ManagedWriteCheckpointOptions): Promise<bigint>;
62
+ }
63
+
64
+ /**
65
+ * Write Checkpoint API to be used in conjunction with a {@link SyncRulesBucketStorage}.
66
+ * This storage corresponds with a set of sync rules. These APIs don't require specifying a
67
+ * sync rules id.
68
+ */
69
+ export interface SyncStorageWriteCheckpointAPI extends BaseWriteCheckpointAPI {
70
+ batchCreateCustomWriteCheckpoints(checkpoints: BatchedCustomWriteCheckpointOptions[]): Promise<void>;
71
+ createCustomWriteCheckpoint(checkpoint: BatchedCustomWriteCheckpointOptions): Promise<bigint>;
72
+ lastWriteCheckpoint(filters: SyncStorageLastWriteCheckpointFilters): Promise<bigint | null>;
73
+ }
63
74
 
75
+ /**
76
+ * Write Checkpoint API which is interfaced directly with the storage layer. This requires
77
+ * sync rules identifiers for custom write checkpoints.
78
+ */
79
+ export interface WriteCheckpointAPI extends BaseWriteCheckpointAPI {
80
+ batchCreateCustomWriteCheckpoints(checkpoints: CustomWriteCheckpointOptions[]): Promise<void>;
81
+ createCustomWriteCheckpoint(checkpoint: CustomWriteCheckpointOptions): Promise<bigint>;
64
82
  lastWriteCheckpoint(filters: LastWriteCheckpointFilters): Promise<bigint | null>;
65
83
  }
66
84
 
@@ -12,7 +12,7 @@ import {
12
12
  SaveOptions
13
13
  } from '../BucketStorage.js';
14
14
  import { SourceTable } from '../SourceTable.js';
15
- import { CustomWriteCheckpointOptions } from '../write-checkpoint.js';
15
+ import { BatchedCustomWriteCheckpointOptions, CustomWriteCheckpointOptions } from '../WriteCheckpointAPI.js';
16
16
  import { PowerSyncMongo } from './db.js';
17
17
  import { CurrentBucket, CurrentDataDocument, SourceKey, SyncRuleDocument } from './models.js';
18
18
  import { MongoIdSequence } from './MongoIdSequence.js';
@@ -81,10 +81,9 @@ 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
- addCustomWriteCheckpoint(checkpoint: CustomWriteCheckpointOptions): void {
86
+ addCustomWriteCheckpoint(checkpoint: BatchedCustomWriteCheckpointOptions): void {
88
87
  this.write_checkpoint_batch.push({
89
88
  ...checkpoint,
90
89
  sync_rules_id: this.group_id
@@ -19,8 +19,7 @@ export class MongoStorageProvider implements BucketStorageProvider {
19
19
  return {
20
20
  storage: new MongoBucketStorage(database, {
21
21
  // TODO currently need the entire resolved config due to this
22
- slot_name_prefix: resolvedConfig.slot_name_prefix,
23
- write_checkpoint_mode: options.writeCheckpointMode
22
+ slot_name_prefix: resolvedConfig.slot_name_prefix
24
23
  }),
25
24
  shutDown: () => client.close(),
26
25
  tearDown: () => {
@@ -2,13 +2,13 @@ import { SqliteJsonRow, SqliteJsonValue, SqlSyncRules } from '@powersync/service
2
2
  import * as bson from 'bson';
3
3
  import * as mongo from 'mongodb';
4
4
 
5
- import { DisposableObserver } from '@powersync/lib-services-framework';
5
+ import { DisposableObserver, logger } from '@powersync/lib-services-framework';
6
+ import * as timers from 'timers/promises';
6
7
  import * as db from '../../db/db-index.js';
7
8
  import * as util from '../../util/util-index.js';
8
9
  import {
9
10
  BucketDataBatchOptions,
10
11
  BucketStorageBatch,
11
- Checkpoint,
12
12
  CompactOptions,
13
13
  DEFAULT_DOCUMENT_BATCH_LIMIT,
14
14
  DEFAULT_DOCUMENT_CHUNK_LIMIT_BYTES,
@@ -27,13 +27,19 @@ import {
27
27
  import { ChecksumCache, FetchPartialBucketChecksum, PartialChecksum, PartialChecksumMap } from '../ChecksumCache.js';
28
28
  import { MongoBucketStorage } from '../MongoBucketStorage.js';
29
29
  import { SourceTable } from '../SourceTable.js';
30
+ import {
31
+ BatchedCustomWriteCheckpointOptions,
32
+ ManagedWriteCheckpointOptions,
33
+ SyncStorageLastWriteCheckpointFilters,
34
+ WriteCheckpointAPI,
35
+ WriteCheckpointMode
36
+ } from '../WriteCheckpointAPI.js';
30
37
  import { PowerSyncMongo } from './db.js';
31
38
  import { BucketDataDocument, BucketDataKey, SourceKey, SyncRuleState } from './models.js';
32
39
  import { MongoBucketBatch } from './MongoBucketBatch.js';
33
40
  import { MongoCompactor } from './MongoCompactor.js';
41
+ import { MongoWriteCheckpointAPI } from './MongoWriteCheckpointAPI.js';
34
42
  import { BSON_DESERIALIZE_OPTIONS, idPrefixFilter, mapOpEntry, readSingleBatch, serializeLookup } from './util.js';
35
- import { logger } from '@powersync/lib-services-framework';
36
- import * as timers from 'timers/promises';
37
43
 
38
44
  export class MongoSyncBucketStorage
39
45
  extends DisposableObserver<SyncRulesBucketStorageListener>
@@ -47,15 +53,53 @@ export class MongoSyncBucketStorage
47
53
  });
48
54
 
49
55
  private parsedSyncRulesCache: SqlSyncRules | undefined;
56
+ private writeCheckpointAPI: WriteCheckpointAPI;
50
57
 
51
58
  constructor(
52
59
  public readonly factory: MongoBucketStorage,
53
60
  public readonly group_id: number,
54
61
  private readonly sync_rules: PersistedSyncRulesContent,
55
- public readonly slot_name: string
62
+ public readonly slot_name: string,
63
+ writeCheckpointMode: WriteCheckpointMode = WriteCheckpointMode.MANAGED
56
64
  ) {
57
65
  super();
58
66
  this.db = factory.db;
67
+ this.writeCheckpointAPI = new MongoWriteCheckpointAPI({
68
+ db: this.db,
69
+ mode: writeCheckpointMode
70
+ });
71
+ }
72
+
73
+ get writeCheckpointMode() {
74
+ return this.writeCheckpointAPI.writeCheckpointMode;
75
+ }
76
+
77
+ setWriteCheckpointMode(mode: WriteCheckpointMode): void {
78
+ this.writeCheckpointAPI.setWriteCheckpointMode(mode);
79
+ }
80
+
81
+ batchCreateCustomWriteCheckpoints(checkpoints: BatchedCustomWriteCheckpointOptions[]): Promise<void> {
82
+ return this.writeCheckpointAPI.batchCreateCustomWriteCheckpoints(
83
+ checkpoints.map((checkpoint) => ({ ...checkpoint, sync_rules_id: this.group_id }))
84
+ );
85
+ }
86
+
87
+ createCustomWriteCheckpoint(checkpoint: BatchedCustomWriteCheckpointOptions): Promise<bigint> {
88
+ return this.writeCheckpointAPI.createCustomWriteCheckpoint({
89
+ ...checkpoint,
90
+ sync_rules_id: this.group_id
91
+ });
92
+ }
93
+
94
+ createManagedWriteCheckpoint(checkpoint: ManagedWriteCheckpointOptions): Promise<bigint> {
95
+ return this.writeCheckpointAPI.createManagedWriteCheckpoint(checkpoint);
96
+ }
97
+
98
+ lastWriteCheckpoint(filters: SyncStorageLastWriteCheckpointFilters): Promise<bigint | null> {
99
+ return this.writeCheckpointAPI.lastWriteCheckpoint({
100
+ ...filters,
101
+ sync_rules_id: this.group_id
102
+ });
59
103
  }
60
104
 
61
105
  getParsedSyncRules(options: ParseSyncRulesOptions): SqlSyncRules {
@@ -63,16 +107,15 @@ export class MongoSyncBucketStorage
63
107
  return this.parsedSyncRulesCache;
64
108
  }
65
109
 
66
- async getCheckpoint(): Promise<Checkpoint> {
110
+ async getCheckpoint() {
67
111
  const doc = await this.db.sync_rules.findOne(
68
112
  { _id: this.group_id },
69
113
  {
70
- projection: { last_checkpoint: 1, last_checkpoint_lsn: 1 }
114
+ projection: { last_checkpoint: 1 }
71
115
  }
72
116
  );
73
117
  return {
74
- checkpoint: util.timestampToOpId(doc?.last_checkpoint ?? 0n),
75
- lsn: doc?.last_checkpoint_lsn ?? null
118
+ checkpoint: util.timestampToOpId(doc?.last_checkpoint ?? 0n)
76
119
  };
77
120
  }
78
121
 
@@ -7,7 +7,7 @@ import {
7
7
  ManagedWriteCheckpointOptions,
8
8
  WriteCheckpointAPI,
9
9
  WriteCheckpointMode
10
- } from '../write-checkpoint.js';
10
+ } from '../WriteCheckpointAPI.js';
11
11
  import { PowerSyncMongo } from './db.js';
12
12
 
13
13
  export type MongoCheckpointAPIOptions = {
@@ -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 writeCheckpointMode() {
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,9 +37,9 @@ export class MongoWriteCheckpointAPI implements WriteCheckpointAPI {
29
37
  }
30
38
 
31
39
  async createCustomWriteCheckpoint(options: CustomWriteCheckpointOptions): Promise<bigint> {
32
- if (this.mode !== WriteCheckpointMode.CUSTOM) {
40
+ if (this.writeCheckpointMode !== WriteCheckpointMode.CUSTOM) {
33
41
  throw new framework.errors.ValidationError(
34
- `Creating a custom Write Checkpoint when the current Write Checkpoint mode is set to "${this.mode}"`
42
+ `Creating a custom Write Checkpoint when the current Write Checkpoint mode is set to "${this.writeCheckpointMode}"`
35
43
  );
36
44
  }
37
45
 
@@ -52,9 +60,9 @@ export class MongoWriteCheckpointAPI implements WriteCheckpointAPI {
52
60
  }
53
61
 
54
62
  async createManagedWriteCheckpoint(checkpoint: ManagedWriteCheckpointOptions): Promise<bigint> {
55
- if (this.mode !== WriteCheckpointMode.MANAGED) {
63
+ if (this.writeCheckpointMode !== WriteCheckpointMode.MANAGED) {
56
64
  throw new framework.errors.ValidationError(
57
- `Creating a managed Write Checkpoint when the current Write Checkpoint mode is set to "${this.mode}"`
65
+ `Attempting to create a managed Write Checkpoint when the current Write Checkpoint mode is set to "${this.writeCheckpointMode}"`
58
66
  );
59
67
  }
60
68
 
@@ -77,7 +85,7 @@ export class MongoWriteCheckpointAPI implements WriteCheckpointAPI {
77
85
  }
78
86
 
79
87
  async lastWriteCheckpoint(filters: LastWriteCheckpointFilters): Promise<bigint | null> {
80
- switch (this.mode) {
88
+ switch (this.writeCheckpointMode) {
81
89
  case WriteCheckpointMode.CUSTOM:
82
90
  if (false == 'sync_rules_id' in filters) {
83
91
  throw new framework.errors.ValidationError(`Sync rules ID is required for custom Write Checkpoint filtering`);
@@ -5,6 +5,7 @@ export * from './SourceEntity.js';
5
5
  export * from './SourceTable.js';
6
6
  export * from './StorageEngine.js';
7
7
 
8
+ export * from './mongo/config.js';
8
9
  export * from './mongo/db.js';
9
10
  export * from './mongo/models.js';
10
11
  export * from './mongo/MongoBucketBatch.js';
@@ -17,5 +18,4 @@ export * from './mongo/MongoSyncRulesLock.js';
17
18
  export * from './mongo/OperationBatch.js';
18
19
  export * from './mongo/PersistedBatch.js';
19
20
  export * from './mongo/util.js';
20
- export * from './mongo/config.js';
21
- export * from './write-checkpoint.js';
21
+ export * from './WriteCheckpointAPI.js';
@@ -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;