@powersync/service-module-postgres-storage 0.0.0-dev-20260203155513 → 0.0.0-dev-20260223080959

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 (40) hide show
  1. package/CHANGELOG.md +55 -9
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/@types/migrations/scripts/1771232439485-storage-version.d.ts +3 -0
  4. package/dist/@types/storage/PostgresBucketStorageFactory.d.ts +2 -10
  5. package/dist/@types/storage/PostgresReportStorage.d.ts +1 -5
  6. package/dist/@types/storage/sync-rules/PostgresPersistedSyncRulesContent.d.ts +1 -10
  7. package/dist/@types/types/models/SyncRules.d.ts +3 -2
  8. package/dist/@types/utils/db.d.ts +9 -0
  9. package/dist/migrations/scripts/1771232439485-storage-version.js +111 -0
  10. package/dist/migrations/scripts/1771232439485-storage-version.js.map +1 -0
  11. package/dist/storage/PostgresBucketStorageFactory.js +8 -55
  12. package/dist/storage/PostgresBucketStorageFactory.js.map +1 -1
  13. package/dist/storage/PostgresReportStorage.js +0 -12
  14. package/dist/storage/PostgresReportStorage.js.map +1 -1
  15. package/dist/storage/batch/PostgresBucketBatch.js +4 -3
  16. package/dist/storage/batch/PostgresBucketBatch.js.map +1 -1
  17. package/dist/storage/sync-rules/PostgresPersistedSyncRulesContent.js +13 -30
  18. package/dist/storage/sync-rules/PostgresPersistedSyncRulesContent.js.map +1 -1
  19. package/dist/types/models/SyncRules.js +1 -0
  20. package/dist/types/models/SyncRules.js.map +1 -1
  21. package/dist/utils/db.js +32 -0
  22. package/dist/utils/db.js.map +1 -1
  23. package/dist/utils/test-utils.js +39 -10
  24. package/dist/utils/test-utils.js.map +1 -1
  25. package/package.json +8 -8
  26. package/src/migrations/scripts/1771232439485-storage-version.ts +44 -0
  27. package/src/storage/PostgresBucketStorageFactory.ts +9 -63
  28. package/src/storage/PostgresReportStorage.ts +3 -16
  29. package/src/storage/batch/PostgresBucketBatch.ts +10 -3
  30. package/src/storage/sync-rules/PostgresPersistedSyncRulesContent.ts +19 -33
  31. package/src/types/models/SyncRules.ts +1 -0
  32. package/src/utils/db.ts +37 -0
  33. package/src/utils/test-utils.ts +30 -10
  34. package/test/src/__snapshots__/storage_sync.test.ts.snap +1116 -21
  35. package/test/src/migrations.test.ts +8 -1
  36. package/test/src/storage.test.ts +11 -11
  37. package/test/src/storage_sync.test.ts +146 -4
  38. package/test/src/util.ts +3 -0
  39. package/test/tsconfig.json +2 -6
  40. package/test/src/__snapshots__/storage.test.ts.snap +0 -9
@@ -1,39 +1,22 @@
1
1
  import * as lib_postgres from '@powersync/lib-service-postgres';
2
2
  import { ErrorCode, logger, ServiceError } from '@powersync/lib-services-framework';
3
- import { SqlSyncRules, versionedHydrationState } from '@powersync/service-sync-rules';
4
- export class PostgresPersistedSyncRulesContent {
3
+ import { storage } from '@powersync/service-core';
4
+ export class PostgresPersistedSyncRulesContent extends storage.PersistedSyncRulesContent {
5
5
  db;
6
- slot_name;
7
- id;
8
- sync_rules_content;
9
- last_checkpoint_lsn;
10
- last_fatal_error;
11
- last_keepalive_ts;
12
- last_checkpoint_ts;
13
- active;
14
6
  current_lock = null;
15
7
  constructor(db, row) {
8
+ super({
9
+ id: Number(row.id),
10
+ sync_rules_content: row.content,
11
+ last_checkpoint_lsn: row.last_checkpoint_lsn,
12
+ slot_name: row.slot_name,
13
+ last_fatal_error: row.last_fatal_error,
14
+ last_checkpoint_ts: row.last_checkpoint_ts ? new Date(row.last_checkpoint_ts) : null,
15
+ last_keepalive_ts: row.last_keepalive_ts ? new Date(row.last_keepalive_ts) : null,
16
+ active: row.state == 'ACTIVE',
17
+ storageVersion: row.storage_version ?? storage.LEGACY_STORAGE_VERSION
18
+ });
16
19
  this.db = db;
17
- this.id = Number(row.id);
18
- this.sync_rules_content = row.content;
19
- this.last_checkpoint_lsn = row.last_checkpoint_lsn;
20
- this.slot_name = row.slot_name;
21
- this.last_fatal_error = row.last_fatal_error;
22
- this.last_checkpoint_ts = row.last_checkpoint_ts ? new Date(row.last_checkpoint_ts) : null;
23
- this.last_keepalive_ts = row.last_keepalive_ts ? new Date(row.last_keepalive_ts) : null;
24
- this.active = row.state == 'ACTIVE';
25
- }
26
- parsed(options) {
27
- return {
28
- id: this.id,
29
- slot_name: this.slot_name,
30
- sync_rules: SqlSyncRules.fromYaml(this.sync_rules_content, options),
31
- hydratedSyncRules() {
32
- return this.sync_rules.hydrate({
33
- hydrationState: versionedHydrationState(this.id)
34
- });
35
- }
36
- };
37
20
  }
38
21
  async lock() {
39
22
  const manager = new lib_postgres.PostgresLockManager({
@@ -1 +1 @@
1
- {"version":3,"file":"PostgresPersistedSyncRulesContent.js","sourceRoot":"","sources":["../../../src/storage/sync-rules/PostgresPersistedSyncRulesContent.ts"],"names":[],"mappings":"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"}
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;AAUlD,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,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;QAbK,OAAE,GAAF,EAAE,CAA6B;IAczC,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"}
@@ -46,6 +46,7 @@ export const SyncRules = t.object({
46
46
  */
47
47
  last_fatal_error: t.Null.or(t.string),
48
48
  keepalive_op: t.Null.or(bigint),
49
+ storage_version: t.Null.or(pgwire_number).optional(),
49
50
  content: t.string
50
51
  });
51
52
  //# sourceMappingURL=SyncRules.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SyncRules.js","sourceRoot":"","sources":["../../../src/types/models/SyncRules.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,KAAK,CAAC,MAAM,UAAU,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAErD,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC;IAChC,EAAE,EAAE,aAAa;IACjB,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC;IACpC;;;;OAIG;IACH,aAAa,EAAE,CAAC,CAAC,OAAO;IACxB;;OAEG;IACH,YAAY,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IACjC;;;;OAIG;IACH,eAAe,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC;IAClC;;OAEG;IACH,mBAAmB,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IACxC;;OAEG;IACH,oBAAoB,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IACzC,SAAS,EAAE,CAAC,CAAC,MAAM;IACnB;;;;OAIG;IACH,kBAAkB,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC;IACpD;;;;OAIG;IACH,iBAAiB,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC;IACnD;;OAEG;IACH,gBAAgB,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IACrC,YAAY,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC;IAC/B,OAAO,EAAE,CAAC,CAAC,MAAM;CAClB,CAAC,CAAC"}
1
+ {"version":3,"file":"SyncRules.js","sourceRoot":"","sources":["../../../src/types/models/SyncRules.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,KAAK,CAAC,MAAM,UAAU,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAErD,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC;IAChC,EAAE,EAAE,aAAa;IACjB,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC;IACpC;;;;OAIG;IACH,aAAa,EAAE,CAAC,CAAC,OAAO;IACxB;;OAEG;IACH,YAAY,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IACjC;;;;OAIG;IACH,eAAe,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC;IAClC;;OAEG;IACH,mBAAmB,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IACxC;;OAEG;IACH,oBAAoB,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IACzC,SAAS,EAAE,CAAC,CAAC,MAAM;IACnB;;;;OAIG;IACH,kBAAkB,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC;IACpD;;;;OAIG;IACH,iBAAiB,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC;IACnD;;OAEG;IACH,gBAAgB,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IACrC,YAAY,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC;IAC/B,eAAe,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,QAAQ,EAAE;IACpD,OAAO,EAAE,CAAC,CAAC,MAAM;CAClB,CAAC,CAAC"}
package/dist/utils/db.js CHANGED
@@ -5,6 +5,9 @@ export const NOTIFICATION_CHANNEL = 'powersynccheckpoints';
5
5
  * Re export for prettier to detect the tag better
6
6
  */
7
7
  export const sql = lib_postgres.sql;
8
+ /**
9
+ * Drop all Postgres storage tables used by the service, including migrations.
10
+ */
8
11
  export const dropTables = async (client) => {
9
12
  // Lock a connection for automatic schema search paths
10
13
  await client.lockConnection(async (db) => {
@@ -19,6 +22,35 @@ export const dropTables = async (client) => {
19
22
  await db.sql `DROP TABLE IF EXISTS custom_write_checkpoints`.execute();
20
23
  await db.sql `DROP SEQUENCE IF EXISTS op_id_sequence`.execute();
21
24
  await db.sql `DROP SEQUENCE IF EXISTS sync_rules_id_sequence`.execute();
25
+ await db.sql `DROP TABLE IF EXISTS migrations`.execute();
26
+ });
27
+ };
28
+ /**
29
+ * Clear all Postgres storage tables and reset sequences.
30
+ *
31
+ * Does not clear migration state.
32
+ */
33
+ export const truncateTables = async (db) => {
34
+ // Lock a connection for automatic schema search paths
35
+ await db.query({
36
+ statement: `TRUNCATE TABLE bucket_data,
37
+ bucket_parameters,
38
+ sync_rules,
39
+ instance,
40
+ current_data,
41
+ source_tables,
42
+ write_checkpoints,
43
+ custom_write_checkpoints,
44
+ connection_report_events RESTART IDENTITY CASCADE
45
+ `
46
+ }, {
47
+ statement: `ALTER SEQUENCE IF EXISTS op_id_sequence RESTART
48
+ WITH
49
+ 1`
50
+ }, {
51
+ statement: `ALTER SEQUENCE IF EXISTS sync_rules_id_sequence RESTART
52
+ WITH
53
+ 1`
22
54
  });
23
55
  };
24
56
  //# sourceMappingURL=db.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/utils/db.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,iCAAiC,CAAC;AAEhE,MAAM,CAAC,MAAM,mBAAmB,GAAG,WAAW,CAAC;AAE/C,MAAM,CAAC,MAAM,oBAAoB,GAAG,sBAAsB,CAAC;AAE3D;;GAEG;AACH,MAAM,CAAC,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC;AAEpC,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,EAAE,MAAmC,EAAE,EAAE;IACtE,sDAAsD;IACtD,MAAM,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;QACvC,MAAM,EAAE,CAAC,GAAG,CAAA,kCAAkC,CAAC,OAAO,EAAE,CAAC;QACzD,MAAM,EAAE,CAAC,GAAG,CAAA,wCAAwC,CAAC,OAAO,EAAE,CAAC;QAC/D,MAAM,EAAE,CAAC,GAAG,CAAA,iCAAiC,CAAC,OAAO,EAAE,CAAC;QACxD,MAAM,EAAE,CAAC,GAAG,CAAA,+BAA+B,CAAC,OAAO,EAAE,CAAC;QACtD,MAAM,EAAE,CAAC,GAAG,CAAA,kCAAkC,CAAC,OAAO,EAAE,CAAC;QACzD,MAAM,EAAE,CAAC,GAAG,CAAA,mCAAmC,CAAC,OAAO,EAAE,CAAC;QAC1D,MAAM,EAAE,CAAC,GAAG,CAAA,oCAAoC,CAAC,OAAO,EAAE,CAAC;QAC3D,MAAM,EAAE,CAAC,GAAG,CAAA,wCAAwC,CAAC,OAAO,EAAE,CAAC;QAC/D,MAAM,EAAE,CAAC,GAAG,CAAA,+CAA+C,CAAC,OAAO,EAAE,CAAC;QACtE,MAAM,EAAE,CAAC,GAAG,CAAA,wCAAwC,CAAC,OAAO,EAAE,CAAC;QAC/D,MAAM,EAAE,CAAC,GAAG,CAAA,gDAAgD,CAAC,OAAO,EAAE,CAAC;IACzE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC"}
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/utils/db.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,iCAAiC,CAAC;AAEhE,MAAM,CAAC,MAAM,mBAAmB,GAAG,WAAW,CAAC;AAE/C,MAAM,CAAC,MAAM,oBAAoB,GAAG,sBAAsB,CAAC;AAE3D;;GAEG;AACH,MAAM,CAAC,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC;AAEpC;;GAEG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,EAAE,MAAmC,EAAE,EAAE;IACtE,sDAAsD;IACtD,MAAM,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;QACvC,MAAM,EAAE,CAAC,GAAG,CAAA,kCAAkC,CAAC,OAAO,EAAE,CAAC;QACzD,MAAM,EAAE,CAAC,GAAG,CAAA,wCAAwC,CAAC,OAAO,EAAE,CAAC;QAC/D,MAAM,EAAE,CAAC,GAAG,CAAA,iCAAiC,CAAC,OAAO,EAAE,CAAC;QACxD,MAAM,EAAE,CAAC,GAAG,CAAA,+BAA+B,CAAC,OAAO,EAAE,CAAC;QACtD,MAAM,EAAE,CAAC,GAAG,CAAA,kCAAkC,CAAC,OAAO,EAAE,CAAC;QACzD,MAAM,EAAE,CAAC,GAAG,CAAA,mCAAmC,CAAC,OAAO,EAAE,CAAC;QAC1D,MAAM,EAAE,CAAC,GAAG,CAAA,oCAAoC,CAAC,OAAO,EAAE,CAAC;QAC3D,MAAM,EAAE,CAAC,GAAG,CAAA,wCAAwC,CAAC,OAAO,EAAE,CAAC;QAC/D,MAAM,EAAE,CAAC,GAAG,CAAA,+CAA+C,CAAC,OAAO,EAAE,CAAC;QACtE,MAAM,EAAE,CAAC,GAAG,CAAA,wCAAwC,CAAC,OAAO,EAAE,CAAC;QAC/D,MAAM,EAAE,CAAC,GAAG,CAAA,gDAAgD,CAAC,OAAO,EAAE,CAAC;QACvE,MAAM,EAAE,CAAC,GAAG,CAAA,iCAAiC,CAAC,OAAO,EAAE,CAAC;IAC1D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,EAAE,EAA+B,EAAE,EAAE;IACtE,sDAAsD;IACtD,MAAM,EAAE,CAAC,KAAK,CACZ;QACE,SAAS,EAAE;;;;;;;;;KASZ;KACA,EACD;QACE,SAAS,EAAE;;UAEP;KACL,EACD;QACE,SAAS,EAAE;;UAEP;KACL,CACF,CAAC;AACJ,CAAC,CAAC"}
@@ -55,6 +55,7 @@ import { PostgresMigrationAgent } from '../migrations/PostgresMigrationAgent.js'
55
55
  import { normalizePostgresStorageConfig } from '../types/types.js';
56
56
  import { PostgresReportStorage } from '../storage/PostgresReportStorage.js';
57
57
  import { PostgresBucketStorageFactory } from '../storage/PostgresBucketStorageFactory.js';
58
+ import { truncateTables } from './db.js';
58
59
  export function postgresTestSetup(factoryOptions) {
59
60
  const BASE_CONFIG = {
60
61
  type: 'postgresql',
@@ -62,7 +63,7 @@ export function postgresTestSetup(factoryOptions) {
62
63
  sslmode: 'disable'
63
64
  };
64
65
  const TEST_CONNECTION_OPTIONS = normalizePostgresStorageConfig(BASE_CONFIG);
65
- const migrate = async (direction) => {
66
+ const runMigrations = async (options) => {
66
67
  const env_1 = { stack: [], error: void 0, hasError: false };
67
68
  try {
68
69
  const migrationManager = __addDisposableResource(env_1, new framework.MigrationManager(), true);
@@ -71,13 +72,15 @@ export function postgresTestSetup(factoryOptions) {
71
72
  : new PostgresMigrationAgent(BASE_CONFIG), true);
72
73
  migrationManager.registerMigrationAgent(migrationAgent);
73
74
  const mockServiceContext = { configuration: { storage: BASE_CONFIG } };
74
- await migrationManager.migrate({
75
- direction: framework.migrations.Direction.Down,
76
- migrationContext: {
77
- service_context: mockServiceContext
78
- }
79
- });
80
- if (direction == framework.migrations.Direction.Up) {
75
+ if (options.down) {
76
+ await migrationManager.migrate({
77
+ direction: framework.migrations.Direction.Down,
78
+ migrationContext: {
79
+ service_context: mockServiceContext
80
+ }
81
+ });
82
+ }
83
+ if (options.up) {
81
84
  await migrationManager.migrate({
82
85
  direction: framework.migrations.Direction.Up,
83
86
  migrationContext: {
@@ -96,11 +99,37 @@ export function postgresTestSetup(factoryOptions) {
96
99
  await result_1;
97
100
  }
98
101
  };
102
+ const migrate = async (direction) => {
103
+ await runMigrations({
104
+ down: true,
105
+ up: direction == framework.migrations.Direction.Up
106
+ });
107
+ };
108
+ const clearStorage = async () => {
109
+ const env_2 = { stack: [], error: void 0, hasError: false };
110
+ try {
111
+ await runMigrations({ down: false, up: true });
112
+ const storageFactory = __addDisposableResource(env_2, new PostgresBucketStorageFactory({
113
+ config: TEST_CONNECTION_OPTIONS,
114
+ slot_name_prefix: 'test_'
115
+ }), true);
116
+ await truncateTables(storageFactory.db);
117
+ }
118
+ catch (e_2) {
119
+ env_2.error = e_2;
120
+ env_2.hasError = true;
121
+ }
122
+ finally {
123
+ const result_2 = __disposeResources(env_2);
124
+ if (result_2)
125
+ await result_2;
126
+ }
127
+ };
99
128
  return {
100
129
  reportFactory: async (options) => {
101
130
  try {
102
131
  if (!options?.doNotClear) {
103
- await migrate(framework.migrations.Direction.Up);
132
+ await clearStorage();
104
133
  }
105
134
  return new PostgresReportStorage({
106
135
  config: TEST_CONNECTION_OPTIONS
@@ -115,7 +144,7 @@ export function postgresTestSetup(factoryOptions) {
115
144
  factory: async (options) => {
116
145
  try {
117
146
  if (!options?.doNotClear) {
118
- await migrate(framework.migrations.Direction.Up);
147
+ await clearStorage();
119
148
  }
120
149
  return new PostgresBucketStorageFactory({
121
150
  config: TEST_CONNECTION_OPTIONS,
@@ -1 +1 @@
1
- {"version":3,"file":"test-utils.js","sourceRoot":"","sources":["../../src/utils/test-utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"test-utils.js","sourceRoot":"","sources":["../../src/utils/test-utils.ts"],"names":[],"mappings":"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"}
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.0.0-dev-20260203155513",
5
+ "version": "0.0.0-dev-20260223080959",
6
6
  "main": "dist/index.js",
7
7
  "license": "FSL-1.1-ALv2",
8
8
  "type": "module",
@@ -29,17 +29,17 @@
29
29
  "p-defer": "^4.0.1",
30
30
  "ts-codec": "^1.3.0",
31
31
  "uuid": "^11.1.0",
32
- "@powersync/lib-service-postgres": "0.0.0-dev-20260203155513",
33
- "@powersync/lib-services-framework": "0.0.0-dev-20260203155513",
34
- "@powersync/service-core": "0.0.0-dev-20260203155513",
35
- "@powersync/service-types": "0.0.0-dev-20260203155513",
36
- "@powersync/service-jpgwire": "0.0.0-dev-20260203155513",
32
+ "@powersync/lib-service-postgres": "0.0.0-dev-20260223080959",
33
+ "@powersync/lib-services-framework": "0.0.0-dev-20260223080959",
34
+ "@powersync/service-core": "0.0.0-dev-20260223080959",
35
+ "@powersync/service-types": "0.0.0-dev-20260223080959",
36
+ "@powersync/service-jpgwire": "0.0.0-dev-20260223080959",
37
37
  "@powersync/service-jsonbig": "0.17.12",
38
- "@powersync/service-sync-rules": "0.0.0-dev-20260203155513"
38
+ "@powersync/service-sync-rules": "0.0.0-dev-20260223080959"
39
39
  },
40
40
  "devDependencies": {
41
41
  "typescript": "^5.7.3",
42
- "@powersync/service-core-tests": "0.0.0-dev-20260203155513"
42
+ "@powersync/service-core-tests": "0.0.0-dev-20260223080959"
43
43
  },
44
44
  "scripts": {
45
45
  "build": "tsc -b",
@@ -0,0 +1,44 @@
1
+ import { migrations, storage } from '@powersync/service-core';
2
+ import { openMigrationDB } from '../migration-utils.js';
3
+
4
+ export const up: migrations.PowerSyncMigrationFunction = async (context) => {
5
+ const {
6
+ service_context: { configuration }
7
+ } = context;
8
+ await using client = openMigrationDB(configuration.storage);
9
+ await client.transaction(async (db) => {
10
+ await db.sql`
11
+ ALTER TABLE sync_rules
12
+ ADD COLUMN storage_version integer NOT NULL DEFAULT 1
13
+ `.execute();
14
+ });
15
+ };
16
+
17
+ export const down: migrations.PowerSyncMigrationFunction = async (context) => {
18
+ const {
19
+ service_context: { configuration }
20
+ } = context;
21
+ await using client = openMigrationDB(configuration.storage);
22
+ await client.transaction(async (db) => {
23
+ const newRules = await db.sql`
24
+ SELECT
25
+ id,
26
+ storage_version
27
+ FROM
28
+ sync_rules
29
+ WHERE
30
+ storage_version > ${{ type: 'int4', value: storage.LEGACY_STORAGE_VERSION }}
31
+ `.rows<{ id: number | bigint; storage_version: number | bigint }>();
32
+
33
+ if (newRules.length > 0) {
34
+ throw new Error(
35
+ `Cannot revert migration due to newer storage versions in use: ${newRules.map((r) => `${r.id}: v${r.storage_version}`).join(', ')}`
36
+ );
37
+ }
38
+
39
+ await db.sql`
40
+ ALTER TABLE sync_rules
41
+ DROP COLUMN storage_version
42
+ `.execute();
43
+ });
44
+ };
@@ -1,4 +1,3 @@
1
- import * as framework from '@powersync/lib-services-framework';
2
1
  import { GetIntanceOptions, storage, SyncRulesBucketStorage, UpdateSyncRulesOptions } from '@powersync/service-core';
3
2
  import * as pg_wire from '@powersync/service-jpgwire';
4
3
  import * as sync_rules from '@powersync/service-sync-rules';
@@ -19,10 +18,7 @@ export type PostgresBucketStorageOptions = {
19
18
  slot_name_prefix: string;
20
19
  };
21
20
 
22
- export class PostgresBucketStorageFactory
23
- extends framework.BaseObserver<storage.BucketStorageFactoryListener>
24
- implements storage.BucketStorageFactory
25
- {
21
+ export class PostgresBucketStorageFactory extends storage.BucketStorageFactory {
26
22
  readonly db: lib_postgres.DatabaseClient;
27
23
  public readonly slot_name_prefix: string;
28
24
 
@@ -145,42 +141,8 @@ export class PostgresBucketStorageFactory
145
141
  };
146
142
  }
147
143
 
148
- // TODO possibly share implementation in abstract class
149
- async configureSyncRules(options: UpdateSyncRulesOptions): Promise<{
150
- updated: boolean;
151
- persisted_sync_rules?: storage.PersistedSyncRulesContent;
152
- lock?: storage.ReplicationLock;
153
- }> {
154
- const next = await this.getNextSyncRulesContent();
155
- const active = await this.getActiveSyncRulesContent();
156
-
157
- if (next?.sync_rules_content == options.content) {
158
- framework.logger.info('Sync rules from configuration unchanged');
159
- return { updated: false };
160
- } else if (next == null && active?.sync_rules_content == options.content) {
161
- framework.logger.info('Sync rules from configuration unchanged');
162
- return { updated: false };
163
- } else {
164
- framework.logger.info('Sync rules updated from configuration');
165
- const persisted_sync_rules = await this.updateSyncRules(options);
166
- return { updated: true, persisted_sync_rules, lock: persisted_sync_rules.current_lock ?? undefined };
167
- }
168
- }
169
-
170
144
  async updateSyncRules(options: storage.UpdateSyncRulesOptions): Promise<PostgresPersistedSyncRulesContent> {
171
- // TODO some shared implementation for this might be nice
172
- if (options.validate) {
173
- // Parse and validate before applying any changes
174
- sync_rules.SqlSyncRules.fromYaml(options.content, {
175
- // No schema-based validation at this point
176
- schema: undefined,
177
- defaultSchema: 'not_applicable', // Not needed for validation
178
- throwOnError: true
179
- });
180
- } else {
181
- // Apply unconditionally. Any errors will be reported via the diagnostics API.
182
- }
183
-
145
+ const storageVersion = options.storageVersion ?? storage.CURRENT_STORAGE_VERSION;
184
146
  return this.db.transaction(async (db) => {
185
147
  await db.sql`
186
148
  UPDATE sync_rules
@@ -197,7 +159,7 @@ export class PostgresBucketStorageFactory
197
159
  nextval('sync_rules_id_sequence') AS id
198
160
  )
199
161
  INSERT INTO
200
- sync_rules (id, content, state, slot_name)
162
+ sync_rules (id, content, state, slot_name, storage_version)
201
163
  VALUES
202
164
  (
203
165
  (
@@ -206,7 +168,7 @@ export class PostgresBucketStorageFactory
206
168
  FROM
207
169
  next_id
208
170
  ),
209
- ${{ type: 'varchar', value: options.content }},
171
+ ${{ type: 'varchar', value: options.config.yaml }},
210
172
  ${{ type: 'varchar', value: storage.SyncRuleState.PROCESSING }},
211
173
  CONCAT(
212
174
  ${{ type: 'varchar', value: this.slot_name_prefix }},
@@ -218,7 +180,8 @@ export class PostgresBucketStorageFactory
218
180
  ),
219
181
  '_',
220
182
  ${{ type: 'varchar', value: crypto.randomBytes(2).toString('hex') }}
221
- )
183
+ ),
184
+ ${{ type: 'int4', value: storageVersion }}
222
185
  )
223
186
  RETURNING
224
187
  *
@@ -240,10 +203,8 @@ export class PostgresBucketStorageFactory
240
203
  // The current one will continue serving sync requests until the next one has finished processing.
241
204
  if (next != null && next.id == sync_rules_group_id) {
242
205
  // We need to redo the "next" sync rules
243
- await this.updateSyncRules({
244
- content: next.sync_rules_content,
245
- validate: false
246
- });
206
+
207
+ await this.updateSyncRules(next.asUpdateOptions());
247
208
  // Pro-actively stop replicating
248
209
  await this.db.sql`
249
210
  UPDATE sync_rules
@@ -255,10 +216,7 @@ export class PostgresBucketStorageFactory
255
216
  `.execute();
256
217
  } else if (next == null && active?.id == sync_rules_group_id) {
257
218
  // Slot removed for "active" sync rules, while there is no "next" one.
258
- await this.updateSyncRules({
259
- content: active.sync_rules_content,
260
- validate: false
261
- });
219
+ await this.updateSyncRules(active.asUpdateOptions());
262
220
 
263
221
  // Pro-actively stop replicating, but still serve clients with existing data
264
222
  await this.db.sql`
@@ -284,12 +242,6 @@ export class PostgresBucketStorageFactory
284
242
  }
285
243
  }
286
244
 
287
- // TODO possibly share via abstract class
288
- async getActiveSyncRules(options: storage.ParseSyncRulesOptions): Promise<storage.PersistedSyncRules | null> {
289
- const content = await this.getActiveSyncRulesContent();
290
- return content?.parsed(options) ?? null;
291
- }
292
-
293
245
  async getActiveSyncRulesContent(): Promise<storage.PersistedSyncRulesContent | null> {
294
246
  const activeRow = await this.db.sql`
295
247
  SELECT
@@ -313,12 +265,6 @@ export class PostgresBucketStorageFactory
313
265
  return new PostgresPersistedSyncRulesContent(this.db, activeRow);
314
266
  }
315
267
 
316
- // TODO possibly share via abstract class
317
- async getNextSyncRules(options: storage.ParseSyncRulesOptions): Promise<storage.PersistedSyncRules | null> {
318
- const content = await this.getNextSyncRulesContent();
319
- return content?.parsed(options) ?? null;
320
- }
321
-
322
268
  async getNextSyncRulesContent(): Promise<storage.PersistedSyncRulesContent | null> {
323
269
  const nextRow = await this.db.sql`
324
270
  SELECT
@@ -28,13 +28,6 @@ export class PostgresReportStorage implements storage.ReportStorage {
28
28
  connectionCreated: async (connection) => this.prepareStatements(connection)
29
29
  });
30
30
  }
31
- async getLastSyncReport(data: event_types.LastSyncRequest): Promise<any> {
32
- throw new Error('Method not implemented.');
33
- }
34
-
35
- async getSyncBucketStats(data: event_types.SyncBucketStatsRequest): Promise<any> {
36
- throw new Error('Method not implemented.');
37
- }
38
31
 
39
32
  private parseJsDate(date: Date) {
40
33
  const year = date.getUTCFullYear();
@@ -181,14 +174,6 @@ export class PostgresReportStorage implements storage.ReportStorage {
181
174
  };
182
175
  }
183
176
 
184
- getSyncCheckpoint(data: event_types.SyncCheckpointRequest): Promise<event_types.PaginatedResponse<any>> {
185
- throw new Error('Method not implemented.');
186
- }
187
-
188
- async reportSyncAnalyticsEvent(data: event_types.SyncAnalyticsEventData): Promise<void> {
189
- console.log(data);
190
- }
191
-
192
177
  async reportClientConnection(data: event_types.ClientConnectionBucketData): Promise<void> {
193
178
  const { sdk, connected_at, user_id, user_agent, jwt_exp, client_id } = data;
194
179
  const connectIsoString = connected_at.toISOString();
@@ -254,7 +239,9 @@ export class PostgresReportStorage implements storage.ReportStorage {
254
239
  return this.mapListCurrentConnectionsResponse(result);
255
240
  }
256
241
 
257
- async getClientConnectionReports(data: event_types.DateRange): Promise<event_types.ClientConnectionReportResponse> {
242
+ async getClientConnectionReports(
243
+ data: event_types.ClientConnectionReportRequest
244
+ ): Promise<event_types.ClientConnectionReportResponse> {
258
245
  const { start, end } = data;
259
246
  const result = await this.db.sql`
260
247
  WITH
@@ -9,7 +9,13 @@ import {
9
9
  ServiceAssertionError,
10
10
  ServiceError
11
11
  } from '@powersync/lib-services-framework';
12
- import { BucketStorageMarkRecordUnavailable, InternalOpId, storage, utils } from '@powersync/service-core';
12
+ import {
13
+ BucketStorageMarkRecordUnavailable,
14
+ deserializeReplicaId,
15
+ InternalOpId,
16
+ storage,
17
+ utils
18
+ } from '@powersync/service-core';
13
19
  import * as sync_rules from '@powersync/service-sync-rules';
14
20
  import * as timers from 'timers/promises';
15
21
  import * as t from 'ts-codec';
@@ -199,17 +205,18 @@ export class PostgresBucketBatch
199
205
 
200
206
  const decodedRows = rows.map((row) => codec.decode(row));
201
207
  for (const value of decodedRows) {
208
+ const source_key = deserializeReplicaId(value.source_key);
202
209
  persistedBatch.saveBucketData({
203
210
  before_buckets: value.buckets,
204
211
  evaluated: [],
205
212
  table: sourceTable,
206
- source_key: value.source_key
213
+ source_key
207
214
  });
208
215
  persistedBatch.saveParameterData({
209
216
  existing_lookups: value.lookups,
210
217
  evaluated: [],
211
218
  table: sourceTable,
212
- source_key: value.source_key
219
+ source_key
213
220
  });
214
221
  persistedBatch.deleteCurrentData({
215
222
  // This is serialized since we got it from a DB query
@@ -1,47 +1,33 @@
1
1
  import * as lib_postgres from '@powersync/lib-service-postgres';
2
2
  import { ErrorCode, logger, ServiceError } from '@powersync/lib-services-framework';
3
3
  import { storage } from '@powersync/service-core';
4
- import { SqlSyncRules, versionedHydrationState } from '@powersync/service-sync-rules';
5
-
4
+ import {
5
+ CompatibilityOption,
6
+ DEFAULT_HYDRATION_STATE,
7
+ HydrationState,
8
+ SqlSyncRules,
9
+ versionedHydrationState
10
+ } from '@powersync/service-sync-rules';
6
11
  import { models } from '../../types/types.js';
7
12
 
8
- export class PostgresPersistedSyncRulesContent implements storage.PersistedSyncRulesContent {
9
- public readonly slot_name: string;
10
-
11
- public readonly id: number;
12
- public readonly sync_rules_content: string;
13
- public readonly last_checkpoint_lsn: string | null;
14
- public readonly last_fatal_error: string | null;
15
- public readonly last_keepalive_ts: Date | null;
16
- public readonly last_checkpoint_ts: Date | null;
17
- public readonly active: boolean;
13
+ export class PostgresPersistedSyncRulesContent extends storage.PersistedSyncRulesContent {
18
14
  current_lock: storage.ReplicationLock | null = null;
19
15
 
20
16
  constructor(
21
17
  private db: lib_postgres.DatabaseClient,
22
18
  row: models.SyncRulesDecoded
23
19
  ) {
24
- this.id = Number(row.id);
25
- this.sync_rules_content = row.content;
26
- this.last_checkpoint_lsn = row.last_checkpoint_lsn;
27
- this.slot_name = row.slot_name;
28
- this.last_fatal_error = row.last_fatal_error;
29
- this.last_checkpoint_ts = row.last_checkpoint_ts ? new Date(row.last_checkpoint_ts) : null;
30
- this.last_keepalive_ts = row.last_keepalive_ts ? new Date(row.last_keepalive_ts) : null;
31
- this.active = row.state == 'ACTIVE';
32
- }
33
-
34
- parsed(options: storage.ParseSyncRulesOptions): storage.PersistedSyncRules {
35
- return {
36
- id: this.id,
37
- slot_name: this.slot_name,
38
- sync_rules: SqlSyncRules.fromYaml(this.sync_rules_content, options),
39
- hydratedSyncRules() {
40
- return this.sync_rules.hydrate({
41
- hydrationState: versionedHydrationState(this.id)
42
- });
43
- }
44
- };
20
+ super({
21
+ id: Number(row.id),
22
+ sync_rules_content: row.content,
23
+ last_checkpoint_lsn: row.last_checkpoint_lsn,
24
+ slot_name: row.slot_name,
25
+ last_fatal_error: row.last_fatal_error,
26
+ last_checkpoint_ts: row.last_checkpoint_ts ? new Date(row.last_checkpoint_ts) : null,
27
+ last_keepalive_ts: row.last_keepalive_ts ? new Date(row.last_keepalive_ts) : null,
28
+ active: row.state == 'ACTIVE',
29
+ storageVersion: row.storage_version ?? storage.LEGACY_STORAGE_VERSION
30
+ });
45
31
  }
46
32
 
47
33
  async lock(): Promise<storage.ReplicationLock> {
@@ -47,6 +47,7 @@ export const SyncRules = t.object({
47
47
  */
48
48
  last_fatal_error: t.Null.or(t.string),
49
49
  keepalive_op: t.Null.or(bigint),
50
+ storage_version: t.Null.or(pgwire_number).optional(),
50
51
  content: t.string
51
52
  });
52
53