@powersync/service-module-postgres-storage 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/@types/migrations/scripts/1771424826685-current-data-pending-deletes.d.ts +3 -0
  4. package/dist/@types/storage/PostgresBucketStorageFactory.d.ts +4 -0
  5. package/dist/@types/storage/PostgresCompactor.d.ts +8 -2
  6. package/dist/@types/storage/PostgresSyncRulesStorage.d.ts +5 -3
  7. package/dist/@types/storage/batch/OperationBatch.d.ts +2 -2
  8. package/dist/@types/storage/batch/PostgresBucketBatch.d.ts +12 -9
  9. package/dist/@types/storage/batch/PostgresPersistedBatch.d.ts +17 -5
  10. package/dist/@types/storage/current-data-store.d.ts +85 -0
  11. package/dist/@types/storage/current-data-table.d.ts +9 -0
  12. package/dist/@types/storage/table-id.d.ts +2 -0
  13. package/dist/@types/types/models/CurrentData.d.ts +18 -3
  14. package/dist/@types/utils/bson.d.ts +1 -1
  15. package/dist/@types/utils/test-utils.d.ts +1 -1
  16. package/dist/migrations/scripts/1771424826685-current-data-pending-deletes.js +8 -0
  17. package/dist/migrations/scripts/1771424826685-current-data-pending-deletes.js.map +1 -0
  18. package/dist/storage/PostgresBucketStorageFactory.js +41 -4
  19. package/dist/storage/PostgresBucketStorageFactory.js.map +1 -1
  20. package/dist/storage/PostgresCompactor.js +14 -6
  21. package/dist/storage/PostgresCompactor.js.map +1 -1
  22. package/dist/storage/PostgresSyncRulesStorage.js +23 -15
  23. package/dist/storage/PostgresSyncRulesStorage.js.map +1 -1
  24. package/dist/storage/batch/OperationBatch.js +2 -1
  25. package/dist/storage/batch/OperationBatch.js.map +1 -1
  26. package/dist/storage/batch/PostgresBucketBatch.js +286 -213
  27. package/dist/storage/batch/PostgresBucketBatch.js.map +1 -1
  28. package/dist/storage/batch/PostgresPersistedBatch.js +86 -81
  29. package/dist/storage/batch/PostgresPersistedBatch.js.map +1 -1
  30. package/dist/storage/current-data-store.js +270 -0
  31. package/dist/storage/current-data-store.js.map +1 -0
  32. package/dist/storage/current-data-table.js +22 -0
  33. package/dist/storage/current-data-table.js.map +1 -0
  34. package/dist/storage/table-id.js +8 -0
  35. package/dist/storage/table-id.js.map +1 -0
  36. package/dist/types/models/CurrentData.js +11 -2
  37. package/dist/types/models/CurrentData.js.map +1 -1
  38. package/dist/utils/bson.js.map +1 -1
  39. package/dist/utils/db.js +9 -0
  40. package/dist/utils/db.js.map +1 -1
  41. package/dist/utils/test-utils.js +13 -6
  42. package/dist/utils/test-utils.js.map +1 -1
  43. package/package.json +8 -8
  44. package/src/migrations/scripts/1771424826685-current-data-pending-deletes.ts +10 -0
  45. package/src/storage/PostgresBucketStorageFactory.ts +53 -5
  46. package/src/storage/PostgresCompactor.ts +17 -8
  47. package/src/storage/PostgresSyncRulesStorage.ts +30 -17
  48. package/src/storage/batch/OperationBatch.ts +4 -3
  49. package/src/storage/batch/PostgresBucketBatch.ts +306 -238
  50. package/src/storage/batch/PostgresPersistedBatch.ts +92 -84
  51. package/src/storage/current-data-store.ts +326 -0
  52. package/src/storage/current-data-table.ts +26 -0
  53. package/src/storage/table-id.ts +9 -0
  54. package/src/types/models/CurrentData.ts +17 -4
  55. package/src/utils/bson.ts +1 -1
  56. package/src/utils/db.ts +10 -0
  57. package/src/utils/test-utils.ts +14 -7
  58. package/test/src/__snapshots__/storage.test.ts.snap +151 -0
  59. package/test/src/__snapshots__/storage_compacting.test.ts.snap +17 -0
  60. package/test/src/__snapshots__/storage_sync.test.ts.snap +1095 -0
  61. package/test/src/migrations.test.ts +1 -1
  62. package/test/src/storage.test.ts +136 -130
  63. package/test/src/storage_compacting.test.ts +65 -3
  64. package/test/src/storage_sync.test.ts +11 -9
  65. package/test/src/util.ts +4 -4
@@ -14,6 +14,7 @@ import {
14
14
  PopulateChecksumCacheResults,
15
15
  ReplicationCheckpoint,
16
16
  storage,
17
+ StorageVersionConfig,
17
18
  utils,
18
19
  WatchWriteCheckpointOptions
19
20
  } from '@powersync/service-core';
@@ -35,6 +36,7 @@ import { PostgresBucketBatch } from './batch/PostgresBucketBatch.js';
35
36
  import { PostgresWriteCheckpointAPI } from './checkpoints/PostgresWriteCheckpointAPI.js';
36
37
  import { PostgresBucketStorageFactory } from './PostgresBucketStorageFactory.js';
37
38
  import { PostgresCompactor } from './PostgresCompactor.js';
39
+ import { PostgresCurrentDataStore } from './current-data-store.js';
38
40
 
39
41
  export type PostgresSyncRulesStorageOptions = {
40
42
  factory: PostgresBucketStorageFactory;
@@ -52,11 +54,13 @@ export class PostgresSyncRulesStorage
52
54
  public readonly sync_rules: storage.PersistedSyncRulesContent;
53
55
  public readonly slot_name: string;
54
56
  public readonly factory: PostgresBucketStorageFactory;
57
+ public readonly storageConfig: StorageVersionConfig;
55
58
 
56
59
  private sharedIterator = new BroadcastIterable((signal) => this.watchActiveCheckpoint(signal));
57
60
 
58
61
  protected db: lib_postgres.DatabaseClient;
59
62
  protected writeCheckpointAPI: PostgresWriteCheckpointAPI;
63
+ private readonly currentDataStore: PostgresCurrentDataStore;
60
64
 
61
65
  // TODO we might be able to share this in an abstract class
62
66
  private parsedSyncRulesCache:
@@ -71,6 +75,8 @@ export class PostgresSyncRulesStorage
71
75
  this.sync_rules = options.sync_rules;
72
76
  this.slot_name = options.sync_rules.slot_name;
73
77
  this.factory = options.factory;
78
+ this.storageConfig = options.sync_rules.getStorageConfig();
79
+ this.currentDataStore = new PostgresCurrentDataStore(this.storageConfig);
74
80
 
75
81
  this.writeCheckpointAPI = new PostgresWriteCheckpointAPI({
76
82
  db: this.db,
@@ -121,8 +127,18 @@ export class PostgresSyncRulesStorage
121
127
  `.execute();
122
128
  }
123
129
 
124
- compact(options?: storage.CompactOptions): Promise<void> {
125
- return new PostgresCompactor(this.db, this.group_id, options).compact();
130
+ async compact(options?: storage.CompactOptions): Promise<void> {
131
+ let maxOpId = options?.maxOpId;
132
+ if (maxOpId == null) {
133
+ const checkpoint = await this.getCheckpoint();
134
+ // Note: If there is no active checkpoint, this will be 0, in which case no compacting is performed
135
+ maxOpId = checkpoint.checkpoint;
136
+ }
137
+
138
+ return new PostgresCompactor(this.db, this.group_id, {
139
+ ...options,
140
+ maxOpId
141
+ }).compact();
126
142
  }
127
143
 
128
144
  async populatePersistentChecksumCache(options: PopulateChecksumCacheOptions): Promise<PopulateChecksumCacheResults> {
@@ -355,12 +371,12 @@ export class PostgresSyncRulesStorage
355
371
  slot_name: this.slot_name,
356
372
  last_checkpoint_lsn: checkpoint_lsn,
357
373
  keep_alive_op: syncRules?.keepalive_op,
358
- no_checkpoint_before_lsn: syncRules?.no_checkpoint_before ?? options.zeroLSN,
359
374
  resumeFromLsn: maxLsn(syncRules?.snapshot_lsn, checkpoint_lsn),
360
375
  store_current_data: options.storeCurrentData,
361
376
  skip_existing_rows: options.skipExistingRows ?? false,
362
377
  batch_limits: this.options.batchLimits,
363
- markRecordUnavailable: options.markRecordUnavailable
378
+ markRecordUnavailable: options.markRecordUnavailable,
379
+ storageConfig: this.storageConfig
364
380
  });
365
381
  this.iterateListeners((cb) => cb.batchStarted?.(batch));
366
382
 
@@ -415,10 +431,10 @@ export class PostgresSyncRulesStorage
415
431
 
416
432
  async *getBucketDataBatch(
417
433
  checkpoint: InternalOpId,
418
- dataBuckets: Map<string, InternalOpId>,
434
+ dataBuckets: storage.BucketDataRequest[],
419
435
  options?: storage.BucketDataBatchOptions
420
436
  ): AsyncIterable<storage.SyncBucketDataChunk> {
421
- if (dataBuckets.size == 0) {
437
+ if (dataBuckets.length == 0) {
422
438
  return;
423
439
  }
424
440
 
@@ -430,10 +446,8 @@ export class PostgresSyncRulesStorage
430
446
  // not match up with chunks.
431
447
 
432
448
  const end = checkpoint ?? BIGINT_MAX;
433
- const filters = Array.from(dataBuckets.entries()).map(([name, start]) => ({
434
- bucket_name: name,
435
- start: start
436
- }));
449
+ const filters = dataBuckets.map((request) => ({ bucket_name: request.bucket, start: request.start }));
450
+ const startOpByBucket = new Map(dataBuckets.map((request) => [request.bucket, request.start]));
437
451
 
438
452
  const batchRowLimit = options?.limit ?? storage.DEFAULT_DOCUMENT_BATCH_LIMIT;
439
453
  const chunkSizeLimitBytes = options?.chunkLimitBytes ?? storage.DEFAULT_DOCUMENT_CHUNK_LIMIT_BYTES;
@@ -533,7 +547,7 @@ export class PostgresSyncRulesStorage
533
547
  }
534
548
 
535
549
  if (start == null) {
536
- const startOpId = dataBuckets.get(bucket_name);
550
+ const startOpId = startOpByBucket.get(bucket_name);
537
551
  if (startOpId == null) {
538
552
  throw new framework.ServiceAssertionError(`data for unexpected bucket: ${bucket_name}`);
539
553
  }
@@ -588,7 +602,10 @@ export class PostgresSyncRulesStorage
588
602
  }
589
603
  }
590
604
 
591
- async getChecksums(checkpoint: utils.InternalOpId, buckets: string[]): Promise<utils.ChecksumMap> {
605
+ async getChecksums(
606
+ checkpoint: utils.InternalOpId,
607
+ buckets: storage.BucketChecksumRequest[]
608
+ ): Promise<utils.ChecksumMap> {
592
609
  return this.checksumCache.getChecksumMap(checkpoint, buckets);
593
610
  }
594
611
 
@@ -662,11 +679,7 @@ export class PostgresSyncRulesStorage
662
679
  group_id = ${{ type: 'int4', value: this.group_id }}
663
680
  `.execute();
664
681
 
665
- await this.db.sql`
666
- DELETE FROM current_data
667
- WHERE
668
- group_id = ${{ type: 'int4', value: this.group_id }}
669
- `.execute();
682
+ await this.currentDataStore.deleteGroupRows(this.db, { groupId: this.group_id });
670
683
 
671
684
  await this.db.sql`
672
685
  DELETE FROM source_tables
@@ -5,6 +5,7 @@
5
5
 
6
6
  import { storage, utils } from '@powersync/service-core';
7
7
  import { RequiredOperationBatchLimits } from '../../types/types.js';
8
+ import { postgresTableId } from '../table-id.js';
8
9
 
9
10
  /**
10
11
  * Batch of input operations.
@@ -89,13 +90,13 @@ export class RecordOperation {
89
90
  /**
90
91
  * In-memory cache key - must not be persisted.
91
92
  */
92
- export function cacheKey(sourceTableId: string, id: storage.ReplicaId) {
93
+ export function cacheKey(sourceTableId: storage.SourceTableId, id: storage.ReplicaId) {
93
94
  return encodedCacheKey(sourceTableId, storage.serializeReplicaId(id));
94
95
  }
95
96
 
96
97
  /**
97
98
  * Calculates a cache key for a stored ReplicaId. This is usually stored as a bytea/Buffer.
98
99
  */
99
- export function encodedCacheKey(sourceTableId: string, storedKey: Buffer) {
100
- return `${sourceTableId}.${storedKey.toString('base64')}`;
100
+ export function encodedCacheKey(sourceTableId: storage.SourceTableId, storedKey: Buffer) {
101
+ return `${postgresTableId(sourceTableId)}.${storedKey.toString('base64')}`;
101
102
  }