@powersync/service-module-postgres-storage 0.13.4 → 0.14.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.
- package/CHANGELOG.md +37 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/storage/PostgresSyncRulesStorage.d.ts +3 -2
- package/dist/@types/storage/batch/PostgresBucketBatch.d.ts +1 -1
- package/dist/migrations/scripts/1771424826685-current-data-pending-deletes.js +1 -1
- package/dist/storage/PostgresBucketStorageFactory.js +5 -5
- package/dist/storage/PostgresBucketStorageFactory.js.map +1 -1
- package/dist/storage/PostgresSyncRulesStorage.js +70 -38
- package/dist/storage/PostgresSyncRulesStorage.js.map +1 -1
- package/dist/storage/batch/PostgresBucketBatch.js +2 -2
- package/dist/storage/batch/PostgresBucketBatch.js.map +1 -1
- package/dist/storage/checkpoints/PostgresWriteCheckpointAPI.js +1 -1
- package/dist/storage/checkpoints/PostgresWriteCheckpointAPI.js.map +1 -1
- package/dist/storage/sync-rules/PostgresPersistedSyncRulesContent.js +3 -3
- package/dist/storage/sync-rules/PostgresPersistedSyncRulesContent.js.map +1 -1
- package/package.json +11 -11
- package/src/migrations/scripts/1771424826685-current-data-pending-deletes.ts +1 -1
- package/src/storage/PostgresBucketStorageFactory.ts +6 -5
- package/src/storage/PostgresSyncRulesStorage.ts +78 -38
- package/src/storage/batch/PostgresBucketBatch.ts +2 -2
- package/src/storage/checkpoints/PostgresWriteCheckpointAPI.ts +3 -1
- package/src/storage/sync-rules/PostgresPersistedSyncRulesContent.ts +3 -6
- package/test/tsconfig.json +0 -1
- package/dist/@types/storage/current-data-table.d.ts +0 -9
- package/dist/storage/current-data-table.js +0 -22
- package/dist/storage/current-data-table.js.map +0 -1
- package/src/storage/current-data-table.ts +0 -26
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as lib_postgres from '@powersync/lib-service-postgres';
|
|
2
|
-
import { ErrorCode,
|
|
2
|
+
import { ErrorCode, ServiceError } from '@powersync/lib-services-framework';
|
|
3
3
|
import { storage } from '@powersync/service-core';
|
|
4
4
|
export class PostgresPersistedSyncRulesContent extends storage.PersistedSyncRulesContent {
|
|
5
5
|
db;
|
|
@@ -26,14 +26,14 @@ export class PostgresPersistedSyncRulesContent extends storage.PersistedSyncRule
|
|
|
26
26
|
});
|
|
27
27
|
const lockHandle = await manager.acquire();
|
|
28
28
|
if (!lockHandle) {
|
|
29
|
-
throw new ServiceError(ErrorCode.PSYNC_S1003, `
|
|
29
|
+
throw new ServiceError(ErrorCode.PSYNC_S1003, `Replication stream is locked by another process, standing by.`);
|
|
30
30
|
}
|
|
31
31
|
const interval = setInterval(async () => {
|
|
32
32
|
try {
|
|
33
33
|
await lockHandle.refresh();
|
|
34
34
|
}
|
|
35
35
|
catch (e) {
|
|
36
|
-
logger.error('Failed to refresh lock', e);
|
|
36
|
+
this.logger.error('Failed to refresh lock', e);
|
|
37
37
|
clearInterval(interval);
|
|
38
38
|
}
|
|
39
39
|
}, 30_130);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PostgresPersistedSyncRulesContent.js","sourceRoot":"","sources":["../../../src/storage/sync-rules/PostgresPersistedSyncRulesContent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"PostgresPersistedSyncRulesContent.js","sourceRoot":"","sources":["../../../src/storage/sync-rules/PostgresPersistedSyncRulesContent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAC5E,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAGlD,MAAM,OAAO,iCAAkC,SAAQ,OAAO,CAAC,yBAAyB;IAI5E;IAHV,YAAY,GAAmC,IAAI,CAAC;IAEpD,YACU,EAA+B,EACvC,GAA4B;QAE5B,KAAK,CAAC;YACJ,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YAClB,kBAAkB,EAAE,GAAG,CAAC,OAAO;YAC/B,aAAa,EAAE,GAAG,CAAC,SAAS;YAC5B,mBAAmB,EAAE,GAAG,CAAC,mBAAmB;YAC5C,SAAS,EAAE,GAAG,CAAC,SAAS;YACxB,gBAAgB,EAAE,GAAG,CAAC,gBAAgB;YACtC,kBAAkB,EAAE,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,IAAI;YACpF,iBAAiB,EAAE,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,IAAI;YACjF,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,QAAQ;YAC7B,cAAc,EAAE,GAAG,CAAC,eAAe,IAAI,OAAO,CAAC,sBAAsB;SACtE,CAAC,CAAC;QAdK,OAAE,GAAF,EAAE,CAA6B;IAezC,CAAC;IAED,KAAK,CAAC,IAAI;QACR,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,mBAAmB,CAAC;YACnD,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,IAAI,EAAE,cAAc,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,SAAS,EAAE;SAChD,CAAC,CAAC;QACH,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;QAC3C,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,YAAY,CAAC,SAAS,CAAC,WAAW,EAAE,+DAA+D,CAAC,CAAC;QACjH,CAAC;QAED,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;YACtC,IAAI,CAAC;gBACH,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC;YAC7B,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,wBAAwB,EAAE,CAAC,CAAC,CAAC;gBAC/C,aAAa,CAAC,QAAQ,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC,EAAE,MAAM,CAAC,CAAC;QAEX,OAAO,CAAC,IAAI,CAAC,YAAY,GAAG;YAC1B,aAAa,EAAE,IAAI,CAAC,EAAE;YACtB,OAAO,EAAE,KAAK,IAAI,EAAE;gBAClB,aAAa,CAAC,QAAQ,CAAC,CAAC;gBACxB,OAAO,UAAU,CAAC,OAAO,EAAE,CAAC;YAC9B,CAAC;SACF,CAAC,CAAC;IACL,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@powersync/service-module-postgres-storage",
|
|
3
3
|
"repository": "https://github.com/powersync-ja/powersync-service",
|
|
4
4
|
"types": "dist/@types/index.d.ts",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.14.0",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"license": "FSL-1.1-ALv2",
|
|
8
8
|
"type": "module",
|
|
@@ -27,18 +27,18 @@
|
|
|
27
27
|
"ix": "^5.0.0",
|
|
28
28
|
"lru-cache": "^10.2.2",
|
|
29
29
|
"ts-codec": "^1.3.0",
|
|
30
|
-
"uuid": "^
|
|
31
|
-
"@powersync/lib-service-postgres": "0.4.
|
|
32
|
-
"@powersync/lib-services-framework": "0.9.
|
|
33
|
-
"@powersync/service-core": "1.
|
|
34
|
-
"@powersync/service-types": "0.15.
|
|
35
|
-
"@powersync/service-jpgwire": "0.21.
|
|
36
|
-
"@powersync/service-jsonbig": "0.17.
|
|
37
|
-
"@powersync/service-sync-rules": "0.
|
|
30
|
+
"uuid": "^14.0.0",
|
|
31
|
+
"@powersync/lib-service-postgres": "0.4.27",
|
|
32
|
+
"@powersync/lib-services-framework": "0.9.4",
|
|
33
|
+
"@powersync/service-core": "1.21.0",
|
|
34
|
+
"@powersync/service-types": "0.15.2",
|
|
35
|
+
"@powersync/service-jpgwire": "0.21.18",
|
|
36
|
+
"@powersync/service-jsonbig": "0.17.13",
|
|
37
|
+
"@powersync/service-sync-rules": "0.36.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"typescript": "~
|
|
41
|
-
"@powersync/service-core-tests": "0.15.
|
|
40
|
+
"typescript": "~6.0.3",
|
|
41
|
+
"@powersync/service-core-tests": "0.15.5"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsc -b",
|
|
@@ -2,7 +2,7 @@ import { migrations } from '@powersync/service-core';
|
|
|
2
2
|
|
|
3
3
|
export const up: migrations.PowerSyncMigrationFunction = async (_context) => {
|
|
4
4
|
// No-op.
|
|
5
|
-
// Pending-delete support is now storage-version specific and initialized when v3 sync
|
|
5
|
+
// Pending-delete support is now storage-version specific and initialized when v3 sync config is deployed.
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
export const down: migrations.PowerSyncMigrationFunction = async (_context) => {
|
|
@@ -154,7 +154,8 @@ export class PostgresBucketStorageFactory extends storage.BucketStorageFactory {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
async updateSyncRules(options: storage.UpdateSyncRulesOptions): Promise<PostgresPersistedSyncRulesContent> {
|
|
157
|
-
const storageVersion =
|
|
157
|
+
const storageVersion =
|
|
158
|
+
options.storageVersion ?? options.config.parsed.config.storageVersion ?? storage.CURRENT_STORAGE_VERSION;
|
|
158
159
|
const storageConfig = storage.STORAGE_VERSION_CONFIG[storageVersion];
|
|
159
160
|
if (storageConfig == null) {
|
|
160
161
|
throw new framework.ServiceError(
|
|
@@ -255,10 +256,10 @@ export class PostgresBucketStorageFactory extends storage.BucketStorageFactory {
|
|
|
255
256
|
const next = await this.getNextSyncRulesContent();
|
|
256
257
|
const active = await this.getActiveSyncRulesContent();
|
|
257
258
|
|
|
258
|
-
// In both the below cases, we create a new
|
|
259
|
+
// In both the below cases, we create a new replication stream.
|
|
259
260
|
// The current one will continue serving sync requests until the next one has finished processing.
|
|
260
261
|
if (next != null && next.id == sync_rules_group_id) {
|
|
261
|
-
// We need to redo the "next"
|
|
262
|
+
// We need to redo the "next" replication stream
|
|
262
263
|
|
|
263
264
|
await this.updateSyncRules(next.asUpdateOptions());
|
|
264
265
|
// Pro-actively stop replicating
|
|
@@ -271,7 +272,7 @@ export class PostgresBucketStorageFactory extends storage.BucketStorageFactory {
|
|
|
271
272
|
AND state = ${{ value: storage.SyncRuleState.PROCESSING, type: 'varchar' }}
|
|
272
273
|
`.execute();
|
|
273
274
|
} else if (next == null && active?.id == sync_rules_group_id) {
|
|
274
|
-
// Slot removed for "active"
|
|
275
|
+
// Slot removed for "active" replication stream, while there is no "next" one.
|
|
275
276
|
await this.updateSyncRules(active.asUpdateOptions());
|
|
276
277
|
|
|
277
278
|
// Pro-actively stop replicating, but still serve clients with existing data
|
|
@@ -284,7 +285,7 @@ export class PostgresBucketStorageFactory extends storage.BucketStorageFactory {
|
|
|
284
285
|
AND state = ${{ value: storage.SyncRuleState.ACTIVE, type: 'varchar' }}
|
|
285
286
|
`.execute();
|
|
286
287
|
} else if (next != null && active?.id == sync_rules_group_id) {
|
|
287
|
-
// Already have "next"
|
|
288
|
+
// Already have "next" replication stream - don't update any.
|
|
288
289
|
|
|
289
290
|
// Pro-actively stop replicating, but still serve clients with existing data
|
|
290
291
|
await this.db.sql`
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
internalToExternalOpId,
|
|
10
10
|
LastValueSink,
|
|
11
11
|
maxLsn,
|
|
12
|
+
ParameterSetLimitExceededError,
|
|
12
13
|
PartialChecksum,
|
|
13
14
|
PopulateChecksumCacheOptions,
|
|
14
15
|
PopulateChecksumCacheResults,
|
|
@@ -22,7 +23,7 @@ import { JSONBig } from '@powersync/service-jsonbig';
|
|
|
22
23
|
import * as sync_rules from '@powersync/service-sync-rules';
|
|
23
24
|
import * as timers from 'timers/promises';
|
|
24
25
|
import * as uuid from 'uuid';
|
|
25
|
-
import { BIGINT_MAX } from '../types/codecs.js';
|
|
26
|
+
import { bigint, BIGINT_MAX } from '../types/codecs.js';
|
|
26
27
|
import { models, RequiredOperationBatchLimits } from '../types/types.js';
|
|
27
28
|
import { replicaIdToSubkey } from '../utils/bson.js';
|
|
28
29
|
import { mapOpEntry } from '../utils/bucket-data.js';
|
|
@@ -30,6 +31,7 @@ import { mapOpEntry } from '../utils/bucket-data.js';
|
|
|
30
31
|
import * as framework from '@powersync/lib-services-framework';
|
|
31
32
|
import { StatementParam } from '@powersync/service-jpgwire';
|
|
32
33
|
import { wrapWithAbort } from 'ix/asynciterable/operators/withabort.js';
|
|
34
|
+
import * as t from 'ts-codec';
|
|
33
35
|
import { SourceTableDecoded, StoredRelationId } from '../types/models/SourceTable.js';
|
|
34
36
|
import { pick } from '../utils/ts-codec.js';
|
|
35
37
|
import { PostgresBucketBatch } from './batch/PostgresBucketBatch.js';
|
|
@@ -57,6 +59,7 @@ export class PostgresSyncRulesStorage
|
|
|
57
59
|
public readonly slot_name: string;
|
|
58
60
|
public readonly factory: PostgresBucketStorageFactory;
|
|
59
61
|
public readonly storageConfig: StorageVersionConfig;
|
|
62
|
+
public readonly logger: framework.Logger;
|
|
60
63
|
|
|
61
64
|
private sharedIterator = new BroadcastIterable((signal) => this.watchActiveCheckpoint(signal));
|
|
62
65
|
|
|
@@ -79,6 +82,7 @@ export class PostgresSyncRulesStorage
|
|
|
79
82
|
this.factory = options.factory;
|
|
80
83
|
this.storageConfig = options.sync_rules.getStorageConfig();
|
|
81
84
|
this.currentDataStore = new PostgresCurrentDataStore(this.storageConfig);
|
|
85
|
+
this.logger = options.sync_rules.logger;
|
|
82
86
|
|
|
83
87
|
this.writeCheckpointAPI = new PostgresWriteCheckpointAPI({
|
|
84
88
|
db: this.db,
|
|
@@ -108,8 +112,8 @@ export class PostgresSyncRulesStorage
|
|
|
108
112
|
getParsedSyncRules(options: storage.ParseSyncRulesOptions): sync_rules.HydratedSyncRules {
|
|
109
113
|
const { parsed, options: cachedOptions } = this.parsedSyncRulesCache ?? {};
|
|
110
114
|
/**
|
|
111
|
-
* Check if the cached sync
|
|
112
|
-
* Parse sync
|
|
115
|
+
* Check if the cached sync config, if present, had the same options.
|
|
116
|
+
* Parse sync config if the options are different or if there is no cached value.
|
|
113
117
|
*/
|
|
114
118
|
if (!parsed || options.defaultSchema != cachedOptions?.defaultSchema) {
|
|
115
119
|
this.parsedSyncRulesCache = { parsed: this.sync_rules.parsed(options).hydratedSyncRules(), options };
|
|
@@ -139,11 +143,12 @@ export class PostgresSyncRulesStorage
|
|
|
139
143
|
|
|
140
144
|
return new PostgresCompactor(this.db, this.group_id, {
|
|
141
145
|
...options,
|
|
142
|
-
maxOpId
|
|
146
|
+
maxOpId,
|
|
147
|
+
logger: this.logger
|
|
143
148
|
}).compact();
|
|
144
149
|
}
|
|
145
150
|
|
|
146
|
-
async populatePersistentChecksumCache(
|
|
151
|
+
async populatePersistentChecksumCache(_options: PopulateChecksumCacheOptions): Promise<PopulateChecksumCacheResults> {
|
|
147
152
|
// no-op - checksum cache is not implemented for Postgres yet
|
|
148
153
|
return { buckets: 0 };
|
|
149
154
|
}
|
|
@@ -363,7 +368,7 @@ export class PostgresSyncRulesStorage
|
|
|
363
368
|
const checkpoint_lsn = syncRules?.last_checkpoint_lsn ?? null;
|
|
364
369
|
|
|
365
370
|
const writer = new PostgresBucketBatch({
|
|
366
|
-
logger: options.logger ??
|
|
371
|
+
logger: options.logger ?? this.logger,
|
|
367
372
|
db: this.db,
|
|
368
373
|
sync_rules: this.sync_rules.parsed(options).hydratedSyncRules(),
|
|
369
374
|
group_id: this.group_id,
|
|
@@ -396,42 +401,69 @@ export class PostgresSyncRulesStorage
|
|
|
396
401
|
|
|
397
402
|
async getParameterSets(
|
|
398
403
|
checkpoint: ReplicationCheckpoint,
|
|
399
|
-
lookups: sync_rules.ScopedParameterLookup[]
|
|
400
|
-
|
|
404
|
+
lookups: sync_rules.ScopedParameterLookup[],
|
|
405
|
+
limit: number
|
|
406
|
+
): Promise<sync_rules.ParameterLookupRows[]> {
|
|
401
407
|
const rows = await this.db.sql`
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
bucket_parameters
|
|
408
|
-
FROM
|
|
409
|
-
bucket_parameters
|
|
410
|
-
WHERE
|
|
411
|
-
group_id = ${{ type: 'int4', value: this.group_id }}
|
|
412
|
-
AND lookup = ANY (
|
|
413
|
-
SELECT
|
|
414
|
-
decode((FILTER ->> 0)::text, 'hex') -- Decode the hex string to bytea
|
|
408
|
+
WITH
|
|
409
|
+
rows AS (
|
|
410
|
+
SELECT DISTINCT
|
|
411
|
+
ON (lookup, source_table, source_key) requested.index - 1 AS index,
|
|
412
|
+
bucket_parameters
|
|
415
413
|
FROM
|
|
414
|
+
bucket_parameters,
|
|
416
415
|
jsonb_array_elements(${{
|
|
417
416
|
type: 'jsonb',
|
|
418
417
|
value: lookups.map((l) => storage.serializeLookupBuffer(l).toString('hex'))
|
|
419
|
-
}}) AS
|
|
418
|
+
}}) WITH ORDINALITY AS requested (value, index)
|
|
419
|
+
WHERE
|
|
420
|
+
group_id = ${{ type: 'int4', value: this.group_id }}
|
|
421
|
+
AND lookup = decode((requested.value ->> 0)::text, 'hex') -- Decode the hex string to bytea
|
|
422
|
+
AND id <= ${{ type: 'int8', value: checkpoint.checkpoint }}
|
|
423
|
+
ORDER BY
|
|
424
|
+
lookup,
|
|
425
|
+
source_table,
|
|
426
|
+
source_key,
|
|
427
|
+
id DESC
|
|
420
428
|
)
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
429
|
+
SELECT
|
|
430
|
+
index,
|
|
431
|
+
bucket_parameters
|
|
432
|
+
FROM
|
|
433
|
+
rows
|
|
434
|
+
WHERE
|
|
435
|
+
bucket_parameters != '[]'
|
|
436
|
+
LIMIT
|
|
437
|
+
${{ type: 'int4', value: limit + 1 }}
|
|
427
438
|
`
|
|
428
|
-
.decoded(
|
|
439
|
+
.decoded(parameterSetsRow)
|
|
429
440
|
.rows();
|
|
430
441
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
442
|
+
let totalRows = 0;
|
|
443
|
+
const resultsByLookup = new Map<sync_rules.ScopedParameterLookup, sync_rules.SqliteJsonRow[]>();
|
|
444
|
+
for (const row of rows) {
|
|
445
|
+
const parameterRows = JSONBig.parse(row.bucket_parameters) as sync_rules.SqliteJsonRow[];
|
|
446
|
+
const lookup = lookups[Number(row.index)];
|
|
447
|
+
totalRows += parameterRows.length;
|
|
448
|
+
|
|
449
|
+
const existingResults = resultsByLookup.get(lookup);
|
|
450
|
+
if (existingResults != null) {
|
|
451
|
+
existingResults.push(...parameterRows);
|
|
452
|
+
} else {
|
|
453
|
+
resultsByLookup.set(lookup, parameterRows);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (totalRows > limit) {
|
|
458
|
+
// Note that the LIMIT in the query allows more rows than parameters (because each row stores an array of
|
|
459
|
+
// parameter results). That array is very small though, and it doesn't allow fewer rows (due to the != []), so
|
|
460
|
+
// the SQL limit is good enough.
|
|
461
|
+
throw new ParameterSetLimitExceededError(limit);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const results: sync_rules.ParameterLookupRows[] = [];
|
|
465
|
+
resultsByLookup.forEach((rows, lookup) => results.push({ lookup, rows }));
|
|
466
|
+
return results;
|
|
435
467
|
}
|
|
436
468
|
|
|
437
469
|
async *getBucketDataBatch(
|
|
@@ -648,7 +680,7 @@ export class PostgresSyncRulesStorage
|
|
|
648
680
|
.first();
|
|
649
681
|
|
|
650
682
|
if (syncRulesRow == null) {
|
|
651
|
-
throw new Error('Cannot find
|
|
683
|
+
throw new Error('Cannot find replication stream status');
|
|
652
684
|
}
|
|
653
685
|
|
|
654
686
|
return {
|
|
@@ -841,7 +873,7 @@ export class PostgresSyncRulesStorage
|
|
|
841
873
|
|
|
842
874
|
if (doc == null) {
|
|
843
875
|
// Abort the connections - clients will have to retry later.
|
|
844
|
-
throw new framework.ServiceError(framework.ErrorCode.PSYNC_S2302, 'No active
|
|
876
|
+
throw new framework.ServiceError(framework.ErrorCode.PSYNC_S2302, 'No active replication stream available');
|
|
845
877
|
}
|
|
846
878
|
|
|
847
879
|
const sink = new LastValueSink<string>(undefined);
|
|
@@ -868,7 +900,7 @@ export class PostgresSyncRulesStorage
|
|
|
868
900
|
continue;
|
|
869
901
|
}
|
|
870
902
|
if (Number(notification.active_checkpoint.id) != doc.id) {
|
|
871
|
-
// Active
|
|
903
|
+
// Active replication stream changed - abort and restart the stream
|
|
872
904
|
break;
|
|
873
905
|
}
|
|
874
906
|
|
|
@@ -898,7 +930,15 @@ class PostgresReplicationCheckpoint implements storage.ReplicationCheckpoint {
|
|
|
898
930
|
public readonly lsn: string | null
|
|
899
931
|
) {}
|
|
900
932
|
|
|
901
|
-
getParameterSets(
|
|
902
|
-
|
|
933
|
+
getParameterSets(
|
|
934
|
+
lookups: sync_rules.ScopedParameterLookup[],
|
|
935
|
+
limit: number
|
|
936
|
+
): Promise<sync_rules.ParameterLookupRows[]> {
|
|
937
|
+
return this.storage.getParameterSets(this, lookups, limit);
|
|
903
938
|
}
|
|
904
939
|
}
|
|
940
|
+
|
|
941
|
+
const parameterSetsRow = t.object({
|
|
942
|
+
index: bigint,
|
|
943
|
+
bucket_parameters: t.string
|
|
944
|
+
});
|
|
@@ -52,7 +52,7 @@ export interface PostgresBucketBatchOptions {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
|
-
* Intermediate type which helps for only watching the active
|
|
55
|
+
* Intermediate type which helps for only watching the active replication stream
|
|
56
56
|
* via the Postgres NOTIFY protocol.
|
|
57
57
|
*/
|
|
58
58
|
const StatefulCheckpoint = models.ActiveCheckpoint.and(t.object({ state: t.Enum(storage.SyncRuleState) }));
|
|
@@ -1054,7 +1054,7 @@ export class PostgresBucketBatch
|
|
|
1054
1054
|
}
|
|
1055
1055
|
});
|
|
1056
1056
|
if (didActivate) {
|
|
1057
|
-
this.logger.info(`Activated new
|
|
1057
|
+
this.logger.info(`Activated new replication stream at ${lsn}`);
|
|
1058
1058
|
}
|
|
1059
1059
|
}
|
|
1060
1060
|
|
|
@@ -64,7 +64,9 @@ export class PostgresWriteCheckpointAPI implements storage.WriteCheckpointAPI {
|
|
|
64
64
|
switch (this.writeCheckpointMode) {
|
|
65
65
|
case storage.WriteCheckpointMode.CUSTOM:
|
|
66
66
|
if (false == 'sync_rules_id' in filters) {
|
|
67
|
-
throw new framework.errors.ValidationError(
|
|
67
|
+
throw new framework.errors.ValidationError(
|
|
68
|
+
`Replication stream ID is required for custom Write Checkpoint filtering`
|
|
69
|
+
);
|
|
68
70
|
}
|
|
69
71
|
return this.lastCustomWriteCheckpoint(filters as storage.CustomWriteCheckpointFilters);
|
|
70
72
|
case storage.WriteCheckpointMode.MANAGED:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as lib_postgres from '@powersync/lib-service-postgres';
|
|
2
|
-
import { ErrorCode,
|
|
2
|
+
import { ErrorCode, ServiceError } from '@powersync/lib-services-framework';
|
|
3
3
|
import { storage } from '@powersync/service-core';
|
|
4
4
|
import { models } from '../../types/types.js';
|
|
5
5
|
|
|
@@ -31,17 +31,14 @@ export class PostgresPersistedSyncRulesContent extends storage.PersistedSyncRule
|
|
|
31
31
|
});
|
|
32
32
|
const lockHandle = await manager.acquire();
|
|
33
33
|
if (!lockHandle) {
|
|
34
|
-
throw new ServiceError(
|
|
35
|
-
ErrorCode.PSYNC_S1003,
|
|
36
|
-
`Sync rules: ${this.id} have been locked by another process for replication.`
|
|
37
|
-
);
|
|
34
|
+
throw new ServiceError(ErrorCode.PSYNC_S1003, `Replication stream is locked by another process, standing by.`);
|
|
38
35
|
}
|
|
39
36
|
|
|
40
37
|
const interval = setInterval(async () => {
|
|
41
38
|
try {
|
|
42
39
|
await lockHandle.refresh();
|
|
43
40
|
} catch (e) {
|
|
44
|
-
logger.error('Failed to refresh lock', e);
|
|
41
|
+
this.logger.error('Failed to refresh lock', e);
|
|
45
42
|
clearInterval(interval);
|
|
46
43
|
}
|
|
47
44
|
}, 30_130);
|
package/test/tsconfig.json
CHANGED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { storage } from '@powersync/service-core';
|
|
2
|
-
export declare const V1_CURRENT_DATA_TABLE = "current_data";
|
|
3
|
-
export declare const V3_CURRENT_DATA_TABLE = "v3_current_data";
|
|
4
|
-
/**
|
|
5
|
-
* The table used by a specific storage version for general current_data access.
|
|
6
|
-
*/
|
|
7
|
-
export declare function getCommonCurrentDataTable(storageConfig: storage.StorageVersionConfig): "current_data" | "v3_current_data";
|
|
8
|
-
export declare function getV1CurrentDataTable(storageConfig: storage.StorageVersionConfig): string;
|
|
9
|
-
export declare function getV3CurrentDataTable(storageConfig: storage.StorageVersionConfig): string;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
2
|
-
export const V1_CURRENT_DATA_TABLE = 'current_data';
|
|
3
|
-
export const V3_CURRENT_DATA_TABLE = 'v3_current_data';
|
|
4
|
-
/**
|
|
5
|
-
* The table used by a specific storage version for general current_data access.
|
|
6
|
-
*/
|
|
7
|
-
export function getCommonCurrentDataTable(storageConfig) {
|
|
8
|
-
return storageConfig.softDeleteCurrentData ? V3_CURRENT_DATA_TABLE : V1_CURRENT_DATA_TABLE;
|
|
9
|
-
}
|
|
10
|
-
export function getV1CurrentDataTable(storageConfig) {
|
|
11
|
-
if (storageConfig.softDeleteCurrentData) {
|
|
12
|
-
throw new ServiceAssertionError('current_data table cannot be used when softDeleteCurrentData is enabled');
|
|
13
|
-
}
|
|
14
|
-
return V1_CURRENT_DATA_TABLE;
|
|
15
|
-
}
|
|
16
|
-
export function getV3CurrentDataTable(storageConfig) {
|
|
17
|
-
if (!storageConfig.softDeleteCurrentData) {
|
|
18
|
-
throw new ServiceAssertionError('v3_current_data table cannot be used when softDeleteCurrentData is disabled');
|
|
19
|
-
}
|
|
20
|
-
return V3_CURRENT_DATA_TABLE;
|
|
21
|
-
}
|
|
22
|
-
//# sourceMappingURL=current-data-table.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"current-data-table.js","sourceRoot":"","sources":["../../src/storage/current-data-table.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAG1E,MAAM,CAAC,MAAM,qBAAqB,GAAG,cAAc,CAAC;AACpD,MAAM,CAAC,MAAM,qBAAqB,GAAG,iBAAiB,CAAC;AAEvD;;GAEG;AACH,MAAM,UAAU,yBAAyB,CAAC,aAA2C;IACnF,OAAO,aAAa,CAAC,qBAAqB,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,qBAAqB,CAAC;AAC7F,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,aAA2C;IAC/E,IAAI,aAAa,CAAC,qBAAqB,EAAE,CAAC;QACxC,MAAM,IAAI,qBAAqB,CAAC,yEAAyE,CAAC,CAAC;IAC7G,CAAC;IACD,OAAO,qBAAqB,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,aAA2C;IAC/E,IAAI,CAAC,aAAa,CAAC,qBAAqB,EAAE,CAAC;QACzC,MAAM,IAAI,qBAAqB,CAAC,6EAA6E,CAAC,CAAC;IACjH,CAAC;IACD,OAAO,qBAAqB,CAAC;AAC/B,CAAC"}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
2
|
-
import { storage } from '@powersync/service-core';
|
|
3
|
-
|
|
4
|
-
export const V1_CURRENT_DATA_TABLE = 'current_data';
|
|
5
|
-
export const V3_CURRENT_DATA_TABLE = 'v3_current_data';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* The table used by a specific storage version for general current_data access.
|
|
9
|
-
*/
|
|
10
|
-
export function getCommonCurrentDataTable(storageConfig: storage.StorageVersionConfig) {
|
|
11
|
-
return storageConfig.softDeleteCurrentData ? V3_CURRENT_DATA_TABLE : V1_CURRENT_DATA_TABLE;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function getV1CurrentDataTable(storageConfig: storage.StorageVersionConfig) {
|
|
15
|
-
if (storageConfig.softDeleteCurrentData) {
|
|
16
|
-
throw new ServiceAssertionError('current_data table cannot be used when softDeleteCurrentData is enabled');
|
|
17
|
-
}
|
|
18
|
-
return V1_CURRENT_DATA_TABLE;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function getV3CurrentDataTable(storageConfig: storage.StorageVersionConfig) {
|
|
22
|
-
if (!storageConfig.softDeleteCurrentData) {
|
|
23
|
-
throw new ServiceAssertionError('v3_current_data table cannot be used when softDeleteCurrentData is disabled');
|
|
24
|
-
}
|
|
25
|
-
return V3_CURRENT_DATA_TABLE;
|
|
26
|
-
}
|