@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.
Files changed (27) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/@types/storage/PostgresSyncRulesStorage.d.ts +3 -2
  4. package/dist/@types/storage/batch/PostgresBucketBatch.d.ts +1 -1
  5. package/dist/migrations/scripts/1771424826685-current-data-pending-deletes.js +1 -1
  6. package/dist/storage/PostgresBucketStorageFactory.js +5 -5
  7. package/dist/storage/PostgresBucketStorageFactory.js.map +1 -1
  8. package/dist/storage/PostgresSyncRulesStorage.js +70 -38
  9. package/dist/storage/PostgresSyncRulesStorage.js.map +1 -1
  10. package/dist/storage/batch/PostgresBucketBatch.js +2 -2
  11. package/dist/storage/batch/PostgresBucketBatch.js.map +1 -1
  12. package/dist/storage/checkpoints/PostgresWriteCheckpointAPI.js +1 -1
  13. package/dist/storage/checkpoints/PostgresWriteCheckpointAPI.js.map +1 -1
  14. package/dist/storage/sync-rules/PostgresPersistedSyncRulesContent.js +3 -3
  15. package/dist/storage/sync-rules/PostgresPersistedSyncRulesContent.js.map +1 -1
  16. package/package.json +11 -11
  17. package/src/migrations/scripts/1771424826685-current-data-pending-deletes.ts +1 -1
  18. package/src/storage/PostgresBucketStorageFactory.ts +6 -5
  19. package/src/storage/PostgresSyncRulesStorage.ts +78 -38
  20. package/src/storage/batch/PostgresBucketBatch.ts +2 -2
  21. package/src/storage/checkpoints/PostgresWriteCheckpointAPI.ts +3 -1
  22. package/src/storage/sync-rules/PostgresPersistedSyncRulesContent.ts +3 -6
  23. package/test/tsconfig.json +0 -1
  24. package/dist/@types/storage/current-data-table.d.ts +0 -9
  25. package/dist/storage/current-data-table.js +0 -22
  26. package/dist/storage/current-data-table.js.map +0 -1
  27. 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, logger, ServiceError } from '@powersync/lib-services-framework';
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, `Sync rules: ${this.id} have been locked by another process for replication.`);
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,MAAM,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACpF,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,CACpB,SAAS,CAAC,WAAW,EACrB,eAAe,IAAI,CAAC,EAAE,uDAAuD,CAC9E,CAAC;QACJ,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,MAAM,CAAC,KAAK,CAAC,wBAAwB,EAAE,CAAC,CAAC,CAAC;gBAC1C,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"}
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.13.4",
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": "^11.1.0",
31
- "@powersync/lib-service-postgres": "0.4.26",
32
- "@powersync/lib-services-framework": "0.9.3",
33
- "@powersync/service-core": "1.20.5",
34
- "@powersync/service-types": "0.15.1",
35
- "@powersync/service-jpgwire": "0.21.17",
36
- "@powersync/service-jsonbig": "0.17.12",
37
- "@powersync/service-sync-rules": "0.35.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": "~5.9.3",
41
- "@powersync/service-core-tests": "0.15.4"
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 rules are deployed.
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 = options.storageVersion ?? storage.CURRENT_STORAGE_VERSION;
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 sync rules instance.
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" sync rules
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" sync rules, while there is no "next" one.
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" sync rules - don't update any.
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 rules, if present, had the same options.
112
- * Parse sync rules if the options are different or if there is no cached value.
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(options: PopulateChecksumCacheOptions): Promise<PopulateChecksumCacheResults> {
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 ?? framework.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
- ): Promise<sync_rules.SqliteJsonRow[]> {
404
+ lookups: sync_rules.ScopedParameterLookup[],
405
+ limit: number
406
+ ): Promise<sync_rules.ParameterLookupRows[]> {
401
407
  const rows = await this.db.sql`
402
- SELECT DISTINCT
403
- ON (lookup, source_table, source_key) lookup,
404
- source_table,
405
- source_key,
406
- id,
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 FILTER
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
- AND id <= ${{ type: 'int8', value: checkpoint.checkpoint }}
422
- ORDER BY
423
- lookup,
424
- source_table,
425
- source_key,
426
- id DESC
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(pick(models.BucketParameters, ['bucket_parameters']))
439
+ .decoded(parameterSetsRow)
429
440
  .rows();
430
441
 
431
- const groupedParameters = rows.map((row) => {
432
- return JSONBig.parse(row.bucket_parameters) as sync_rules.SqliteJsonRow;
433
- });
434
- return groupedParameters.flat();
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 sync rules status');
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 sync rules available');
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 sync rules changed - abort and restart the stream
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(lookups: sync_rules.ScopedParameterLookup[]): Promise<sync_rules.SqliteJsonRow[]> {
902
- return this.storage.getParameterSets(this, lookups);
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 sync rules
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 sync rules at ${lsn}`);
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(`Sync rules ID is required for custom Write Checkpoint filtering`);
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, logger, ServiceError } from '@powersync/lib-services-framework';
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);
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "extends": "../../../tsconfig.tests.json",
3
3
  "compilerOptions": {
4
- "baseUrl": "./",
5
4
  "declarationDir": "dist/@types",
6
5
  "tsBuildInfoFile": "dist/.tsbuildinfo",
7
6
  "lib": ["ES2022", "esnext.disposable"],
@@ -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
- }