@powersync/service-module-postgres 0.0.0-dev-20260203155513 → 0.0.0-dev-20260223082111

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.
@@ -1,23 +1,27 @@
1
1
  import { PgManager } from '@module/replication/PgManager.js';
2
2
  import { PUBLICATION_NAME, WalStream, WalStreamOptions } from '@module/replication/WalStream.js';
3
+ import { CustomTypeRegistry } from '@module/types/registry.js';
3
4
  import {
4
5
  BucketStorageFactory,
5
6
  createCoreReplicationMetrics,
6
7
  initializeCoreReplicationMetrics,
7
8
  InternalOpId,
9
+ LEGACY_STORAGE_VERSION,
8
10
  OplogEntry,
11
+ STORAGE_VERSION_CONFIG,
9
12
  storage,
10
- SyncRulesBucketStorage
13
+ SyncRulesBucketStorage,
14
+ updateSyncRulesFromYaml
11
15
  } from '@powersync/service-core';
12
16
  import { METRICS_HELPER, test_utils } from '@powersync/service-core-tests';
13
17
  import * as pgwire from '@powersync/service-jpgwire';
14
18
  import { clearTestDb, getClientCheckpoint, TEST_CONNECTION_OPTIONS } from './util.js';
15
- import { CustomTypeRegistry } from '@module/types/registry.js';
16
19
 
17
20
  export class WalStreamTestContext implements AsyncDisposable {
18
21
  private _walStream?: WalStream;
19
22
  private abortController = new AbortController();
20
23
  private streamPromise?: Promise<void>;
24
+ private syncRulesId?: number;
21
25
  public storage?: SyncRulesBucketStorage;
22
26
  private replicationConnection?: pgwire.PgConnection;
23
27
  private snapshotPromise?: Promise<void>;
@@ -30,7 +34,7 @@ export class WalStreamTestContext implements AsyncDisposable {
30
34
  */
31
35
  static async open(
32
36
  factory: (options: storage.TestStorageOptions) => Promise<BucketStorageFactory>,
33
- options?: { doNotClear?: boolean; walStreamOptions?: Partial<WalStreamOptions> }
37
+ options?: { doNotClear?: boolean; storageVersion?: number; walStreamOptions?: Partial<WalStreamOptions> }
34
38
  ) {
35
39
  const f = await factory({ doNotClear: options?.doNotClear });
36
40
  const connectionManager = new PgManager(TEST_CONNECTION_OPTIONS, {});
@@ -39,13 +43,18 @@ export class WalStreamTestContext implements AsyncDisposable {
39
43
  await clearTestDb(connectionManager.pool);
40
44
  }
41
45
 
42
- return new WalStreamTestContext(f, connectionManager, options?.walStreamOptions);
46
+ const storageVersion = options?.storageVersion ?? LEGACY_STORAGE_VERSION;
47
+ const versionedBuckets = STORAGE_VERSION_CONFIG[storageVersion]?.versionedBuckets ?? false;
48
+
49
+ return new WalStreamTestContext(f, connectionManager, options?.walStreamOptions, storageVersion, versionedBuckets);
43
50
  }
44
51
 
45
52
  constructor(
46
53
  public factory: BucketStorageFactory,
47
54
  public connectionManager: PgManager,
48
- private walStreamOptions?: Partial<WalStreamOptions>
55
+ private walStreamOptions?: Partial<WalStreamOptions>,
56
+ private storageVersion: number = LEGACY_STORAGE_VERSION,
57
+ private versionedBuckets: boolean = STORAGE_VERSION_CONFIG[storageVersion]?.versionedBuckets ?? false
49
58
  ) {
50
59
  createCoreReplicationMetrics(METRICS_HELPER.metricsEngine);
51
60
  initializeCoreReplicationMetrics(METRICS_HELPER.metricsEngine);
@@ -95,7 +104,10 @@ export class WalStreamTestContext implements AsyncDisposable {
95
104
  }
96
105
 
97
106
  async updateSyncRules(content: string) {
98
- const syncRules = await this.factory.updateSyncRules({ content: content, validate: true });
107
+ const syncRules = await this.factory.updateSyncRules(
108
+ updateSyncRulesFromYaml(content, { validate: true, storageVersion: this.storageVersion })
109
+ );
110
+ this.syncRulesId = syncRules.id;
99
111
  this.storage = this.factory.getInstance(syncRules);
100
112
  return this.storage!;
101
113
  }
@@ -106,6 +118,7 @@ export class WalStreamTestContext implements AsyncDisposable {
106
118
  throw new Error(`Next sync rules not available`);
107
119
  }
108
120
 
121
+ this.syncRulesId = syncRules.id;
109
122
  this.storage = this.factory.getInstance(syncRules);
110
123
  return this.storage!;
111
124
  }
@@ -116,6 +129,7 @@ export class WalStreamTestContext implements AsyncDisposable {
116
129
  throw new Error(`Active sync rules not available`);
117
130
  }
118
131
 
132
+ this.syncRulesId = syncRules.id;
119
133
  this.storage = this.factory.getInstance(syncRules);
120
134
  return this.storage!;
121
135
  }
@@ -177,9 +191,21 @@ export class WalStreamTestContext implements AsyncDisposable {
177
191
  return checkpoint;
178
192
  }
179
193
 
194
+ private resolveBucketName(bucket: string) {
195
+ if (!this.versionedBuckets || /^\d+#/.test(bucket)) {
196
+ return bucket;
197
+ }
198
+ if (this.syncRulesId == null) {
199
+ throw new Error('Sync rules not configured - call updateSyncRules() first');
200
+ }
201
+ return `${this.syncRulesId}#${bucket}`;
202
+ }
203
+
180
204
  async getBucketsDataBatch(buckets: Record<string, InternalOpId>, options?: { timeout?: number }) {
181
205
  let checkpoint = await this.getCheckpoint(options);
182
- const map = new Map<string, InternalOpId>(Object.entries(buckets));
206
+ const map = new Map<string, InternalOpId>(
207
+ Object.entries(buckets).map(([bucket, opId]) => [this.resolveBucketName(bucket), opId])
208
+ );
183
209
  return test_utils.fromAsync(this.storage!.getBucketDataBatch(checkpoint, map));
184
210
  }
185
211
 
@@ -191,8 +217,9 @@ export class WalStreamTestContext implements AsyncDisposable {
191
217
  if (typeof start == 'string') {
192
218
  start = BigInt(start);
193
219
  }
220
+ const resolvedBucket = this.resolveBucketName(bucket);
194
221
  const checkpoint = await this.getCheckpoint(options);
195
- const map = new Map<string, InternalOpId>([[bucket, start]]);
222
+ const map = new Map<string, InternalOpId>([[resolvedBucket, start]]);
196
223
  let data: OplogEntry[] = [];
197
224
  while (true) {
198
225
  const batch = this.storage!.getBucketDataBatch(checkpoint, map);
@@ -202,11 +229,29 @@ export class WalStreamTestContext implements AsyncDisposable {
202
229
  if (batches.length == 0 || !batches[0]!.chunkData.has_more) {
203
230
  break;
204
231
  }
205
- map.set(bucket, BigInt(batches[0]!.chunkData.next_after));
232
+ map.set(resolvedBucket, BigInt(batches[0]!.chunkData.next_after));
206
233
  }
207
234
  return data;
208
235
  }
209
236
 
237
+ async getChecksums(buckets: string[], options?: { timeout?: number }) {
238
+ const checkpoint = await this.getCheckpoint(options);
239
+ const versionedBuckets = buckets.map((bucket) => this.resolveBucketName(bucket));
240
+ const checksums = await this.storage!.getChecksums(checkpoint, versionedBuckets);
241
+
242
+ const unversioned = new Map();
243
+ for (let i = 0; i < buckets.length; i++) {
244
+ unversioned.set(buckets[i], checksums.get(versionedBuckets[i])!);
245
+ }
246
+
247
+ return unversioned;
248
+ }
249
+
250
+ async getChecksum(bucket: string, options?: { timeout?: number }) {
251
+ const checksums = await this.getChecksums([bucket], options);
252
+ return checksums.get(bucket);
253
+ }
254
+
210
255
  /**
211
256
  * This does not wait for a client checkpoint.
212
257
  */
@@ -215,8 +260,9 @@ export class WalStreamTestContext implements AsyncDisposable {
215
260
  if (typeof start == 'string') {
216
261
  start = BigInt(start);
217
262
  }
263
+ const resolvedBucket = this.resolveBucketName(bucket);
218
264
  const { checkpoint } = await this.storage!.getCheckpoint();
219
- const map = new Map<string, InternalOpId>([[bucket, start]]);
265
+ const map = new Map<string, InternalOpId>([[resolvedBucket, start]]);
220
266
  const batch = this.storage!.getBucketDataBatch(checkpoint, map);
221
267
  const batches = await test_utils.fromAsync(batch);
222
268
  return batches[0]?.chunkData.data ?? [];
@@ -1,17 +1,13 @@
1
1
  {
2
- "extends": "../../../tsconfig.base.json",
2
+ "extends": "../../../tsconfig.tests.json",
3
3
  "compilerOptions": {
4
- "rootDir": "src",
5
4
  "baseUrl": "./",
6
- "noEmit": true,
7
- "esModuleInterop": true,
8
- "skipLibCheck": true,
9
- "sourceMap": true,
10
5
  "paths": {
11
6
  "@/*": ["../../../packages/service-core/src/*"],
12
7
  "@module/*": ["../src/*"],
13
8
  "@core-tests/*": ["../../../packages/service-core/test/src/*"]
14
- }
9
+ },
10
+ "rootDir": "src"
15
11
  },
16
12
  "include": ["src"],
17
13
  "references": [