@powersync/service-core 0.0.0-dev-20250304151813 → 0.0.0-dev-20250306152715

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 (107) hide show
  1. package/CHANGELOG.md +18 -4
  2. package/dist/api/api-index.d.ts +1 -0
  3. package/dist/api/api-index.js +1 -0
  4. package/dist/api/api-index.js.map +1 -1
  5. package/dist/api/api-metrics.d.ts +11 -0
  6. package/dist/api/api-metrics.js +30 -0
  7. package/dist/api/api-metrics.js.map +1 -0
  8. package/dist/index.d.ts +2 -2
  9. package/dist/index.js +2 -2
  10. package/dist/index.js.map +1 -1
  11. package/dist/metrics/MetricsEngine.d.ts +21 -0
  12. package/dist/metrics/MetricsEngine.js +79 -0
  13. package/dist/metrics/MetricsEngine.js.map +1 -0
  14. package/dist/metrics/metrics-index.d.ts +4 -0
  15. package/dist/metrics/metrics-index.js +5 -0
  16. package/dist/metrics/metrics-index.js.map +1 -0
  17. package/dist/metrics/metrics-interfaces.d.ts +36 -0
  18. package/dist/metrics/metrics-interfaces.js +6 -0
  19. package/dist/metrics/metrics-interfaces.js.map +1 -0
  20. package/dist/metrics/open-telemetry/OpenTelemetryMetricsFactory.d.ts +10 -0
  21. package/dist/metrics/open-telemetry/OpenTelemetryMetricsFactory.js +51 -0
  22. package/dist/metrics/open-telemetry/OpenTelemetryMetricsFactory.js.map +1 -0
  23. package/dist/metrics/open-telemetry/util.d.ts +6 -0
  24. package/dist/metrics/open-telemetry/util.js +56 -0
  25. package/dist/metrics/open-telemetry/util.js.map +1 -0
  26. package/dist/replication/AbstractReplicationJob.d.ts +2 -0
  27. package/dist/replication/AbstractReplicationJob.js.map +1 -1
  28. package/dist/replication/AbstractReplicator.d.ts +3 -0
  29. package/dist/replication/AbstractReplicator.js +3 -0
  30. package/dist/replication/AbstractReplicator.js.map +1 -1
  31. package/dist/replication/ReplicationModule.d.ts +7 -0
  32. package/dist/replication/ReplicationModule.js +1 -0
  33. package/dist/replication/ReplicationModule.js.map +1 -1
  34. package/dist/replication/replication-index.d.ts +1 -0
  35. package/dist/replication/replication-index.js +1 -0
  36. package/dist/replication/replication-index.js.map +1 -1
  37. package/dist/replication/replication-metrics.d.ts +11 -0
  38. package/dist/replication/replication-metrics.js +39 -0
  39. package/dist/replication/replication-metrics.js.map +1 -0
  40. package/dist/routes/configure-fastify.d.ts +3 -3
  41. package/dist/routes/endpoints/checkpointing.d.ts +6 -6
  42. package/dist/routes/endpoints/socket-route.js +5 -5
  43. package/dist/routes/endpoints/socket-route.js.map +1 -1
  44. package/dist/routes/endpoints/sync-stream.js +6 -6
  45. package/dist/routes/endpoints/sync-stream.js.map +1 -1
  46. package/dist/storage/BucketStorageBatch.d.ts +2 -1
  47. package/dist/storage/BucketStorageBatch.js.map +1 -1
  48. package/dist/storage/ChecksumCache.d.ts +6 -6
  49. package/dist/storage/ChecksumCache.js +5 -6
  50. package/dist/storage/ChecksumCache.js.map +1 -1
  51. package/dist/storage/SyncRulesBucketStorage.d.ts +9 -9
  52. package/dist/storage/storage-index.d.ts +1 -0
  53. package/dist/storage/storage-index.js +1 -0
  54. package/dist/storage/storage-index.js.map +1 -1
  55. package/dist/storage/storage-metrics.d.ts +4 -0
  56. package/dist/storage/storage-metrics.js +56 -0
  57. package/dist/storage/storage-metrics.js.map +1 -0
  58. package/dist/sync/BucketChecksumState.d.ts +3 -3
  59. package/dist/sync/BucketChecksumState.js +3 -3
  60. package/dist/sync/BucketChecksumState.js.map +1 -1
  61. package/dist/sync/RequestTracker.d.ts +3 -0
  62. package/dist/sync/RequestTracker.js +8 -3
  63. package/dist/sync/RequestTracker.js.map +1 -1
  64. package/dist/sync/sync.d.ts +1 -1
  65. package/dist/sync/sync.js +8 -6
  66. package/dist/sync/sync.js.map +1 -1
  67. package/dist/system/ServiceContext.d.ts +3 -3
  68. package/dist/system/ServiceContext.js +7 -3
  69. package/dist/system/ServiceContext.js.map +1 -1
  70. package/dist/util/protocol-types.d.ts +10 -10
  71. package/dist/util/utils.d.ts +12 -2
  72. package/dist/util/utils.js +5 -1
  73. package/dist/util/utils.js.map +1 -1
  74. package/package.json +8 -8
  75. package/src/api/api-index.ts +1 -0
  76. package/src/api/api-metrics.ts +35 -0
  77. package/src/index.ts +2 -2
  78. package/src/metrics/MetricsEngine.ts +98 -0
  79. package/src/metrics/metrics-index.ts +4 -0
  80. package/src/metrics/metrics-interfaces.ts +41 -0
  81. package/src/metrics/open-telemetry/OpenTelemetryMetricsFactory.ts +66 -0
  82. package/src/metrics/open-telemetry/util.ts +74 -0
  83. package/src/replication/AbstractReplicationJob.ts +2 -0
  84. package/src/replication/AbstractReplicator.ts +7 -0
  85. package/src/replication/ReplicationModule.ts +10 -0
  86. package/src/replication/replication-index.ts +1 -0
  87. package/src/replication/replication-metrics.ts +45 -0
  88. package/src/routes/endpoints/socket-route.ts +6 -5
  89. package/src/routes/endpoints/sync-stream.ts +7 -6
  90. package/src/storage/BucketStorageBatch.ts +2 -1
  91. package/src/storage/ChecksumCache.ts +13 -14
  92. package/src/storage/SyncRulesBucketStorage.ts +10 -10
  93. package/src/storage/storage-index.ts +1 -0
  94. package/src/storage/storage-metrics.ts +67 -0
  95. package/src/sync/BucketChecksumState.ts +7 -7
  96. package/src/sync/RequestTracker.ts +9 -3
  97. package/src/sync/sync.ts +10 -8
  98. package/src/system/ServiceContext.ts +9 -4
  99. package/src/util/protocol-types.ts +10 -10
  100. package/src/util/utils.ts +13 -2
  101. package/test/src/checksum_cache.test.ts +83 -84
  102. package/test/src/sync/BucketChecksumState.test.ts +47 -41
  103. package/tsconfig.tsbuildinfo +1 -1
  104. package/dist/metrics/Metrics.d.ts +0 -30
  105. package/dist/metrics/Metrics.js +0 -202
  106. package/dist/metrics/Metrics.js.map +0 -1
  107. package/src/metrics/Metrics.ts +0 -255
@@ -13,7 +13,7 @@ export interface BucketChecksumStateOptions {
13
13
  bucketStorage: BucketChecksumStateStorage;
14
14
  syncRules: SqlSyncRules;
15
15
  syncParams: RequestParameters;
16
- initialBucketPositions?: { name: string; after: string }[];
16
+ initialBucketPositions?: { name: string; after: util.InternalOpId }[];
17
17
  }
18
18
 
19
19
  /**
@@ -78,7 +78,7 @@ export class BucketChecksumState {
78
78
  for (let bucket of allBuckets) {
79
79
  dataBucketsNew.set(bucket.bucket, {
80
80
  description: bucket,
81
- start_op_id: this.bucketDataPositions.get(bucket.bucket)?.start_op_id ?? '0'
81
+ start_op_id: this.bucketDataPositions.get(bucket.bucket)?.start_op_id ?? 0n
82
82
  });
83
83
  }
84
84
  this.bucketDataPositions = dataBucketsNew;
@@ -180,7 +180,7 @@ export class BucketChecksumState {
180
180
 
181
181
  checkpointLine = {
182
182
  checkpoint_diff: {
183
- last_op_id: base.checkpoint,
183
+ last_op_id: util.internalToExternalOpId(base.checkpoint),
184
184
  write_checkpoint: writeCheckpoint ? String(writeCheckpoint) : undefined,
185
185
  removed_buckets: diff.removedBuckets,
186
186
  updated_buckets: updatedBucketDescriptions
@@ -193,7 +193,7 @@ export class BucketChecksumState {
193
193
  bucketsToFetch = allBuckets;
194
194
  checkpointLine = {
195
195
  checkpoint: {
196
- last_op_id: base.checkpoint,
196
+ last_op_id: util.internalToExternalOpId(base.checkpoint),
197
197
  write_checkpoint: writeCheckpoint ? String(writeCheckpoint) : undefined,
198
198
  buckets: [...checksumMap.values()].map((e) => ({
199
199
  ...e,
@@ -219,8 +219,8 @@ export class BucketChecksumState {
219
219
  * @param bucketsToFetch List of buckets to fetch, typically from buildNextCheckpointLine, or a subset of that
220
220
  * @returns
221
221
  */
222
- getFilteredBucketPositions(bucketsToFetch: BucketDescription[]): Map<string, string> {
223
- const filtered = new Map<string, string>();
222
+ getFilteredBucketPositions(bucketsToFetch: BucketDescription[]): Map<string, util.InternalOpId> {
223
+ const filtered = new Map<string, util.InternalOpId>();
224
224
  for (let bucket of bucketsToFetch) {
225
225
  const state = this.bucketDataPositions.get(bucket.bucket);
226
226
  if (state) {
@@ -236,7 +236,7 @@ export class BucketChecksumState {
236
236
  * @param bucket the bucket name
237
237
  * @param nextAfter sync operations >= this value in the next batch
238
238
  */
239
- updateBucketPosition(options: { bucket: string; nextAfter: string; hasMore: boolean }) {
239
+ updateBucketPosition(options: { bucket: string; nextAfter: util.InternalOpId; hasMore: boolean }) {
240
240
  const state = this.bucketDataPositions.get(options.bucket);
241
241
  if (state) {
242
242
  state.start_op_id = options.nextAfter;
@@ -1,4 +1,6 @@
1
- import { Metrics } from '../metrics/Metrics.js';
1
+ import { MetricsEngine } from '../metrics/MetricsEngine.js';
2
+
3
+ import { APIMetric } from '@powersync/service-types';
2
4
 
3
5
  /**
4
6
  * Record sync stats per request stream.
@@ -7,15 +9,19 @@ export class RequestTracker {
7
9
  operationsSynced = 0;
8
10
  dataSyncedBytes = 0;
9
11
 
12
+ constructor(private metrics: MetricsEngine) {
13
+ this.metrics = metrics;
14
+ }
15
+
10
16
  addOperationsSynced(operations: number) {
11
17
  this.operationsSynced += operations;
12
18
 
13
- Metrics.getInstance().operations_synced_total.add(operations);
19
+ this.metrics.getCounter(APIMetric.OPERATIONS_SYNCED_TOTAL).add(operations);
14
20
  }
15
21
 
16
22
  addDataSynced(bytes: number) {
17
23
  this.dataSyncedBytes += bytes;
18
24
 
19
- Metrics.getInstance().data_synced_bytes.add(bytes);
25
+ this.metrics.getCounter(APIMetric.DATA_SYNCED_BYTES).add(bytes);
20
26
  }
21
27
  }
package/src/sync/sync.ts CHANGED
@@ -79,7 +79,7 @@ export async function* streamResponse(
79
79
 
80
80
  export type BucketSyncState = {
81
81
  description?: BucketDescription; // Undefined if the bucket has not yet been resolved by us.
82
- start_op_id: string;
82
+ start_op_id: util.InternalOpId;
83
83
  };
84
84
 
85
85
  async function* streamResponseInner(
@@ -100,7 +100,10 @@ async function* streamResponseInner(
100
100
  bucketStorage,
101
101
  syncRules,
102
102
  syncParams,
103
- initialBucketPositions: params.buckets
103
+ initialBucketPositions: params.buckets?.map((bucket) => ({
104
+ name: bucket.name,
105
+ after: BigInt(bucket.after)
106
+ }))
104
107
  });
105
108
  const stream = bucketStorage.watchWriteCheckpoint({
106
109
  user_id: checkpointUserId,
@@ -218,7 +221,7 @@ async function* streamResponseInner(
218
221
  interface BucketDataRequest {
219
222
  syncContext: SyncContext;
220
223
  bucketStorage: storage.SyncRulesBucketStorage;
221
- checkpoint: string;
224
+ checkpoint: util.InternalOpId;
222
225
  bucketsToFetch: BucketDescription[];
223
226
  /** Contains current bucket state. Modified by the request as data is sent. */
224
227
  checksumState: BucketChecksumState;
@@ -290,7 +293,6 @@ async function* bucketDataBatch(request: BucketDataRequest): AsyncGenerator<Buck
290
293
  onRowsSent
291
294
  } = request;
292
295
 
293
- const checkpointOp = BigInt(checkpoint);
294
296
  let checkpointInvalidated = false;
295
297
 
296
298
  if (syncContext.syncSemaphore.isLocked()) {
@@ -326,7 +328,7 @@ async function* bucketDataBatch(request: BucketDataRequest): AsyncGenerator<Buck
326
328
  if (r.has_more) {
327
329
  has_more = true;
328
330
  }
329
- if (targetOp != null && targetOp > checkpointOp) {
331
+ if (targetOp != null && targetOp > checkpoint) {
330
332
  checkpointInvalidated = true;
331
333
  }
332
334
  if (r.data.length == 0) {
@@ -362,7 +364,7 @@ async function* bucketDataBatch(request: BucketDataRequest): AsyncGenerator<Buck
362
364
  }
363
365
  onRowsSent(r.data.length);
364
366
 
365
- checksumState.updateBucketPosition({ bucket: r.bucket, nextAfter: r.next_after, hasMore: r.has_more });
367
+ checksumState.updateBucketPosition({ bucket: r.bucket, nextAfter: BigInt(r.next_after), hasMore: r.has_more });
366
368
 
367
369
  // Check if syncing bucket data is supposed to stop before fetching more data
368
370
  // from storage.
@@ -381,7 +383,7 @@ async function* bucketDataBatch(request: BucketDataRequest): AsyncGenerator<Buck
381
383
  if (request.forPriority != null) {
382
384
  const line: util.StreamingSyncCheckpointPartiallyComplete = {
383
385
  partial_checkpoint_complete: {
384
- last_op_id: checkpoint,
386
+ last_op_id: util.internalToExternalOpId(checkpoint),
385
387
  priority: request.forPriority
386
388
  }
387
389
  };
@@ -389,7 +391,7 @@ async function* bucketDataBatch(request: BucketDataRequest): AsyncGenerator<Buck
389
391
  } else {
390
392
  const line: util.StreamingSyncCheckpointComplete = {
391
393
  checkpoint_complete: {
392
- last_op_id: checkpoint
394
+ last_op_id: util.internalToExternalOpId(checkpoint)
393
395
  }
394
396
  };
395
397
  yield { data: line, done: true };
@@ -1,7 +1,7 @@
1
1
  import { LifeCycledSystem, MigrationManager, ServiceIdentifier, container } from '@powersync/lib-services-framework';
2
2
 
3
3
  import { framework } from '../index.js';
4
- import * as metrics from '../metrics/Metrics.js';
4
+ import * as metrics from '../metrics/MetricsEngine.js';
5
5
  import { PowerSyncMigrationManager } from '../migrations/PowerSyncMigrationManager.js';
6
6
  import * as replication from '../replication/replication-index.js';
7
7
  import * as routes from '../routes/routes-index.js';
@@ -12,7 +12,7 @@ import { SyncContext } from '../sync/SyncContext.js';
12
12
  export interface ServiceContext {
13
13
  configuration: utils.ResolvedPowerSyncConfig;
14
14
  lifeCycleEngine: LifeCycledSystem;
15
- metrics: metrics.Metrics | null;
15
+ metricsEngine: metrics.MetricsEngine;
16
16
  replicationEngine: replication.ReplicationEngine | null;
17
17
  routerEngine: routes.RouterEngine | null;
18
18
  storageEngine: storage.StorageEngine;
@@ -37,6 +37,11 @@ export class ServiceContextContainer implements ServiceContext {
37
37
  configuration
38
38
  });
39
39
 
40
+ this.lifeCycleEngine.withLifecycle(this.storageEngine, {
41
+ start: (storageEngine) => storageEngine.start(),
42
+ stop: (storageEngine) => storageEngine.shutDown()
43
+ });
44
+
40
45
  this.syncContext = new SyncContext({
41
46
  maxDataFetchConcurrency: configuration.api_parameters.max_data_fetch_concurrency,
42
47
  maxBuckets: configuration.api_parameters.max_buckets_per_connection,
@@ -65,8 +70,8 @@ export class ServiceContextContainer implements ServiceContext {
65
70
  return container.getOptional(routes.RouterEngine);
66
71
  }
67
72
 
68
- get metrics(): metrics.Metrics | null {
69
- return container.getOptional(metrics.Metrics);
73
+ get metricsEngine(): metrics.MetricsEngine {
74
+ return container.getImplementation(metrics.MetricsEngine);
70
75
  }
71
76
 
72
77
  get migrations(): PowerSyncMigrationManager {
@@ -57,8 +57,8 @@ export interface StreamingSyncCheckpoint {
57
57
 
58
58
  export interface StreamingSyncCheckpointDiff {
59
59
  checkpoint_diff: {
60
- last_op_id: OpId;
61
- write_checkpoint?: OpId;
60
+ last_op_id: ProtocolOpId;
61
+ write_checkpoint?: ProtocolOpId;
62
62
  updated_buckets: BucketChecksumWithDescription[];
63
63
  removed_buckets: string[];
64
64
  };
@@ -70,13 +70,13 @@ export interface StreamingSyncData {
70
70
 
71
71
  export interface StreamingSyncCheckpointComplete {
72
72
  checkpoint_complete: {
73
- last_op_id: OpId;
73
+ last_op_id: ProtocolOpId;
74
74
  };
75
75
  }
76
76
 
77
77
  export interface StreamingSyncCheckpointPartiallyComplete {
78
78
  partial_checkpoint_complete: {
79
- last_op_id: OpId;
79
+ last_op_id: ProtocolOpId;
80
80
  priority: BucketPriority;
81
81
  };
82
82
  }
@@ -96,11 +96,11 @@ export type StreamingSyncLine =
96
96
  /**
97
97
  * 64-bit unsigned number, as a base-10 string.
98
98
  */
99
- export type OpId = string;
99
+ export type ProtocolOpId = string;
100
100
 
101
101
  export interface Checkpoint {
102
- last_op_id: OpId;
103
- write_checkpoint?: OpId;
102
+ last_op_id: ProtocolOpId;
103
+ write_checkpoint?: ProtocolOpId;
104
104
  buckets: BucketChecksumWithDescription[];
105
105
  }
106
106
 
@@ -123,15 +123,15 @@ export interface SyncBucketData {
123
123
  /**
124
124
  * The `after` specified in the request.
125
125
  */
126
- after: OpId;
126
+ after: ProtocolOpId;
127
127
  /**
128
128
  * Use this for the next request.
129
129
  */
130
- next_after: OpId;
130
+ next_after: ProtocolOpId;
131
131
  }
132
132
 
133
133
  export interface OplogEntry {
134
- op_id: OpId;
134
+ op_id: ProtocolOpId;
135
135
  op: 'PUT' | 'REMOVE' | 'MOVE' | 'CLEAR';
136
136
  object_type?: string;
137
137
  object_id?: string;
package/src/util/utils.ts CHANGED
@@ -2,7 +2,7 @@ import * as sync_rules from '@powersync/service-sync-rules';
2
2
  import * as bson from 'bson';
3
3
  import crypto from 'crypto';
4
4
  import * as uuid from 'uuid';
5
- import { BucketChecksum, OpId, OplogEntry } from './protocol-types.js';
5
+ import { BucketChecksum, ProtocolOpId, OplogEntry } from './protocol-types.js';
6
6
 
7
7
  import * as storage from '../storage/storage-index.js';
8
8
 
@@ -11,6 +11,13 @@ import { ServiceAssertionError } from '@powersync/lib-services-framework';
11
11
 
12
12
  export type ChecksumMap = Map<string, BucketChecksum>;
13
13
 
14
+ /**
15
+ * op_id as used internally, for individual operations and checkpoints.
16
+ *
17
+ * This is just a type alias, but serves to document that we're working with an op_id.
18
+ */
19
+ export type InternalOpId = bigint;
20
+
14
21
  export const ID_NAMESPACE = 'a396dd91-09fc-4017-a28d-3df722f651e9';
15
22
 
16
23
  export function escapeIdentifier(identifier: string) {
@@ -31,7 +38,11 @@ export function hashDelete(sourceKey: string) {
31
38
  return buffer.readUInt32LE(0);
32
39
  }
33
40
 
34
- export function timestampToOpId(ts: bigint): OpId {
41
+ /**
42
+ * Internally we always use bigint for op_ids. Externally (in JSON) we use strings.
43
+ * This converts between the two.
44
+ */
45
+ export function internalToExternalOpId(ts: InternalOpId): ProtocolOpId {
35
46
  // Dynamic values are passed in in some cases, so we make extra sure that the
36
47
  // number is a bigint and not number or Long.
37
48
  if (typeof ts != 'bigint') {