@powersync/service-module-postgres-storage 0.14.0 → 0.15.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.
@@ -22,7 +22,6 @@ import {
22
22
  import { JSONBig } from '@powersync/service-jsonbig';
23
23
  import * as sync_rules from '@powersync/service-sync-rules';
24
24
  import * as timers from 'timers/promises';
25
- import * as uuid from 'uuid';
26
25
  import { bigint, BIGINT_MAX } from '../types/codecs.js';
27
26
  import { models, RequiredOperationBatchLimits } from '../types/types.js';
28
27
  import { replicaIdToSubkey } from '../utils/bson.js';
@@ -32,7 +31,6 @@ import * as framework from '@powersync/lib-services-framework';
32
31
  import { StatementParam } from '@powersync/service-jpgwire';
33
32
  import { wrapWithAbort } from 'ix/asynciterable/operators/withabort.js';
34
33
  import * as t from 'ts-codec';
35
- import { SourceTableDecoded, StoredRelationId } from '../types/models/SourceTable.js';
36
34
  import { pick } from '../utils/ts-codec.js';
37
35
  import { PostgresBucketBatch } from './batch/PostgresBucketBatch.js';
38
36
  import { PostgresWriteCheckpointAPI } from './checkpoints/PostgresWriteCheckpointAPI.js';
@@ -69,7 +67,7 @@ export class PostgresSyncRulesStorage
69
67
 
70
68
  // TODO we might be able to share this in an abstract class
71
69
  private parsedSyncRulesCache:
72
- | { parsed: sync_rules.HydratedSyncRules; options: storage.ParseSyncRulesOptions }
70
+ | { parsed: sync_rules.HydratedSyncConfig; options: storage.ParseSyncRulesOptions }
73
71
  | undefined;
74
72
  private _checksumCache: storage.ChecksumCache | undefined;
75
73
 
@@ -109,14 +107,14 @@ export class PostgresSyncRulesStorage
109
107
  }
110
108
 
111
109
  // TODO we might be able to share this in an abstract class
112
- getParsedSyncRules(options: storage.ParseSyncRulesOptions): sync_rules.HydratedSyncRules {
110
+ getParsedSyncRules(options: storage.ParseSyncRulesOptions): sync_rules.HydratedSyncConfig {
113
111
  const { parsed, options: cachedOptions } = this.parsedSyncRulesCache ?? {};
114
112
  /**
115
113
  * Check if the cached sync config, if present, had the same options.
116
114
  * Parse sync config if the options are different or if there is no cached value.
117
115
  */
118
116
  if (!parsed || options.defaultSchema != cachedOptions?.defaultSchema) {
119
- this.parsedSyncRulesCache = { parsed: this.sync_rules.parsed(options).hydratedSyncRules(), options };
117
+ this.parsedSyncRulesCache = { parsed: this.sync_rules.parsed(options).hydratedSyncConfig(), options };
120
118
  }
121
119
 
122
120
  return this.parsedSyncRulesCache!.parsed;
@@ -188,168 +186,6 @@ export class PostgresSyncRulesStorage
188
186
  );
189
187
  }
190
188
 
191
- async resolveTable(options: storage.ResolveTableOptions): Promise<storage.ResolveTableResult> {
192
- const { group_id, connection_id, connection_tag, entity_descriptor } = options;
193
-
194
- const { schema, name: table, objectId, replicaIdColumns } = entity_descriptor;
195
-
196
- const normalizedReplicaIdColumns = replicaIdColumns.map((column) => ({
197
- name: column.name,
198
- type: column.type,
199
- // The PGWire returns this as a BigInt. We want to store this as JSONB
200
- type_oid: typeof column.typeId !== 'undefined' ? Number(column.typeId) : column.typeId
201
- }));
202
- return this.db.transaction(async (db) => {
203
- let sourceTableRow: SourceTableDecoded | null;
204
- if (objectId != null) {
205
- sourceTableRow = await db.sql`
206
- SELECT
207
- *
208
- FROM
209
- source_tables
210
- WHERE
211
- group_id = ${{ type: 'int4', value: group_id }}
212
- AND connection_id = ${{ type: 'int4', value: connection_id }}
213
- AND relation_id = ${{ type: 'jsonb', value: { object_id: objectId } satisfies StoredRelationId }}
214
- AND schema_name = ${{ type: 'varchar', value: schema }}
215
- AND table_name = ${{ type: 'varchar', value: table }}
216
- AND replica_id_columns = ${{ type: 'jsonb', value: normalizedReplicaIdColumns }}
217
- `
218
- .decoded(models.SourceTable)
219
- .first();
220
- } else {
221
- sourceTableRow = await db.sql`
222
- SELECT
223
- *
224
- FROM
225
- source_tables
226
- WHERE
227
- group_id = ${{ type: 'int4', value: group_id }}
228
- AND connection_id = ${{ type: 'int4', value: connection_id }}
229
- AND schema_name = ${{ type: 'varchar', value: schema }}
230
- AND table_name = ${{ type: 'varchar', value: table }}
231
- AND replica_id_columns = ${{ type: 'jsonb', value: normalizedReplicaIdColumns }}
232
- `
233
- .decoded(models.SourceTable)
234
- .first();
235
- }
236
-
237
- if (sourceTableRow == null) {
238
- const row = await db.sql`
239
- INSERT INTO
240
- source_tables (
241
- id,
242
- group_id,
243
- connection_id,
244
- relation_id,
245
- schema_name,
246
- table_name,
247
- replica_id_columns
248
- )
249
- VALUES
250
- (
251
- ${{ type: 'varchar', value: uuid.v4() }},
252
- ${{ type: 'int4', value: group_id }},
253
- ${{ type: 'int4', value: connection_id }},
254
- --- The objectId can be string | number | undefined, we store it as jsonb value
255
- ${{ type: 'jsonb', value: { object_id: objectId } satisfies StoredRelationId }},
256
- ${{ type: 'varchar', value: schema }},
257
- ${{ type: 'varchar', value: table }},
258
- ${{ type: 'jsonb', value: normalizedReplicaIdColumns }}
259
- )
260
- RETURNING
261
- *
262
- `
263
- .decoded(models.SourceTable)
264
- .first();
265
- sourceTableRow = row;
266
- }
267
-
268
- const sourceTable = new storage.SourceTable({
269
- id: sourceTableRow!.id,
270
- connectionTag: connection_tag,
271
- objectId: objectId,
272
- schema: schema,
273
- name: table,
274
- replicaIdColumns: replicaIdColumns,
275
- snapshotComplete: sourceTableRow!.snapshot_done ?? true
276
- });
277
- if (!sourceTable.snapshotComplete) {
278
- sourceTable.snapshotStatus = {
279
- totalEstimatedCount: Number(sourceTableRow!.snapshot_total_estimated_count ?? -1n),
280
- replicatedCount: Number(sourceTableRow!.snapshot_replicated_count ?? 0n),
281
- lastKey: sourceTableRow!.snapshot_last_key
282
- };
283
- }
284
- sourceTable.syncEvent = options.sync_rules.tableTriggersEvent(sourceTable);
285
- sourceTable.syncData = options.sync_rules.tableSyncsData(sourceTable);
286
- sourceTable.syncParameters = options.sync_rules.tableSyncsParameters(sourceTable);
287
-
288
- let truncatedTables: SourceTableDecoded[] = [];
289
- if (objectId != null) {
290
- // relation_id present - check for renamed tables
291
- truncatedTables = await db.sql`
292
- SELECT
293
- *
294
- FROM
295
- source_tables
296
- WHERE
297
- group_id = ${{ type: 'int4', value: group_id }}
298
- AND connection_id = ${{ type: 'int4', value: connection_id }}
299
- AND id != ${{ type: 'varchar', value: sourceTableRow!.id }}
300
- AND (
301
- relation_id = ${{ type: 'jsonb', value: { object_id: objectId } satisfies StoredRelationId }}
302
- OR (
303
- schema_name = ${{ type: 'varchar', value: schema }}
304
- AND table_name = ${{ type: 'varchar', value: table }}
305
- )
306
- )
307
- `
308
- .decoded(models.SourceTable)
309
- .rows();
310
- } else {
311
- // relation_id not present - only check for changed replica_id_columns
312
- truncatedTables = await db.sql`
313
- SELECT
314
- *
315
- FROM
316
- source_tables
317
- WHERE
318
- group_id = ${{ type: 'int4', value: group_id }}
319
- AND connection_id = ${{ type: 'int4', value: connection_id }}
320
- AND id != ${{ type: 'varchar', value: sourceTableRow!.id }}
321
- AND (
322
- schema_name = ${{ type: 'varchar', value: schema }}
323
- AND table_name = ${{ type: 'varchar', value: table }}
324
- )
325
- `
326
- .decoded(models.SourceTable)
327
- .rows();
328
- }
329
-
330
- return {
331
- table: sourceTable,
332
- dropTables: truncatedTables.map(
333
- (doc) =>
334
- new storage.SourceTable({
335
- id: doc.id,
336
- connectionTag: connection_tag,
337
- objectId: doc.relation_id?.object_id ?? 0,
338
- schema: doc.schema_name,
339
- name: doc.table_name,
340
- replicaIdColumns:
341
- doc.replica_id_columns?.map((c) => ({
342
- name: c.name,
343
- typeOid: c.typeId,
344
- type: c.type
345
- })) ?? [],
346
- snapshotComplete: doc.snapshot_done ?? true
347
- })
348
- )
349
- };
350
- });
351
- }
352
-
353
189
  async createWriter(options: storage.CreateWriterOptions): Promise<storage.BucketStorageBatch> {
354
190
  const syncRules = await this.db.sql`
355
191
  SELECT
@@ -370,7 +206,7 @@ export class PostgresSyncRulesStorage
370
206
  const writer = new PostgresBucketBatch({
371
207
  logger: options.logger ?? this.logger,
372
208
  db: this.db,
373
- sync_rules: this.sync_rules.parsed(options).hydratedSyncRules(),
209
+ sync_rules: this.sync_rules.parsed(options).hydratedSyncConfig(),
374
210
  group_id: this.group_id,
375
211
  slot_name: this.slot_name,
376
212
  last_checkpoint_lsn: checkpoint_lsn,
@@ -380,6 +216,7 @@ export class PostgresSyncRulesStorage
380
216
  skip_existing_rows: options.skipExistingRows ?? false,
381
217
  batch_limits: this.options.batchLimits,
382
218
  markRecordUnavailable: options.markRecordUnavailable,
219
+ hooks: options.hooks,
383
220
  storageConfig: this.storageConfig
384
221
  });
385
222
  this.iterateListeners((cb) => cb.batchStarted?.(writer));
@@ -670,13 +507,16 @@ export class PostgresSyncRulesStorage
670
507
  snapshot_done,
671
508
  snapshot_lsn,
672
509
  last_checkpoint_lsn,
673
- state
510
+ state,
511
+ keepalive_op
674
512
  FROM
675
513
  sync_rules
676
514
  WHERE
677
515
  id = ${{ type: 'int4', value: this.group_id }}
678
516
  `
679
- .decoded(pick(models.SyncRules, ['snapshot_done', 'last_checkpoint_lsn', 'state', 'snapshot_lsn']))
517
+ .decoded(
518
+ pick(models.SyncRules, ['snapshot_done', 'last_checkpoint_lsn', 'state', 'snapshot_lsn', 'keepalive_op'])
519
+ )
680
520
  .first();
681
521
 
682
522
  if (syncRulesRow == null) {
@@ -687,7 +527,8 @@ export class PostgresSyncRulesStorage
687
527
  snapshot_done: syncRulesRow.snapshot_done,
688
528
  active: syncRulesRow.state == storage.SyncRuleState.ACTIVE,
689
529
  checkpoint_lsn: syncRulesRow.last_checkpoint_lsn ?? null,
690
- snapshot_lsn: syncRulesRow.snapshot_lsn ?? null
530
+ snapshot_lsn: syncRulesRow.snapshot_lsn ?? null,
531
+ keepalive_op: syncRulesRow.keepalive_op ?? null
691
532
  };
692
533
  }
693
534