@powersync/service-core 0.0.0-dev-20250317122913 → 0.0.0-dev-20250326092547

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 (108) 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 +5 -0
  15. package/dist/metrics/metrics-index.js +6 -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/metrics/register-metrics.d.ts +11 -0
  27. package/dist/metrics/register-metrics.js +44 -0
  28. package/dist/metrics/register-metrics.js.map +1 -0
  29. package/dist/replication/AbstractReplicationJob.d.ts +2 -0
  30. package/dist/replication/AbstractReplicationJob.js.map +1 -1
  31. package/dist/replication/AbstractReplicator.d.ts +3 -0
  32. package/dist/replication/AbstractReplicator.js +3 -0
  33. package/dist/replication/AbstractReplicator.js.map +1 -1
  34. package/dist/replication/ReplicationModule.d.ts +7 -0
  35. package/dist/replication/ReplicationModule.js +1 -0
  36. package/dist/replication/ReplicationModule.js.map +1 -1
  37. package/dist/replication/replication-index.d.ts +1 -0
  38. package/dist/replication/replication-index.js +1 -0
  39. package/dist/replication/replication-index.js.map +1 -1
  40. package/dist/replication/replication-metrics.d.ts +11 -0
  41. package/dist/replication/replication-metrics.js +39 -0
  42. package/dist/replication/replication-metrics.js.map +1 -0
  43. package/dist/routes/configure-fastify.d.ts +1 -1
  44. package/dist/routes/endpoints/probes.d.ts +2 -2
  45. package/dist/routes/endpoints/probes.js +16 -2
  46. package/dist/routes/endpoints/probes.js.map +1 -1
  47. package/dist/routes/endpoints/socket-route.js +5 -5
  48. package/dist/routes/endpoints/socket-route.js.map +1 -1
  49. package/dist/routes/endpoints/sync-stream.js +6 -6
  50. package/dist/routes/endpoints/sync-stream.js.map +1 -1
  51. package/dist/storage/SyncRulesBucketStorage.d.ts +11 -1
  52. package/dist/storage/SyncRulesBucketStorage.js +1 -1
  53. package/dist/storage/SyncRulesBucketStorage.js.map +1 -1
  54. package/dist/storage/WriteCheckpointAPI.d.ts +0 -2
  55. package/dist/storage/WriteCheckpointAPI.js.map +1 -1
  56. package/dist/storage/storage-index.d.ts +1 -0
  57. package/dist/storage/storage-index.js +1 -0
  58. package/dist/storage/storage-index.js.map +1 -1
  59. package/dist/storage/storage-metrics.d.ts +4 -0
  60. package/dist/storage/storage-metrics.js +56 -0
  61. package/dist/storage/storage-metrics.js.map +1 -0
  62. package/dist/sync/BucketChecksumState.d.ts +4 -2
  63. package/dist/sync/BucketChecksumState.js +17 -26
  64. package/dist/sync/BucketChecksumState.js.map +1 -1
  65. package/dist/sync/RequestTracker.d.ts +3 -0
  66. package/dist/sync/RequestTracker.js +8 -3
  67. package/dist/sync/RequestTracker.js.map +1 -1
  68. package/dist/sync/util.d.ts +10 -2
  69. package/dist/sync/util.js +25 -6
  70. package/dist/sync/util.js.map +1 -1
  71. package/dist/system/ServiceContext.d.ts +3 -3
  72. package/dist/system/ServiceContext.js +7 -3
  73. package/dist/system/ServiceContext.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 +5 -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/metrics/register-metrics.ts +56 -0
  84. package/src/replication/AbstractReplicationJob.ts +2 -0
  85. package/src/replication/AbstractReplicator.ts +7 -0
  86. package/src/replication/ReplicationModule.ts +10 -0
  87. package/src/replication/replication-index.ts +1 -0
  88. package/src/replication/replication-metrics.ts +45 -0
  89. package/src/routes/endpoints/probes.ts +18 -2
  90. package/src/routes/endpoints/socket-route.ts +6 -5
  91. package/src/routes/endpoints/sync-stream.ts +7 -6
  92. package/src/storage/SyncRulesBucketStorage.ts +12 -2
  93. package/src/storage/WriteCheckpointAPI.ts +0 -2
  94. package/src/storage/storage-index.ts +1 -0
  95. package/src/storage/storage-metrics.ts +67 -0
  96. package/src/sync/BucketChecksumState.ts +25 -41
  97. package/src/sync/RequestTracker.ts +9 -3
  98. package/src/sync/util.ts +29 -8
  99. package/src/system/ServiceContext.ts +9 -4
  100. package/test/src/routes/probes.integration.test.ts +5 -5
  101. package/test/src/routes/probes.test.ts +5 -4
  102. package/test/src/sync/BucketChecksumState.test.ts +5 -5
  103. package/test/src/util.test.ts +48 -0
  104. package/tsconfig.tsbuildinfo +1 -1
  105. package/dist/metrics/Metrics.d.ts +0 -30
  106. package/dist/metrics/Metrics.js +0 -202
  107. package/dist/metrics/Metrics.js.map +0 -1
  108. package/src/metrics/Metrics.ts +0 -255
package/src/sync/util.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as timers from 'timers/promises';
2
2
 
3
+ import { SemaphoreInterface } from 'async-mutex';
3
4
  import * as util from '../util/util-index.js';
4
5
  import { RequestTracker } from './RequestTracker.js';
5
- import { SemaphoreInterface } from 'async-mutex';
6
6
 
7
7
  export type TokenStreamOptions = {
8
8
  /**
@@ -154,14 +154,35 @@ export function settledPromise<T>(promise: Promise<T>): Promise<PromiseSettledRe
154
154
  );
155
155
  }
156
156
 
157
- export function hasIntersection<T>(a: Set<T>, b: Set<T>) {
158
- if (a.size > b.size) {
159
- [a, b] = [b, a];
157
+ export type MapOrSet<T> = Map<T, any> | Set<T>;
158
+
159
+ /**
160
+ * Check if two sets have any element(s) in common.
161
+ */
162
+ export function hasIntersection<T>(a: MapOrSet<T>, b: MapOrSet<T>) {
163
+ for (let _ of getIntersection(a, b)) {
164
+ return true;
160
165
  }
161
- // Now, a is always smaller than b, so iterate over a
162
- for (let value of a) {
163
- if (b.has(value)) {
164
- return true;
166
+ return false;
167
+ }
168
+
169
+ /**
170
+ * Return the intersection of two sets or maps.
171
+ */
172
+ export function* getIntersection<T>(a: MapOrSet<T>, b: MapOrSet<T>): IterableIterator<T> {
173
+ // Iterate over the smaller set to reduce the number of lookups
174
+ if (a.size < b.size) {
175
+ for (let key of a.keys()) {
176
+ if (b.has(key)) {
177
+ yield key;
178
+ }
179
+ }
180
+ return false;
181
+ } else {
182
+ for (let key of b.keys()) {
183
+ if (a.has(key)) {
184
+ yield key;
185
+ }
165
186
  }
166
187
  }
167
188
  }
@@ -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 {
@@ -1,10 +1,10 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import Fastify, { FastifyInstance } from 'fastify';
3
1
  import { container } from '@powersync/lib-services-framework';
4
- import * as auth from '../../../src/routes/auth.js';
5
- import * as system from '../../../src/system/system-index.js';
2
+ import Fastify, { FastifyInstance } from 'fastify';
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
6
4
  import { configureFastifyServer } from '../../../src/index.js';
5
+ import * as auth from '../../../src/routes/auth.js';
7
6
  import { ProbeRoutes } from '../../../src/routes/endpoints/probes.js';
7
+ import * as system from '../../../src/system/system-index.js';
8
8
 
9
9
  vi.mock('@powersync/lib-services-framework', async () => {
10
10
  const actual = (await vi.importActual('@powersync/lib-services-framework')) as any;
@@ -25,7 +25,7 @@ describe('Probe Routes Integration', () => {
25
25
 
26
26
  beforeEach(async () => {
27
27
  app = Fastify();
28
- mockSystem = { routerEngine: {} } as system.ServiceContext;
28
+ mockSystem = { routerEngine: {}, replicationEngine: {} } as system.ServiceContext;
29
29
  await configureFastifyServer(app, { service_context: mockSystem });
30
30
  await app.ready();
31
31
  });
@@ -1,6 +1,6 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
1
  import { container } from '@powersync/lib-services-framework';
3
- import { startupCheck, livenessCheck, readinessCheck } from '../../../src/routes/endpoints/probes.js';
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import { livenessCheck, readinessCheck, startupCheck } from '../../../src/routes/endpoints/probes.js';
4
4
 
5
5
  // Mock the container
6
6
  vi.mock('@powersync/lib-services-framework', () => ({
@@ -83,6 +83,7 @@ describe('Probe Routes', () => {
83
83
  });
84
84
 
85
85
  describe('livenessCheck', () => {
86
+ const mockedContext = { context: { service_context: { replicationEngine: {} } } } as any;
86
87
  it('has the correct route definitions', () => {
87
88
  expect(livenessCheck.path).toBe('/probes/liveness');
88
89
  expect(livenessCheck.method).toBe('GET');
@@ -97,7 +98,7 @@ describe('Probe Routes', () => {
97
98
 
98
99
  vi.mocked(container.probes.state).mockReturnValue(mockState);
99
100
 
100
- const response = await livenessCheck.handler();
101
+ const response = await livenessCheck.handler(mockedContext);
101
102
 
102
103
  expect(response.status).toBe(200);
103
104
  expect(response.data).toEqual(mockState);
@@ -112,7 +113,7 @@ describe('Probe Routes', () => {
112
113
 
113
114
  vi.mocked(container.probes.state).mockReturnValue(mockState);
114
115
 
115
- const response = await livenessCheck.handler();
116
+ const response = await livenessCheck.handler(mockedContext);
116
117
 
117
118
  expect(response.status).toBe(400);
118
119
  expect(response.data).toEqual(mockState);
@@ -98,7 +98,7 @@ bucket_definitions:
98
98
  base: { checkpoint: 2n, lsn: '2' },
99
99
  writeCheckpoint: null,
100
100
  update: {
101
- updatedDataBuckets: ['global[]'],
101
+ updatedDataBuckets: new Set(['global[]']),
102
102
  invalidateDataBuckets: false,
103
103
  updatedParameterLookups: new Set(),
104
104
  invalidateParameterBuckets: false
@@ -201,7 +201,7 @@ bucket_definitions:
201
201
  writeCheckpoint: null,
202
202
  update: {
203
203
  ...CHECKPOINT_INVALIDATE_ALL,
204
- updatedDataBuckets: ['global[1]', 'global[2]'],
204
+ updatedDataBuckets: new Set(['global[1]', 'global[2]']),
205
205
  invalidateDataBuckets: false
206
206
  }
207
207
  }))!;
@@ -294,7 +294,7 @@ bucket_definitions:
294
294
  // Invalidate the state for global[1] - will only re-check the single bucket.
295
295
  // This is essentially inconsistent state, but is the simplest way to test that
296
296
  // the filter is working.
297
- updatedDataBuckets: ['global[1]'],
297
+ updatedDataBuckets: new Set(['global[1]']),
298
298
  invalidateDataBuckets: false
299
299
  }
300
300
  }))!;
@@ -421,7 +421,7 @@ bucket_definitions:
421
421
  update: {
422
422
  ...CHECKPOINT_INVALIDATE_ALL,
423
423
  invalidateDataBuckets: false,
424
- updatedDataBuckets: ['global[1]']
424
+ updatedDataBuckets: new Set(['global[1]'])
425
425
  }
426
426
  }))!;
427
427
  expect(line2.checkpointLine).toEqual({
@@ -533,7 +533,7 @@ bucket_definitions:
533
533
  writeCheckpoint: null,
534
534
  update: {
535
535
  invalidateDataBuckets: false,
536
- updatedDataBuckets: [],
536
+ updatedDataBuckets: new Set(),
537
537
  updatedParameterLookups: new Set([JSONBig.stringify(['by_project', '1', 'u1'])]),
538
538
  invalidateParameterBuckets: false
539
539
  }
@@ -0,0 +1,48 @@
1
+ import { getIntersection, hasIntersection } from '@/index.js';
2
+ import { describe, expect, test } from 'vitest';
3
+
4
+ describe('utils', () => {
5
+ function testInstersection(a: Set<any>, b: Set<any>, expected: boolean) {
6
+ expect(hasIntersection(a, b)).toBe(expected);
7
+ expect(hasIntersection(b, a)).toBe(expected);
8
+ const mapA = new Map([...a].map((v) => [v, 1]));
9
+ const mapB = new Map([...b].map((v) => [v, 1]));
10
+ expect(hasIntersection(mapA, b)).toBe(expected);
11
+ expect(hasIntersection(mapB, a)).toBe(expected);
12
+ expect(hasIntersection(mapA, mapB)).toBe(expected);
13
+ }
14
+
15
+ test('hasIntersection', async () => {
16
+ testInstersection(new Set(['a']), new Set(['a']), true);
17
+ testInstersection(new Set(['a', 'b', 'c']), new Set(['a', 'b', 'c']), true);
18
+ testInstersection(new Set(['a', 'b', 'c']), new Set(['d', 'e']), false);
19
+ testInstersection(new Set(['a', 'b', 'c']), new Set(['d', 'c', 'e']), true);
20
+ testInstersection(new Set(['a', 'b', 'c']), new Set(['c', 'e']), true);
21
+ testInstersection(new Set(['a', 'b', 'c', 2]), new Set([1, 2, 3]), true);
22
+ testInstersection(new Set(['a', 'b', 'c', 4]), new Set([1, 2, 3]), false);
23
+ testInstersection(new Set([]), new Set([1, 2, 3]), false);
24
+ testInstersection(new Set([]), new Set([]), false);
25
+ });
26
+
27
+ function testGetIntersection(a: Set<any>, b: Set<any>, expected: any[]) {
28
+ expect([...getIntersection(a, b)]).toEqual(expected);
29
+ expect([...getIntersection(b, a)]).toEqual(expected);
30
+ const mapA = new Map([...a].map((v) => [v, 1]));
31
+ const mapB = new Map([...b].map((v) => [v, 1]));
32
+ expect([...getIntersection(mapA, b)]).toEqual(expected);
33
+ expect([...getIntersection(mapB, a)]).toEqual(expected);
34
+ expect([...getIntersection(mapA, mapB)]).toEqual(expected);
35
+ }
36
+
37
+ test('getIntersection', async () => {
38
+ testGetIntersection(new Set(['a']), new Set(['a']), ['a']);
39
+ testGetIntersection(new Set(['a', 'b', 'c']), new Set(['a', 'b', 'c']), ['a', 'b', 'c']);
40
+ testGetIntersection(new Set(['a', 'b', 'c']), new Set(['d', 'e']), []);
41
+ testGetIntersection(new Set(['a', 'b', 'c']), new Set(['d', 'c', 'e']), ['c']);
42
+ testGetIntersection(new Set(['a', 'b', 'c']), new Set(['c', 'e']), ['c']);
43
+ testGetIntersection(new Set(['a', 'b', 'c', 2]), new Set([1, 2, 3]), [2]);
44
+ testGetIntersection(new Set(['a', 'b', 'c', 4]), new Set([1, 2, 3]), []);
45
+ testGetIntersection(new Set([]), new Set([1, 2, 3]), []);
46
+ testGetIntersection(new Set([]), new Set([]), []);
47
+ });
48
+ });