@powersync/service-module-postgres-storage 0.16.3 → 0.17.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 (44) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/migrations/scripts/1782950400000-checkpoint-requested-at.d.ts +3 -0
  3. package/dist/migrations/scripts/1782950400000-checkpoint-requested-at.js +42 -0
  4. package/dist/migrations/scripts/1782950400000-checkpoint-requested-at.js.map +1 -0
  5. package/dist/storage/PostgresBucketStorageFactory.js +0 -2
  6. package/dist/storage/PostgresBucketStorageFactory.js.map +1 -1
  7. package/dist/storage/PostgresCompactor.d.ts +2 -0
  8. package/dist/storage/PostgresCompactor.js +20 -0
  9. package/dist/storage/PostgresCompactor.js.map +1 -1
  10. package/dist/storage/PostgresSyncRulesStorage.d.ts +16 -1
  11. package/dist/storage/PostgresSyncRulesStorage.js +200 -131
  12. package/dist/storage/PostgresSyncRulesStorage.js.map +1 -1
  13. package/dist/storage/batch/PostgresBucketBatch.d.ts +2 -20
  14. package/dist/storage/batch/PostgresBucketBatch.js +162 -151
  15. package/dist/storage/batch/PostgresBucketBatch.js.map +1 -1
  16. package/dist/storage/checkpoints/PostgresWriteCheckpointAPI.d.ts +1 -1
  17. package/dist/storage/checkpoints/PostgresWriteCheckpointAPI.js +156 -36
  18. package/dist/storage/checkpoints/PostgresWriteCheckpointAPI.js.map +1 -1
  19. package/dist/storage/current-data-store.d.ts +8 -2
  20. package/dist/storage/current-data-store.js +66 -11
  21. package/dist/storage/current-data-store.js.map +1 -1
  22. package/dist/types/models/WriteCheckpoint.d.ts +2 -0
  23. package/dist/types/models/WriteCheckpoint.js +5 -2
  24. package/dist/types/models/WriteCheckpoint.js.map +1 -1
  25. package/dist/utils/checkpoints.d.ts +9 -0
  26. package/dist/utils/checkpoints.js +26 -0
  27. package/dist/utils/checkpoints.js.map +1 -0
  28. package/package.json +8 -8
  29. package/src/migrations/scripts/1782950400000-checkpoint-requested-at.ts +51 -0
  30. package/src/storage/PostgresBucketStorageFactory.ts +0 -3
  31. package/src/storage/PostgresCompactor.ts +24 -0
  32. package/src/storage/PostgresSyncRulesStorage.ts +219 -137
  33. package/src/storage/batch/PostgresBucketBatch.ts +169 -154
  34. package/src/storage/checkpoints/PostgresWriteCheckpointAPI.ts +165 -37
  35. package/src/storage/current-data-store.ts +66 -11
  36. package/src/types/models/WriteCheckpoint.ts +5 -2
  37. package/src/utils/checkpoints.ts +31 -0
  38. package/test/src/__snapshots__/storage_sync.test.ts.snap +0 -582
  39. package/test/src/checkpoint_notifications.test.ts +522 -0
  40. package/test/src/storage.test.ts +214 -5
  41. package/test/src/storage_compacting.test.ts +3 -3
  42. package/test/src/storage_sync.test.ts +4 -4
  43. package/test/tsconfig.json +1 -1
  44. package/tsconfig.tsbuildinfo +1 -1
@@ -21,42 +21,152 @@ export class PostgresWriteCheckpointAPI {
21
21
  if (this.writeCheckpointMode !== storage.WriteCheckpointMode.MANAGED) {
22
22
  throw new framework.errors.ValidationError(`Attempting to create a managed Write Checkpoint when the current Write Checkpoint mode is set to "${this.writeCheckpointMode}"`);
23
23
  }
24
- const uniqueCheckpoints = [...new Map(checkpoints.map((checkpoint) => [checkpoint.user_id, checkpoint])).values()];
24
+ const uniqueCheckpoints = storage.uniqueManagedWriteCheckpoints(checkpoints);
25
25
  if (uniqueCheckpoints.length == 0) {
26
- return new Map();
26
+ return { writeCheckpoints: new Map(), shouldAdvance: false };
27
27
  }
28
- const mappedCheckpoints = uniqueCheckpoints.map((checkpoint) => ({
29
- user_id: checkpoint.user_id,
30
- lsns: checkpoint.heads
31
- }));
32
- const rows = await this.db.sql `
33
- WITH
34
- json_data AS (
28
+ const writeCheckpoints = new Map();
29
+ const generatedCheckpoints = uniqueCheckpoints.filter((checkpoint) => checkpoint.checkpoint_request_id == null);
30
+ const suppliedCheckpoints = uniqueCheckpoints.filter((checkpoint) => checkpoint.checkpoint_request_id != null);
31
+ if (generatedCheckpoints.length > 0) {
32
+ const mappedCheckpoints = generatedCheckpoints.map((checkpoint) => ({
33
+ user_id: checkpoint.user_id,
34
+ lsns: checkpoint.heads
35
+ }));
36
+ const generatedRows = await this.db.sql `
37
+ WITH
38
+ json_data AS (
39
+ SELECT
40
+ CHECKPOINT ->> 'user_id' AS user_id,
41
+ CHECKPOINT -> 'lsns' AS lsns
42
+ FROM
43
+ jsonb_array_elements(${{ type: 'jsonb', value: mappedCheckpoints }}) AS
44
+ CHECKPOINT
45
+ )
46
+ INSERT INTO
47
+ write_checkpoints (
48
+ user_id,
49
+ lsns,
50
+ write_checkpoint,
51
+ checkpoint_requested_at
52
+ )
53
+ SELECT
54
+ user_id,
55
+ lsns,
56
+ 1,
57
+ NULL
58
+ FROM
59
+ json_data
60
+ ON CONFLICT (user_id) DO UPDATE
61
+ SET
62
+ write_checkpoint = write_checkpoints.write_checkpoint + 1,
63
+ lsns = EXCLUDED.lsns,
64
+ checkpoint_requested_at = NULL
65
+ RETURNING
66
+ *;
67
+ `
68
+ .decoded(models.WriteCheckpoint)
69
+ .rows();
70
+ for (const row of generatedRows) {
71
+ writeCheckpoints.set(row.user_id, row.write_checkpoint);
72
+ }
73
+ }
74
+ if (suppliedCheckpoints.length > 0) {
75
+ // Supplied request ids are monotonic. Greater values update the checkpoint
76
+ // id and heads, while equal retries only refresh the retention timestamp.
77
+ // Stale requests return the stored id without changing the row.
78
+ const mappedCheckpoints = suppliedCheckpoints.map((checkpoint) => ({
79
+ user_id: checkpoint.user_id,
80
+ lsns: checkpoint.heads,
81
+ checkpoint_request_id: String(checkpoint.checkpoint_request_id)
82
+ }));
83
+ const suppliedRows = await this.db.sql `
84
+ WITH
85
+ json_data AS (
86
+ SELECT
87
+ CHECKPOINT ->> 'user_id' AS user_id,
88
+ CHECKPOINT -> 'lsns' AS lsns,
89
+ (
90
+ CHECKPOINT ->> 'checkpoint_request_id'
91
+ )::int8 AS checkpoint_request_id
92
+ FROM
93
+ jsonb_array_elements(${{ type: 'jsonb', value: mappedCheckpoints }}) AS
94
+ CHECKPOINT
95
+ )
96
+ INSERT INTO
97
+ write_checkpoints (
98
+ user_id,
99
+ lsns,
100
+ write_checkpoint,
101
+ checkpoint_requested_at
102
+ )
103
+ SELECT
104
+ user_id,
105
+ lsns,
106
+ checkpoint_request_id,
107
+ NOW()
108
+ FROM
109
+ json_data
110
+ ON CONFLICT (user_id) DO UPDATE
111
+ SET
112
+ write_checkpoint = CASE
113
+ WHEN EXCLUDED.write_checkpoint > write_checkpoints.write_checkpoint THEN EXCLUDED.write_checkpoint
114
+ ELSE write_checkpoints.write_checkpoint
115
+ END,
116
+ lsns = CASE
117
+ WHEN EXCLUDED.write_checkpoint > write_checkpoints.write_checkpoint THEN EXCLUDED.lsns
118
+ ELSE write_checkpoints.lsns
119
+ END,
120
+ checkpoint_requested_at = NOW()
121
+ WHERE
122
+ EXCLUDED.write_checkpoint >= write_checkpoints.write_checkpoint
123
+ RETURNING
124
+ *;
125
+ `
126
+ .decoded(models.WriteCheckpoint)
127
+ .rows();
128
+ for (const row of suppliedRows) {
129
+ writeCheckpoints.set(row.user_id, row.write_checkpoint);
130
+ }
131
+ // RETURNING only excludes stale requests rejected by the monotonic
132
+ // conflict condition. Those requests still need the stored id.
133
+ const returnedSuppliedUserIds = new Set(suppliedRows.map((row) => row.user_id));
134
+ const unchangedUserIds = suppliedCheckpoints
135
+ .map((checkpoint) => checkpoint.user_id)
136
+ .filter((userId) => !returnedSuppliedUserIds.has(userId));
137
+ if (unchangedUserIds.length > 0) {
138
+ const mappedUserIds = unchangedUserIds.map((user_id) => ({ user_id }));
139
+ const unchangedRows = await this.db.sql `
140
+ WITH
141
+ json_data AS (
142
+ SELECT
143
+ CHECKPOINT ->> 'user_id' AS user_id
144
+ FROM
145
+ jsonb_array_elements(${{ type: 'jsonb', value: mappedUserIds }}) AS
146
+ CHECKPOINT
147
+ )
35
148
  SELECT
36
- CHECKPOINT ->> 'user_id' AS user_id,
37
- CHECKPOINT -> 'lsns' AS lsns
149
+ write_checkpoints.*
38
150
  FROM
39
- jsonb_array_elements(${{ type: 'jsonb', value: mappedCheckpoints }}) AS
40
- CHECKPOINT
41
- )
42
- INSERT INTO
43
- write_checkpoints (user_id, lsns, write_checkpoint)
44
- SELECT
45
- user_id,
46
- lsns,
47
- 1
48
- FROM
49
- json_data
50
- ON CONFLICT (user_id) DO UPDATE
51
- SET
52
- write_checkpoint = write_checkpoints.write_checkpoint + 1,
53
- lsns = EXCLUDED.lsns
54
- RETURNING
55
- *;
56
- `
57
- .decoded(models.WriteCheckpoint)
58
- .rows();
59
- return new Map(rows.map((row) => [row.user_id, row.write_checkpoint]));
151
+ write_checkpoints
152
+ JOIN json_data ON write_checkpoints.user_id = json_data.user_id;
153
+ `
154
+ .decoded(models.WriteCheckpoint)
155
+ .rows();
156
+ for (const row of unchangedRows) {
157
+ writeCheckpoints.set(row.user_id, row.write_checkpoint);
158
+ }
159
+ }
160
+ }
161
+ // Postgres storage does not track a per-row processed indicator: a write
162
+ // checkpoint is considered processed at read time by comparing its lsns
163
+ // against the replicated head (see lastManagedWriteCheckpoint). We therefore
164
+ // force the source marker whenever any checkpoint was matched, which also
165
+ // covers stale or duplicate requests whose stored checkpoint may still be
166
+ // pending. Forcing a marker for an already-processed checkpoint is wasteful
167
+ // but harmless.
168
+ const shouldAdvance = generatedCheckpoints.length > 0 || suppliedCheckpoints.length > 0;
169
+ return { writeCheckpoints, shouldAdvance };
60
170
  }
61
171
  async lastWriteCheckpoint(filters) {
62
172
  switch (this.writeCheckpointMode) {
@@ -120,7 +230,8 @@ export async function batchCreateCustomWriteCheckpoints(db, checkpoints) {
120
230
  // Cannot encode bigint directly using JSON.stringify.
121
231
  // The ::int8 in the query below will take care of casting back to a number
122
232
  checkpoint: String(cp.checkpoint),
123
- sync_rules_id: cp.sync_rules_id
233
+ sync_rules_id: cp.sync_rules_id,
234
+ checkpoint_requested_at: cp.checkpoint_requested_at?.toISOString() ?? null
124
235
  };
125
236
  });
126
237
  await db.sql `
@@ -131,7 +242,12 @@ export async function batchCreateCustomWriteCheckpoints(db, checkpoints) {
131
242
  CHECKPOINT
132
243
  )
133
244
  INSERT INTO
134
- custom_write_checkpoints (user_id, write_checkpoint, sync_rules_id)
245
+ custom_write_checkpoints (
246
+ user_id,
247
+ write_checkpoint,
248
+ sync_rules_id,
249
+ checkpoint_requested_at
250
+ )
135
251
  SELECT
136
252
  CHECKPOINT ->> 'user_id'::varchar,
137
253
  (
@@ -139,12 +255,16 @@ export async function batchCreateCustomWriteCheckpoints(db, checkpoints) {
139
255
  )::int8,
140
256
  (
141
257
  CHECKPOINT ->> 'sync_rules_id'
142
- )::int4
258
+ )::int4,
259
+ (
260
+ CHECKPOINT ->> 'checkpoint_requested_at'
261
+ )::timestamptz
143
262
  FROM
144
263
  json_data
145
264
  ON CONFLICT (user_id, sync_rules_id) DO UPDATE
146
265
  SET
147
- write_checkpoint = EXCLUDED.write_checkpoint;
266
+ write_checkpoint = EXCLUDED.write_checkpoint,
267
+ checkpoint_requested_at = EXCLUDED.checkpoint_requested_at;
148
268
  `.execute();
149
269
  }
150
270
  //# sourceMappingURL=PostgresWriteCheckpointAPI.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PostgresWriteCheckpointAPI.js","sourceRoot":"","sources":["../../../src/storage/checkpoints/PostgresWriteCheckpointAPI.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PostgresWriteCheckpointAPI.js","sourceRoot":"","sources":["../../../src/storage/checkpoints/PostgresWriteCheckpointAPI.ts"],"names":[],"mappings":"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"}
@@ -77,9 +77,15 @@ export declare class PostgresCurrentDataStore {
77
77
  groupId: number;
78
78
  lastCheckpoint: bigint;
79
79
  }): Promise<void>;
80
- deleteGroupRows(db: Queryable, options: {
80
+ /**
81
+ * Delete up to `limit` rows for the group, returning the number of candidate
82
+ * rows found by the scan (see PostgresSyncRulesStorage#deleteGroupBatch for
83
+ * why the scan is counted rather than the delete).
84
+ */
85
+ deleteGroupRowsBatch(db: Queryable, options: {
81
86
  groupId: number;
82
- }): Promise<void>;
87
+ limit: number;
88
+ }): Promise<bigint>;
83
89
  private wherePendingDelete;
84
90
  }
85
91
  export {};
@@ -244,20 +244,75 @@ export class PostgresCurrentDataStore {
244
244
  AND pending_delete <= ${{ type: 'int8', value: options.lastCheckpoint }}
245
245
  `.execute();
246
246
  }
247
- async deleteGroupRows(db, options) {
247
+ /**
248
+ * Delete up to `limit` rows for the group, returning the number of candidate
249
+ * rows found by the scan (see PostgresSyncRulesStorage#deleteGroupBatch for
250
+ * why the scan is counted rather than the delete).
251
+ */
252
+ async deleteGroupRowsBatch(db, options) {
248
253
  if (this.softDeleteEnabled) {
249
- await db.sql `
250
- DELETE FROM v3_current_data
251
- WHERE
252
- group_id = ${{ type: 'int4', value: options.groupId }}
253
- `.execute();
254
+ const result = await db.sql `
255
+ WITH
256
+ batch AS (
257
+ SELECT
258
+ ctid
259
+ FROM
260
+ v3_current_data
261
+ WHERE
262
+ group_id = ${{ type: 'int4', value: options.groupId }}
263
+ LIMIT
264
+ ${{ type: 'int4', value: options.limit }}
265
+ ),
266
+ deleted AS (
267
+ DELETE FROM v3_current_data
268
+ WHERE
269
+ ctid IN (
270
+ SELECT
271
+ ctid
272
+ FROM
273
+ batch
274
+ )
275
+ RETURNING
276
+ 1
277
+ )
278
+ SELECT
279
+ COUNT(*) AS count
280
+ FROM
281
+ batch
282
+ `.first();
283
+ return result?.count ?? 0n;
254
284
  }
255
285
  else {
256
- await db.sql `
257
- DELETE FROM current_data
258
- WHERE
259
- group_id = ${{ type: 'int4', value: options.groupId }}
260
- `.execute();
286
+ const result = await db.sql `
287
+ WITH
288
+ batch AS (
289
+ SELECT
290
+ ctid
291
+ FROM
292
+ current_data
293
+ WHERE
294
+ group_id = ${{ type: 'int4', value: options.groupId }}
295
+ LIMIT
296
+ ${{ type: 'int4', value: options.limit }}
297
+ ),
298
+ deleted AS (
299
+ DELETE FROM current_data
300
+ WHERE
301
+ ctid IN (
302
+ SELECT
303
+ ctid
304
+ FROM
305
+ batch
306
+ )
307
+ RETURNING
308
+ 1
309
+ )
310
+ SELECT
311
+ COUNT(*) AS count
312
+ FROM
313
+ batch
314
+ `.first();
315
+ return result?.count ?? 0n;
261
316
  }
262
317
  }
263
318
  wherePendingDelete(options) {
@@ -1 +1 @@
1
- {"version":3,"file":"current-data-store.js","sourceRoot":"","sources":["../../src/storage/current-data-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"current-data-store.js","sourceRoot":"","sources":["../../src/storage/current-data-store.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,7 @@ export declare const WriteCheckpoint: t.ObjectCodec<{
3
3
  user_id: t.IdentityCodec<t.CodecType.String>;
4
4
  lsns: t.Codec<Record<string, string>, string, string, t.CodecProps>;
5
5
  write_checkpoint: t.Codec<bigint, string | number, string, t.CodecProps>;
6
+ checkpoint_requested_at: t.Union<t.Codec<null, null, string, t.CodecProps>, t.Codec<Date, string | import("@powersync/service-sync-rules").DateTimeValue, string, t.CodecProps>>;
6
7
  }>;
7
8
  export type WriteCheckpoint = t.Encoded<typeof WriteCheckpoint>;
8
9
  export type WriteCheckpointDecoded = t.Decoded<typeof WriteCheckpoint>;
@@ -10,6 +11,7 @@ export declare const CustomWriteCheckpoint: t.ObjectCodec<{
10
11
  user_id: t.IdentityCodec<t.CodecType.String>;
11
12
  write_checkpoint: t.Codec<bigint, string | number, string, t.CodecProps>;
12
13
  sync_rules_id: t.Codec<bigint, string | number, string, t.CodecProps>;
14
+ checkpoint_requested_at: t.Union<t.Codec<null, null, string, t.CodecProps>, t.Codec<Date, string | import("@powersync/service-sync-rules").DateTimeValue, string, t.CodecProps>>;
13
15
  }>;
14
16
  export type CustomWriteCheckpoint = t.Encoded<typeof CustomWriteCheckpoint>;
15
17
  export type CustomWriteCheckpointDecoded = t.Decoded<typeof CustomWriteCheckpoint>;
@@ -1,13 +1,16 @@
1
+ import { framework } from '@powersync/service-core';
1
2
  import * as t from 'ts-codec';
2
3
  import { bigint, jsonb } from '../codecs.js';
3
4
  export const WriteCheckpoint = t.object({
4
5
  user_id: t.string,
5
6
  lsns: jsonb(t.record(t.string)),
6
- write_checkpoint: bigint
7
+ write_checkpoint: bigint,
8
+ checkpoint_requested_at: t.Null.or(framework.codecs.date)
7
9
  });
8
10
  export const CustomWriteCheckpoint = t.object({
9
11
  user_id: t.string,
10
12
  write_checkpoint: bigint,
11
- sync_rules_id: bigint
13
+ sync_rules_id: bigint,
14
+ checkpoint_requested_at: t.Null.or(framework.codecs.date)
12
15
  });
13
16
  //# sourceMappingURL=WriteCheckpoint.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"WriteCheckpoint.js","sourceRoot":"","sources":["../../../src/types/models/WriteCheckpoint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,UAAU,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,OAAO,EAAE,CAAC,CAAC,MAAM;IACjB,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAC/B,gBAAgB,EAAE,MAAM;CACzB,CAAC,CAAC;AAKH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,OAAO,EAAE,CAAC,CAAC,MAAM;IACjB,gBAAgB,EAAE,MAAM;IACxB,aAAa,EAAE,MAAM;CACtB,CAAC,CAAC"}
1
+ {"version":3,"file":"WriteCheckpoint.js","sourceRoot":"","sources":["../../../src/types/models/WriteCheckpoint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,CAAC,MAAM,UAAU,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,OAAO,EAAE,CAAC,CAAC,MAAM;IACjB,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAC/B,gBAAgB,EAAE,MAAM;IACxB,uBAAuB,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC;CAC1D,CAAC,CAAC;AAKH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,OAAO,EAAE,CAAC,CAAC,MAAM;IACjB,gBAAgB,EAAE,MAAM;IACxB,aAAa,EAAE,MAAM;IACrB,uBAAuB,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC;CAC1D,CAAC,CAAC"}
@@ -0,0 +1,9 @@
1
+ import * as lib_postgres from '@powersync/lib-service-postgres';
2
+ import { models } from '../types/types.js';
3
+ /**
4
+ * Gets the latest active checkpoint document.
5
+ * This is mainly exported for mocking in tests.
6
+ */
7
+ export declare function getActiveCheckpointDocument({ db }: {
8
+ db: lib_postgres.DatabaseClient;
9
+ }): Promise<models.ActiveCheckpointDecoded | null>;
@@ -0,0 +1,26 @@
1
+ import { storage } from '@powersync/service-core';
2
+ import { models } from '../types/types.js';
3
+ /**
4
+ * Gets the latest active checkpoint document.
5
+ * This is mainly exported for mocking in tests.
6
+ */
7
+ export async function getActiveCheckpointDocument({ db }) {
8
+ return db.sql `
9
+ SELECT
10
+ id,
11
+ last_checkpoint,
12
+ last_checkpoint_lsn
13
+ FROM
14
+ sync_rules
15
+ WHERE
16
+ state = ${{ value: storage.SyncRuleState.ACTIVE, type: 'varchar' }}
17
+ OR state = ${{ value: storage.SyncRuleState.ERRORED, type: 'varchar' }}
18
+ ORDER BY
19
+ id DESC
20
+ LIMIT
21
+ 1
22
+ `
23
+ .decoded(models.ActiveCheckpoint)
24
+ .first();
25
+ }
26
+ //# sourceMappingURL=checkpoints.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkpoints.js","sourceRoot":"","sources":["../../src/utils/checkpoints.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE3C;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAAC,EAChD,EAAE,EAGH;IACC,OAAO,EAAE,CAAC,GAAG,CAAA;;;;;;;;gBAQC,EAAE,KAAK,EAAE,OAAO,CAAC,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE;mBACrD,EAAE,KAAK,EAAE,OAAO,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE;;;;;GAKzE;SACE,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC;SAChC,KAAK,EAAE,CAAC;AACb,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@powersync/service-module-postgres-storage",
3
3
  "repository": "https://github.com/powersync-ja/powersync-service",
4
- "version": "0.16.3",
4
+ "version": "0.17.0",
5
5
  "license": "FSL-1.1-ALv2",
6
6
  "type": "module",
7
7
  "publishConfig": {
@@ -16,17 +16,17 @@
16
16
  "lru-cache": "^10.2.2",
17
17
  "ts-codec": "^1.3.0",
18
18
  "uuid": "^14.0.0",
19
- "@powersync/lib-service-postgres": "0.5.2",
20
- "@powersync/lib-services-framework": "0.9.8",
21
- "@powersync/service-core": "1.23.3",
22
- "@powersync/service-types": "0.16.1",
23
- "@powersync/service-jpgwire": "0.21.21",
19
+ "@powersync/lib-service-postgres": "0.5.3",
20
+ "@powersync/lib-services-framework": "0.10.0",
21
+ "@powersync/service-core": "1.24.0",
22
+ "@powersync/service-types": "0.17.0",
24
23
  "@powersync/service-jsonbig": "0.17.13",
25
- "@powersync/service-sync-rules": "0.39.0"
24
+ "@powersync/service-jpgwire": "0.21.22",
25
+ "@powersync/service-sync-rules": "0.40.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "typescript": "~6.0.3",
29
- "@powersync/service-core-tests": "0.17.2"
29
+ "@powersync/service-core-tests": "0.18.0"
30
30
  },
31
31
  "scripts": {
32
32
  "build": "tsc -b",
@@ -0,0 +1,51 @@
1
+ import { migrations } from '@powersync/service-core';
2
+
3
+ import { openMigrationDB } from '../migration-utils.js';
4
+
5
+ export const up: migrations.PowerSyncMigrationFunction = async (context) => {
6
+ const {
7
+ service_context: { configuration }
8
+ } = context;
9
+ await using client = openMigrationDB(configuration.storage);
10
+
11
+ await client.transaction(async (db) => {
12
+ await db.sql`
13
+ ALTER TABLE write_checkpoints
14
+ ADD COLUMN checkpoint_requested_at TIMESTAMP WITH TIME ZONE
15
+ `.execute();
16
+ await db.sql`
17
+ ALTER TABLE custom_write_checkpoints
18
+ ADD COLUMN checkpoint_requested_at TIMESTAMP WITH TIME ZONE
19
+ `.execute();
20
+ await db.sql`
21
+ CREATE INDEX write_checkpoints_checkpoint_requested_at ON write_checkpoints (checkpoint_requested_at)
22
+ WHERE
23
+ checkpoint_requested_at IS NOT NULL
24
+ `.execute();
25
+ await db.sql`
26
+ CREATE INDEX custom_write_checkpoints_checkpoint_requested_at ON custom_write_checkpoints (checkpoint_requested_at)
27
+ WHERE
28
+ checkpoint_requested_at IS NOT NULL
29
+ `.execute();
30
+ });
31
+ };
32
+
33
+ export const down: migrations.PowerSyncMigrationFunction = async (context) => {
34
+ const {
35
+ service_context: { configuration }
36
+ } = context;
37
+ await using client = openMigrationDB(configuration.storage);
38
+
39
+ await client.transaction(async (db) => {
40
+ // Also drops the write_checkpoints_checkpoint_requested_at index.
41
+ await db.sql`
42
+ ALTER TABLE write_checkpoints
43
+ DROP COLUMN checkpoint_requested_at
44
+ `.execute();
45
+ // Also drops the custom_write_checkpoints_checkpoint_requested_at index.
46
+ await db.sql`
47
+ ALTER TABLE custom_write_checkpoints
48
+ DROP COLUMN checkpoint_requested_at
49
+ `.execute();
50
+ });
51
+ };
@@ -8,7 +8,6 @@ import { models, NormalizedPostgresStorageConfig } from '../types/types.js';
8
8
 
9
9
  import { getStorageApplicationName } from '../utils/application-name.js';
10
10
  import { NOTIFICATION_CHANNEL, STORAGE_SCHEMA_NAME } from '../utils/db.js';
11
- import { notifySyncRulesUpdate } from './batch/PostgresBucketBatch.js';
12
11
  import { PostgresSyncRulesStorage } from './PostgresSyncRulesStorage.js';
13
12
  import { PostgresPersistedReplicationStream } from './sync-rules/PostgresPersistedSyncConfigContent.js';
14
13
 
@@ -220,8 +219,6 @@ export class PostgresBucketStorageFactory extends storage.BucketStorageFactory {
220
219
  .decoded(models.SyncRules)
221
220
  .first();
222
221
 
223
- await notifySyncRulesUpdate(this.db, newSyncRulesRow!);
224
-
225
222
  return new PostgresPersistedReplicationStream(this.db, newSyncRulesRow!);
226
223
  });
227
224
  }
@@ -53,6 +53,7 @@ export class PostgresCompactor {
53
53
  private clearBatchLimit: number;
54
54
  private maxOpId: InternalOpId;
55
55
  private buckets: string[] | undefined;
56
+ private deleteCheckpointRequestsBefore: Date | undefined;
56
57
 
57
58
  constructor(
58
59
  private db: lib_postgres.DatabaseClient,
@@ -65,6 +66,7 @@ export class PostgresCompactor {
65
66
  this.clearBatchLimit = options.clearBatchLimit ?? DEFAULT_CLEAR_BATCH_LIMIT;
66
67
  this.maxOpId = options.maxOpId ?? 0n;
67
68
  this.buckets = options.compactBuckets;
69
+ this.deleteCheckpointRequestsBefore = options.deleteCheckpointRequestsBefore;
68
70
  }
69
71
 
70
72
  /**
@@ -73,6 +75,8 @@ export class PostgresCompactor {
73
75
  * See /docs/storage/compacting-operations.md for details.
74
76
  */
75
77
  async compact() {
78
+ await this.deleteOldCheckpointRequests();
79
+
76
80
  if (this.buckets) {
77
81
  for (let bucket of this.buckets) {
78
82
  await this.compactSingleBucket(bucket);
@@ -82,6 +86,26 @@ export class PostgresCompactor {
82
86
  }
83
87
  }
84
88
 
89
+ private async deleteOldCheckpointRequests() {
90
+ if (this.deleteCheckpointRequestsBefore == null) {
91
+ return;
92
+ }
93
+
94
+ await this.db.sql`
95
+ DELETE FROM write_checkpoints
96
+ WHERE
97
+ checkpoint_requested_at IS NOT NULL
98
+ AND checkpoint_requested_at < ${{ type: 1184, value: this.deleteCheckpointRequestsBefore.toISOString() }}
99
+ `.execute();
100
+
101
+ await this.db.sql`
102
+ DELETE FROM custom_write_checkpoints
103
+ WHERE
104
+ checkpoint_requested_at IS NOT NULL
105
+ AND checkpoint_requested_at < ${{ type: 1184, value: this.deleteCheckpointRequestsBefore.toISOString() }}
106
+ `.execute();
107
+ }
108
+
85
109
  private async compactAllBuckets() {
86
110
  const DISCOVERY_BATCH_SIZE = 200;
87
111
  let lastBucket = '';