@powersync/service-module-postgres 0.0.4 → 0.1.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.
@@ -1,29 +1,10 @@
1
1
  import { fromAsync } from '@core-tests/stream_utils.js';
2
2
  import { PgManager } from '@module/replication/PgManager.js';
3
3
  import { PUBLICATION_NAME, WalStream, WalStreamOptions } from '@module/replication/WalStream.js';
4
- import { BucketStorageFactory, SyncRulesBucketStorage } from '@powersync/service-core';
4
+ import { BucketStorageFactory, OplogEntry, SyncRulesBucketStorage } from '@powersync/service-core';
5
5
  import * as pgwire from '@powersync/service-jpgwire';
6
6
  import { clearTestDb, getClientCheckpoint, TEST_CONNECTION_OPTIONS } from './util.js';
7
-
8
- /**
9
- * Tests operating on the wal stream need to configure the stream and manage asynchronous
10
- * replication, which gets a little tricky.
11
- *
12
- * This wraps a test in a function that configures all the context, and tears it down afterwards.
13
- */
14
- export function walStreamTest(
15
- factory: () => Promise<BucketStorageFactory>,
16
- test: (context: WalStreamTestContext) => Promise<void>
17
- ): () => Promise<void> {
18
- return async () => {
19
- const f = await factory();
20
- const connectionManager = new PgManager(TEST_CONNECTION_OPTIONS, {});
21
-
22
- await clearTestDb(connectionManager.pool);
23
- await using context = new WalStreamTestContext(f, connectionManager);
24
- await test(context);
25
- };
26
- }
7
+ import { StorageOptions } from '@core-tests/util.js';
27
8
 
28
9
  export class WalStreamTestContext implements AsyncDisposable {
29
10
  private _walStream?: WalStream;
@@ -32,12 +13,36 @@ export class WalStreamTestContext implements AsyncDisposable {
32
13
  public storage?: SyncRulesBucketStorage;
33
14
  private replicationConnection?: pgwire.PgConnection;
34
15
 
16
+ /**
17
+ * Tests operating on the wal stream need to configure the stream and manage asynchronous
18
+ * replication, which gets a little tricky.
19
+ *
20
+ * This configures all the context, and tears it down afterwards.
21
+ */
22
+ static async open(
23
+ factory: (options: StorageOptions) => Promise<BucketStorageFactory>,
24
+ options?: { doNotClear?: boolean }
25
+ ) {
26
+ const f = await factory({ doNotClear: options?.doNotClear });
27
+ const connectionManager = new PgManager(TEST_CONNECTION_OPTIONS, {});
28
+
29
+ if (!options?.doNotClear) {
30
+ await clearTestDb(connectionManager.pool);
31
+ }
32
+
33
+ return new WalStreamTestContext(f, connectionManager);
34
+ }
35
+
35
36
  constructor(
36
37
  public factory: BucketStorageFactory,
37
38
  public connectionManager: PgManager
38
39
  ) {}
39
40
 
40
41
  async [Symbol.asyncDispose]() {
42
+ await this.dispose();
43
+ }
44
+
45
+ async dispose() {
41
46
  this.abortController.abort();
42
47
  await this.streamPromise;
43
48
  await this.connectionManager.destroy();
@@ -62,6 +67,16 @@ export class WalStreamTestContext implements AsyncDisposable {
62
67
  return this.storage!;
63
68
  }
64
69
 
70
+ async loadNextSyncRules() {
71
+ const syncRules = await this.factory.getNextSyncRulesContent();
72
+ if (syncRules == null) {
73
+ throw new Error(`Next sync rules not available`);
74
+ }
75
+
76
+ this.storage = this.factory.getInstance(syncRules);
77
+ return this.storage!;
78
+ }
79
+
65
80
  get walStream() {
66
81
  if (this.storage == null) {
67
82
  throw new Error('updateSyncRules() first');
@@ -110,9 +125,33 @@ export class WalStreamTestContext implements AsyncDisposable {
110
125
  return fromAsync(this.storage!.getBucketDataBatch(checkpoint, map));
111
126
  }
112
127
 
128
+ /**
129
+ * This waits for a client checkpoint.
130
+ */
113
131
  async getBucketData(bucket: string, start?: string, options?: { timeout?: number }) {
114
132
  start ??= '0';
115
- let checkpoint = await this.getCheckpoint(options);
133
+ const checkpoint = await this.getCheckpoint(options);
134
+ const map = new Map<string, string>([[bucket, start]]);
135
+ let data: OplogEntry[] = [];
136
+ while (true) {
137
+ const batch = this.storage!.getBucketDataBatch(checkpoint, map);
138
+
139
+ const batches = await fromAsync(batch);
140
+ data = data.concat(batches[0]?.batch.data ?? []);
141
+ if (batches.length == 0 || !batches[0]!.batch.has_more) {
142
+ break;
143
+ }
144
+ map.set(bucket, batches[0]!.batch.next_after);
145
+ }
146
+ return data;
147
+ }
148
+
149
+ /**
150
+ * This does not wait for a client checkpoint.
151
+ */
152
+ async getCurrentBucketData(bucket: string, start?: string) {
153
+ start ??= '0';
154
+ const { checkpoint } = await this.storage!.getCheckpoint();
116
155
  const map = new Map<string, string>([[bucket, start]]);
117
156
  const batch = this.storage!.getBucketDataBatch(checkpoint, map);
118
157
  const batches = await fromAsync(batch);