@powersync/service-module-postgres-storage 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -0
- package/dist/migrations/scripts/1784900000000-source-metadata.d.ts +3 -0
- package/dist/migrations/scripts/1784900000000-source-metadata.js +22 -0
- package/dist/migrations/scripts/1784900000000-source-metadata.js.map +1 -0
- package/dist/migrations/scripts/1788000000000-sync-config-version-label.d.ts +3 -0
- package/dist/migrations/scripts/1788000000000-sync-config-version-label.js +12 -0
- package/dist/migrations/scripts/1788000000000-sync-config-version-label.js.map +1 -0
- package/dist/storage/PostgresBucketStorageFactory.d.ts +1 -0
- package/dist/storage/PostgresBucketStorageFactory.js +14 -7
- package/dist/storage/PostgresBucketStorageFactory.js.map +1 -1
- package/dist/storage/PostgresStorageProvider.js +2 -1
- package/dist/storage/PostgresStorageProvider.js.map +1 -1
- package/dist/storage/PostgresSyncRulesStorage.d.ts +4 -3
- package/dist/storage/PostgresSyncRulesStorage.js +17 -5
- package/dist/storage/PostgresSyncRulesStorage.js.map +1 -1
- package/dist/storage/batch/PostgresBucketBatch.d.ts +2 -2
- package/dist/storage/batch/PostgresBucketBatch.js +63 -94
- package/dist/storage/batch/PostgresBucketBatch.js.map +1 -1
- package/dist/storage/checkpoints/PostgresWriteCheckpointAPI.d.ts +1 -1
- package/dist/storage/checkpoints/PostgresWriteCheckpointAPI.js +2 -2
- package/dist/storage/checkpoints/PostgresWriteCheckpointAPI.js.map +1 -1
- package/dist/storage/sync-rules/PostgresPersistedSyncConfigContent.js +5 -1
- package/dist/storage/sync-rules/PostgresPersistedSyncConfigContent.js.map +1 -1
- package/dist/types/models/ActiveCheckpoint.d.ts +2 -2
- package/dist/types/models/ActiveCheckpoint.js +3 -2
- package/dist/types/models/ActiveCheckpoint.js.map +1 -1
- package/dist/types/models/ActiveCheckpointNotification.d.ts +2 -2
- package/dist/types/models/BucketData.d.ts +6 -6
- package/dist/types/models/BucketData.js +7 -6
- package/dist/types/models/BucketData.js.map +1 -1
- package/dist/types/models/CurrentData.d.ts +1 -1
- package/dist/types/models/CurrentData.js +2 -1
- package/dist/types/models/CurrentData.js.map +1 -1
- package/dist/types/models/SdkReporting.d.ts +3 -3
- package/dist/types/models/SdkReporting.js +3 -5
- package/dist/types/models/SdkReporting.js.map +1 -1
- package/dist/types/models/SourceTable.d.ts +13 -8
- package/dist/types/models/SourceTable.js +11 -6
- package/dist/types/models/SourceTable.js.map +1 -1
- package/dist/types/models/SyncRules.d.ts +11 -10
- package/dist/types/models/SyncRules.js +12 -11
- package/dist/types/models/SyncRules.js.map +1 -1
- package/dist/types/models/WriteCheckpoint.d.ts +2 -2
- package/dist/types/models/WriteCheckpoint.js +3 -2
- package/dist/types/models/WriteCheckpoint.js.map +1 -1
- package/dist/types/types.d.ts +2 -0
- package/package.json +8 -8
- package/src/migrations/scripts/1784900000000-source-metadata.ts +31 -0
- package/src/migrations/scripts/1788000000000-sync-config-version-label.ts +18 -0
- package/src/storage/PostgresBucketStorageFactory.ts +15 -7
- package/src/storage/PostgresStorageProvider.ts +2 -1
- package/src/storage/PostgresSyncRulesStorage.ts +21 -6
- package/src/storage/batch/PostgresBucketBatch.ts +67 -98
- package/src/storage/checkpoints/PostgresWriteCheckpointAPI.ts +2 -2
- package/src/storage/sync-rules/PostgresPersistedSyncConfigContent.ts +8 -1
- package/src/types/models/ActiveCheckpoint.ts +3 -2
- package/src/types/models/BucketData.ts +7 -6
- package/src/types/models/CurrentData.ts +2 -1
- package/src/types/models/SdkReporting.ts +4 -4
- package/src/types/models/SourceTable.ts +12 -6
- package/src/types/models/SyncRules.ts +12 -11
- package/src/types/models/WriteCheckpoint.ts +3 -2
- package/test/src/__snapshots__/storage_sync.test.ts.snap +0 -995
- package/test/src/checkpoint_notifications.test.ts +8 -2
- package/test/src/storage.test.ts +12 -4
- package/test/src/storage_compacting.test.ts +2 -2
- package/test/src/storage_sync.test.ts +44 -0
- package/test/src/util.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
utils
|
|
20
20
|
} from '@powersync/service-core';
|
|
21
21
|
import * as sync_rules from '@powersync/service-sync-rules';
|
|
22
|
+
import { orNull } from '@powersync/service-types';
|
|
22
23
|
import * as timers from 'timers/promises';
|
|
23
24
|
import * as t from 'ts-codec';
|
|
24
25
|
import * as uuid from 'uuid';
|
|
@@ -64,10 +65,10 @@ const StatefulCheckpoint = models.ActiveCheckpoint.and(t.object({ state: t.Enum(
|
|
|
64
65
|
const CheckpointWithStatus = StatefulCheckpoint.and(
|
|
65
66
|
t.object({
|
|
66
67
|
snapshot_done: t.boolean,
|
|
67
|
-
no_checkpoint_before: t.string
|
|
68
|
+
no_checkpoint_before: orNull(t.string),
|
|
68
69
|
can_checkpoint: t.boolean,
|
|
69
|
-
keepalive_op: bigint
|
|
70
|
-
new_last_checkpoint: bigint
|
|
70
|
+
keepalive_op: orNull(bigint),
|
|
71
|
+
new_last_checkpoint: orNull(bigint),
|
|
71
72
|
created_checkpoint: t.boolean
|
|
72
73
|
})
|
|
73
74
|
);
|
|
@@ -144,6 +145,7 @@ export class PostgresBucketBatch
|
|
|
144
145
|
|
|
145
146
|
async resolveTables(options: storage.ResolveTablesOptions): Promise<storage.ResolveTablesResult> {
|
|
146
147
|
const syncRules = options.parsedSyncConfig?.hydratedSyncConfig ?? this.sync_rules;
|
|
148
|
+
const reconcile = options.reconcileSourceTables ?? storage.defaultSourceTableReconciler;
|
|
147
149
|
const { connection_id, source } = options;
|
|
148
150
|
const { schema, name: table, objectId, replicaIdColumns, connectionTag, sendsCompleteRows } = source;
|
|
149
151
|
|
|
@@ -154,9 +156,10 @@ export class PostgresBucketBatch
|
|
|
154
156
|
}));
|
|
155
157
|
|
|
156
158
|
return this.db.transaction(async (db) => {
|
|
157
|
-
|
|
159
|
+
// Find records that overlap by name or relation id.
|
|
160
|
+
let candidateRows: SourceTableDecoded[];
|
|
158
161
|
if (objectId != null) {
|
|
159
|
-
|
|
162
|
+
candidateRows = await db.sql`
|
|
160
163
|
SELECT
|
|
161
164
|
*
|
|
162
165
|
FROM
|
|
@@ -164,15 +167,18 @@ export class PostgresBucketBatch
|
|
|
164
167
|
WHERE
|
|
165
168
|
group_id = ${{ type: 'int4', value: this.group_id }}
|
|
166
169
|
AND connection_id = ${{ type: 'int4', value: connection_id }}
|
|
167
|
-
AND
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
170
|
+
AND (
|
|
171
|
+
relation_id = ${{ type: 'jsonb', value: { object_id: objectId } satisfies StoredRelationId }}
|
|
172
|
+
OR (
|
|
173
|
+
schema_name = ${{ type: 'varchar', value: schema }}
|
|
174
|
+
AND table_name = ${{ type: 'varchar', value: table }}
|
|
175
|
+
)
|
|
176
|
+
)
|
|
171
177
|
`
|
|
172
178
|
.decoded(models.SourceTable)
|
|
173
|
-
.
|
|
179
|
+
.rows();
|
|
174
180
|
} else {
|
|
175
|
-
|
|
181
|
+
candidateRows = await db.sql`
|
|
176
182
|
SELECT
|
|
177
183
|
*
|
|
178
184
|
FROM
|
|
@@ -182,15 +188,42 @@ export class PostgresBucketBatch
|
|
|
182
188
|
AND connection_id = ${{ type: 'int4', value: connection_id }}
|
|
183
189
|
AND schema_name = ${{ type: 'varchar', value: schema }}
|
|
184
190
|
AND table_name = ${{ type: 'varchar', value: table }}
|
|
185
|
-
AND replica_id_columns = ${{ type: 'jsonb', value: normalizedReplicaIdColumns }}
|
|
186
191
|
`
|
|
187
192
|
.decoded(models.SourceTable)
|
|
188
|
-
.
|
|
193
|
+
.rows();
|
|
189
194
|
}
|
|
190
195
|
|
|
191
|
-
|
|
196
|
+
const candidateTables = candidateRows.map((row) => {
|
|
197
|
+
const table = this.sourceTableFromRow(row, connectionTag, syncRules);
|
|
198
|
+
table.storeCurrentData = sendsCompleteRows !== true;
|
|
199
|
+
return table;
|
|
200
|
+
});
|
|
201
|
+
const candidates = candidateTables.map((table) => table.clone());
|
|
202
|
+
const resolution = await reconcile({ source, candidates });
|
|
203
|
+
storage.validateSourceTableCandidateResolution(candidates, resolution);
|
|
204
|
+
|
|
205
|
+
for (const { id, sourceMetadata } of storage.diffSourceTableUpdates(candidateTables, resolution)) {
|
|
206
|
+
await db.sql`
|
|
207
|
+
UPDATE source_tables
|
|
208
|
+
SET
|
|
209
|
+
source_metadata = ${{ type: 'jsonb', value: sourceMetadata ?? null }}
|
|
210
|
+
WHERE
|
|
211
|
+
id = ${{ type: 'varchar', value: id.toString() }}
|
|
212
|
+
`.execute();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const materializedResolution = storage.materializeSourceTableResolution(candidateTables, resolution);
|
|
216
|
+
const compatibleTables = materializedResolution.compatibleTables;
|
|
217
|
+
|
|
218
|
+
// Keep one record, preferring one that has already been snapshotted.
|
|
219
|
+
const reuse = compatibleTables.find((candidate) => candidate.snapshotComplete) ?? compatibleTables[0] ?? null;
|
|
220
|
+
|
|
221
|
+
let sourceTable: storage.SourceTable;
|
|
222
|
+
if (reuse != null) {
|
|
223
|
+
sourceTable = reuse;
|
|
224
|
+
} else {
|
|
192
225
|
const id = options.idGenerator ? postgresTableId(options.idGenerator()) : uuid.v4();
|
|
193
|
-
|
|
226
|
+
const insertedRow = await db.sql`
|
|
194
227
|
INSERT INTO
|
|
195
228
|
source_tables (
|
|
196
229
|
id,
|
|
@@ -199,7 +232,8 @@ export class PostgresBucketBatch
|
|
|
199
232
|
relation_id,
|
|
200
233
|
schema_name,
|
|
201
234
|
table_name,
|
|
202
|
-
replica_id_columns
|
|
235
|
+
replica_id_columns,
|
|
236
|
+
source_metadata
|
|
203
237
|
)
|
|
204
238
|
VALUES
|
|
205
239
|
(
|
|
@@ -209,95 +243,27 @@ export class PostgresBucketBatch
|
|
|
209
243
|
${{ type: 'jsonb', value: { object_id: objectId } satisfies StoredRelationId }},
|
|
210
244
|
${{ type: 'varchar', value: schema }},
|
|
211
245
|
${{ type: 'varchar', value: table }},
|
|
212
|
-
${{ type: 'jsonb', value: normalizedReplicaIdColumns }}
|
|
246
|
+
${{ type: 'jsonb', value: normalizedReplicaIdColumns }},
|
|
247
|
+
${{ type: 'jsonb', value: resolution.newTableValues.sourceMetadata ?? null }}
|
|
213
248
|
)
|
|
214
249
|
RETURNING
|
|
215
250
|
*
|
|
216
251
|
`
|
|
217
252
|
.decoded(models.SourceTable)
|
|
218
253
|
.first();
|
|
254
|
+
sourceTable = this.sourceTableFromRow(insertedRow!, connectionTag, syncRules);
|
|
255
|
+
sourceTable.storeCurrentData = sendsCompleteRows !== true;
|
|
219
256
|
}
|
|
220
257
|
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
snapshotComplete: sourceTableRow!.snapshot_done ?? true,
|
|
227
|
-
...syncRules.getMatchingSources(source)
|
|
228
|
-
});
|
|
229
|
-
if (!sourceTable.snapshotComplete) {
|
|
230
|
-
sourceTable.snapshotStatus = {
|
|
231
|
-
totalEstimatedCount: Number(sourceTableRow!.snapshot_total_estimated_count ?? -1n),
|
|
232
|
-
replicatedCount: Number(sourceTableRow!.snapshot_replicated_count ?? 0n),
|
|
233
|
-
lastKey: sourceTableRow!.snapshot_last_key
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
sourceTable.syncEvent = syncRules.tableTriggersEvent(source);
|
|
237
|
-
sourceTable.syncData = sourceTable.bucketDataSources.length > 0;
|
|
238
|
-
sourceTable.syncParameters = sourceTable.parameterLookupSources.length > 0;
|
|
239
|
-
sourceTable.storeCurrentData = sendsCompleteRows !== true;
|
|
240
|
-
|
|
241
|
-
let truncatedTables: SourceTableDecoded[] = [];
|
|
242
|
-
if (objectId != null) {
|
|
243
|
-
truncatedTables = await db.sql`
|
|
244
|
-
SELECT
|
|
245
|
-
*
|
|
246
|
-
FROM
|
|
247
|
-
source_tables
|
|
248
|
-
WHERE
|
|
249
|
-
group_id = ${{ type: 'int4', value: this.group_id }}
|
|
250
|
-
AND connection_id = ${{ type: 'int4', value: connection_id }}
|
|
251
|
-
AND id != ${{ type: 'varchar', value: sourceTableRow!.id }}
|
|
252
|
-
AND (
|
|
253
|
-
relation_id = ${{ type: 'jsonb', value: { object_id: objectId } satisfies StoredRelationId }}
|
|
254
|
-
OR (
|
|
255
|
-
schema_name = ${{ type: 'varchar', value: schema }}
|
|
256
|
-
AND table_name = ${{ type: 'varchar', value: table }}
|
|
257
|
-
)
|
|
258
|
-
)
|
|
259
|
-
`
|
|
260
|
-
.decoded(models.SourceTable)
|
|
261
|
-
.rows();
|
|
262
|
-
} else {
|
|
263
|
-
truncatedTables = await db.sql`
|
|
264
|
-
SELECT
|
|
265
|
-
*
|
|
266
|
-
FROM
|
|
267
|
-
source_tables
|
|
268
|
-
WHERE
|
|
269
|
-
group_id = ${{ type: 'int4', value: this.group_id }}
|
|
270
|
-
AND connection_id = ${{ type: 'int4', value: connection_id }}
|
|
271
|
-
AND id != ${{ type: 'varchar', value: sourceTableRow!.id }}
|
|
272
|
-
AND (
|
|
273
|
-
schema_name = ${{ type: 'varchar', value: schema }}
|
|
274
|
-
AND table_name = ${{ type: 'varchar', value: table }}
|
|
275
|
-
)
|
|
276
|
-
`
|
|
277
|
-
.decoded(models.SourceTable)
|
|
278
|
-
.rows();
|
|
279
|
-
}
|
|
258
|
+
const dropTables = [
|
|
259
|
+
...materializedResolution.incompatibleTables,
|
|
260
|
+
// PostgreSQL storage only keeps one SourceTable per physical table.
|
|
261
|
+
...compatibleTables.filter((candidate) => !storage.sourceTableIdEquals(candidate.id, sourceTable.id))
|
|
262
|
+
];
|
|
280
263
|
|
|
281
264
|
return {
|
|
282
265
|
tables: [sourceTable],
|
|
283
|
-
dropTables
|
|
284
|
-
const ref = { connectionTag, schema: doc.schema_name, name: doc.table_name };
|
|
285
|
-
const dropTable = new storage.SourceTable({
|
|
286
|
-
id: doc.id,
|
|
287
|
-
ref,
|
|
288
|
-
objectId: doc.relation_id?.object_id ?? 0,
|
|
289
|
-
replicaIdColumns:
|
|
290
|
-
doc.replica_id_columns?.map(
|
|
291
|
-
(c) => ({ name: c.name, typeId: c.typeId, type: c.type }) satisfies ColumnDescriptor
|
|
292
|
-
) ?? [],
|
|
293
|
-
snapshotComplete: doc.snapshot_done ?? true,
|
|
294
|
-
...syncRules.getMatchingSources(ref)
|
|
295
|
-
});
|
|
296
|
-
dropTable.syncEvent = syncRules.tableTriggersEvent(ref);
|
|
297
|
-
dropTable.syncData = dropTable.bucketDataSources.length > 0;
|
|
298
|
-
dropTable.syncParameters = dropTable.parameterLookupSources.length > 0;
|
|
299
|
-
return dropTable;
|
|
300
|
-
})
|
|
266
|
+
dropTables
|
|
301
267
|
};
|
|
302
268
|
});
|
|
303
269
|
}
|
|
@@ -334,9 +300,10 @@ export class PostgresBucketBatch
|
|
|
334
300
|
objectId: row.relation_id?.object_id,
|
|
335
301
|
replicaIdColumns:
|
|
336
302
|
row.replica_id_columns?.map(
|
|
337
|
-
(
|
|
303
|
+
(column) => ({ name: column.name, typeId: column.type_oid, type: column.type }) satisfies ColumnDescriptor
|
|
338
304
|
) ?? [],
|
|
339
305
|
snapshotComplete: row.snapshot_done ?? true,
|
|
306
|
+
sourceMetadata: row.source_metadata,
|
|
340
307
|
...syncRules.getMatchingSources(ref)
|
|
341
308
|
});
|
|
342
309
|
|
|
@@ -357,7 +324,9 @@ export class PostgresBucketBatch
|
|
|
357
324
|
// TODO maybe share with abstract class
|
|
358
325
|
const { after, before, sourceTable, tag } = record;
|
|
359
326
|
const storeCurrentData = this.options.store_current_data && sourceTable.storeCurrentData;
|
|
360
|
-
//
|
|
327
|
+
// PostgreSQL storage resolves each physical table to one SourceTable. syncEvent records whether
|
|
328
|
+
// that table matches any event definition; each matching definition is evaluated once here and
|
|
329
|
+
// may produce multiple payloads from its matching queries.
|
|
361
330
|
if (sourceTable.syncEvent) {
|
|
362
331
|
for (const event of this.getTableEvents(sourceTable)) {
|
|
363
332
|
this.iterateListeners((cb) =>
|
|
@@ -1368,10 +1337,10 @@ export class PostgresBucketBatch
|
|
|
1368
1337
|
}
|
|
1369
1338
|
|
|
1370
1339
|
/**
|
|
1371
|
-
* Gets relevant {@link
|
|
1340
|
+
* Gets relevant {@link HydratedEventDescriptor}s for the given {@link SourceTable}
|
|
1372
1341
|
* TODO maybe share this with an abstract class
|
|
1373
1342
|
*/
|
|
1374
|
-
protected getTableEvents(table: storage.SourceTable): sync_rules.
|
|
1343
|
+
protected getTableEvents(table: storage.SourceTable): sync_rules.HydratedEventDescriptor[] {
|
|
1375
1344
|
return this.sync_rules.eventDescriptors.filter((evt) =>
|
|
1376
1345
|
[...evt.getSourceTables()].some((sourceTable) => sourceTable.matches(table.ref))
|
|
1377
1346
|
);
|
|
@@ -21,8 +21,8 @@ export class PostgresWriteCheckpointAPI implements storage.WriteCheckpointAPI {
|
|
|
21
21
|
return this._mode;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
setWriteCheckpointMode(
|
|
25
|
-
this._mode = mode;
|
|
24
|
+
setWriteCheckpointMode(config: storage.WriteCheckpointModeConfig): void {
|
|
25
|
+
this._mode = config.mode;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
async batchCreateCustomWriteCheckpoints(
|
|
@@ -14,7 +14,8 @@ export class PostgresPersistedSyncConfigContent extends storage.PersistedSyncCon
|
|
|
14
14
|
compiled_plan: row.sync_plan,
|
|
15
15
|
replicationStreamName: row.slot_name,
|
|
16
16
|
storageVersion: row.storage_version ?? storage.LEGACY_STORAGE_VERSION,
|
|
17
|
-
syncConfigState: row.state as storage.SyncRuleState
|
|
17
|
+
syncConfigState: row.state as storage.SyncRuleState,
|
|
18
|
+
version_label: row.version_label ?? undefined
|
|
18
19
|
});
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -61,6 +62,12 @@ export class PostgresPersistedReplicationStream extends storage.PersistedReplica
|
|
|
61
62
|
state: row.state as storage.SyncRuleState,
|
|
62
63
|
storageVersion: row.storage_version ?? storage.LEGACY_STORAGE_VERSION
|
|
63
64
|
});
|
|
65
|
+
if (this.storageVersion >= storage.STORAGE_VERSION_4) {
|
|
66
|
+
throw new ServiceError(
|
|
67
|
+
ErrorCode.PSYNC_S1005,
|
|
68
|
+
`Unsupported storage version ${this.storageVersion} for PostgreSQL storage`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
64
71
|
this.syncConfigContent = [new PostgresPersistedSyncConfigContent(this.db, this.row)];
|
|
65
72
|
}
|
|
66
73
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { orNull } from '@powersync/service-types';
|
|
1
2
|
import * as t from 'ts-codec';
|
|
2
3
|
import { bigint, pgwire_number } from '../codecs.js';
|
|
3
4
|
|
|
@@ -7,8 +8,8 @@ import { bigint, pgwire_number } from '../codecs.js';
|
|
|
7
8
|
*/
|
|
8
9
|
export const ActiveCheckpoint = t.object({
|
|
9
10
|
id: pgwire_number,
|
|
10
|
-
last_checkpoint:
|
|
11
|
-
last_checkpoint_lsn:
|
|
11
|
+
last_checkpoint: orNull(bigint),
|
|
12
|
+
last_checkpoint_lsn: orNull(t.string)
|
|
12
13
|
});
|
|
13
14
|
|
|
14
15
|
export type ActiveCheckpoint = t.Encoded<typeof ActiveCheckpoint>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { orNull } from '@powersync/service-types';
|
|
1
2
|
import * as t from 'ts-codec';
|
|
2
3
|
import { bigint, hexBuffer, pgwire_number } from '../codecs.js';
|
|
3
4
|
|
|
@@ -13,13 +14,13 @@ export const BucketData = t.object({
|
|
|
13
14
|
bucket_name: t.string,
|
|
14
15
|
op_id: bigint,
|
|
15
16
|
op: t.Enum(OpType),
|
|
16
|
-
source_table:
|
|
17
|
-
source_key:
|
|
18
|
-
table_name: t.string
|
|
19
|
-
row_id: t.string
|
|
17
|
+
source_table: orNull(t.string),
|
|
18
|
+
source_key: orNull(hexBuffer),
|
|
19
|
+
table_name: orNull(t.string),
|
|
20
|
+
row_id: orNull(t.string),
|
|
20
21
|
checksum: bigint,
|
|
21
|
-
data:
|
|
22
|
-
target_op:
|
|
22
|
+
data: orNull(t.string),
|
|
23
|
+
target_op: orNull(bigint)
|
|
23
24
|
});
|
|
24
25
|
|
|
25
26
|
export type BucketData = t.Encoded<typeof BucketData>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { orNull } from '@powersync/service-types';
|
|
1
2
|
import * as t from 'ts-codec';
|
|
2
3
|
import { bigint, hexBuffer, jsonb, pgwire_number } from '../codecs.js';
|
|
3
4
|
|
|
@@ -26,7 +27,7 @@ export const V3CurrentData = t.object({
|
|
|
26
27
|
lookups: t.array(hexBuffer),
|
|
27
28
|
source_key: hexBuffer,
|
|
28
29
|
source_table: t.string,
|
|
29
|
-
pending_delete:
|
|
30
|
+
pending_delete: orNull(bigint)
|
|
30
31
|
});
|
|
31
32
|
|
|
32
33
|
export type V1CurrentData = t.Encoded<typeof V1CurrentData>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { orNull } from '@powersync/service-types';
|
|
1
2
|
import * as t from 'ts-codec';
|
|
2
3
|
import { bigint, jsonb } from '../codecs.js';
|
|
3
4
|
|
|
@@ -11,12 +12,11 @@ export type Sdks = t.Encoded<typeof Sdks>;
|
|
|
11
12
|
|
|
12
13
|
export const SdkReporting = t.object({
|
|
13
14
|
users: bigint,
|
|
14
|
-
sdks:
|
|
15
|
-
.object({
|
|
15
|
+
sdks: orNull(
|
|
16
|
+
t.object({
|
|
16
17
|
data: jsonb<Sdks[]>(t.array(Sdks))
|
|
17
18
|
})
|
|
18
|
-
|
|
19
|
-
.or(t.Null)
|
|
19
|
+
).optional()
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
export type SdkReporting = t.Encoded<typeof SdkReporting>;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { JsonValue } from '@powersync/service-core';
|
|
2
|
+
import { orNull } from '@powersync/service-types';
|
|
1
3
|
import * as t from 'ts-codec';
|
|
2
4
|
import { bigint, hexBuffer, jsonb, jsonb_raw, pgwire_number } from '../codecs.js';
|
|
3
5
|
|
|
@@ -14,21 +16,25 @@ export const ColumnDescriptor = t.object({
|
|
|
14
16
|
/**
|
|
15
17
|
* Some data sources have a type id that can be used to identify the type of the column
|
|
16
18
|
*/
|
|
17
|
-
|
|
19
|
+
type_oid: t.number.optional()
|
|
18
20
|
});
|
|
19
21
|
|
|
20
22
|
export const SourceTable = t.object({
|
|
21
23
|
id: t.string,
|
|
22
24
|
group_id: pgwire_number,
|
|
23
25
|
connection_id: bigint,
|
|
24
|
-
relation_id:
|
|
26
|
+
relation_id: orNull(jsonb_raw<StoredRelationId>()),
|
|
25
27
|
schema_name: t.string,
|
|
26
28
|
table_name: t.string,
|
|
27
|
-
replica_id_columns:
|
|
29
|
+
replica_id_columns: orNull(jsonb(t.array(ColumnDescriptor))),
|
|
28
30
|
snapshot_done: t.boolean,
|
|
29
|
-
snapshot_total_estimated_count:
|
|
30
|
-
snapshot_replicated_count:
|
|
31
|
-
snapshot_last_key:
|
|
31
|
+
snapshot_total_estimated_count: orNull(bigint),
|
|
32
|
+
snapshot_replicated_count: orNull(bigint),
|
|
33
|
+
snapshot_last_key: orNull(hexBuffer),
|
|
34
|
+
/**
|
|
35
|
+
* Source-specific metadata. Null for legacy records.
|
|
36
|
+
*/
|
|
37
|
+
source_metadata: orNull(jsonb_raw<JsonValue>())
|
|
32
38
|
});
|
|
33
39
|
|
|
34
40
|
export type SourceTable = t.Encoded<typeof SourceTable>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { framework, storage } from '@powersync/service-core';
|
|
2
|
-
import { ReplicationError } from '@powersync/service-types';
|
|
2
|
+
import { orNull, ReplicationError } from '@powersync/service-types';
|
|
3
3
|
import * as t from 'ts-codec';
|
|
4
4
|
import { bigint, pgwire_number } from '../codecs.js';
|
|
5
5
|
import { jsonContainerObject } from './json.js';
|
|
@@ -16,42 +16,43 @@ export const SyncRules = t.object({
|
|
|
16
16
|
/**
|
|
17
17
|
* May be set if snapshot_done = false, if the replication stream requires it.
|
|
18
18
|
*/
|
|
19
|
-
snapshot_lsn:
|
|
19
|
+
snapshot_lsn: orNull(t.string),
|
|
20
20
|
/**
|
|
21
21
|
* The last consistent checkpoint.
|
|
22
22
|
*
|
|
23
23
|
* There may be higher OpIds used in the database if we're in the middle of replicating a large transaction.
|
|
24
24
|
*/
|
|
25
|
-
last_checkpoint:
|
|
25
|
+
last_checkpoint: orNull(bigint),
|
|
26
26
|
/**
|
|
27
27
|
* The LSN associated with the last consistent checkpoint.
|
|
28
28
|
*/
|
|
29
|
-
last_checkpoint_lsn:
|
|
29
|
+
last_checkpoint_lsn: orNull(t.string),
|
|
30
30
|
/**
|
|
31
31
|
* If set, no new checkpoints may be created < this value.
|
|
32
32
|
*/
|
|
33
|
-
no_checkpoint_before:
|
|
33
|
+
no_checkpoint_before: orNull(t.string),
|
|
34
34
|
slot_name: t.string,
|
|
35
35
|
/**
|
|
36
36
|
* Last time we persisted a checkpoint.
|
|
37
37
|
*
|
|
38
38
|
* This may be old if no data is incoming.
|
|
39
39
|
*/
|
|
40
|
-
last_checkpoint_ts:
|
|
40
|
+
last_checkpoint_ts: orNull(framework.codecs.date),
|
|
41
41
|
/**
|
|
42
42
|
* Last time we persisted a checkpoint or keepalive.
|
|
43
43
|
*
|
|
44
44
|
* This should stay fairly current while replicating.
|
|
45
45
|
*/
|
|
46
|
-
last_keepalive_ts:
|
|
46
|
+
last_keepalive_ts: orNull(framework.codecs.date),
|
|
47
47
|
/**
|
|
48
48
|
* If an error is stopping replication, it will be stored here.
|
|
49
49
|
*/
|
|
50
|
-
last_fatal_error:
|
|
51
|
-
keepalive_op:
|
|
52
|
-
storage_version:
|
|
50
|
+
last_fatal_error: orNull(t.string),
|
|
51
|
+
keepalive_op: orNull(bigint),
|
|
52
|
+
storage_version: orNull(pgwire_number).optional(),
|
|
53
|
+
version_label: orNull(t.string).optional(),
|
|
53
54
|
content: t.string,
|
|
54
|
-
sync_plan:
|
|
55
|
+
sync_plan: orNull(
|
|
55
56
|
jsonContainerObject(
|
|
56
57
|
t.object({
|
|
57
58
|
plan: t.any,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { framework } from '@powersync/service-core';
|
|
2
|
+
import { orNull } from '@powersync/service-types';
|
|
2
3
|
import * as t from 'ts-codec';
|
|
3
4
|
import { bigint, jsonb } from '../codecs.js';
|
|
4
5
|
|
|
@@ -6,7 +7,7 @@ export const WriteCheckpoint = t.object({
|
|
|
6
7
|
user_id: t.string,
|
|
7
8
|
lsns: jsonb(t.record(t.string)),
|
|
8
9
|
write_checkpoint: bigint,
|
|
9
|
-
checkpoint_requested_at:
|
|
10
|
+
checkpoint_requested_at: orNull(framework.codecs.date)
|
|
10
11
|
});
|
|
11
12
|
|
|
12
13
|
export type WriteCheckpoint = t.Encoded<typeof WriteCheckpoint>;
|
|
@@ -16,7 +17,7 @@ export const CustomWriteCheckpoint = t.object({
|
|
|
16
17
|
user_id: t.string,
|
|
17
18
|
write_checkpoint: bigint,
|
|
18
19
|
sync_rules_id: bigint,
|
|
19
|
-
checkpoint_requested_at:
|
|
20
|
+
checkpoint_requested_at: orNull(framework.codecs.date)
|
|
20
21
|
});
|
|
21
22
|
|
|
22
23
|
export type CustomWriteCheckpoint = t.Encoded<typeof CustomWriteCheckpoint>;
|