@powersync/service-core 0.15.0 → 0.16.1

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 (55) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/auth/RemoteJWKSCollector.d.ts +9 -7
  3. package/dist/auth/RemoteJWKSCollector.js +16 -29
  4. package/dist/auth/RemoteJWKSCollector.js.map +1 -1
  5. package/dist/entry/cli-entry.js +2 -0
  6. package/dist/entry/cli-entry.js.map +1 -1
  7. package/dist/entry/commands/teardown-action.js +2 -1
  8. package/dist/entry/commands/teardown-action.js.map +1 -1
  9. package/dist/entry/commands/test-connection-action.d.ts +2 -0
  10. package/dist/entry/commands/test-connection-action.js +32 -0
  11. package/dist/entry/commands/test-connection-action.js.map +1 -0
  12. package/dist/metrics/Metrics.js +2 -2
  13. package/dist/metrics/Metrics.js.map +1 -1
  14. package/dist/replication/AbstractReplicator.d.ts +2 -0
  15. package/dist/replication/AbstractReplicator.js.map +1 -1
  16. package/dist/replication/ReplicationEngine.d.ts +2 -0
  17. package/dist/replication/ReplicationEngine.js +3 -0
  18. package/dist/replication/ReplicationEngine.js.map +1 -1
  19. package/dist/replication/ReplicationModule.d.ts +8 -2
  20. package/dist/replication/ReplicationModule.js +6 -11
  21. package/dist/replication/ReplicationModule.js.map +1 -1
  22. package/dist/routes/endpoints/admin.js +3 -3
  23. package/dist/routes/endpoints/admin.js.map +1 -1
  24. package/dist/routes/endpoints/socket-route.js +5 -5
  25. package/dist/routes/endpoints/socket-route.js.map +1 -1
  26. package/dist/routes/endpoints/sync-rules.js +9 -9
  27. package/dist/routes/endpoints/sync-rules.js.map +1 -1
  28. package/dist/routes/endpoints/sync-stream.js +5 -5
  29. package/dist/routes/endpoints/sync-stream.js.map +1 -1
  30. package/dist/routes/route-register.js +4 -4
  31. package/dist/routes/route-register.js.map +1 -1
  32. package/dist/util/config/compound-config-collector.js +14 -4
  33. package/dist/util/config/compound-config-collector.js.map +1 -1
  34. package/dist/util/config/types.d.ts +0 -3
  35. package/dist/util/utils.js +2 -1
  36. package/dist/util/utils.js.map +1 -1
  37. package/package.json +5 -5
  38. package/src/auth/RemoteJWKSCollector.ts +31 -35
  39. package/src/entry/cli-entry.ts +2 -0
  40. package/src/entry/commands/teardown-action.ts +2 -1
  41. package/src/entry/commands/test-connection-action.ts +41 -0
  42. package/src/metrics/Metrics.ts +2 -2
  43. package/src/replication/AbstractReplicator.ts +3 -0
  44. package/src/replication/ReplicationEngine.ts +5 -0
  45. package/src/replication/ReplicationModule.ts +15 -13
  46. package/src/routes/endpoints/admin.ts +3 -3
  47. package/src/routes/endpoints/socket-route.ts +5 -5
  48. package/src/routes/endpoints/sync-rules.ts +9 -9
  49. package/src/routes/endpoints/sync-stream.ts +5 -5
  50. package/src/routes/route-register.ts +4 -4
  51. package/src/util/config/compound-config-collector.ts +17 -9
  52. package/src/util/config/types.ts +0 -3
  53. package/src/util/utils.ts +2 -1
  54. package/test/src/auth.test.ts +20 -5
  55. package/tsconfig.tsbuildinfo +1 -1
@@ -1,4 +1,4 @@
1
- import { errors, router, schema } from '@powersync/lib-services-framework';
1
+ import { ErrorCode, errors, router, schema } from '@powersync/lib-services-framework';
2
2
  import { SqlSyncRules, StaticSchema } from '@powersync/service-sync-rules';
3
3
  import { internal_routes } from '@powersync/service-types';
4
4
 
@@ -120,9 +120,9 @@ export const reprocess = routeDefinition({
120
120
 
121
121
  const active = await activeBucketStorage.getActiveSyncRules(apiHandler.getParseSyncRulesOptions());
122
122
  if (active == null) {
123
- throw new errors.JourneyError({
123
+ throw new errors.ServiceError({
124
124
  status: 422,
125
- code: 'NO_SYNC_RULES',
125
+ code: ErrorCode.PSYNC_S4104,
126
126
  description: 'No active sync rules'
127
127
  });
128
128
  }
@@ -1,4 +1,4 @@
1
- import { errors, logger, schema } from '@powersync/lib-services-framework';
1
+ import { ErrorCode, errors, logger, schema } from '@powersync/lib-services-framework';
2
2
  import { RequestParameters } from '@powersync/service-sync-rules';
3
3
  import { serialize } from 'bson';
4
4
 
@@ -34,9 +34,9 @@ export const syncStreamReactive: SocketRouteGenerator = (router) =>
34
34
 
35
35
  if (routerEngine!.closed) {
36
36
  responder.onError(
37
- new errors.JourneyError({
37
+ new errors.ServiceError({
38
38
  status: 503,
39
- code: 'SERVICE_UNAVAILABLE',
39
+ code: ErrorCode.PSYNC_S2003,
40
40
  description: 'Service temporarily unavailable'
41
41
  })
42
42
  );
@@ -53,9 +53,9 @@ export const syncStreamReactive: SocketRouteGenerator = (router) =>
53
53
  const cp = await activeBucketStorage.getActiveCheckpoint();
54
54
  if (!cp.hasSyncRules()) {
55
55
  responder.onError(
56
- new errors.JourneyError({
56
+ new errors.ServiceError({
57
57
  status: 500,
58
- code: 'NO_SYNC_RULES',
58
+ code: ErrorCode.PSYNC_S2302,
59
59
  description: 'No sync rules available'
60
60
  })
61
61
  );
@@ -1,4 +1,4 @@
1
- import { errors, router, schema } from '@powersync/lib-services-framework';
1
+ import { ErrorCode, errors, router, schema } from '@powersync/lib-services-framework';
2
2
  import { SqlSyncRules, SyncRulesErrors } from '@powersync/service-sync-rules';
3
3
  import type { FastifyPluginAsync } from 'fastify';
4
4
  import * as t from 'ts-codec';
@@ -43,9 +43,9 @@ export const deploySyncRules = routeDefinition({
43
43
 
44
44
  if (service_context.configuration.sync_rules.present) {
45
45
  // If sync rules are configured via the config, disable deploy via the API.
46
- throw new errors.JourneyError({
46
+ throw new errors.ServiceError({
47
47
  status: 422,
48
- code: 'API_DISABLED',
48
+ code: ErrorCode.PSYNC_S4105,
49
49
  description: 'Sync rules API disabled',
50
50
  details: 'Use the management API to deploy sync rules'
51
51
  });
@@ -60,9 +60,9 @@ export const deploySyncRules = routeDefinition({
60
60
  schema: undefined
61
61
  });
62
62
  } catch (e) {
63
- throw new errors.JourneyError({
63
+ throw new errors.ServiceError({
64
64
  status: 422,
65
- code: 'INVALID_SYNC_RULES',
65
+ code: ErrorCode.PSYNC_R0001,
66
66
  description: 'Sync rules parsing failed',
67
67
  details: e.message
68
68
  });
@@ -112,9 +112,9 @@ export const currentSyncRules = routeDefinition({
112
112
 
113
113
  const sync_rules = await activeBucketStorage.getActiveSyncRulesContent();
114
114
  if (!sync_rules) {
115
- throw new errors.JourneyError({
115
+ throw new errors.ServiceError({
116
116
  status: 422,
117
- code: 'NO_SYNC_RULES',
117
+ code: ErrorCode.PSYNC_S4104,
118
118
  description: 'No active sync rules'
119
119
  });
120
120
  }
@@ -159,9 +159,9 @@ export const reprocessSyncRules = routeDefinition({
159
159
  const apiHandler = payload.context.service_context.routerEngine!.getAPI();
160
160
  const sync_rules = await activeBucketStorage.getActiveSyncRules(apiHandler.getParseSyncRulesOptions());
161
161
  if (sync_rules == null) {
162
- throw new errors.JourneyError({
162
+ throw new errors.ServiceError({
163
163
  status: 422,
164
- code: 'NO_SYNC_RULES',
164
+ code: ErrorCode.PSYNC_S4104,
165
165
  description: 'No active sync rules'
166
166
  });
167
167
  }
@@ -1,4 +1,4 @@
1
- import { errors, logger, router, schema } from '@powersync/lib-services-framework';
1
+ import { ErrorCode, errors, logger, router, schema } from '@powersync/lib-services-framework';
2
2
  import { RequestParameters } from '@powersync/service-sync-rules';
3
3
  import { Readable } from 'stream';
4
4
 
@@ -26,9 +26,9 @@ export const syncStreamed = routeDefinition({
26
26
  const clientId = payload.params.client_id;
27
27
 
28
28
  if (routerEngine!.closed) {
29
- throw new errors.JourneyError({
29
+ throw new errors.ServiceError({
30
30
  status: 503,
31
- code: 'SERVICE_UNAVAILABLE',
31
+ code: ErrorCode.PSYNC_S2003,
32
32
  description: 'Service temporarily unavailable'
33
33
  });
34
34
  }
@@ -39,9 +39,9 @@ export const syncStreamed = routeDefinition({
39
39
  // Sanity check before we start the stream
40
40
  const cp = await storageEngine.activeBucketStorage.getActiveCheckpoint();
41
41
  if (!cp.hasSyncRules()) {
42
- throw new errors.JourneyError({
42
+ throw new errors.ServiceError({
43
43
  status: 500,
44
- code: 'NO_SYNC_RULES',
44
+ code: ErrorCode.PSYNC_S2302,
45
45
  description: 'No sync rules available'
46
46
  });
47
47
  }
@@ -62,16 +62,16 @@ export function registerFastifyRoutes(
62
62
  });
63
63
  }
64
64
  } catch (ex) {
65
- const journeyError = errors.JourneyError.isJourneyError(ex) ? ex : new errors.InternalServerError(ex);
66
- logger.error(`Request failed`, journeyError);
65
+ const serviceError = errors.asServiceError(ex);
66
+ logger.error(`Request failed`, serviceError);
67
67
 
68
68
  response = new router.RouterResponse({
69
- status: journeyError.errorData.status || 500,
69
+ status: serviceError.errorData.status || 500,
70
70
  headers: {
71
71
  'Content-Type': 'application/json'
72
72
  },
73
73
  data: {
74
- error: journeyError.errorData
74
+ error: serviceError.errorData
75
75
  }
76
76
  });
77
77
  }
@@ -1,4 +1,4 @@
1
- import { logger } from '@powersync/lib-services-framework';
1
+ import { logger, LookupOptions } from '@powersync/lib-services-framework';
2
2
  import { configFile } from '@powersync/service-types';
3
3
  import * as auth from '../../auth/auth-index.js';
4
4
  import { ConfigCollector } from './collectors/config-collector.js';
@@ -91,12 +91,23 @@ export class CompoundConfigCollector {
91
91
  jwks_uris = [jwks_uris];
92
92
  }
93
93
 
94
+ let jwksLookup: LookupOptions = {
95
+ reject_ip_ranges: []
96
+ };
97
+
98
+ if (baseConfig.client_auth?.jwks_reject_ip_ranges != null) {
99
+ jwksLookup = {
100
+ reject_ip_ranges: baseConfig.client_auth?.jwks_reject_ip_ranges
101
+ };
102
+ }
103
+ if (baseConfig.client_auth?.block_local_jwks) {
104
+ // Deprecated - recommend method is to use jwks_reject_ip_ranges
105
+ jwksLookup.reject_ip_ranges.push('local');
106
+ jwksLookup.reject_ipv6 = true;
107
+ }
108
+
94
109
  for (let uri of jwks_uris) {
95
- collectors.add(
96
- new auth.CachedKeyCollector(
97
- new auth.RemoteJWKSCollector(uri, { block_local_ip: !!baseConfig.client_auth?.block_local_jwks })
98
- )
99
- );
110
+ collectors.add(new auth.CachedKeyCollector(new auth.RemoteJWKSCollector(uri, { lookupOptions: jwksLookup })));
100
111
  }
101
112
 
102
113
  const baseDevKey = (baseConfig.client_auth?.jwks?.keys ?? []).find((key) => key.kid == POWERSYNC_DEV_KID);
@@ -121,9 +132,6 @@ export class CompoundConfigCollector {
121
132
  api_tokens: baseConfig.api?.tokens ?? [],
122
133
  dev: {
123
134
  demo_auth: baseConfig.dev?.demo_auth ?? false,
124
- demo_client: baseConfig.dev?.demo_client ?? false,
125
- demo_password: baseConfig.dev?.demo_password,
126
- crud_api: baseConfig.dev?.crud_api ?? false,
127
135
  dev_key: devKey
128
136
  },
129
137
  port: baseConfig.port ?? 8080,
@@ -34,9 +34,6 @@ export type ResolvedPowerSyncConfig = {
34
34
  storage: configFile.GenericStorageConfig;
35
35
  dev: {
36
36
  demo_auth: boolean;
37
- demo_password?: string;
38
- crud_api: boolean;
39
- demo_client: boolean;
40
37
  /**
41
38
  * Only present when demo_auth == true
42
39
  */
package/src/util/utils.ts CHANGED
@@ -7,6 +7,7 @@ import { BucketChecksum, OpId, OplogEntry } from './protocol-types.js';
7
7
  import * as storage from '../storage/storage-index.js';
8
8
 
9
9
  import { PartialChecksum } from '../storage/ChecksumCache.js';
10
+ import { ServiceAssertionError } from '@powersync/lib-services-framework';
10
11
 
11
12
  export type ChecksumMap = Map<string, BucketChecksum>;
12
13
 
@@ -34,7 +35,7 @@ export function timestampToOpId(ts: bigint): OpId {
34
35
  // Dynamic values are passed in in some cases, so we make extra sure that the
35
36
  // number is a bigint and not number or Long.
36
37
  if (typeof ts != 'bigint') {
37
- throw new Error(`bigint expected, got: ${ts} (${typeof ts})`);
38
+ throw new ServiceAssertionError(`bigint expected, got: ${ts} (${typeof ts})`);
38
39
  }
39
40
  return ts.toString(10);
40
41
  }
@@ -284,11 +284,23 @@ describe('JWT Auth', () => {
284
284
  expect(errors).toEqual([]);
285
285
  expect(keys.length).toBeGreaterThanOrEqual(1);
286
286
 
287
- // The localhost hostname fails to resolve correctly on MacOS https://github.com/nodejs/help/issues/2163
288
- const invalid = new RemoteJWKSCollector('https://127.0.0.1/.well-known/jwks.json', {
289
- block_local_ip: true
287
+ // Domain names are resolved when retrieving keys
288
+ const invalid = new RemoteJWKSCollector('https://localhost/.well-known/jwks.json', {
289
+ lookupOptions: {
290
+ reject_ip_ranges: ['local']
291
+ }
290
292
  });
291
293
  expect(invalid.getKeys()).rejects.toThrow('IPs in this range are not supported');
294
+
295
+ // IPS throw an error immediately
296
+ expect(
297
+ () =>
298
+ new RemoteJWKSCollector('https://127.0.0.1/.well-known/jwks.json', {
299
+ lookupOptions: {
300
+ reject_ip_ranges: ['local']
301
+ }
302
+ })
303
+ ).toThrowError('IPs in this range are not supported');
292
304
  });
293
305
 
294
306
  test('http not blocking local IPs', async () => {
@@ -301,10 +313,13 @@ describe('JWT Auth', () => {
301
313
  expect(errors).toEqual([]);
302
314
  expect(keys.length).toBeGreaterThanOrEqual(1);
303
315
 
304
- // The localhost hostname fails to resolve correctly on MacOS https://github.com/nodejs/help/issues/2163
305
316
  const invalid = new RemoteJWKSCollector('https://127.0.0.1/.well-known/jwks.json');
306
317
  // Should try and fetch
307
- expect(invalid.getKeys()).rejects.toThrow('ECONNREFUSED');
318
+ expect(invalid.getKeys()).rejects.toThrow();
319
+
320
+ const invalid2 = new RemoteJWKSCollector('https://localhost/.well-known/jwks.json');
321
+ // Should try and fetch
322
+ expect(invalid2.getKeys()).rejects.toThrow();
308
323
  });
309
324
 
310
325
  test('caching', async () => {