@powersync/service-core 0.2.2 → 0.4.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.
Files changed (262) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/api/diagnostics.js +2 -2
  3. package/dist/api/diagnostics.js.map +1 -1
  4. package/dist/api/schema.js.map +1 -1
  5. package/dist/auth/CachedKeyCollector.js.map +1 -1
  6. package/dist/auth/JwtPayload.d.ts +6 -2
  7. package/dist/auth/KeySpec.js.map +1 -1
  8. package/dist/auth/KeyStore.js +3 -9
  9. package/dist/auth/KeyStore.js.map +1 -1
  10. package/dist/auth/LeakyBucket.js.map +1 -1
  11. package/dist/auth/RemoteJWKSCollector.js.map +1 -1
  12. package/dist/auth/SupabaseKeyCollector.js.map +1 -1
  13. package/dist/db/mongo.js.map +1 -1
  14. package/dist/entry/cli-entry.js +2 -2
  15. package/dist/entry/cli-entry.js.map +1 -1
  16. package/dist/entry/commands/config-command.js.map +1 -1
  17. package/dist/entry/commands/migrate-action.js +12 -4
  18. package/dist/entry/commands/migrate-action.js.map +1 -1
  19. package/dist/entry/commands/start-action.js.map +1 -1
  20. package/dist/entry/commands/teardown-action.js.map +1 -1
  21. package/dist/index.d.ts +3 -2
  22. package/dist/index.js +4 -2
  23. package/dist/index.js.map +1 -1
  24. package/dist/locks/LockManager.d.ts +10 -0
  25. package/dist/locks/LockManager.js +7 -0
  26. package/dist/locks/LockManager.js.map +1 -0
  27. package/dist/locks/MongoLocks.d.ts +36 -0
  28. package/dist/locks/MongoLocks.js +81 -0
  29. package/dist/locks/MongoLocks.js.map +1 -0
  30. package/dist/locks/locks-index.d.ts +2 -0
  31. package/dist/locks/locks-index.js +3 -0
  32. package/dist/locks/locks-index.js.map +1 -0
  33. package/dist/metrics/Metrics.js +6 -6
  34. package/dist/metrics/Metrics.js.map +1 -1
  35. package/dist/migrations/db/migrations/1684951997326-init.js.map +1 -1
  36. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +1 -1
  37. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js.map +1 -1
  38. package/dist/migrations/definitions.d.ts +18 -0
  39. package/dist/migrations/definitions.js +6 -0
  40. package/dist/migrations/definitions.js.map +1 -0
  41. package/dist/migrations/executor.d.ts +16 -0
  42. package/dist/migrations/executor.js +64 -0
  43. package/dist/migrations/executor.js.map +1 -0
  44. package/dist/migrations/migrations-index.d.ts +3 -0
  45. package/dist/migrations/migrations-index.js +4 -0
  46. package/dist/migrations/migrations-index.js.map +1 -0
  47. package/dist/migrations/migrations.d.ts +1 -1
  48. package/dist/migrations/migrations.js +12 -8
  49. package/dist/migrations/migrations.js.map +1 -1
  50. package/dist/migrations/store/migration-store.d.ts +11 -0
  51. package/dist/migrations/store/migration-store.js +46 -0
  52. package/dist/migrations/store/migration-store.js.map +1 -0
  53. package/dist/replication/ErrorRateLimiter.js.map +1 -1
  54. package/dist/replication/PgRelation.js.map +1 -1
  55. package/dist/replication/WalConnection.js.map +1 -1
  56. package/dist/replication/WalStream.d.ts +0 -1
  57. package/dist/replication/WalStream.js +21 -25
  58. package/dist/replication/WalStream.js.map +1 -1
  59. package/dist/replication/WalStreamManager.js +12 -13
  60. package/dist/replication/WalStreamManager.js.map +1 -1
  61. package/dist/replication/WalStreamRunner.js +8 -8
  62. package/dist/replication/WalStreamRunner.js.map +1 -1
  63. package/dist/replication/util.js.map +1 -1
  64. package/dist/routes/auth.d.ts +8 -10
  65. package/dist/routes/auth.js.map +1 -1
  66. package/dist/routes/endpoints/admin.d.ts +1011 -0
  67. package/dist/routes/{admin.js → endpoints/admin.js} +33 -18
  68. package/dist/routes/endpoints/admin.js.map +1 -0
  69. package/dist/routes/endpoints/checkpointing.d.ts +76 -0
  70. package/dist/routes/endpoints/checkpointing.js +36 -0
  71. package/dist/routes/endpoints/checkpointing.js.map +1 -0
  72. package/dist/routes/endpoints/dev.d.ts +312 -0
  73. package/dist/routes/{dev.js → endpoints/dev.js} +25 -16
  74. package/dist/routes/endpoints/dev.js.map +1 -0
  75. package/dist/routes/endpoints/route-endpoints-index.d.ts +6 -0
  76. package/dist/routes/endpoints/route-endpoints-index.js +7 -0
  77. package/dist/routes/endpoints/route-endpoints-index.js.map +1 -0
  78. package/dist/routes/endpoints/socket-route.d.ts +2 -0
  79. package/dist/routes/{socket-route.js → endpoints/socket-route.js} +12 -12
  80. package/dist/routes/endpoints/socket-route.js.map +1 -0
  81. package/dist/routes/endpoints/sync-rules.d.ts +174 -0
  82. package/dist/routes/{sync-rules.js → endpoints/sync-rules.js} +44 -24
  83. package/dist/routes/endpoints/sync-rules.js.map +1 -0
  84. package/dist/routes/endpoints/sync-stream.d.ts +132 -0
  85. package/dist/routes/{sync-stream.js → endpoints/sync-stream.js} +28 -19
  86. package/dist/routes/endpoints/sync-stream.js.map +1 -0
  87. package/dist/routes/hooks.d.ts +10 -0
  88. package/dist/routes/hooks.js +31 -0
  89. package/dist/routes/hooks.js.map +1 -0
  90. package/dist/routes/route-register.d.ts +10 -0
  91. package/dist/routes/route-register.js +87 -0
  92. package/dist/routes/route-register.js.map +1 -0
  93. package/dist/routes/router.d.ts +16 -4
  94. package/dist/routes/router.js +6 -1
  95. package/dist/routes/router.js.map +1 -1
  96. package/dist/routes/routes-index.d.ts +5 -3
  97. package/dist/routes/routes-index.js +5 -3
  98. package/dist/routes/routes-index.js.map +1 -1
  99. package/dist/runner/teardown.js +9 -9
  100. package/dist/runner/teardown.js.map +1 -1
  101. package/dist/storage/BucketStorage.d.ts +3 -0
  102. package/dist/storage/BucketStorage.js.map +1 -1
  103. package/dist/storage/ChecksumCache.js.map +1 -1
  104. package/dist/storage/MongoBucketStorage.js +5 -5
  105. package/dist/storage/MongoBucketStorage.js.map +1 -1
  106. package/dist/storage/SourceTable.js.map +1 -1
  107. package/dist/storage/mongo/MongoBucketBatch.js +23 -18
  108. package/dist/storage/mongo/MongoBucketBatch.js.map +1 -1
  109. package/dist/storage/mongo/MongoIdSequence.js.map +1 -1
  110. package/dist/storage/mongo/MongoSyncBucketStorage.js.map +1 -1
  111. package/dist/storage/mongo/MongoSyncRulesLock.js +3 -3
  112. package/dist/storage/mongo/MongoSyncRulesLock.js.map +1 -1
  113. package/dist/storage/mongo/OperationBatch.js.map +1 -1
  114. package/dist/storage/mongo/PersistedBatch.js +2 -2
  115. package/dist/storage/mongo/PersistedBatch.js.map +1 -1
  116. package/dist/storage/mongo/db.d.ts +2 -2
  117. package/dist/storage/mongo/db.js.map +1 -1
  118. package/dist/storage/mongo/util.js.map +1 -1
  119. package/dist/sync/BroadcastIterable.js.map +1 -1
  120. package/dist/sync/LastValueSink.js.map +1 -1
  121. package/dist/sync/merge.js.map +1 -1
  122. package/dist/sync/safeRace.js.map +1 -1
  123. package/dist/sync/sync.d.ts +2 -2
  124. package/dist/sync/sync.js +5 -5
  125. package/dist/sync/sync.js.map +1 -1
  126. package/dist/sync/util.js.map +1 -1
  127. package/dist/system/CorePowerSyncSystem.d.ts +12 -7
  128. package/dist/system/CorePowerSyncSystem.js +26 -2
  129. package/dist/system/CorePowerSyncSystem.js.map +1 -1
  130. package/dist/system/system-index.d.ts +1 -0
  131. package/dist/system/system-index.js +2 -0
  132. package/dist/system/system-index.js.map +1 -0
  133. package/dist/util/Mutex.js.map +1 -1
  134. package/dist/util/PgManager.js.map +1 -1
  135. package/dist/util/alerting.d.ts +0 -2
  136. package/dist/util/alerting.js +0 -6
  137. package/dist/util/alerting.js.map +1 -1
  138. package/dist/util/config/collectors/config-collector.js +3 -3
  139. package/dist/util/config/collectors/config-collector.js.map +1 -1
  140. package/dist/util/config/collectors/impl/base64-config-collector.js.map +1 -1
  141. package/dist/util/config/collectors/impl/filesystem-config-collector.js +7 -5
  142. package/dist/util/config/collectors/impl/filesystem-config-collector.js.map +1 -1
  143. package/dist/util/config/compound-config-collector.js +4 -4
  144. package/dist/util/config/compound-config-collector.js.map +1 -1
  145. package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.js.map +1 -1
  146. package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js.map +1 -1
  147. package/dist/util/config/sync-rules/impl/inline-sync-rules-collector.js.map +1 -1
  148. package/dist/util/config.js.map +1 -1
  149. package/dist/util/env.d.ts +1 -2
  150. package/dist/util/env.js +3 -2
  151. package/dist/util/env.js.map +1 -1
  152. package/dist/util/memory-tracking.js +2 -2
  153. package/dist/util/memory-tracking.js.map +1 -1
  154. package/dist/util/migration_lib.js.map +1 -1
  155. package/dist/util/pgwire_utils.js +2 -2
  156. package/dist/util/pgwire_utils.js.map +1 -1
  157. package/dist/util/populate_test_data.js.map +1 -1
  158. package/dist/util/secs.js.map +1 -1
  159. package/dist/util/utils.js +4 -4
  160. package/dist/util/utils.js.map +1 -1
  161. package/package.json +13 -10
  162. package/src/api/diagnostics.ts +5 -5
  163. package/src/api/schema.ts +1 -1
  164. package/src/auth/JwtPayload.ts +6 -2
  165. package/src/auth/KeyStore.ts +3 -9
  166. package/src/entry/cli-entry.ts +3 -4
  167. package/src/entry/commands/config-command.ts +1 -1
  168. package/src/entry/commands/migrate-action.ts +14 -6
  169. package/src/entry/commands/start-action.ts +1 -1
  170. package/src/entry/commands/teardown-action.ts +1 -1
  171. package/src/index.ts +5 -2
  172. package/src/locks/LockManager.ts +16 -0
  173. package/src/locks/MongoLocks.ts +142 -0
  174. package/src/locks/locks-index.ts +2 -0
  175. package/src/metrics/Metrics.ts +8 -8
  176. package/src/migrations/db/migrations/1684951997326-init.ts +3 -3
  177. package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +3 -3
  178. package/src/migrations/db/migrations/1711543888062-write-checkpoint-index.ts +2 -2
  179. package/src/migrations/definitions.ts +21 -0
  180. package/src/migrations/executor.ts +87 -0
  181. package/src/migrations/migrations-index.ts +3 -0
  182. package/src/migrations/migrations.ts +15 -11
  183. package/src/migrations/store/migration-store.ts +63 -0
  184. package/src/replication/WalConnection.ts +2 -2
  185. package/src/replication/WalStream.ts +24 -29
  186. package/src/replication/WalStreamManager.ts +14 -15
  187. package/src/replication/WalStreamRunner.ts +10 -10
  188. package/src/replication/util.ts +1 -1
  189. package/src/routes/auth.ts +22 -16
  190. package/src/routes/endpoints/admin.ts +237 -0
  191. package/src/routes/endpoints/checkpointing.ts +41 -0
  192. package/src/routes/endpoints/dev.ts +199 -0
  193. package/src/routes/endpoints/route-endpoints-index.ts +6 -0
  194. package/src/routes/{socket-route.ts → endpoints/socket-route.ts} +13 -16
  195. package/src/routes/endpoints/sync-rules.ts +227 -0
  196. package/src/routes/endpoints/sync-stream.ts +98 -0
  197. package/src/routes/hooks.ts +45 -0
  198. package/src/routes/route-register.ts +104 -0
  199. package/src/routes/router.ts +34 -6
  200. package/src/routes/routes-index.ts +5 -4
  201. package/src/runner/teardown.ts +9 -9
  202. package/src/storage/BucketStorage.ts +7 -2
  203. package/src/storage/ChecksumCache.ts +2 -2
  204. package/src/storage/MongoBucketStorage.ts +8 -8
  205. package/src/storage/SourceTable.ts +2 -2
  206. package/src/storage/mongo/MongoBucketBatch.ts +29 -22
  207. package/src/storage/mongo/MongoSyncBucketStorage.ts +3 -3
  208. package/src/storage/mongo/MongoSyncRulesLock.ts +3 -3
  209. package/src/storage/mongo/OperationBatch.ts +1 -1
  210. package/src/storage/mongo/PersistedBatch.ts +3 -3
  211. package/src/storage/mongo/db.ts +3 -4
  212. package/src/sync/sync.ts +11 -11
  213. package/src/sync/util.ts +2 -2
  214. package/src/system/CorePowerSyncSystem.ts +31 -10
  215. package/src/system/system-index.ts +1 -0
  216. package/src/util/alerting.ts +0 -8
  217. package/src/util/config/collectors/config-collector.ts +5 -3
  218. package/src/util/config/collectors/impl/filesystem-config-collector.ts +8 -6
  219. package/src/util/config/compound-config-collector.ts +4 -4
  220. package/src/util/env.ts +4 -2
  221. package/src/util/memory-tracking.ts +2 -2
  222. package/src/util/pgwire_utils.ts +3 -3
  223. package/src/util/utils.ts +5 -5
  224. package/test/src/auth.test.ts +4 -2
  225. package/test/src/data_storage.test.ts +181 -19
  226. package/test/src/env.ts +6 -6
  227. package/test/src/setup.ts +7 -0
  228. package/test/src/slow_tests.test.ts +45 -6
  229. package/test/src/sync.test.ts +6 -5
  230. package/test/tsconfig.json +1 -1
  231. package/tsconfig.json +5 -6
  232. package/tsconfig.tsbuildinfo +1 -1
  233. package/vitest.config.ts +1 -3
  234. package/dist/migrations/db/store.d.ts +0 -3
  235. package/dist/migrations/db/store.js +0 -10
  236. package/dist/migrations/db/store.js.map +0 -1
  237. package/dist/routes/admin.d.ts +0 -7
  238. package/dist/routes/admin.js.map +0 -1
  239. package/dist/routes/checkpointing.d.ts +0 -3
  240. package/dist/routes/checkpointing.js +0 -30
  241. package/dist/routes/checkpointing.js.map +0 -1
  242. package/dist/routes/dev.d.ts +0 -6
  243. package/dist/routes/dev.js.map +0 -1
  244. package/dist/routes/route-generators.d.ts +0 -15
  245. package/dist/routes/route-generators.js +0 -32
  246. package/dist/routes/route-generators.js.map +0 -1
  247. package/dist/routes/socket-route.d.ts +0 -2
  248. package/dist/routes/socket-route.js.map +0 -1
  249. package/dist/routes/sync-rules.d.ts +0 -6
  250. package/dist/routes/sync-rules.js.map +0 -1
  251. package/dist/routes/sync-stream.d.ts +0 -5
  252. package/dist/routes/sync-stream.js.map +0 -1
  253. package/src/migrations/db/store.ts +0 -11
  254. package/src/routes/admin.ts +0 -229
  255. package/src/routes/checkpointing.ts +0 -38
  256. package/src/routes/dev.ts +0 -194
  257. package/src/routes/route-generators.ts +0 -39
  258. package/src/routes/sync-rules.ts +0 -210
  259. package/src/routes/sync-stream.ts +0 -95
  260. package/test/src/sql_functions.test.ts +0 -254
  261. package/test/src/sql_operators.test.ts +0 -132
  262. package/test/src/sync_rules.test.ts +0 -1053
@@ -1,22 +1,19 @@
1
- import * as fs from 'fs/promises';
2
1
  import * as pgwire from '@powersync/service-jpgwire';
3
- import * as micro from '@journeyapps-platform/micro';
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 '@/storage/storage-index.js';
8
- import * as util from '@/util/util-index.js';
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 '@/metrics/Metrics.js';
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
- micro.logger.info(`Skipping ${tablePattern.schema}.${name} - not part of ${this.publication_name} publication`);
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
- micro.logger.info(`${slotName} Initial replication already done`);
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
- util.captureException(last_error, {
203
- level: micro.errors.ErrorSeverity.ERROR,
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
- micro.logger.info(`Slot ${slotName} appears healthy`);
222
+ logger.info(`Slot ${slotName} appears healthy`);
226
223
  return { needsInitialSync: false };
227
224
  } catch (e) {
228
225
  last_error = e;
229
- micro.logger.warn(`${slotName} Replication slot error`, e);
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
- util.captureException(e, {
244
- level: micro.errors.ErrorSeverity.WARNING,
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
- micro.logger.info(`${slotName} does not exist anymore, will create new slot`);
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
- micro.logger.info(`Created replication slot ${slotName} at ${lsn} with snapshot ${snapshot}`);
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
- micro.logger.info(`${slotName} Starting initial replication`);
338
+ logger.info(`${slotName} Starting initial replication`);
342
339
  await this.initialReplication(db, lsn);
343
- micro.logger.info(`${slotName} Initial replication done`);
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
- micro.logger.info(`${this.slot_name} Replicating ${table.qualifiedName}`);
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
- micro.logger.info(`${this.slot_name} Replicating ${table.qualifiedName} ${at}/${estimatedCount}`);
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
- micro.logger.info(`${this.slot_name} replicating op ${count} ${msg.lsn}`);
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
- export async function touch() {
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
- await micro.signals.getSystemProbe().touch();
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
  }
@@ -1,14 +1,13 @@
1
1
  import * as pgwire from '@powersync/service-jpgwire';
2
- import * as micro from '@journeyapps-platform/micro';
3
2
  import { hrtime } from 'node:process';
4
3
 
5
- import * as storage from '@/storage/storage-index.js';
6
- import * as util from '@/util/util-index.js';
4
+ import * as storage from '../storage/storage-index.js';
5
+ import * as util from '../util/util-index.js';
7
6
 
8
7
  import { DefaultErrorRateLimiter } from './ErrorRateLimiter.js';
9
- import { touch } from './WalStream.js';
10
8
  import { WalStreamRunner } from './WalStreamRunner.js';
11
9
  import { CorePowerSyncSystem } from '../system/CorePowerSyncSystem.js';
10
+ import { container, logger } from '@powersync/lib-services-framework';
12
11
 
13
12
  // 5 minutes
14
13
  const PING_INTERVAL = 1_000_000_000n * 300n;
@@ -37,8 +36,8 @@ export class WalStreamManager {
37
36
 
38
37
  start() {
39
38
  this.runLoop().catch((e) => {
40
- micro.logger.error(`Fatal WalStream error`, e);
41
- util.captureException(e);
39
+ logger.error(`Fatal WalStream error`, e);
40
+ container.reporter.captureException(e);
42
41
  setTimeout(() => {
43
42
  process.exit(1);
44
43
  }, 1000);
@@ -58,7 +57,7 @@ export class WalStreamManager {
58
57
  const configured_sync_rules = await util.loadSyncRules(this.system.config);
59
58
  let configured_lock: storage.ReplicationLock | undefined = undefined;
60
59
  if (configured_sync_rules != null) {
61
- micro.logger.info('Loading sync rules from configuration');
60
+ logger.info('Loading sync rules from configuration');
62
61
  try {
63
62
  // Configure new sync rules, if it has changed.
64
63
  // In that case, also immediately take out a lock, so that another process doesn't start replication on it.
@@ -70,13 +69,13 @@ export class WalStreamManager {
70
69
  }
71
70
  } catch (e) {
72
71
  // Log, but continue with previous sync rules
73
- micro.logger.error(`Failed to load sync rules from configuration`, e);
72
+ logger.error(`Failed to load sync rules from configuration`, e);
74
73
  }
75
74
  } else {
76
- micro.logger.info('No sync rules configured - configure via API');
75
+ logger.info('No sync rules configured - configure via API');
77
76
  }
78
77
  while (!this.stopped) {
79
- await touch();
78
+ await container.probes.touch();
80
79
  try {
81
80
  const pool = this.system.pgwire_pool;
82
81
  if (pool) {
@@ -93,7 +92,7 @@ export class WalStreamManager {
93
92
  }
94
93
  }
95
94
  } catch (e) {
96
- micro.logger.error(`Failed to refresh wal streams`, e);
95
+ logger.error(`Failed to refresh wal streams`, e);
97
96
  }
98
97
  await new Promise((resolve) => setTimeout(resolve, 5000));
99
98
  }
@@ -117,7 +116,7 @@ export class WalStreamManager {
117
116
  try {
118
117
  await db.query(`SELECT * FROM pg_logical_emit_message(false, 'powersync', 'ping')`);
119
118
  } catch (e) {
120
- micro.logger.warn(`Failed to ping`, e);
119
+ logger.warn(`Failed to ping`, e);
121
120
  }
122
121
  this.lastPing = now;
123
122
  }
@@ -168,7 +167,7 @@ export class WalStreamManager {
168
167
  // for example from stricter validation that was added.
169
168
  // This will be retried every couple of seconds.
170
169
  // When new (valid) sync rules are deployed and processed, this one be disabled.
171
- micro.logger.error(`Failed to start replication for ${syncRules.slot_name}`, e);
170
+ logger.error(`Failed to start replication for ${syncRules.slot_name}`, e);
172
171
  }
173
172
  }
174
173
  }
@@ -184,7 +183,7 @@ export class WalStreamManager {
184
183
  await stream.terminate();
185
184
  } catch (e) {
186
185
  // This will be retried
187
- micro.logger.warn(`Failed to terminate ${stream.slot_name}`, e);
186
+ logger.warn(`Failed to terminate ${stream.slot_name}`, e);
188
187
  }
189
188
  }
190
189
 
@@ -207,7 +206,7 @@ export class WalStreamManager {
207
206
  await lock.release();
208
207
  }
209
208
  } catch (e) {
210
- micro.logger.warn(`Failed to terminate ${syncRules.slot_name}`, e);
209
+ logger.warn(`Failed to terminate ${syncRules.slot_name}`, e);
211
210
  }
212
211
  }
213
212
  }
@@ -1,12 +1,12 @@
1
1
  import * as pgwire from '@powersync/service-jpgwire';
2
- import * as micro from '@journeyapps-platform/micro';
3
2
 
4
- import * as storage from '@/storage/storage-index.js';
5
- import * as util from '@/util/util-index.js';
3
+ import * as storage from '../storage/storage-index.js';
4
+ import * as util from '../util/util-index.js';
6
5
 
7
6
  import { ErrorRateLimiter } from './ErrorRateLimiter.js';
8
7
  import { MissingReplicationSlotError, WalStream } from './WalStream.js';
9
8
  import { ResolvedConnection } from '../util/config/types.js';
9
+ import { container, logger } from '@powersync/lib-services-framework';
10
10
 
11
11
  export interface WalStreamRunnerOptions {
12
12
  factory: storage.BucketStorageFactory;
@@ -46,12 +46,12 @@ export class WalStreamRunner {
46
46
  await this.replicateLoop();
47
47
  } catch (e) {
48
48
  // Fatal exception
49
- util.captureException(e, {
49
+ container.reporter.captureException(e, {
50
50
  metadata: {
51
51
  replication_slot: this.slot_name
52
52
  }
53
53
  });
54
- micro.logger.error(`Replication failed on ${this.slot_name}`, e);
54
+ logger.error(`Replication failed on ${this.slot_name}`, e);
55
55
 
56
56
  if (e instanceof MissingReplicationSlotError) {
57
57
  // This stops replication on this slot, and creates a new slot
@@ -96,7 +96,7 @@ export class WalStreamRunner {
96
96
  });
97
97
  await stream.replicate();
98
98
  } catch (e) {
99
- micro.logger.error(`Replication error`, e);
99
+ logger.error(`Replication error`, e);
100
100
  if (e.cause != null) {
101
101
  // Example:
102
102
  // PgError.conn_ended: Unable to do postgres query on ended connection
@@ -118,13 +118,13 @@ export class WalStreamRunner {
118
118
  // [Symbol(pg.ErrorResponse)]: undefined
119
119
  // }
120
120
  // Without this additional log, the cause would not be visible in the logs.
121
- micro.logger.error(`cause`, e.cause);
121
+ logger.error(`cause`, e.cause);
122
122
  }
123
123
  if (e instanceof MissingReplicationSlotError) {
124
124
  throw e;
125
125
  } else {
126
126
  // Report the error if relevant, before retrying
127
- util.captureException(e, {
127
+ container.reporter.captureException(e, {
128
128
  metadata: {
129
129
  replication_slot: this.slot_name
130
130
  }
@@ -144,7 +144,7 @@ export class WalStreamRunner {
144
144
  * This will also release the lock if start() was called earlier.
145
145
  */
146
146
  async stop(options?: { force?: boolean }) {
147
- micro.logger.info(`${this.slot_name} Stopping replication`);
147
+ logger.info(`${this.slot_name} Stopping replication`);
148
148
  // End gracefully
149
149
  this.abortController.abort();
150
150
 
@@ -161,7 +161,7 @@ export class WalStreamRunner {
161
161
  * Stops replication if needed.
162
162
  */
163
163
  async terminate(options?: { force?: boolean }) {
164
- micro.logger.info(`${this.slot_name} Terminating replication`);
164
+ logger.info(`${this.slot_name} Terminating replication`);
165
165
  await this.stop(options);
166
166
 
167
167
  const slotName = this.slot_name;
@@ -1,6 +1,6 @@
1
1
  import * as pgwire from '@powersync/service-jpgwire';
2
2
 
3
- import * as util from '@/util/util-index.js';
3
+ import * as util from '../util/util-index.js';
4
4
  import { ReplicationColumn, ReplicationIdentity } from './PgRelation.js';
5
5
 
6
6
  export interface ReplicaIdentityResult {
@@ -1,19 +1,17 @@
1
- import * as micro from '@journeyapps-platform/micro';
2
- import { FastifyRequest } from 'fastify';
3
1
  import * as jose from 'jose';
4
2
 
5
- import * as auth from '@/auth/auth-index.js';
6
- import * as util from '@/util/util-index.js';
7
- import { Context } from './router.js';
3
+ import * as auth from '../auth/auth-index.js';
4
+ import * as util from '../util/util-index.js';
5
+ import { BasicRouterRequest, Context, RequestEndpointHandlerPayload } from './router.js';
8
6
  import { CorePowerSyncSystem } from '../system/CorePowerSyncSystem.js';
9
7
 
10
- export function endpoint(req: FastifyRequest) {
8
+ export function endpoint(req: BasicRouterRequest) {
11
9
  const protocol = req.headers['x-forwarded-proto'] ?? req.protocol;
12
10
  const host = req.hostname;
13
11
  return `${protocol}://${host}`;
14
12
  }
15
13
 
16
- function devAudience(req: FastifyRequest): string {
14
+ function devAudience(req: BasicRouterRequest): string {
17
15
  return `${endpoint(req)}/dev`;
18
16
  }
19
17
 
@@ -22,7 +20,7 @@ function devAudience(req: FastifyRequest): string {
22
20
  *
23
21
  * Will be replaced by temporary tokens issued by PowerSync Management service.
24
22
  */
25
- export async function issueDevToken(req: FastifyRequest, user_id: string, config: util.ResolvedPowerSyncConfig) {
23
+ export async function issueDevToken(req: BasicRouterRequest, user_id: string, config: util.ResolvedPowerSyncConfig) {
26
24
  const iss = devAudience(req);
27
25
  const aud = devAudience(req);
28
26
 
@@ -42,7 +40,11 @@ export async function issueDevToken(req: FastifyRequest, user_id: string, config
42
40
  }
43
41
 
44
42
  /** @deprecated */
45
- export async function issueLegacyDevToken(req: FastifyRequest, user_id: string, config: util.ResolvedPowerSyncConfig) {
43
+ export async function issueLegacyDevToken(
44
+ req: BasicRouterRequest,
45
+ user_id: string,
46
+ config: util.ResolvedPowerSyncConfig
47
+ ) {
46
48
  const iss = devAudience(req);
47
49
  const aud = config.jwt_audiences[0];
48
50
 
@@ -61,7 +63,11 @@ export async function issueLegacyDevToken(req: FastifyRequest, user_id: string,
61
63
  .sign(key.key);
62
64
  }
63
65
 
64
- export async function issuePowerSyncToken(req: FastifyRequest, user_id: string, config: util.ResolvedPowerSyncConfig) {
66
+ export async function issuePowerSyncToken(
67
+ req: BasicRouterRequest,
68
+ user_id: string,
69
+ config: util.ResolvedPowerSyncConfig
70
+ ) {
65
71
  const iss = devAudience(req);
66
72
  const aud = config.jwt_audiences[0];
67
73
  const key = config.dev.dev_key;
@@ -89,8 +95,8 @@ export function getTokenFromHeader(authHeader: string = ''): string | null {
89
95
  return token ?? null;
90
96
  }
91
97
 
92
- export const authUser = async (payload: micro.fastify.FastifyHandlerPayload<any, Context>) => {
93
- return authorizeUser(payload.context, payload.request.headers.authorization);
98
+ export const authUser = async (payload: RequestEndpointHandlerPayload) => {
99
+ return authorizeUser(payload.context, payload.request.headers.authorization as string);
94
100
  };
95
101
 
96
102
  export async function authorizeUser(context: Context, authHeader: string = '') {
@@ -142,9 +148,9 @@ export async function generateContext(system: CorePowerSyncSystem, token: string
142
148
  /**
143
149
  * @deprecated
144
150
  */
145
- export const authDevUser = async (payload: micro.fastify.FastifyHandlerPayload<any, Context>) => {
151
+ export const authDevUser = async (payload: RequestEndpointHandlerPayload) => {
146
152
  const context = payload.context;
147
- const token = getTokenFromHeader(payload.request.headers.authorization);
153
+ const token = getTokenFromHeader(payload.request.headers.authorization as string);
148
154
  if (!context.system.config.dev.demo_auth) {
149
155
  return {
150
156
  authorized: false,
@@ -179,7 +185,7 @@ export const authDevUser = async (payload: micro.fastify.FastifyHandlerPayload<a
179
185
  return { authorized: true };
180
186
  };
181
187
 
182
- export const authApi = (payload: micro.fastify.FastifyHandlerPayload<any, Context>) => {
188
+ export const authApi = (payload: RequestEndpointHandlerPayload) => {
183
189
  const context = payload.context;
184
190
  const api_keys = context.system.config.api_tokens;
185
191
  if (api_keys.length == 0) {
@@ -188,7 +194,7 @@ export const authApi = (payload: micro.fastify.FastifyHandlerPayload<any, Contex
188
194
  errors: ['Authentication disabled']
189
195
  };
190
196
  }
191
- const auth = payload.request.headers.authorization ?? '';
197
+ const auth = (payload.request.headers.authorization as string) ?? '';
192
198
 
193
199
  const tokenMatch = /^(Token|Bearer) (\S+)$/.exec(auth);
194
200
  if (!tokenMatch) {
@@ -0,0 +1,237 @@
1
+ import { errors, router, schema } from '@powersync/lib-services-framework';
2
+ import { SqlSyncRules, SqliteValue, StaticSchema, isJsonValue, toSyncRulesValue } from '@powersync/service-sync-rules';
3
+ import { internal_routes } from '@powersync/service-types';
4
+
5
+ import * as api from '../../api/api-index.js';
6
+ import * as util from '../../util/util-index.js';
7
+
8
+ import { routeDefinition } from '../router.js';
9
+ import { PersistedSyncRulesContent } from '../../storage/BucketStorage.js';
10
+ import { authApi } from '../auth.js';
11
+
12
+ const demoCredentials = routeDefinition({
13
+ path: '/api/admin/v1/demo-credentials',
14
+ method: router.HTTPMethod.POST,
15
+ authorize: authApi,
16
+ validator: schema.createTsCodecValidator(internal_routes.DemoCredentialsRequest, {
17
+ allowAdditional: true
18
+ }),
19
+ handler: async (payload) => {
20
+ const connection = payload.context.system.config.connection;
21
+ if (connection == null || !connection.demo_database) {
22
+ return internal_routes.DemoCredentialsResponse.encode({});
23
+ }
24
+
25
+ const uri = util.buildDemoPgUri(connection);
26
+ return internal_routes.DemoCredentialsResponse.encode({
27
+ credentials: {
28
+ postgres_uri: uri
29
+ }
30
+ });
31
+ }
32
+ });
33
+
34
+ export const executeSql = routeDefinition({
35
+ path: '/api/admin/v1/execute-sql',
36
+ method: router.HTTPMethod.POST,
37
+ authorize: authApi,
38
+ validator: schema.createTsCodecValidator(internal_routes.ExecuteSqlRequest, { allowAdditional: true }),
39
+ handler: async (payload) => {
40
+ const connection = payload.context.system.config.connection;
41
+ if (connection == null || !connection.debug_api) {
42
+ return internal_routes.ExecuteSqlResponse.encode({
43
+ results: {
44
+ columns: [],
45
+ rows: []
46
+ },
47
+ success: false,
48
+ error: 'SQL querying is not enabled'
49
+ });
50
+ }
51
+
52
+ const pool = payload.context.system.requirePgPool();
53
+
54
+ const { query, args } = payload.params.sql;
55
+
56
+ try {
57
+ const result = await pool.query({
58
+ statement: query,
59
+ params: args.map(util.autoParameter)
60
+ });
61
+
62
+ return internal_routes.ExecuteSqlResponse.encode({
63
+ success: true,
64
+ results: {
65
+ columns: result.columns.map((c) => c.name),
66
+ rows: result.rows.map((row) => {
67
+ return row.map((value) => mapColumnValue(toSyncRulesValue(value)));
68
+ })
69
+ }
70
+ });
71
+ } catch (e) {
72
+ return internal_routes.ExecuteSqlResponse.encode({
73
+ results: {
74
+ columns: [],
75
+ rows: []
76
+ },
77
+ success: false,
78
+ error: e.message
79
+ });
80
+ }
81
+ }
82
+ });
83
+
84
+ export const diagnostics = routeDefinition({
85
+ path: '/api/admin/v1/diagnostics',
86
+ method: router.HTTPMethod.POST,
87
+ authorize: authApi,
88
+ validator: schema.createTsCodecValidator(internal_routes.DiagnosticsRequest, { allowAdditional: true }),
89
+ handler: async (payload) => {
90
+ const include_content = payload.params.sync_rules_content ?? false;
91
+ const system = payload.context.system;
92
+
93
+ const status = await api.getConnectionStatus(system);
94
+ if (status == null) {
95
+ return internal_routes.DiagnosticsResponse.encode({
96
+ connections: []
97
+ });
98
+ }
99
+
100
+ const { storage } = system;
101
+ const active = await storage.getActiveSyncRulesContent();
102
+ const next = await storage.getNextSyncRulesContent();
103
+
104
+ const active_status = await api.getSyncRulesStatus(active, system, {
105
+ include_content,
106
+ check_connection: status.connected,
107
+ live_status: true
108
+ });
109
+
110
+ const next_status = await api.getSyncRulesStatus(next, system, {
111
+ include_content,
112
+ check_connection: status.connected,
113
+ live_status: true
114
+ });
115
+
116
+ return internal_routes.DiagnosticsResponse.encode({
117
+ connections: [status],
118
+ active_sync_rules: active_status,
119
+ deploying_sync_rules: next_status
120
+ });
121
+ }
122
+ });
123
+
124
+ export const getSchema = routeDefinition({
125
+ path: '/api/admin/v1/schema',
126
+ method: router.HTTPMethod.POST,
127
+ authorize: authApi,
128
+ validator: schema.createTsCodecValidator(internal_routes.GetSchemaRequest, { allowAdditional: true }),
129
+ handler: async (payload) => {
130
+ const system = payload.context.system;
131
+
132
+ return internal_routes.GetSchemaResponse.encode(await api.getConnectionsSchema(system));
133
+ }
134
+ });
135
+
136
+ export const reprocess = routeDefinition({
137
+ path: '/api/admin/v1/reprocess',
138
+ method: router.HTTPMethod.POST,
139
+ authorize: authApi,
140
+ validator: schema.createTsCodecValidator(internal_routes.ReprocessRequest, { allowAdditional: true }),
141
+ handler: async (payload) => {
142
+ const system = payload.context.system;
143
+
144
+ const storage = system.storage;
145
+ const next = await storage.getNextSyncRules();
146
+ if (next != null) {
147
+ throw new Error(`Busy processing sync rules - cannot reprocess`);
148
+ }
149
+
150
+ const active = await storage.getActiveSyncRules();
151
+ if (active == null) {
152
+ throw new errors.JourneyError({
153
+ status: 422,
154
+ code: 'NO_SYNC_RULES',
155
+ description: 'No active sync rules'
156
+ });
157
+ }
158
+
159
+ const new_rules = await storage.updateSyncRules({
160
+ content: active.sync_rules.content
161
+ });
162
+
163
+ return internal_routes.ReprocessResponse.encode({
164
+ connections: [
165
+ {
166
+ tag: system.config.connection!.tag,
167
+ id: system.config.connection!.id,
168
+ slot_name: new_rules.slot_name
169
+ }
170
+ ]
171
+ });
172
+ }
173
+ });
174
+
175
+ export const validate = routeDefinition({
176
+ path: '/api/admin/v1/validate',
177
+ method: router.HTTPMethod.POST,
178
+ authorize: authApi,
179
+ validator: schema.createTsCodecValidator(internal_routes.ValidateRequest, { allowAdditional: true }),
180
+ handler: async (payload) => {
181
+ const system = payload.context.system;
182
+
183
+ const content = payload.params.sync_rules;
184
+
185
+ const schemaData = await api.getConnectionsSchema(system);
186
+ const schema = new StaticSchema(schemaData.connections);
187
+
188
+ const sync_rules: PersistedSyncRulesContent = {
189
+ // Dummy values
190
+ id: 0,
191
+ slot_name: '',
192
+
193
+ parsed() {
194
+ return {
195
+ ...this,
196
+ sync_rules: SqlSyncRules.fromYaml(content, { throwOnError: false, schema })
197
+ };
198
+ },
199
+ sync_rules_content: content,
200
+ async lock() {
201
+ throw new Error('Lock not implemented');
202
+ }
203
+ };
204
+
205
+ const connectionStatus = await api.getConnectionStatus(system);
206
+ if (connectionStatus == null) {
207
+ return internal_routes.ValidateResponse.encode({
208
+ errors: [{ level: 'fatal', message: 'No connection configured' }],
209
+ connections: []
210
+ });
211
+ }
212
+
213
+ const status = (await api.getSyncRulesStatus(sync_rules, system, {
214
+ include_content: false,
215
+ check_connection: connectionStatus?.connected,
216
+ live_status: false
217
+ }))!;
218
+
219
+ if (connectionStatus == null) {
220
+ status.errors.push({ level: 'fatal', message: 'No connection configured' });
221
+ }
222
+
223
+ return internal_routes.ValidateResponse.encode(status);
224
+ }
225
+ });
226
+
227
+ function mapColumnValue(value: SqliteValue) {
228
+ if (typeof value == 'bigint') {
229
+ return Number(value);
230
+ } else if (isJsonValue(value)) {
231
+ return value;
232
+ } else {
233
+ return null;
234
+ }
235
+ }
236
+
237
+ export const ADMIN_ROUTES = [demoCredentials, executeSql, diagnostics, getSchema, reprocess, validate];