@powersync/service-core 0.2.2 → 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 +13 -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 +9 -9
- 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 +12 -9
- 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 +9 -9
- 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/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/routes/dev.ts
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import * as t from 'ts-codec';
|
|
2
|
-
import * as micro from '@journeyapps-platform/micro';
|
|
3
|
-
import * as pgwire from '@powersync/service-jpgwire';
|
|
4
|
-
|
|
5
|
-
import * as util from '@/util/util-index.js';
|
|
6
|
-
import { authDevUser, authUser, endpoint, issueDevToken, issueLegacyDevToken, issuePowerSyncToken } from './auth.js';
|
|
7
|
-
import { RouteGenerator } from './router.js';
|
|
8
|
-
|
|
9
|
-
const AuthParams = t.object({
|
|
10
|
-
user: t.string,
|
|
11
|
-
password: t.string
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
// For legacy web client only. Remove soon.
|
|
15
|
-
export const auth: RouteGenerator = (router) =>
|
|
16
|
-
router.post('/auth.json', {
|
|
17
|
-
validator: micro.schema.createTsCodecValidator(AuthParams, { allowAdditional: true }),
|
|
18
|
-
handler: async (payload) => {
|
|
19
|
-
const { user, password } = payload.params;
|
|
20
|
-
const config = payload.context.system.config;
|
|
21
|
-
|
|
22
|
-
if (config.dev.demo_auth == false || config.dev.demo_password == null) {
|
|
23
|
-
throw new micro.errors.AuthorizationError(['Demo auth disabled']);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (password == config.dev.demo_password) {
|
|
27
|
-
const token = await issueLegacyDevToken(payload.request, user, payload.context.system.config);
|
|
28
|
-
return { token, user_id: user, endpoint: endpoint(payload.request) };
|
|
29
|
-
} else {
|
|
30
|
-
throw new micro.errors.AuthorizationError(['Authentication failed']);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
export const auth2: RouteGenerator = (router) =>
|
|
36
|
-
router.post('/dev/auth.json', {
|
|
37
|
-
validator: micro.schema.createTsCodecValidator(AuthParams, { allowAdditional: true }),
|
|
38
|
-
handler: async (payload) => {
|
|
39
|
-
const { user, password } = payload.params;
|
|
40
|
-
const config = payload.context.system.config;
|
|
41
|
-
|
|
42
|
-
if (config.dev.demo_auth == false || config.dev.demo_password == null) {
|
|
43
|
-
throw new micro.errors.AuthorizationError(['Demo auth disabled']);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (password == config.dev.demo_password) {
|
|
47
|
-
const token = await issueDevToken(payload.request, user, payload.context.system.config);
|
|
48
|
-
return { token, user_id: user };
|
|
49
|
-
} else {
|
|
50
|
-
throw new micro.errors.AuthorizationError(['Authentication failed']);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
const TokenParams = t.object({});
|
|
56
|
-
|
|
57
|
-
export const token: RouteGenerator = (router) =>
|
|
58
|
-
router.post('/dev/token.json', {
|
|
59
|
-
validator: micro.schema.createTsCodecValidator(TokenParams, { allowAdditional: true }),
|
|
60
|
-
authorize: authDevUser,
|
|
61
|
-
handler: async (payload) => {
|
|
62
|
-
const { user_id } = payload.context;
|
|
63
|
-
const outToken = await issuePowerSyncToken(payload.request, user_id!, payload.context.system.config);
|
|
64
|
-
return { token: outToken, user_id: user_id, endpoint: endpoint(payload.request) };
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
const OpType = {
|
|
69
|
-
PUT: 'PUT',
|
|
70
|
-
PATCH: 'PATCH',
|
|
71
|
-
DELETE: 'DELETE'
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const CrudEntry = t.object({
|
|
75
|
-
op: t.Enum(OpType),
|
|
76
|
-
type: t.string,
|
|
77
|
-
id: t.string,
|
|
78
|
-
op_id: t.number.optional(),
|
|
79
|
-
data: t.any.optional()
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const CrudRequest = t.object({
|
|
83
|
-
data: t.array(CrudEntry),
|
|
84
|
-
write_checkpoint: t.boolean.optional()
|
|
85
|
-
});
|
|
86
|
-
export const crud: RouteGenerator = (router) =>
|
|
87
|
-
router.post('/crud.json', {
|
|
88
|
-
validator: micro.schema.createTsCodecValidator(CrudRequest, { allowAdditional: true }),
|
|
89
|
-
authorize: authUser,
|
|
90
|
-
|
|
91
|
-
handler: async (payload) => {
|
|
92
|
-
const { user_id, system } = payload.context;
|
|
93
|
-
|
|
94
|
-
const pool = system.requirePgPool();
|
|
95
|
-
|
|
96
|
-
if (!system.config.dev.crud_api) {
|
|
97
|
-
throw new Error('CRUD api disabled');
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const params = payload.params;
|
|
101
|
-
|
|
102
|
-
let statements: pgwire.Statement[] = [];
|
|
103
|
-
|
|
104
|
-
// Implementation note:
|
|
105
|
-
// Postgres does automatic "assigment cast" for query literals,
|
|
106
|
-
// e.g. a string literal to uuid. However, the same doesn't apply
|
|
107
|
-
// to query parameters.
|
|
108
|
-
// To handle those automatically, we use `json_populate_record`
|
|
109
|
-
// to automatically cast to the correct types.
|
|
110
|
-
|
|
111
|
-
for (let op of params.data) {
|
|
112
|
-
const table = util.escapeIdentifier(op.type);
|
|
113
|
-
if (op.op == 'PUT') {
|
|
114
|
-
const data = op.data as Record<string, any>;
|
|
115
|
-
const with_id = { ...data, id: op.id };
|
|
116
|
-
|
|
117
|
-
const columnsEscaped = Object.keys(with_id).map(util.escapeIdentifier);
|
|
118
|
-
const columnsJoined = columnsEscaped.join(', ');
|
|
119
|
-
|
|
120
|
-
let updateClauses: string[] = [];
|
|
121
|
-
|
|
122
|
-
for (let key of Object.keys(data)) {
|
|
123
|
-
updateClauses.push(`${util.escapeIdentifier(key)} = EXCLUDED.${util.escapeIdentifier(key)}`);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const updateClause = updateClauses.length > 0 ? `DO UPDATE SET ${updateClauses.join(', ')}` : `DO NOTHING`;
|
|
127
|
-
|
|
128
|
-
const statement = `
|
|
129
|
-
WITH data_row AS (
|
|
130
|
-
SELECT (json_populate_record(null::${table}, $1::json)).*
|
|
131
|
-
)
|
|
132
|
-
INSERT INTO ${table} (${columnsJoined})
|
|
133
|
-
SELECT ${columnsJoined} FROM data_row
|
|
134
|
-
ON CONFLICT(id) ${updateClause}`;
|
|
135
|
-
|
|
136
|
-
statements.push({
|
|
137
|
-
statement: statement,
|
|
138
|
-
params: [{ type: 'varchar', value: JSON.stringify(with_id) }]
|
|
139
|
-
});
|
|
140
|
-
} else if (op.op == 'PATCH') {
|
|
141
|
-
const data = op.data as Record<string, any>;
|
|
142
|
-
const with_id = { ...data, id: op.id };
|
|
143
|
-
|
|
144
|
-
let updateClauses: string[] = [];
|
|
145
|
-
|
|
146
|
-
for (let key of Object.keys(data)) {
|
|
147
|
-
updateClauses.push(`${util.escapeIdentifier(key)} = data_row.${util.escapeIdentifier(key)}`);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const statement = `
|
|
151
|
-
WITH data_row AS (
|
|
152
|
-
SELECT (json_populate_record(null::${table}, $1::json)).*
|
|
153
|
-
)
|
|
154
|
-
UPDATE ${table}
|
|
155
|
-
SET ${updateClauses.join(', ')}
|
|
156
|
-
FROM data_row
|
|
157
|
-
WHERE ${table}.id = data_row.id`;
|
|
158
|
-
|
|
159
|
-
statements.push({
|
|
160
|
-
statement: statement,
|
|
161
|
-
params: [{ type: 'varchar', value: JSON.stringify(with_id) }]
|
|
162
|
-
});
|
|
163
|
-
} else if (op.op == 'DELETE') {
|
|
164
|
-
statements.push({
|
|
165
|
-
statement: `
|
|
166
|
-
WITH data_row AS (
|
|
167
|
-
SELECT (json_populate_record(null::${table}, $1::json)).*
|
|
168
|
-
)
|
|
169
|
-
DELETE FROM ${table}
|
|
170
|
-
USING data_row
|
|
171
|
-
WHERE ${table}.id = data_row.id`,
|
|
172
|
-
params: [{ type: 'varchar', value: JSON.stringify({ id: op.id }) }]
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
await pool.query(...statements);
|
|
177
|
-
|
|
178
|
-
const storage = system.storage;
|
|
179
|
-
if (payload.params.write_checkpoint === true) {
|
|
180
|
-
const write_checkpoint = await util.createWriteCheckpoint(pool, storage, payload.context.user_id!);
|
|
181
|
-
return { write_checkpoint: String(write_checkpoint) };
|
|
182
|
-
} else if (payload.params.write_checkpoint === false) {
|
|
183
|
-
return {};
|
|
184
|
-
} else {
|
|
185
|
-
// Legacy
|
|
186
|
-
const checkpoint = await util.getClientCheckpoint(pool, storage);
|
|
187
|
-
return {
|
|
188
|
-
checkpoint
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
export const dev_routes = [auth, auth2, token, crud];
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import * as micro from '@journeyapps-platform/micro';
|
|
2
|
-
|
|
3
|
-
import { Context, RouteGenerator } from './router.js';
|
|
4
|
-
import { admin_routes } from './admin.js';
|
|
5
|
-
import { writeCheckpoint, writeCheckpoint2 } from './checkpointing.js';
|
|
6
|
-
import { dev_routes } from './dev.js';
|
|
7
|
-
import { syncRulesRoutes } from './sync-rules.js';
|
|
8
|
-
import { IReactiveStream, ReactiveSocketRouter } from '@powersync/service-rsocket-router';
|
|
9
|
-
import { sync_stream_reactive } from './socket-route.js';
|
|
10
|
-
import { syncStreamed } from './sync-stream.js';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Generates router endpoints using the specified router instance
|
|
14
|
-
*/
|
|
15
|
-
export const generateHTTPRoutes = (router: micro.fastify.FastifyRouter<Context>): micro.router.Route[] => {
|
|
16
|
-
const generators: RouteGenerator[] = [
|
|
17
|
-
...dev_routes,
|
|
18
|
-
writeCheckpoint,
|
|
19
|
-
writeCheckpoint2,
|
|
20
|
-
...syncRulesRoutes,
|
|
21
|
-
...admin_routes
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
return generators.map((generateRoute) => generateRoute(router));
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Generates route endpoint for HTTP sync streaming
|
|
29
|
-
*/
|
|
30
|
-
export const generateHTTPStreamRoutes = (router: micro.fastify.FastifyRouter<Context>): micro.router.Route[] => {
|
|
31
|
-
return [syncStreamed].map((generateRoute) => generateRoute(router));
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Generates socket router endpoints using the specified router instance
|
|
36
|
-
*/
|
|
37
|
-
export const generateSocketRoutes = (router: ReactiveSocketRouter<Context>): IReactiveStream[] => {
|
|
38
|
-
return [sync_stream_reactive].map((generateRoute) => generateRoute(router));
|
|
39
|
-
};
|
package/src/routes/sync-rules.ts
DELETED
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import * as t from 'ts-codec';
|
|
2
|
-
import { FastifyPluginAsync, FastifyReply } from 'fastify';
|
|
3
|
-
import * as micro from '@journeyapps-platform/micro';
|
|
4
|
-
import * as pgwire from '@powersync/service-jpgwire';
|
|
5
|
-
import { SqlSyncRules, SyncRulesErrors } from '@powersync/service-sync-rules';
|
|
6
|
-
|
|
7
|
-
import * as replication from '@/replication/replication-index.js';
|
|
8
|
-
import { authApi } from './auth.js';
|
|
9
|
-
import { RouteGenerator } from './router.js';
|
|
10
|
-
|
|
11
|
-
const DeploySyncRulesRequest = t.object({
|
|
12
|
-
content: t.string
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
const yamlPlugin: FastifyPluginAsync = async (fastify) => {
|
|
16
|
-
fastify.addContentTypeParser('application/yaml', async (request, payload, _d) => {
|
|
17
|
-
const data = await micro.streaming.drain(payload);
|
|
18
|
-
|
|
19
|
-
request.params = { content: Buffer.concat(data).toString('utf8') };
|
|
20
|
-
});
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export const deploySyncRules: RouteGenerator = (router) =>
|
|
24
|
-
router.post('/api/sync-rules/v1/deploy', {
|
|
25
|
-
authorize: authApi,
|
|
26
|
-
parse: true,
|
|
27
|
-
plugins: [yamlPlugin],
|
|
28
|
-
validator: micro.schema.createTsCodecValidator(DeploySyncRulesRequest, { allowAdditional: true }),
|
|
29
|
-
handler: async (payload) => {
|
|
30
|
-
if (payload.context.system.config.sync_rules.present) {
|
|
31
|
-
// If sync rules are configured via the config, disable deploy via the API.
|
|
32
|
-
throw new micro.errors.JourneyError({
|
|
33
|
-
status: 422,
|
|
34
|
-
code: 'API_DISABLED',
|
|
35
|
-
description: 'Sync rules API disabled',
|
|
36
|
-
details: 'Use the management API to deploy sync rules'
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
const content = payload.params.content;
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
SqlSyncRules.fromYaml(payload.params.content);
|
|
43
|
-
} catch (e) {
|
|
44
|
-
throw new micro.errors.JourneyError({
|
|
45
|
-
status: 422,
|
|
46
|
-
code: 'INVALID_SYNC_RULES',
|
|
47
|
-
description: 'Sync rules parsing failed',
|
|
48
|
-
details: e.message
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const sync_rules = await payload.context.system.storage.updateSyncRules({
|
|
53
|
-
content: content
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
return {
|
|
57
|
-
slot_name: sync_rules.slot_name
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
const ValidateSyncRulesRequest = t.object({
|
|
63
|
-
content: t.string
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
export const validateSyncRules: RouteGenerator = (router) =>
|
|
67
|
-
router.post('/api/sync-rules/v1/validate', {
|
|
68
|
-
authorize: authApi,
|
|
69
|
-
parse: true,
|
|
70
|
-
plugins: [yamlPlugin],
|
|
71
|
-
validator: micro.schema.createTsCodecValidator(ValidateSyncRulesRequest, { allowAdditional: true }),
|
|
72
|
-
handler: async (payload) => {
|
|
73
|
-
const content = payload.params.content;
|
|
74
|
-
|
|
75
|
-
const info = await debugSyncRules(payload.context.system.requirePgPool(), content);
|
|
76
|
-
|
|
77
|
-
replyPrettyJson(payload.reply, info);
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
export const currentSyncRules: RouteGenerator = (router) =>
|
|
82
|
-
router.get('/api/sync-rules/v1/current', {
|
|
83
|
-
authorize: authApi,
|
|
84
|
-
handler: async (payload) => {
|
|
85
|
-
const storage = payload.context.system.storage;
|
|
86
|
-
const sync_rules = await storage.getActiveSyncRulesContent();
|
|
87
|
-
if (!sync_rules) {
|
|
88
|
-
throw new micro.errors.JourneyError({
|
|
89
|
-
status: 422,
|
|
90
|
-
code: 'NO_SYNC_RULES',
|
|
91
|
-
description: 'No active sync rules'
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
const info = await debugSyncRules(payload.context.system.requirePgPool(), sync_rules.sync_rules_content);
|
|
95
|
-
const next = await storage.getNextSyncRulesContent();
|
|
96
|
-
|
|
97
|
-
const next_info = next
|
|
98
|
-
? await debugSyncRules(payload.context.system.requirePgPool(), next.sync_rules_content)
|
|
99
|
-
: null;
|
|
100
|
-
|
|
101
|
-
const response = {
|
|
102
|
-
current: {
|
|
103
|
-
slot_name: sync_rules.slot_name,
|
|
104
|
-
content: sync_rules.sync_rules_content,
|
|
105
|
-
...info
|
|
106
|
-
},
|
|
107
|
-
next:
|
|
108
|
-
next == null
|
|
109
|
-
? null
|
|
110
|
-
: {
|
|
111
|
-
slot_name: next.slot_name,
|
|
112
|
-
content: next.sync_rules_content,
|
|
113
|
-
...next_info
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
replyPrettyJson(payload.reply, { data: response });
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
const ReprocessSyncRulesRequest = t.object({});
|
|
121
|
-
|
|
122
|
-
export const reprocessSyncRules: RouteGenerator = (router) =>
|
|
123
|
-
router.post('/api/sync-rules/v1/reprocess', {
|
|
124
|
-
authorize: authApi,
|
|
125
|
-
validator: micro.schema.createTsCodecValidator(ReprocessSyncRulesRequest),
|
|
126
|
-
handler: async (payload) => {
|
|
127
|
-
const storage = payload.context.system.storage;
|
|
128
|
-
const sync_rules = await storage.getActiveSyncRules();
|
|
129
|
-
if (sync_rules == null) {
|
|
130
|
-
throw new micro.errors.JourneyError({
|
|
131
|
-
status: 422,
|
|
132
|
-
code: 'NO_SYNC_RULES',
|
|
133
|
-
description: 'No active sync rules'
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const new_rules = await storage.updateSyncRules({
|
|
138
|
-
content: sync_rules.sync_rules.content
|
|
139
|
-
});
|
|
140
|
-
return {
|
|
141
|
-
slot_name: new_rules.slot_name
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
export const syncRulesRoutes = [validateSyncRules, deploySyncRules, reprocessSyncRules, currentSyncRules];
|
|
147
|
-
|
|
148
|
-
function replyPrettyJson(reply: FastifyReply, payload: any) {
|
|
149
|
-
reply
|
|
150
|
-
.status(200)
|
|
151
|
-
.header('Content-Type', 'application/json')
|
|
152
|
-
.send(JSON.stringify(payload, null, 2) + '\n');
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async function debugSyncRules(db: pgwire.PgClient, sync_rules: string) {
|
|
156
|
-
try {
|
|
157
|
-
const rules = SqlSyncRules.fromYaml(sync_rules);
|
|
158
|
-
const source_table_patterns = rules.getSourceTables();
|
|
159
|
-
const wc = new replication.WalConnection({
|
|
160
|
-
db: db,
|
|
161
|
-
sync_rules: rules
|
|
162
|
-
});
|
|
163
|
-
const resolved_tables = await wc.getDebugTablesInfo(source_table_patterns);
|
|
164
|
-
|
|
165
|
-
return {
|
|
166
|
-
valid: true,
|
|
167
|
-
bucket_definitions: rules.bucket_descriptors.map((d) => {
|
|
168
|
-
let all_parameter_queries = [...d.parameter_queries.values()].flat();
|
|
169
|
-
let all_data_queries = [...d.data_queries.values()].flat();
|
|
170
|
-
return {
|
|
171
|
-
name: d.name,
|
|
172
|
-
bucket_parameters: d.bucket_parameters,
|
|
173
|
-
global_parameter_queries: d.global_parameter_queries.map((q) => {
|
|
174
|
-
return {
|
|
175
|
-
sql: q.sql
|
|
176
|
-
};
|
|
177
|
-
}),
|
|
178
|
-
parameter_queries: all_parameter_queries.map((q) => {
|
|
179
|
-
return {
|
|
180
|
-
sql: q.sql,
|
|
181
|
-
table: q.sourceTable,
|
|
182
|
-
input_parameters: q.input_parameters
|
|
183
|
-
};
|
|
184
|
-
}),
|
|
185
|
-
|
|
186
|
-
data_queries: all_data_queries.map((q) => {
|
|
187
|
-
return {
|
|
188
|
-
sql: q.sql,
|
|
189
|
-
table: q.sourceTable,
|
|
190
|
-
columns: q.columnOutputNames()
|
|
191
|
-
};
|
|
192
|
-
})
|
|
193
|
-
};
|
|
194
|
-
}),
|
|
195
|
-
source_tables: resolved_tables,
|
|
196
|
-
data_tables: rules.debugGetOutputTables()
|
|
197
|
-
};
|
|
198
|
-
} catch (e) {
|
|
199
|
-
if (e instanceof SyncRulesErrors) {
|
|
200
|
-
return {
|
|
201
|
-
valid: false,
|
|
202
|
-
errors: e.errors.map((e) => e.message)
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
return {
|
|
206
|
-
valid: false,
|
|
207
|
-
errors: [e.message]
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { Readable } from 'stream';
|
|
2
|
-
import * as micro from '@journeyapps-platform/micro';
|
|
3
|
-
import { SyncParameters, normalizeTokenParameters } from '@powersync/service-sync-rules';
|
|
4
|
-
|
|
5
|
-
import * as sync from '@/sync/sync-index.js';
|
|
6
|
-
import * as util from '@/util/util-index.js';
|
|
7
|
-
|
|
8
|
-
import { authUser } from './auth.js';
|
|
9
|
-
import { RouteGenerator } from './router.js';
|
|
10
|
-
import { Metrics } from '@/metrics/Metrics.js';
|
|
11
|
-
|
|
12
|
-
export enum SyncRoutes {
|
|
13
|
-
STREAM = '/sync/stream'
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const syncStreamed: RouteGenerator = (router) =>
|
|
17
|
-
router.post(SyncRoutes.STREAM, {
|
|
18
|
-
authorize: authUser,
|
|
19
|
-
validator: micro.schema.createTsCodecValidator(util.StreamingSyncRequest, { allowAdditional: true }),
|
|
20
|
-
handler: async (payload) => {
|
|
21
|
-
const userId = payload.context.user_id!;
|
|
22
|
-
const system = payload.context.system;
|
|
23
|
-
|
|
24
|
-
if (system.closed) {
|
|
25
|
-
throw new micro.errors.JourneyError({
|
|
26
|
-
status: 503,
|
|
27
|
-
code: 'SERVICE_UNAVAILABLE',
|
|
28
|
-
description: 'Service temporarily unavailable'
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const params: util.StreamingSyncRequest = payload.params;
|
|
33
|
-
const syncParams: SyncParameters = normalizeTokenParameters(
|
|
34
|
-
payload.context.token_payload!.parameters ?? {},
|
|
35
|
-
payload.params.parameters ?? {}
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
const storage = system.storage;
|
|
39
|
-
// Sanity check before we start the stream
|
|
40
|
-
const cp = await storage.getActiveCheckpoint();
|
|
41
|
-
if (!cp.hasSyncRules()) {
|
|
42
|
-
throw new micro.errors.JourneyError({
|
|
43
|
-
status: 500,
|
|
44
|
-
code: 'NO_SYNC_RULES',
|
|
45
|
-
description: 'No sync rules available'
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const res = payload.reply;
|
|
50
|
-
|
|
51
|
-
res.status(200).header('Content-Type', 'application/x-ndjson');
|
|
52
|
-
|
|
53
|
-
const controller = new AbortController();
|
|
54
|
-
try {
|
|
55
|
-
Metrics.getInstance().concurrent_connections.add(1);
|
|
56
|
-
const stream = Readable.from(
|
|
57
|
-
sync.transformToBytesTracked(
|
|
58
|
-
sync.ndjson(
|
|
59
|
-
sync.streamResponse({
|
|
60
|
-
storage,
|
|
61
|
-
params,
|
|
62
|
-
syncParams,
|
|
63
|
-
token: payload.context.token_payload!,
|
|
64
|
-
signal: controller.signal
|
|
65
|
-
})
|
|
66
|
-
)
|
|
67
|
-
),
|
|
68
|
-
{ objectMode: false, highWaterMark: 16 * 1024 }
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
const deregister = system.addStopHandler(() => {
|
|
72
|
-
// This error is not currently propagated to the client
|
|
73
|
-
controller.abort();
|
|
74
|
-
stream.destroy(new Error('Shutting down system'));
|
|
75
|
-
});
|
|
76
|
-
stream.on('close', () => {
|
|
77
|
-
deregister();
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
stream.on('error', (error) => {
|
|
81
|
-
controller.abort();
|
|
82
|
-
// Note: This appears as a 200 response in the logs.
|
|
83
|
-
if (error.message != 'Shutting down system') {
|
|
84
|
-
micro.logger.error('Streaming sync request failed', error);
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
await res.send(stream);
|
|
88
|
-
} finally {
|
|
89
|
-
controller.abort();
|
|
90
|
-
Metrics.getInstance().concurrent_connections.add(-1);
|
|
91
|
-
// Prevent double-send
|
|
92
|
-
res.hijack();
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
});
|