@powersync/service-module-postgres-storage 0.0.0-dev-20250116115804
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 +32 -0
- package/LICENSE +67 -0
- package/README.md +67 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/@types/index.d.ts +7 -0
- package/dist/@types/migrations/PostgresMigrationAgent.d.ts +12 -0
- package/dist/@types/migrations/PostgresMigrationStore.d.ts +14 -0
- package/dist/@types/migrations/migration-utils.d.ts +3 -0
- package/dist/@types/migrations/scripts/1684951997326-init.d.ts +3 -0
- package/dist/@types/module/PostgresStorageModule.d.ts +6 -0
- package/dist/@types/storage/PostgresBucketStorageFactory.d.ts +42 -0
- package/dist/@types/storage/PostgresCompactor.d.ts +40 -0
- package/dist/@types/storage/PostgresStorageProvider.d.ts +5 -0
- package/dist/@types/storage/PostgresSyncRulesStorage.d.ts +46 -0
- package/dist/@types/storage/PostgresTestStorageFactoryGenerator.d.ts +13 -0
- package/dist/@types/storage/batch/OperationBatch.d.ts +47 -0
- package/dist/@types/storage/batch/PostgresBucketBatch.d.ts +90 -0
- package/dist/@types/storage/batch/PostgresPersistedBatch.d.ts +64 -0
- package/dist/@types/storage/checkpoints/PostgresWriteCheckpointAPI.d.ts +20 -0
- package/dist/@types/storage/storage-index.d.ts +5 -0
- package/dist/@types/storage/sync-rules/PostgresPersistedSyncRulesContent.d.ts +17 -0
- package/dist/@types/types/codecs.d.ts +61 -0
- package/dist/@types/types/models/ActiveCheckpoint.d.ts +12 -0
- package/dist/@types/types/models/ActiveCheckpointNotification.d.ts +19 -0
- package/dist/@types/types/models/BucketData.d.ts +22 -0
- package/dist/@types/types/models/BucketParameters.d.ts +11 -0
- package/dist/@types/types/models/CurrentData.d.ts +22 -0
- package/dist/@types/types/models/Instance.d.ts +6 -0
- package/dist/@types/types/models/Migration.d.ts +12 -0
- package/dist/@types/types/models/SourceTable.d.ts +31 -0
- package/dist/@types/types/models/SyncRules.d.ts +47 -0
- package/dist/@types/types/models/WriteCheckpoint.d.ts +15 -0
- package/dist/@types/types/models/models-index.d.ts +10 -0
- package/dist/@types/types/types.d.ts +96 -0
- package/dist/@types/utils/bson.d.ts +6 -0
- package/dist/@types/utils/bucket-data.d.ts +18 -0
- package/dist/@types/utils/db.d.ts +8 -0
- package/dist/@types/utils/ts-codec.d.ts +5 -0
- package/dist/@types/utils/utils-index.d.ts +4 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations/PostgresMigrationAgent.js +36 -0
- package/dist/migrations/PostgresMigrationAgent.js.map +1 -0
- package/dist/migrations/PostgresMigrationStore.js +60 -0
- package/dist/migrations/PostgresMigrationStore.js.map +1 -0
- package/dist/migrations/migration-utils.js +13 -0
- package/dist/migrations/migration-utils.js.map +1 -0
- package/dist/migrations/scripts/1684951997326-init.js +196 -0
- package/dist/migrations/scripts/1684951997326-init.js.map +1 -0
- package/dist/module/PostgresStorageModule.js +23 -0
- package/dist/module/PostgresStorageModule.js.map +1 -0
- package/dist/storage/PostgresBucketStorageFactory.js +433 -0
- package/dist/storage/PostgresBucketStorageFactory.js.map +1 -0
- package/dist/storage/PostgresCompactor.js +298 -0
- package/dist/storage/PostgresCompactor.js.map +1 -0
- package/dist/storage/PostgresStorageProvider.js +35 -0
- package/dist/storage/PostgresStorageProvider.js.map +1 -0
- package/dist/storage/PostgresSyncRulesStorage.js +619 -0
- package/dist/storage/PostgresSyncRulesStorage.js.map +1 -0
- package/dist/storage/PostgresTestStorageFactoryGenerator.js +110 -0
- package/dist/storage/PostgresTestStorageFactoryGenerator.js.map +1 -0
- package/dist/storage/batch/OperationBatch.js +93 -0
- package/dist/storage/batch/OperationBatch.js.map +1 -0
- package/dist/storage/batch/PostgresBucketBatch.js +732 -0
- package/dist/storage/batch/PostgresBucketBatch.js.map +1 -0
- package/dist/storage/batch/PostgresPersistedBatch.js +367 -0
- package/dist/storage/batch/PostgresPersistedBatch.js.map +1 -0
- package/dist/storage/checkpoints/PostgresWriteCheckpointAPI.js +148 -0
- package/dist/storage/checkpoints/PostgresWriteCheckpointAPI.js.map +1 -0
- package/dist/storage/storage-index.js +6 -0
- package/dist/storage/storage-index.js.map +1 -0
- package/dist/storage/sync-rules/PostgresPersistedSyncRulesContent.js +58 -0
- package/dist/storage/sync-rules/PostgresPersistedSyncRulesContent.js.map +1 -0
- package/dist/types/codecs.js +97 -0
- package/dist/types/codecs.js.map +1 -0
- package/dist/types/models/ActiveCheckpoint.js +12 -0
- package/dist/types/models/ActiveCheckpoint.js.map +1 -0
- package/dist/types/models/ActiveCheckpointNotification.js +8 -0
- package/dist/types/models/ActiveCheckpointNotification.js.map +1 -0
- package/dist/types/models/BucketData.js +23 -0
- package/dist/types/models/BucketData.js.map +1 -0
- package/dist/types/models/BucketParameters.js +11 -0
- package/dist/types/models/BucketParameters.js.map +1 -0
- package/dist/types/models/CurrentData.js +16 -0
- package/dist/types/models/CurrentData.js.map +1 -0
- package/dist/types/models/Instance.js +5 -0
- package/dist/types/models/Instance.js.map +1 -0
- package/dist/types/models/Migration.js +12 -0
- package/dist/types/models/Migration.js.map +1 -0
- package/dist/types/models/SourceTable.js +24 -0
- package/dist/types/models/SourceTable.js.map +1 -0
- package/dist/types/models/SyncRules.js +47 -0
- package/dist/types/models/SyncRules.js.map +1 -0
- package/dist/types/models/WriteCheckpoint.js +13 -0
- package/dist/types/models/WriteCheckpoint.js.map +1 -0
- package/dist/types/models/models-index.js +11 -0
- package/dist/types/models/models-index.js.map +1 -0
- package/dist/types/types.js +46 -0
- package/dist/types/types.js.map +1 -0
- package/dist/utils/bson.js +16 -0
- package/dist/utils/bson.js.map +1 -0
- package/dist/utils/bucket-data.js +25 -0
- package/dist/utils/bucket-data.js.map +1 -0
- package/dist/utils/db.js +24 -0
- package/dist/utils/db.js.map +1 -0
- package/dist/utils/ts-codec.js +11 -0
- package/dist/utils/ts-codec.js.map +1 -0
- package/dist/utils/utils-index.js +5 -0
- package/dist/utils/utils-index.js.map +1 -0
- package/package.json +50 -0
- package/src/index.ts +10 -0
- package/src/migrations/PostgresMigrationAgent.ts +46 -0
- package/src/migrations/PostgresMigrationStore.ts +70 -0
- package/src/migrations/migration-utils.ts +14 -0
- package/src/migrations/scripts/1684951997326-init.ts +141 -0
- package/src/module/PostgresStorageModule.ts +30 -0
- package/src/storage/PostgresBucketStorageFactory.ts +496 -0
- package/src/storage/PostgresCompactor.ts +366 -0
- package/src/storage/PostgresStorageProvider.ts +42 -0
- package/src/storage/PostgresSyncRulesStorage.ts +666 -0
- package/src/storage/PostgresTestStorageFactoryGenerator.ts +61 -0
- package/src/storage/batch/OperationBatch.ts +101 -0
- package/src/storage/batch/PostgresBucketBatch.ts +885 -0
- package/src/storage/batch/PostgresPersistedBatch.ts +441 -0
- package/src/storage/checkpoints/PostgresWriteCheckpointAPI.ts +176 -0
- package/src/storage/storage-index.ts +5 -0
- package/src/storage/sync-rules/PostgresPersistedSyncRulesContent.ts +67 -0
- package/src/types/codecs.ts +136 -0
- package/src/types/models/ActiveCheckpoint.ts +15 -0
- package/src/types/models/ActiveCheckpointNotification.ts +14 -0
- package/src/types/models/BucketData.ts +26 -0
- package/src/types/models/BucketParameters.ts +14 -0
- package/src/types/models/CurrentData.ts +23 -0
- package/src/types/models/Instance.ts +8 -0
- package/src/types/models/Migration.ts +19 -0
- package/src/types/models/SourceTable.ts +32 -0
- package/src/types/models/SyncRules.ts +50 -0
- package/src/types/models/WriteCheckpoint.ts +20 -0
- package/src/types/models/models-index.ts +10 -0
- package/src/types/types.ts +73 -0
- package/src/utils/bson.ts +17 -0
- package/src/utils/bucket-data.ts +25 -0
- package/src/utils/db.ts +27 -0
- package/src/utils/ts-codec.ts +14 -0
- package/src/utils/utils-index.ts +4 -0
- package/test/src/__snapshots__/storage.test.ts.snap +9 -0
- package/test/src/__snapshots__/storage_sync.test.ts.snap +332 -0
- package/test/src/env.ts +6 -0
- package/test/src/migrations.test.ts +34 -0
- package/test/src/setup.ts +16 -0
- package/test/src/storage.test.ts +131 -0
- package/test/src/storage_compacting.test.ts +5 -0
- package/test/src/storage_sync.test.ts +12 -0
- package/test/src/util.ts +34 -0
- package/test/tsconfig.json +20 -0
- package/tsconfig.json +36 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as lib_postgres from '@powersync/lib-service-postgres';
|
|
2
|
+
import { migrations } from '@powersync/lib-services-framework';
|
|
3
|
+
import { models } from '../types/types.js';
|
|
4
|
+
import { sql } from '../utils/db.js';
|
|
5
|
+
|
|
6
|
+
export type PostgresMigrationStoreOptions = {
|
|
7
|
+
db: lib_postgres.DatabaseClient;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export class PostgresMigrationStore implements migrations.MigrationStore {
|
|
11
|
+
constructor(protected options: PostgresMigrationStoreOptions) {}
|
|
12
|
+
|
|
13
|
+
protected get db() {
|
|
14
|
+
return this.options.db;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async init() {
|
|
18
|
+
await this.db.query(sql`
|
|
19
|
+
CREATE TABLE IF NOT EXISTS migrations (
|
|
20
|
+
id SERIAL PRIMARY KEY,
|
|
21
|
+
last_run TEXT,
|
|
22
|
+
LOG JSONB NOT NULL
|
|
23
|
+
);
|
|
24
|
+
`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async clear() {
|
|
28
|
+
await this.db.query(sql`DELETE FROM migrations;`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async load(): Promise<migrations.MigrationState | undefined> {
|
|
32
|
+
const res = await this.db.sql`
|
|
33
|
+
SELECT
|
|
34
|
+
last_run,
|
|
35
|
+
LOG
|
|
36
|
+
FROM
|
|
37
|
+
migrations
|
|
38
|
+
LIMIT
|
|
39
|
+
1
|
|
40
|
+
`
|
|
41
|
+
.decoded(models.Migration)
|
|
42
|
+
.first();
|
|
43
|
+
|
|
44
|
+
if (!res) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
last_run: res.last_run,
|
|
50
|
+
log: res.log
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async save(state: migrations.MigrationState): Promise<void> {
|
|
55
|
+
await this.db.query(sql`
|
|
56
|
+
INSERT INTO
|
|
57
|
+
migrations (id, last_run, LOG)
|
|
58
|
+
VALUES
|
|
59
|
+
(
|
|
60
|
+
1,
|
|
61
|
+
${{ type: 'varchar', value: state.last_run }},
|
|
62
|
+
${{ type: 'jsonb', value: state.log }}
|
|
63
|
+
)
|
|
64
|
+
ON CONFLICT (id) DO UPDATE
|
|
65
|
+
SET
|
|
66
|
+
last_run = EXCLUDED.last_run,
|
|
67
|
+
LOG = EXCLUDED.log;
|
|
68
|
+
`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as lib_postgres from '@powersync/lib-service-postgres';
|
|
2
|
+
import { configFile } from '@powersync/service-types';
|
|
3
|
+
import { isPostgresStorageConfig, normalizePostgresStorageConfig, PostgresStorageConfig } from '../types/types.js';
|
|
4
|
+
import { STORAGE_SCHEMA_NAME } from '../utils/db.js';
|
|
5
|
+
|
|
6
|
+
export const openMigrationDB = (config: configFile.BaseStorageConfig) => {
|
|
7
|
+
if (!isPostgresStorageConfig(config)) {
|
|
8
|
+
throw new Error(`Input storage configuration is not for Postgres`);
|
|
9
|
+
}
|
|
10
|
+
return new lib_postgres.DatabaseClient({
|
|
11
|
+
config: normalizePostgresStorageConfig(PostgresStorageConfig.decode(config)),
|
|
12
|
+
schema: STORAGE_SCHEMA_NAME
|
|
13
|
+
});
|
|
14
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { migrations } from '@powersync/service-core';
|
|
2
|
+
|
|
3
|
+
import { dropTables } from '../../utils/db.js';
|
|
4
|
+
import { openMigrationDB } from '../migration-utils.js';
|
|
5
|
+
|
|
6
|
+
export const up: migrations.PowerSyncMigrationFunction = async (context) => {
|
|
7
|
+
const {
|
|
8
|
+
service_context: { configuration }
|
|
9
|
+
} = context;
|
|
10
|
+
await using client = openMigrationDB(configuration.storage);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Request an explicit connection which will automatically set the search
|
|
14
|
+
* path to the powersync schema
|
|
15
|
+
*/
|
|
16
|
+
await client.transaction(async (db) => {
|
|
17
|
+
await db.sql`
|
|
18
|
+
CREATE SEQUENCE op_id_sequence AS int8 START
|
|
19
|
+
WITH
|
|
20
|
+
1
|
|
21
|
+
`.execute();
|
|
22
|
+
|
|
23
|
+
await db.sql`
|
|
24
|
+
CREATE SEQUENCE sync_rules_id_sequence AS int START
|
|
25
|
+
WITH
|
|
26
|
+
1
|
|
27
|
+
`.execute();
|
|
28
|
+
|
|
29
|
+
await db.sql`
|
|
30
|
+
CREATE TABLE bucket_data (
|
|
31
|
+
group_id integer NOT NULL,
|
|
32
|
+
bucket_name TEXT NOT NULL,
|
|
33
|
+
op_id bigint NOT NULL,
|
|
34
|
+
CONSTRAINT unique_id PRIMARY KEY (group_id, bucket_name, op_id),
|
|
35
|
+
op text NOT NULL,
|
|
36
|
+
source_table TEXT,
|
|
37
|
+
source_key bytea,
|
|
38
|
+
table_name TEXT,
|
|
39
|
+
row_id TEXT,
|
|
40
|
+
checksum bigint NOT NULL,
|
|
41
|
+
data TEXT,
|
|
42
|
+
target_op bigint
|
|
43
|
+
)
|
|
44
|
+
`.execute();
|
|
45
|
+
|
|
46
|
+
await db.sql`CREATE TABLE instance (id TEXT PRIMARY KEY) `.execute();
|
|
47
|
+
|
|
48
|
+
await db.sql`
|
|
49
|
+
CREATE TABLE sync_rules (
|
|
50
|
+
id INTEGER PRIMARY KEY,
|
|
51
|
+
state TEXT NOT NULL,
|
|
52
|
+
snapshot_done BOOLEAN NOT NULL DEFAULT FALSE,
|
|
53
|
+
last_checkpoint BIGINT,
|
|
54
|
+
last_checkpoint_lsn TEXT,
|
|
55
|
+
no_checkpoint_before TEXT,
|
|
56
|
+
slot_name TEXT,
|
|
57
|
+
last_checkpoint_ts TIMESTAMP WITH TIME ZONE,
|
|
58
|
+
last_keepalive_ts TIMESTAMP WITH TIME ZONE,
|
|
59
|
+
keepalive_op BIGINT,
|
|
60
|
+
last_fatal_error TEXT,
|
|
61
|
+
content TEXT NOT NULL
|
|
62
|
+
);
|
|
63
|
+
`.execute();
|
|
64
|
+
|
|
65
|
+
await db.sql`
|
|
66
|
+
CREATE TABLE bucket_parameters (
|
|
67
|
+
id BIGINT DEFAULT nextval('op_id_sequence') PRIMARY KEY,
|
|
68
|
+
group_id integer NOT NULL,
|
|
69
|
+
source_table TEXT NOT NULL,
|
|
70
|
+
source_key bytea NOT NULL,
|
|
71
|
+
lookup bytea NOT NULL,
|
|
72
|
+
--- Stored as text which is stringified with JSONBig
|
|
73
|
+
--- BigInts are not standard JSON, storing as JSONB seems risky
|
|
74
|
+
bucket_parameters text NOT NULL
|
|
75
|
+
);
|
|
76
|
+
`.execute();
|
|
77
|
+
|
|
78
|
+
await db.sql`
|
|
79
|
+
CREATE INDEX bucket_parameters_lookup_index ON bucket_parameters (group_id ASC, lookup ASC, id DESC)
|
|
80
|
+
`.execute();
|
|
81
|
+
|
|
82
|
+
await db.sql`
|
|
83
|
+
CREATE INDEX bucket_parameters_source_index ON bucket_parameters (group_id, source_table, source_key)
|
|
84
|
+
`.execute();
|
|
85
|
+
|
|
86
|
+
await db.sql`
|
|
87
|
+
CREATE TABLE current_data (
|
|
88
|
+
group_id integer NOT NULL,
|
|
89
|
+
source_table TEXT NOT NULL,
|
|
90
|
+
source_key bytea NOT NULL,
|
|
91
|
+
CONSTRAINT unique_current_data_id PRIMARY KEY (group_id, source_table, source_key),
|
|
92
|
+
buckets jsonb NOT NULL,
|
|
93
|
+
data bytea NOT NULL,
|
|
94
|
+
lookups bytea[] NOT NULL
|
|
95
|
+
);
|
|
96
|
+
`.execute();
|
|
97
|
+
|
|
98
|
+
await db.sql`
|
|
99
|
+
CREATE TABLE source_tables (
|
|
100
|
+
--- This is currently a TEXT column to make the (shared) tests easier to integrate
|
|
101
|
+
--- we could improve this if necessary
|
|
102
|
+
id TEXT PRIMARY KEY,
|
|
103
|
+
group_id integer NOT NULL,
|
|
104
|
+
connection_id integer NOT NULL,
|
|
105
|
+
relation_id jsonb,
|
|
106
|
+
schema_name text NOT NULL,
|
|
107
|
+
table_name text NOT NULL,
|
|
108
|
+
replica_id_columns jsonb,
|
|
109
|
+
snapshot_done BOOLEAN NOT NULL DEFAULT FALSE
|
|
110
|
+
)
|
|
111
|
+
`.execute();
|
|
112
|
+
|
|
113
|
+
await db.sql`CREATE INDEX source_table_lookup ON source_tables (group_id, table_name)`.execute();
|
|
114
|
+
|
|
115
|
+
await db.sql`
|
|
116
|
+
CREATE TABLE write_checkpoints (
|
|
117
|
+
user_id text PRIMARY KEY,
|
|
118
|
+
lsns jsonb NOT NULL,
|
|
119
|
+
write_checkpoint BIGINT NOT NULL
|
|
120
|
+
)
|
|
121
|
+
`.execute();
|
|
122
|
+
|
|
123
|
+
await db.sql`
|
|
124
|
+
CREATE TABLE custom_write_checkpoints (
|
|
125
|
+
user_id text NOT NULL,
|
|
126
|
+
write_checkpoint BIGINT NOT NULL,
|
|
127
|
+
sync_rules_id integer NOT NULL,
|
|
128
|
+
CONSTRAINT unique_user_sync PRIMARY KEY (user_id, sync_rules_id)
|
|
129
|
+
);
|
|
130
|
+
`.execute();
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export const down: migrations.PowerSyncMigrationFunction = async (context) => {
|
|
135
|
+
const {
|
|
136
|
+
service_context: { configuration }
|
|
137
|
+
} = context;
|
|
138
|
+
await using client = openMigrationDB(configuration.storage);
|
|
139
|
+
|
|
140
|
+
await dropTables(client);
|
|
141
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { modules, system } from '@powersync/service-core';
|
|
2
|
+
|
|
3
|
+
import { PostgresMigrationAgent } from '../migrations/PostgresMigrationAgent.js';
|
|
4
|
+
import { PostgresStorageProvider } from '../storage/PostgresStorageProvider.js';
|
|
5
|
+
import { isPostgresStorageConfig, PostgresStorageConfig } from '../types/types.js';
|
|
6
|
+
|
|
7
|
+
export class PostgresStorageModule extends modules.AbstractModule {
|
|
8
|
+
constructor() {
|
|
9
|
+
super({
|
|
10
|
+
name: 'Postgres Bucket Storage'
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async initialize(context: system.ServiceContextContainer): Promise<void> {
|
|
15
|
+
const { storageEngine } = context;
|
|
16
|
+
|
|
17
|
+
// Register the ability to use Postgres as a BucketStorage
|
|
18
|
+
storageEngine.registerProvider(new PostgresStorageProvider());
|
|
19
|
+
|
|
20
|
+
if (isPostgresStorageConfig(context.configuration.storage)) {
|
|
21
|
+
context.migrations.registerMigrationAgent(
|
|
22
|
+
new PostgresMigrationAgent(PostgresStorageConfig.decode(context.configuration.storage))
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async teardown(): Promise<void> {
|
|
28
|
+
// Teardown for this module is implemented in the storage engine
|
|
29
|
+
}
|
|
30
|
+
}
|