@powersync/service-core 0.2.1 → 0.3.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 +22 -0
- package/dist/api/diagnostics.js +2 -2
- package/dist/api/diagnostics.js.map +1 -1
- package/dist/api/schema.js.map +1 -1
- package/dist/auth/CachedKeyCollector.js.map +1 -1
- package/dist/auth/KeySpec.js.map +1 -1
- package/dist/auth/KeyStore.js +2 -2
- package/dist/auth/KeyStore.js.map +1 -1
- package/dist/auth/LeakyBucket.js.map +1 -1
- package/dist/auth/RemoteJWKSCollector.js.map +1 -1
- package/dist/auth/SupabaseKeyCollector.js.map +1 -1
- package/dist/db/mongo.js.map +1 -1
- package/dist/entry/cli-entry.js +2 -2
- package/dist/entry/cli-entry.js.map +1 -1
- package/dist/entry/commands/config-command.js.map +1 -1
- package/dist/entry/commands/migrate-action.js.map +1 -1
- package/dist/entry/commands/start-action.js.map +1 -1
- package/dist/entry/commands/teardown-action.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/locks/LockManager.d.ts +10 -0
- package/dist/locks/LockManager.js +7 -0
- package/dist/locks/LockManager.js.map +1 -0
- package/dist/locks/MongoLocks.d.ts +36 -0
- package/dist/locks/MongoLocks.js +81 -0
- package/dist/locks/MongoLocks.js.map +1 -0
- package/dist/locks/locks-index.d.ts +2 -0
- package/dist/locks/locks-index.js +3 -0
- package/dist/locks/locks-index.js.map +1 -0
- package/dist/metrics/Metrics.js +6 -6
- package/dist/metrics/Metrics.js.map +1 -1
- package/dist/migrations/db/migrations/1684951997326-init.js.map +1 -1
- package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +1 -1
- package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js.map +1 -1
- package/dist/migrations/definitions.d.ts +18 -0
- package/dist/migrations/definitions.js +6 -0
- package/dist/migrations/definitions.js.map +1 -0
- package/dist/migrations/executor.d.ts +16 -0
- package/dist/migrations/executor.js +64 -0
- package/dist/migrations/executor.js.map +1 -0
- package/dist/migrations/migrations-index.d.ts +3 -0
- package/dist/migrations/migrations-index.js +4 -0
- package/dist/migrations/migrations-index.js.map +1 -0
- package/dist/migrations/migrations.d.ts +1 -1
- package/dist/migrations/migrations.js +4 -8
- package/dist/migrations/migrations.js.map +1 -1
- package/dist/migrations/store/migration-store.d.ts +11 -0
- package/dist/migrations/store/migration-store.js +46 -0
- package/dist/migrations/store/migration-store.js.map +1 -0
- package/dist/replication/ErrorRateLimiter.js.map +1 -1
- package/dist/replication/PgRelation.js.map +1 -1
- package/dist/replication/WalConnection.js.map +1 -1
- package/dist/replication/WalStream.d.ts +0 -1
- package/dist/replication/WalStream.js +21 -25
- package/dist/replication/WalStream.js.map +1 -1
- package/dist/replication/WalStreamManager.js +12 -13
- package/dist/replication/WalStreamManager.js.map +1 -1
- package/dist/replication/WalStreamRunner.js +8 -8
- package/dist/replication/WalStreamRunner.js.map +1 -1
- package/dist/replication/util.js.map +1 -1
- package/dist/routes/auth.d.ts +8 -10
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/endpoints/admin.d.ts +1011 -0
- package/dist/routes/{admin.js → endpoints/admin.js} +33 -18
- package/dist/routes/endpoints/admin.js.map +1 -0
- package/dist/routes/endpoints/checkpointing.d.ts +76 -0
- package/dist/routes/endpoints/checkpointing.js +36 -0
- package/dist/routes/endpoints/checkpointing.js.map +1 -0
- package/dist/routes/endpoints/dev.d.ts +312 -0
- package/dist/routes/{dev.js → endpoints/dev.js} +25 -16
- package/dist/routes/endpoints/dev.js.map +1 -0
- package/dist/routes/endpoints/route-endpoints-index.d.ts +6 -0
- package/dist/routes/endpoints/route-endpoints-index.js +7 -0
- package/dist/routes/endpoints/route-endpoints-index.js.map +1 -0
- package/dist/routes/endpoints/socket-route.d.ts +2 -0
- package/dist/routes/{socket-route.js → endpoints/socket-route.js} +10 -10
- package/dist/routes/endpoints/socket-route.js.map +1 -0
- package/dist/routes/endpoints/sync-rules.d.ts +174 -0
- package/dist/routes/{sync-rules.js → endpoints/sync-rules.js} +44 -24
- package/dist/routes/endpoints/sync-rules.js.map +1 -0
- package/dist/routes/endpoints/sync-stream.d.ts +132 -0
- package/dist/routes/{sync-stream.js → endpoints/sync-stream.js} +26 -17
- package/dist/routes/endpoints/sync-stream.js.map +1 -0
- package/dist/routes/hooks.d.ts +10 -0
- package/dist/routes/hooks.js +31 -0
- package/dist/routes/hooks.js.map +1 -0
- package/dist/routes/route-register.d.ts +10 -0
- package/dist/routes/route-register.js +87 -0
- package/dist/routes/route-register.js.map +1 -0
- package/dist/routes/router.d.ts +16 -4
- package/dist/routes/router.js +6 -1
- package/dist/routes/router.js.map +1 -1
- package/dist/routes/routes-index.d.ts +5 -3
- package/dist/routes/routes-index.js +5 -3
- package/dist/routes/routes-index.js.map +1 -1
- package/dist/runner/teardown.js +27 -12
- package/dist/runner/teardown.js.map +1 -1
- package/dist/storage/BucketStorage.d.ts +3 -0
- package/dist/storage/BucketStorage.js.map +1 -1
- package/dist/storage/ChecksumCache.js.map +1 -1
- package/dist/storage/MongoBucketStorage.js +5 -5
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/SourceTable.js.map +1 -1
- package/dist/storage/mongo/MongoBucketBatch.js +23 -18
- package/dist/storage/mongo/MongoBucketBatch.js.map +1 -1
- package/dist/storage/mongo/MongoIdSequence.js.map +1 -1
- package/dist/storage/mongo/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/mongo/MongoSyncRulesLock.js +3 -3
- package/dist/storage/mongo/MongoSyncRulesLock.js.map +1 -1
- package/dist/storage/mongo/OperationBatch.js.map +1 -1
- package/dist/storage/mongo/PersistedBatch.js +2 -2
- package/dist/storage/mongo/PersistedBatch.js.map +1 -1
- package/dist/storage/mongo/db.d.ts +2 -2
- package/dist/storage/mongo/db.js.map +1 -1
- package/dist/storage/mongo/util.js.map +1 -1
- package/dist/sync/BroadcastIterable.js.map +1 -1
- package/dist/sync/LastValueSink.js.map +1 -1
- package/dist/sync/merge.js.map +1 -1
- package/dist/sync/safeRace.js.map +1 -1
- package/dist/sync/sync.js +4 -4
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/util.js.map +1 -1
- package/dist/system/CorePowerSyncSystem.d.ts +12 -7
- package/dist/system/CorePowerSyncSystem.js +26 -2
- package/dist/system/CorePowerSyncSystem.js.map +1 -1
- package/dist/system/system-index.d.ts +1 -0
- package/dist/system/system-index.js +2 -0
- package/dist/system/system-index.js.map +1 -0
- package/dist/util/Mutex.js.map +1 -1
- package/dist/util/PgManager.js.map +1 -1
- package/dist/util/alerting.d.ts +0 -2
- package/dist/util/alerting.js +0 -6
- package/dist/util/alerting.js.map +1 -1
- package/dist/util/config/collectors/config-collector.js +3 -3
- package/dist/util/config/collectors/config-collector.js.map +1 -1
- package/dist/util/config/collectors/impl/base64-config-collector.js.map +1 -1
- package/dist/util/config/collectors/impl/filesystem-config-collector.js +7 -5
- package/dist/util/config/collectors/impl/filesystem-config-collector.js.map +1 -1
- package/dist/util/config/compound-config-collector.js +4 -4
- package/dist/util/config/compound-config-collector.js.map +1 -1
- package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.js.map +1 -1
- package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js.map +1 -1
- package/dist/util/config/sync-rules/impl/inline-sync-rules-collector.js.map +1 -1
- package/dist/util/config.js.map +1 -1
- package/dist/util/env.d.ts +1 -2
- package/dist/util/env.js +3 -2
- package/dist/util/env.js.map +1 -1
- package/dist/util/memory-tracking.js +2 -2
- package/dist/util/memory-tracking.js.map +1 -1
- package/dist/util/migration_lib.js.map +1 -1
- package/dist/util/pgwire_utils.js +2 -2
- package/dist/util/pgwire_utils.js.map +1 -1
- package/dist/util/populate_test_data.js.map +1 -1
- package/dist/util/secs.js.map +1 -1
- package/dist/util/utils.js +4 -4
- package/dist/util/utils.js.map +1 -1
- package/package.json +13 -10
- package/src/api/diagnostics.ts +5 -5
- package/src/api/schema.ts +1 -1
- package/src/auth/KeyStore.ts +2 -2
- package/src/entry/cli-entry.ts +3 -4
- package/src/entry/commands/config-command.ts +1 -1
- package/src/entry/commands/migrate-action.ts +2 -2
- package/src/entry/commands/start-action.ts +1 -1
- package/src/entry/commands/teardown-action.ts +1 -1
- package/src/index.ts +5 -2
- package/src/locks/LockManager.ts +16 -0
- package/src/locks/MongoLocks.ts +142 -0
- package/src/locks/locks-index.ts +2 -0
- package/src/metrics/Metrics.ts +8 -8
- package/src/migrations/db/migrations/1684951997326-init.ts +3 -3
- package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +3 -3
- package/src/migrations/db/migrations/1711543888062-write-checkpoint-index.ts +2 -2
- package/src/migrations/definitions.ts +21 -0
- package/src/migrations/executor.ts +87 -0
- package/src/migrations/migrations-index.ts +3 -0
- package/src/migrations/migrations.ts +7 -11
- package/src/migrations/store/migration-store.ts +63 -0
- package/src/replication/WalConnection.ts +2 -2
- package/src/replication/WalStream.ts +24 -29
- package/src/replication/WalStreamManager.ts +14 -15
- package/src/replication/WalStreamRunner.ts +10 -10
- package/src/replication/util.ts +1 -1
- package/src/routes/auth.ts +22 -16
- package/src/routes/endpoints/admin.ts +237 -0
- package/src/routes/endpoints/checkpointing.ts +41 -0
- package/src/routes/endpoints/dev.ts +199 -0
- package/src/routes/endpoints/route-endpoints-index.ts +6 -0
- package/src/routes/{socket-route.ts → endpoints/socket-route.ts} +11 -11
- package/src/routes/endpoints/sync-rules.ts +227 -0
- package/src/routes/endpoints/sync-stream.ts +101 -0
- package/src/routes/hooks.ts +45 -0
- package/src/routes/route-register.ts +104 -0
- package/src/routes/router.ts +34 -6
- package/src/routes/routes-index.ts +5 -4
- package/src/runner/teardown.ts +30 -13
- package/src/storage/BucketStorage.ts +7 -2
- package/src/storage/ChecksumCache.ts +2 -2
- package/src/storage/MongoBucketStorage.ts +8 -8
- package/src/storage/SourceTable.ts +2 -2
- package/src/storage/mongo/MongoBucketBatch.ts +29 -22
- package/src/storage/mongo/MongoSyncBucketStorage.ts +3 -3
- package/src/storage/mongo/MongoSyncRulesLock.ts +3 -3
- package/src/storage/mongo/OperationBatch.ts +1 -1
- package/src/storage/mongo/PersistedBatch.ts +3 -3
- package/src/storage/mongo/db.ts +3 -4
- package/src/sync/sync.ts +8 -8
- package/src/sync/util.ts +2 -2
- package/src/system/CorePowerSyncSystem.ts +31 -10
- package/src/system/system-index.ts +1 -0
- package/src/util/alerting.ts +0 -8
- package/src/util/config/collectors/config-collector.ts +5 -3
- package/src/util/config/collectors/impl/filesystem-config-collector.ts +8 -6
- package/src/util/config/compound-config-collector.ts +4 -4
- package/src/util/env.ts +4 -2
- package/src/util/memory-tracking.ts +2 -2
- package/src/util/pgwire_utils.ts +3 -3
- package/src/util/utils.ts +5 -5
- package/test/src/auth.test.ts +4 -2
- package/test/src/data_storage.test.ts +177 -0
- package/test/src/env.ts +6 -6
- package/test/src/pg_test.test.ts +18 -0
- package/test/src/setup.ts +7 -0
- package/test/src/slow_tests.test.ts +45 -6
- package/test/tsconfig.json +1 -1
- package/tsconfig.json +5 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +1 -3
- package/dist/migrations/db/store.d.ts +0 -3
- package/dist/migrations/db/store.js +0 -10
- package/dist/migrations/db/store.js.map +0 -1
- package/dist/routes/admin.d.ts +0 -7
- package/dist/routes/admin.js.map +0 -1
- package/dist/routes/checkpointing.d.ts +0 -3
- package/dist/routes/checkpointing.js +0 -30
- package/dist/routes/checkpointing.js.map +0 -1
- package/dist/routes/dev.d.ts +0 -6
- package/dist/routes/dev.js.map +0 -1
- package/dist/routes/route-generators.d.ts +0 -15
- package/dist/routes/route-generators.js +0 -32
- package/dist/routes/route-generators.js.map +0 -1
- package/dist/routes/socket-route.d.ts +0 -2
- package/dist/routes/socket-route.js.map +0 -1
- package/dist/routes/sync-rules.d.ts +0 -6
- package/dist/routes/sync-rules.js.map +0 -1
- package/dist/routes/sync-stream.d.ts +0 -5
- package/dist/routes/sync-stream.js.map +0 -1
- package/src/migrations/db/store.ts +0 -11
- package/src/routes/admin.ts +0 -229
- package/src/routes/checkpointing.ts +0 -38
- package/src/routes/dev.ts +0 -194
- package/src/routes/route-generators.ts +0 -39
- package/src/routes/sync-rules.ts +0 -210
- package/src/routes/sync-stream.ts +0 -95
package/src/index.ts
CHANGED
|
@@ -12,11 +12,14 @@ export * as db from './db/db-index.js';
|
|
|
12
12
|
export * from './entry/entry-index.js';
|
|
13
13
|
export * as entry from './entry/entry-index.js';
|
|
14
14
|
|
|
15
|
+
// Re-export framework for easy use of Container API
|
|
16
|
+
export * as framework from '@powersync/lib-services-framework';
|
|
17
|
+
|
|
15
18
|
export * from './metrics/Metrics.js';
|
|
16
19
|
export * as metrics from './metrics/Metrics.js';
|
|
17
20
|
|
|
18
21
|
export * from './migrations/migrations.js';
|
|
19
|
-
export * as migrations from './migrations/migrations.js';
|
|
22
|
+
export * as migrations from './migrations/migrations-index.js';
|
|
20
23
|
|
|
21
24
|
export * from './replication/replication-index.js';
|
|
22
25
|
export * as replication from './replication/replication-index.js';
|
|
@@ -31,7 +34,7 @@ export * from './sync/sync-index.js';
|
|
|
31
34
|
export * as sync from './sync/sync-index.js';
|
|
32
35
|
|
|
33
36
|
export * from './system/CorePowerSyncSystem.js';
|
|
34
|
-
export * as system from './system/
|
|
37
|
+
export * as system from './system/system-index.js';
|
|
35
38
|
|
|
36
39
|
export * from './util/util-index.js';
|
|
37
40
|
export * as utils from './util/util-index.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as bson from 'bson';
|
|
2
|
+
|
|
3
|
+
export class LockActiveError extends Error {
|
|
4
|
+
constructor() {
|
|
5
|
+
super('Lock is already active');
|
|
6
|
+
this.name = this.constructor.name;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type LockManager = {
|
|
11
|
+
acquire: () => Promise<bson.ObjectId | null>;
|
|
12
|
+
refresh: (lock_id: bson.ObjectId) => Promise<void>;
|
|
13
|
+
release: (lock_id: bson.ObjectId) => Promise<void>;
|
|
14
|
+
|
|
15
|
+
lock: (handler: (refresh: () => Promise<void>) => Promise<void>) => Promise<void>;
|
|
16
|
+
};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import * as mongo from 'mongodb';
|
|
2
|
+
import * as bson from 'bson';
|
|
3
|
+
import { LockActiveError, LockManager } from './LockManager.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Lock Document Schema
|
|
7
|
+
*/
|
|
8
|
+
export type Lock = {
|
|
9
|
+
name: string;
|
|
10
|
+
active_lock?: {
|
|
11
|
+
lock_id: bson.ObjectId;
|
|
12
|
+
ts: Date;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type Collection = mongo.Collection<Lock>;
|
|
17
|
+
|
|
18
|
+
export type AcquireLockParams = {
|
|
19
|
+
/**
|
|
20
|
+
* Name of the process/user trying to acquire the lock.
|
|
21
|
+
*/
|
|
22
|
+
name: string;
|
|
23
|
+
/**
|
|
24
|
+
* The TTL of the lock (ms). Default: 60000 ms (1 min)
|
|
25
|
+
*/
|
|
26
|
+
timeout?: number;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const DEFAULT_LOCK_TIMEOUT = 60 * 1000; // 1 minute
|
|
30
|
+
|
|
31
|
+
const acquireLock = async (collection: Collection, params: AcquireLockParams) => {
|
|
32
|
+
const now = new Date();
|
|
33
|
+
const lock_timeout = params.timeout ?? DEFAULT_LOCK_TIMEOUT;
|
|
34
|
+
const lock_id = new bson.ObjectId();
|
|
35
|
+
|
|
36
|
+
await collection.updateOne(
|
|
37
|
+
{
|
|
38
|
+
name: params.name
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
$setOnInsert: {
|
|
42
|
+
name: params.name
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
upsert: true
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const expired_ts = now.getTime() - lock_timeout;
|
|
51
|
+
|
|
52
|
+
const res = await collection.updateOne(
|
|
53
|
+
{
|
|
54
|
+
$and: [
|
|
55
|
+
{ name: params.name },
|
|
56
|
+
{
|
|
57
|
+
$or: [{ active_lock: { $exists: false } }, { 'active_lock.ts': { $lte: new Date(expired_ts) } }]
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
$set: {
|
|
63
|
+
active_lock: {
|
|
64
|
+
lock_id: lock_id,
|
|
65
|
+
ts: now
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
if (res.modifiedCount === 0) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return lock_id;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const refreshLock = async (collection: Collection, lock_id: bson.ObjectId) => {
|
|
79
|
+
const res = await collection.updateOne(
|
|
80
|
+
{
|
|
81
|
+
'active_lock.lock_id': lock_id
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
$set: {
|
|
85
|
+
'active_lock.ts': new Date()
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
if (res.modifiedCount === 0) {
|
|
91
|
+
throw new Error('Lock not found, could not refresh');
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const releaseLock = async (collection: Collection, lock_id: bson.ObjectId) => {
|
|
96
|
+
const res = await collection.updateOne(
|
|
97
|
+
{
|
|
98
|
+
'active_lock.lock_id': lock_id
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
$unset: {
|
|
102
|
+
active_lock: true
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
if (res.modifiedCount === 0) {
|
|
108
|
+
throw new Error('Lock not found, could not release');
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export type CreateLockManagerParams = {
|
|
113
|
+
/**
|
|
114
|
+
* Name of the process/user trying to acquire the lock.
|
|
115
|
+
*/
|
|
116
|
+
name: string;
|
|
117
|
+
/**
|
|
118
|
+
* The TTL for each lock (ms). Default: 60000 ms (1 min)
|
|
119
|
+
*/
|
|
120
|
+
timeout?: number;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export const createMongoLockManager = (collection: Collection, params: CreateLockManagerParams): LockManager => {
|
|
124
|
+
return {
|
|
125
|
+
acquire: () => acquireLock(collection, params),
|
|
126
|
+
refresh: (lock_id: bson.ObjectId) => refreshLock(collection, lock_id),
|
|
127
|
+
release: (lock_id: bson.ObjectId) => releaseLock(collection, lock_id),
|
|
128
|
+
|
|
129
|
+
lock: async (handler) => {
|
|
130
|
+
const lock_id = await acquireLock(collection, params);
|
|
131
|
+
if (!lock_id) {
|
|
132
|
+
throw new LockActiveError();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
await handler(() => refreshLock(collection, lock_id));
|
|
137
|
+
} finally {
|
|
138
|
+
await releaseLock(collection, lock_id);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
};
|
package/src/metrics/Metrics.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import * as micro from '@journeyapps-platform/micro';
|
|
2
1
|
import { Attributes, Counter, ObservableGauge, UpDownCounter, ValueType } from '@opentelemetry/api';
|
|
3
2
|
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
|
|
4
3
|
import { MeterProvider, MetricReader, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
|
|
5
4
|
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
|
|
6
5
|
import * as jpgwire from '@powersync/service-jpgwire';
|
|
7
|
-
import * as util from '
|
|
8
|
-
import * as storage from '
|
|
6
|
+
import * as util from '../util/util-index.js';
|
|
7
|
+
import * as storage from '../storage/storage-index.js';
|
|
9
8
|
import { CorePowerSyncSystem } from '../system/CorePowerSyncSystem.js';
|
|
10
9
|
import { Resource } from '@opentelemetry/resources';
|
|
10
|
+
import { logger } from '@powersync/lib-services-framework';
|
|
11
11
|
|
|
12
12
|
export interface MetricsOptions {
|
|
13
13
|
disable_telemetry_sharing: boolean;
|
|
@@ -144,9 +144,9 @@ export class Metrics {
|
|
|
144
144
|
if (Metrics.instance) {
|
|
145
145
|
return;
|
|
146
146
|
}
|
|
147
|
-
|
|
147
|
+
logger.info('Configuring telemetry.');
|
|
148
148
|
|
|
149
|
-
|
|
149
|
+
logger.info(
|
|
150
150
|
`
|
|
151
151
|
Attention:
|
|
152
152
|
PowerSync collects completely anonymous telemetry regarding usage.
|
|
@@ -164,7 +164,7 @@ Anonymous telemetry is currently: ${options.disable_telemetry_sharing ? 'disable
|
|
|
164
164
|
configuredExporters.push(prometheusExporter);
|
|
165
165
|
|
|
166
166
|
if (!options.disable_telemetry_sharing) {
|
|
167
|
-
|
|
167
|
+
logger.info('Sharing anonymous telemetry');
|
|
168
168
|
const periodicExporter = new PeriodicExportingMetricReader({
|
|
169
169
|
exporter: new OTLPMetricExporter({
|
|
170
170
|
url: options.internal_metrics_endpoint
|
|
@@ -189,7 +189,7 @@ Anonymous telemetry is currently: ${options.disable_telemetry_sharing ? 'disable
|
|
|
189
189
|
|
|
190
190
|
Metrics.instance = new Metrics(meterProvider, prometheusExporter);
|
|
191
191
|
|
|
192
|
-
|
|
192
|
+
logger.info('Telemetry configuration complete.');
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
public async shutdown(): Promise<void> {
|
|
@@ -212,7 +212,7 @@ Anonymous telemetry is currently: ${options.disable_telemetry_sharing ? 'disable
|
|
|
212
212
|
function getMetrics() {
|
|
213
213
|
if (cachedRequest == null || Date.now() - cacheTimestamp > MINIMUM_INTERVAL) {
|
|
214
214
|
cachedRequest = system.storage.getStorageMetrics().catch((e) => {
|
|
215
|
-
|
|
215
|
+
logger.error(`Failed to get storage metrics`, e);
|
|
216
216
|
return null;
|
|
217
217
|
});
|
|
218
218
|
cacheTimestamp = Date.now();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import * as mongo from '
|
|
2
|
-
import * as storage from '
|
|
3
|
-
import * as utils from '
|
|
1
|
+
import * as mongo from '../../../db/mongo.js';
|
|
2
|
+
import * as storage from '../../../storage/storage-index.js';
|
|
3
|
+
import * as utils from '../../../util/util-index.js';
|
|
4
4
|
|
|
5
5
|
export const up = async (context?: utils.MigrationContext) => {
|
|
6
6
|
const config = await utils.loadConfig(context?.runner_config);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import * as mongo from '
|
|
2
|
-
import * as storage from '
|
|
3
|
-
import * as utils from '
|
|
1
|
+
import * as mongo from '../../../db/mongo.js';
|
|
2
|
+
import * as storage from '../../../storage/storage-index.js';
|
|
3
|
+
import * as utils from '../../../util/util-index.js';
|
|
4
4
|
|
|
5
5
|
interface LegacySyncRulesDocument extends storage.SyncRuleDocument {
|
|
6
6
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as storage from '
|
|
2
|
-
import * as utils from '
|
|
1
|
+
import * as storage from '../../../storage/storage-index.js';
|
|
2
|
+
import * as utils from '../../../util/util-index.js';
|
|
3
3
|
|
|
4
4
|
export const up = async (context?: utils.MigrationContext) => {
|
|
5
5
|
const config = await utils.loadConfig(context?.runner_config);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type Migration = {
|
|
2
|
+
name: string;
|
|
3
|
+
up: () => Promise<void>;
|
|
4
|
+
down: () => Promise<void>;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export enum Direction {
|
|
8
|
+
Up = 'up',
|
|
9
|
+
Down = 'down'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type ExecutedMigration = {
|
|
13
|
+
name: string;
|
|
14
|
+
direction: Direction;
|
|
15
|
+
timestamp: Date;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type MigrationState = {
|
|
19
|
+
last_run: string;
|
|
20
|
+
log: ExecutedMigration[];
|
|
21
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { logger } from '@powersync/lib-services-framework';
|
|
2
|
+
import * as defs from './definitions.js';
|
|
3
|
+
import { MigrationStore } from './store/migration-store.js';
|
|
4
|
+
|
|
5
|
+
type ExecuteParams = {
|
|
6
|
+
migrations: defs.Migration[];
|
|
7
|
+
state?: defs.MigrationState;
|
|
8
|
+
|
|
9
|
+
direction: defs.Direction;
|
|
10
|
+
count?: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export async function* execute(params: ExecuteParams): AsyncGenerator<defs.ExecutedMigration> {
|
|
14
|
+
let migrations = [...params.migrations];
|
|
15
|
+
if (params.direction === defs.Direction.Down) {
|
|
16
|
+
migrations = migrations.reverse();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let index = 0;
|
|
20
|
+
|
|
21
|
+
if (params.state) {
|
|
22
|
+
// Find the index of the last run
|
|
23
|
+
index = migrations.findIndex((migration) => {
|
|
24
|
+
return migration.name === params.state!.last_run;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
if (index === -1) {
|
|
28
|
+
throw new Error(`The last run migration ${params.state?.last_run} was not found in the given set of migrations`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// If we are migrating down then we want to include the last run migration, otherwise we want to start at the next one
|
|
32
|
+
if (params.direction === defs.Direction.Up) {
|
|
33
|
+
index += 1;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
migrations = migrations.slice(index);
|
|
38
|
+
|
|
39
|
+
let i = 0;
|
|
40
|
+
for (const migration of migrations) {
|
|
41
|
+
if (params.count && params.count === i) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
logger.info(`Executing ${migration.name} (${params.direction})`);
|
|
46
|
+
try {
|
|
47
|
+
switch (params.direction) {
|
|
48
|
+
case defs.Direction.Up: {
|
|
49
|
+
await migration.up();
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
case defs.Direction.Down: {
|
|
53
|
+
await migration.down();
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
logger.debug(`Success`);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
logger.error(`Failed`, err);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
yield {
|
|
64
|
+
name: migration.name,
|
|
65
|
+
direction: params.direction,
|
|
66
|
+
timestamp: new Date()
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
i++;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type WriteLogsParams = {
|
|
74
|
+
store: MigrationStore;
|
|
75
|
+
state?: defs.MigrationState;
|
|
76
|
+
log_stream: Iterable<defs.ExecutedMigration> | AsyncIterable<defs.ExecutedMigration>;
|
|
77
|
+
};
|
|
78
|
+
export const writeLogsToStore = async (params: WriteLogsParams): Promise<void> => {
|
|
79
|
+
const log = [...(params.state?.log || [])];
|
|
80
|
+
for await (const migration of params.log_stream) {
|
|
81
|
+
log.push(migration);
|
|
82
|
+
await params.store.save({
|
|
83
|
+
last_run: migration.name,
|
|
84
|
+
log: log
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { locks } from '@journeyapps-platform/micro';
|
|
2
1
|
import * as fs from 'fs/promises';
|
|
3
2
|
import * as path from 'path';
|
|
4
3
|
import { fileURLToPath } from 'url';
|
|
5
4
|
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
import * as
|
|
9
|
-
import
|
|
5
|
+
import * as db from '../db/db-index.js';
|
|
6
|
+
import * as util from '../util/util-index.js';
|
|
7
|
+
import * as locks from '../locks/locks-index.js';
|
|
8
|
+
import { Direction } from './definitions.js';
|
|
9
|
+
import { createMongoMigrationStore } from './store/migration-store.js';
|
|
10
|
+
import { execute, writeLogsToStore } from './executor.js';
|
|
10
11
|
|
|
11
12
|
const DEFAULT_MONGO_LOCK_COLLECTION = 'locks';
|
|
12
13
|
const MONGO_LOCK_PROCESS = 'migrations';
|
|
@@ -94,12 +95,7 @@ export const migrate = async (options: MigrationOptions) => {
|
|
|
94
95
|
const migrations = await loadMigrations(MIGRATIONS_DIR, runner_config);
|
|
95
96
|
|
|
96
97
|
// Use the provided config to connect to Mongo
|
|
97
|
-
const store = createMongoMigrationStore(
|
|
98
|
-
uri: storage.uri,
|
|
99
|
-
database: storage.database,
|
|
100
|
-
username: storage.username,
|
|
101
|
-
password: storage.password
|
|
102
|
-
});
|
|
98
|
+
const store = createMongoMigrationStore(clientDB);
|
|
103
99
|
|
|
104
100
|
const state = await store.load();
|
|
105
101
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Db } from 'mongodb';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as defs from '../definitions.js';
|
|
4
|
+
|
|
5
|
+
export type MigrationStore = {
|
|
6
|
+
load: () => Promise<defs.MigrationState | undefined>;
|
|
7
|
+
save: (state: defs.MigrationState) => Promise<void>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A custom store for node-migrate which is used to save and load migrations that have
|
|
12
|
+
* been operated on to mongo.
|
|
13
|
+
*/
|
|
14
|
+
export const createMongoMigrationStore = (db: Db): MigrationStore => {
|
|
15
|
+
const collection = db.collection<defs.MigrationState>('migrations');
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
load: async () => {
|
|
19
|
+
const state_entry = await collection.findOne();
|
|
20
|
+
if (!state_entry) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { _id, ...state } = state_entry;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* This is for backwards compatibility. A previous version of the migration tool used to save
|
|
28
|
+
* state as `lastRun`.
|
|
29
|
+
*/
|
|
30
|
+
let last_run = state.last_run;
|
|
31
|
+
if ('lastRun' in state) {
|
|
32
|
+
last_run = (state as any).lastRun;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* This is for backwards compatibility. A previous version of the migration tool used to include the
|
|
37
|
+
* file extension in migration names. This strips that extension off if it exists
|
|
38
|
+
*/
|
|
39
|
+
const extension = path.extname(last_run);
|
|
40
|
+
if (extension) {
|
|
41
|
+
last_run = last_run.replace(extension, '');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
last_run,
|
|
46
|
+
log: state.log || []
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
save: async (state: defs.MigrationState) => {
|
|
51
|
+
await collection.replaceOne(
|
|
52
|
+
{},
|
|
53
|
+
{
|
|
54
|
+
last_run: state.last_run,
|
|
55
|
+
log: state.log
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
upsert: true
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
};
|
|
@@ -3,8 +3,8 @@ import { pgwireRows } from '@powersync/service-jpgwire';
|
|
|
3
3
|
import { DEFAULT_TAG, SqlSyncRules, TablePattern } from '@powersync/service-sync-rules';
|
|
4
4
|
import { ReplicationError, TableInfo } from '@powersync/service-types';
|
|
5
5
|
|
|
6
|
-
import * as storage from '
|
|
7
|
-
import * as util from '
|
|
6
|
+
import * as storage from '../storage/storage-index.js';
|
|
7
|
+
import * as util from '../util/util-index.js';
|
|
8
8
|
|
|
9
9
|
import { ReplicaIdentityResult, getReplicationIdentityColumns } from './util.js';
|
|
10
10
|
/**
|
|
@@ -1,22 +1,19 @@
|
|
|
1
|
-
import * as fs from 'fs/promises';
|
|
2
1
|
import * as pgwire from '@powersync/service-jpgwire';
|
|
3
|
-
import
|
|
4
|
-
import { logger } from '@journeyapps-platform/micro';
|
|
2
|
+
import { container, errors, logger } from '@powersync/lib-services-framework';
|
|
5
3
|
import { SqliteRow, SqlSyncRules, TablePattern, toSyncRulesRow } from '@powersync/service-sync-rules';
|
|
6
4
|
|
|
7
|
-
import * as storage from '
|
|
8
|
-
import * as util from '
|
|
5
|
+
import * as storage from '../storage/storage-index.js';
|
|
6
|
+
import * as util from '../util/util-index.js';
|
|
9
7
|
|
|
10
8
|
import { getPgOutputRelation, getRelId, PgRelation } from './PgRelation.js';
|
|
11
9
|
import { getReplicationIdentityColumns } from './util.js';
|
|
12
10
|
import { WalConnection } from './WalConnection.js';
|
|
13
|
-
import { Metrics } from '
|
|
11
|
+
import { Metrics } from '../metrics/Metrics.js';
|
|
14
12
|
|
|
15
13
|
export const ZERO_LSN = '00000000/00000000';
|
|
16
14
|
|
|
17
15
|
export interface WalStreamOptions {
|
|
18
16
|
connections: util.PgManager;
|
|
19
|
-
|
|
20
17
|
factory: storage.BucketStorageFactory;
|
|
21
18
|
storage: storage.SyncRulesBucketStorage;
|
|
22
19
|
abort_signal: AbortSignal;
|
|
@@ -160,7 +157,7 @@ export class WalStream {
|
|
|
160
157
|
]
|
|
161
158
|
});
|
|
162
159
|
if (rs.rows.length == 0) {
|
|
163
|
-
|
|
160
|
+
logger.info(`Skipping ${tablePattern.schema}.${name} - not part of ${this.publication_name} publication`);
|
|
164
161
|
continue;
|
|
165
162
|
}
|
|
166
163
|
|
|
@@ -190,7 +187,7 @@ export class WalStream {
|
|
|
190
187
|
|
|
191
188
|
const status = await this.storage.getStatus();
|
|
192
189
|
if (status.snapshot_done && status.checkpoint_lsn) {
|
|
193
|
-
|
|
190
|
+
logger.info(`${slotName} Initial replication already done`);
|
|
194
191
|
|
|
195
192
|
let last_error = null;
|
|
196
193
|
|
|
@@ -199,8 +196,8 @@ export class WalStream {
|
|
|
199
196
|
await touch();
|
|
200
197
|
|
|
201
198
|
if (i == 0) {
|
|
202
|
-
|
|
203
|
-
level:
|
|
199
|
+
container.reporter.captureException(last_error, {
|
|
200
|
+
level: errors.ErrorSeverity.ERROR,
|
|
204
201
|
metadata: {
|
|
205
202
|
replication_slot: slotName
|
|
206
203
|
}
|
|
@@ -222,11 +219,11 @@ export class WalStream {
|
|
|
222
219
|
]
|
|
223
220
|
});
|
|
224
221
|
// Success
|
|
225
|
-
|
|
222
|
+
logger.info(`Slot ${slotName} appears healthy`);
|
|
226
223
|
return { needsInitialSync: false };
|
|
227
224
|
} catch (e) {
|
|
228
225
|
last_error = e;
|
|
229
|
-
|
|
226
|
+
logger.warn(`${slotName} Replication slot error`, e);
|
|
230
227
|
|
|
231
228
|
if (this.stopped) {
|
|
232
229
|
throw e;
|
|
@@ -240,8 +237,8 @@ export class WalStream {
|
|
|
240
237
|
/replication slot.*does not exist/.test(e.message) ||
|
|
241
238
|
/publication.*does not exist/.test(e.message)
|
|
242
239
|
) {
|
|
243
|
-
|
|
244
|
-
level:
|
|
240
|
+
container.reporter.captureException(e, {
|
|
241
|
+
level: errors.ErrorSeverity.WARNING,
|
|
245
242
|
metadata: {
|
|
246
243
|
try_index: i,
|
|
247
244
|
replication_slot: slotName
|
|
@@ -253,7 +250,7 @@ export class WalStream {
|
|
|
253
250
|
// Sample: publication "powersync" does not exist
|
|
254
251
|
// Happens when publication deleted or never created.
|
|
255
252
|
// Slot must be re-created in this case.
|
|
256
|
-
|
|
253
|
+
logger.info(`${slotName} does not exist anymore, will create new slot`);
|
|
257
254
|
|
|
258
255
|
throw new MissingReplicationSlotError(`Replication slot ${slotName} does not exist anymore`);
|
|
259
256
|
}
|
|
@@ -316,7 +313,7 @@ WHERE oid = $1::regclass`,
|
|
|
316
313
|
// with streaming replication.
|
|
317
314
|
const lsn = pgwire.lsnMakeComparable(row[1]);
|
|
318
315
|
const snapshot = row[2];
|
|
319
|
-
|
|
316
|
+
logger.info(`Created replication slot ${slotName} at ${lsn} with snapshot ${snapshot}`);
|
|
320
317
|
|
|
321
318
|
// https://stackoverflow.com/questions/70160769/postgres-logical-replication-starting-from-given-lsn
|
|
322
319
|
await db.query('BEGIN');
|
|
@@ -338,9 +335,9 @@ WHERE oid = $1::regclass`,
|
|
|
338
335
|
// On Supabase, the default is 2 minutes.
|
|
339
336
|
await db.query(`set local statement_timeout = 0`);
|
|
340
337
|
|
|
341
|
-
|
|
338
|
+
logger.info(`${slotName} Starting initial replication`);
|
|
342
339
|
await this.initialReplication(db, lsn);
|
|
343
|
-
|
|
340
|
+
logger.info(`${slotName} Initial replication done`);
|
|
344
341
|
await db.query('COMMIT');
|
|
345
342
|
} catch (e) {
|
|
346
343
|
await db.query('ROLLBACK');
|
|
@@ -371,7 +368,7 @@ WHERE oid = $1::regclass`,
|
|
|
371
368
|
}
|
|
372
369
|
|
|
373
370
|
private async snapshotTable(batch: storage.BucketStorageBatch, db: pgwire.PgConnection, table: storage.SourceTable) {
|
|
374
|
-
|
|
371
|
+
logger.info(`${this.slot_name} Replicating ${table.qualifiedName}`);
|
|
375
372
|
const estimatedCount = await this.estimatedCount(db, table);
|
|
376
373
|
let at = 0;
|
|
377
374
|
let lastLogIndex = 0;
|
|
@@ -397,7 +394,7 @@ WHERE oid = $1::regclass`,
|
|
|
397
394
|
return q;
|
|
398
395
|
});
|
|
399
396
|
if (rows.length > 0 && at - lastLogIndex >= 5000) {
|
|
400
|
-
|
|
397
|
+
logger.info(`${this.slot_name} Replicating ${table.qualifiedName} ${at}/${estimatedCount}`);
|
|
401
398
|
lastLogIndex = at;
|
|
402
399
|
}
|
|
403
400
|
if (this.abort_signal.aborted) {
|
|
@@ -586,7 +583,7 @@ WHERE oid = $1::regclass`,
|
|
|
586
583
|
await this.ack(msg.lsn!, replicationStream);
|
|
587
584
|
} else {
|
|
588
585
|
if (count % 100 == 0) {
|
|
589
|
-
|
|
586
|
+
logger.info(`${this.slot_name} replicating op ${count} ${msg.lsn}`);
|
|
590
587
|
}
|
|
591
588
|
|
|
592
589
|
count += 1;
|
|
@@ -619,11 +616,9 @@ WHERE oid = $1::regclass`,
|
|
|
619
616
|
}
|
|
620
617
|
}
|
|
621
618
|
|
|
622
|
-
|
|
623
|
-
// FIXME: The probe does not actually check the timestamp on this.
|
|
624
|
-
// FIXME: We need a timeout of around 5+ minutes if we do start checking the timestamp,
|
|
625
|
-
// or reduce PING_INTERVAL.
|
|
626
|
-
|
|
627
|
-
// FIXME: The above probe touches the wrong file
|
|
628
|
-
await fs.writeFile('.probes/poll', `${Date.now()}`);
|
|
619
|
+
async function touch() {
|
|
620
|
+
// FIXME: The hosted Kubernetes probe does not actually check the timestamp on this.
|
|
621
|
+
// FIXME: We need a timeout of around 5+ minutes in Kubernetes if we do start checking the timestamp,
|
|
622
|
+
// or reduce PING_INTERVAL here.
|
|
623
|
+
return container.probes.touch();
|
|
629
624
|
}
|