@powersync/service-core 0.0.0-dev-20241119082750 → 0.0.0-dev-20241219091224

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 (201) hide show
  1. package/CHANGELOG.md +88 -18
  2. package/dist/auth/KeySpec.d.ts +1 -0
  3. package/dist/auth/KeySpec.js +10 -8
  4. package/dist/auth/KeySpec.js.map +1 -1
  5. package/dist/auth/RemoteJWKSCollector.js +2 -2
  6. package/dist/auth/RemoteJWKSCollector.js.map +1 -1
  7. package/dist/auth/StaticSupabaseKeyCollector.d.ts +19 -0
  8. package/dist/auth/StaticSupabaseKeyCollector.js +28 -0
  9. package/dist/auth/StaticSupabaseKeyCollector.js.map +1 -0
  10. package/dist/auth/auth-index.d.ts +1 -0
  11. package/dist/auth/auth-index.js +1 -0
  12. package/dist/auth/auth-index.js.map +1 -1
  13. package/dist/entry/commands/compact-action.js +15 -15
  14. package/dist/entry/commands/compact-action.js.map +1 -1
  15. package/dist/entry/commands/migrate-action.js +15 -4
  16. package/dist/entry/commands/migrate-action.js.map +1 -1
  17. package/dist/index.d.ts +1 -3
  18. package/dist/index.js +1 -3
  19. package/dist/index.js.map +1 -1
  20. package/dist/migrations/PowerSyncMigrationManager.d.ts +17 -0
  21. package/dist/migrations/PowerSyncMigrationManager.js +22 -0
  22. package/dist/migrations/PowerSyncMigrationManager.js.map +1 -0
  23. package/dist/migrations/ensure-automatic-migrations.d.ts +4 -0
  24. package/dist/migrations/ensure-automatic-migrations.js +14 -0
  25. package/dist/migrations/ensure-automatic-migrations.js.map +1 -0
  26. package/dist/migrations/migrations-index.d.ts +2 -3
  27. package/dist/migrations/migrations-index.js +2 -3
  28. package/dist/migrations/migrations-index.js.map +1 -1
  29. package/dist/routes/RouterEngine.js +2 -1
  30. package/dist/routes/RouterEngine.js.map +1 -1
  31. package/dist/routes/configure-fastify.d.ts +28 -28
  32. package/dist/routes/endpoints/admin.d.ts +24 -24
  33. package/dist/routes/endpoints/sync-rules.js.map +1 -1
  34. package/dist/storage/BucketStorage.d.ts +41 -1
  35. package/dist/storage/BucketStorage.js +26 -0
  36. package/dist/storage/BucketStorage.js.map +1 -1
  37. package/dist/storage/storage-index.d.ts +2 -14
  38. package/dist/storage/storage-index.js +2 -14
  39. package/dist/storage/storage-index.js.map +1 -1
  40. package/dist/sync/sync.js +12 -3
  41. package/dist/sync/sync.js.map +1 -1
  42. package/dist/system/ServiceContext.d.ts +3 -0
  43. package/dist/system/ServiceContext.js +11 -3
  44. package/dist/system/ServiceContext.js.map +1 -1
  45. package/dist/util/config/compound-config-collector.js +16 -0
  46. package/dist/util/config/compound-config-collector.js.map +1 -1
  47. package/dist/util/config/types.d.ts +2 -2
  48. package/dist/util/utils.d.ts +14 -1
  49. package/dist/util/utils.js +56 -0
  50. package/dist/util/utils.js.map +1 -1
  51. package/package.json +7 -11
  52. package/src/auth/KeySpec.ts +12 -9
  53. package/src/auth/RemoteJWKSCollector.ts +2 -2
  54. package/src/auth/StaticSupabaseKeyCollector.ts +31 -0
  55. package/src/auth/auth-index.ts +1 -0
  56. package/src/entry/commands/compact-action.ts +20 -15
  57. package/src/entry/commands/migrate-action.ts +17 -4
  58. package/src/index.ts +1 -4
  59. package/src/migrations/PowerSyncMigrationManager.ts +43 -0
  60. package/src/migrations/ensure-automatic-migrations.ts +15 -0
  61. package/src/migrations/migrations-index.ts +2 -3
  62. package/src/routes/RouterEngine.ts +2 -1
  63. package/src/routes/endpoints/sync-rules.ts +1 -2
  64. package/src/storage/BucketStorage.ts +44 -1
  65. package/src/storage/storage-index.ts +3 -15
  66. package/src/sync/sync.ts +12 -3
  67. package/src/system/ServiceContext.ts +17 -4
  68. package/src/util/config/compound-config-collector.ts +19 -1
  69. package/src/util/config/types.ts +2 -2
  70. package/src/util/utils.ts +59 -1
  71. package/test/src/auth.test.ts +54 -21
  72. package/test/src/env.ts +0 -1
  73. package/tsconfig.tsbuildinfo +1 -1
  74. package/dist/db/db-index.d.ts +0 -1
  75. package/dist/db/db-index.js +0 -2
  76. package/dist/db/db-index.js.map +0 -1
  77. package/dist/db/mongo.d.ts +0 -35
  78. package/dist/db/mongo.js +0 -73
  79. package/dist/db/mongo.js.map +0 -1
  80. package/dist/locks/LockManager.d.ts +0 -10
  81. package/dist/locks/LockManager.js +0 -7
  82. package/dist/locks/LockManager.js.map +0 -1
  83. package/dist/locks/MongoLocks.d.ts +0 -36
  84. package/dist/locks/MongoLocks.js +0 -81
  85. package/dist/locks/MongoLocks.js.map +0 -1
  86. package/dist/locks/locks-index.d.ts +0 -2
  87. package/dist/locks/locks-index.js +0 -3
  88. package/dist/locks/locks-index.js.map +0 -1
  89. package/dist/migrations/db/migrations/1684951997326-init.d.ts +0 -3
  90. package/dist/migrations/db/migrations/1684951997326-init.js +0 -33
  91. package/dist/migrations/db/migrations/1684951997326-init.js.map +0 -1
  92. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.d.ts +0 -2
  93. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js +0 -5
  94. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js.map +0 -1
  95. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.d.ts +0 -3
  96. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js +0 -56
  97. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +0 -1
  98. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.d.ts +0 -3
  99. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js +0 -29
  100. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js.map +0 -1
  101. package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.d.ts +0 -3
  102. package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.js +0 -31
  103. package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.js.map +0 -1
  104. package/dist/migrations/definitions.d.ts +0 -18
  105. package/dist/migrations/definitions.js +0 -6
  106. package/dist/migrations/definitions.js.map +0 -1
  107. package/dist/migrations/executor.d.ts +0 -16
  108. package/dist/migrations/executor.js +0 -64
  109. package/dist/migrations/executor.js.map +0 -1
  110. package/dist/migrations/migrations.d.ts +0 -18
  111. package/dist/migrations/migrations.js +0 -110
  112. package/dist/migrations/migrations.js.map +0 -1
  113. package/dist/migrations/store/migration-store.d.ts +0 -11
  114. package/dist/migrations/store/migration-store.js +0 -46
  115. package/dist/migrations/store/migration-store.js.map +0 -1
  116. package/dist/storage/MongoBucketStorage.d.ts +0 -48
  117. package/dist/storage/MongoBucketStorage.js +0 -426
  118. package/dist/storage/MongoBucketStorage.js.map +0 -1
  119. package/dist/storage/mongo/MongoBucketBatch.d.ts +0 -67
  120. package/dist/storage/mongo/MongoBucketBatch.js +0 -643
  121. package/dist/storage/mongo/MongoBucketBatch.js.map +0 -1
  122. package/dist/storage/mongo/MongoCompactor.d.ts +0 -40
  123. package/dist/storage/mongo/MongoCompactor.js +0 -309
  124. package/dist/storage/mongo/MongoCompactor.js.map +0 -1
  125. package/dist/storage/mongo/MongoIdSequence.d.ts +0 -12
  126. package/dist/storage/mongo/MongoIdSequence.js +0 -21
  127. package/dist/storage/mongo/MongoIdSequence.js.map +0 -1
  128. package/dist/storage/mongo/MongoPersistedSyncRules.d.ts +0 -9
  129. package/dist/storage/mongo/MongoPersistedSyncRules.js +0 -9
  130. package/dist/storage/mongo/MongoPersistedSyncRules.js.map +0 -1
  131. package/dist/storage/mongo/MongoPersistedSyncRulesContent.d.ts +0 -20
  132. package/dist/storage/mongo/MongoPersistedSyncRulesContent.js +0 -26
  133. package/dist/storage/mongo/MongoPersistedSyncRulesContent.js.map +0 -1
  134. package/dist/storage/mongo/MongoStorageProvider.d.ts +0 -5
  135. package/dist/storage/mongo/MongoStorageProvider.js +0 -26
  136. package/dist/storage/mongo/MongoStorageProvider.js.map +0 -1
  137. package/dist/storage/mongo/MongoSyncBucketStorage.d.ts +0 -38
  138. package/dist/storage/mongo/MongoSyncBucketStorage.js +0 -531
  139. package/dist/storage/mongo/MongoSyncBucketStorage.js.map +0 -1
  140. package/dist/storage/mongo/MongoSyncRulesLock.d.ts +0 -16
  141. package/dist/storage/mongo/MongoSyncRulesLock.js +0 -65
  142. package/dist/storage/mongo/MongoSyncRulesLock.js.map +0 -1
  143. package/dist/storage/mongo/MongoWriteCheckpointAPI.d.ts +0 -20
  144. package/dist/storage/mongo/MongoWriteCheckpointAPI.js +0 -103
  145. package/dist/storage/mongo/MongoWriteCheckpointAPI.js.map +0 -1
  146. package/dist/storage/mongo/OperationBatch.d.ts +0 -35
  147. package/dist/storage/mongo/OperationBatch.js +0 -119
  148. package/dist/storage/mongo/OperationBatch.js.map +0 -1
  149. package/dist/storage/mongo/PersistedBatch.d.ts +0 -46
  150. package/dist/storage/mongo/PersistedBatch.js +0 -213
  151. package/dist/storage/mongo/PersistedBatch.js.map +0 -1
  152. package/dist/storage/mongo/config.d.ts +0 -19
  153. package/dist/storage/mongo/config.js +0 -26
  154. package/dist/storage/mongo/config.js.map +0 -1
  155. package/dist/storage/mongo/db.d.ts +0 -36
  156. package/dist/storage/mongo/db.js +0 -47
  157. package/dist/storage/mongo/db.js.map +0 -1
  158. package/dist/storage/mongo/models.d.ts +0 -156
  159. package/dist/storage/mongo/models.js +0 -27
  160. package/dist/storage/mongo/models.js.map +0 -1
  161. package/dist/storage/mongo/util.d.ts +0 -40
  162. package/dist/storage/mongo/util.js +0 -151
  163. package/dist/storage/mongo/util.js.map +0 -1
  164. package/src/db/db-index.ts +0 -1
  165. package/src/db/mongo.ts +0 -81
  166. package/src/locks/LockManager.ts +0 -16
  167. package/src/locks/MongoLocks.ts +0 -142
  168. package/src/locks/locks-index.ts +0 -2
  169. package/src/migrations/db/migrations/1684951997326-init.ts +0 -38
  170. package/src/migrations/db/migrations/1688556755264-initial-sync-rules.ts +0 -5
  171. package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +0 -102
  172. package/src/migrations/db/migrations/1711543888062-write-checkpoint-index.ts +0 -34
  173. package/src/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.ts +0 -37
  174. package/src/migrations/definitions.ts +0 -21
  175. package/src/migrations/executor.ts +0 -87
  176. package/src/migrations/migrations.ts +0 -142
  177. package/src/migrations/store/migration-store.ts +0 -63
  178. package/src/storage/MongoBucketStorage.ts +0 -540
  179. package/src/storage/mongo/MongoBucketBatch.ts +0 -841
  180. package/src/storage/mongo/MongoCompactor.ts +0 -392
  181. package/src/storage/mongo/MongoIdSequence.ts +0 -24
  182. package/src/storage/mongo/MongoPersistedSyncRules.ts +0 -16
  183. package/src/storage/mongo/MongoPersistedSyncRulesContent.ts +0 -50
  184. package/src/storage/mongo/MongoStorageProvider.ts +0 -31
  185. package/src/storage/mongo/MongoSyncBucketStorage.ts +0 -636
  186. package/src/storage/mongo/MongoSyncRulesLock.ts +0 -85
  187. package/src/storage/mongo/MongoWriteCheckpointAPI.ts +0 -151
  188. package/src/storage/mongo/OperationBatch.ts +0 -131
  189. package/src/storage/mongo/PersistedBatch.ts +0 -272
  190. package/src/storage/mongo/config.ts +0 -40
  191. package/src/storage/mongo/db.ts +0 -88
  192. package/src/storage/mongo/models.ts +0 -179
  193. package/src/storage/mongo/util.ts +0 -158
  194. package/test/src/__snapshots__/sync.test.ts.snap +0 -332
  195. package/test/src/bucket_validation.test.ts +0 -142
  196. package/test/src/bucket_validation.ts +0 -116
  197. package/test/src/compacting.test.ts +0 -295
  198. package/test/src/data_storage.test.ts +0 -1499
  199. package/test/src/stream_utils.ts +0 -42
  200. package/test/src/sync.test.ts +0 -511
  201. package/test/src/util.ts +0 -148
@@ -1,21 +1,9 @@
1
1
  export * from './BucketStorage.js';
2
- export * from './MongoBucketStorage.js';
2
+ export * from './ChecksumCache.js';
3
3
  export * from './ReplicationEventPayload.js';
4
4
  export * from './SourceEntity.js';
5
5
  export * from './SourceTable.js';
6
6
  export * from './StorageEngine.js';
7
-
8
- export * from './mongo/config.js';
9
- export * from './mongo/db.js';
10
- export * from './mongo/models.js';
11
- export * from './mongo/MongoBucketBatch.js';
12
- export * from './mongo/MongoIdSequence.js';
13
- export * from './mongo/MongoPersistedSyncRules.js';
14
- export * from './mongo/MongoPersistedSyncRulesContent.js';
15
- export * from './mongo/MongoStorageProvider.js';
16
- export * from './mongo/MongoSyncBucketStorage.js';
17
- export * from './mongo/MongoSyncRulesLock.js';
18
- export * from './mongo/OperationBatch.js';
19
- export * from './mongo/PersistedBatch.js';
20
- export * from './mongo/util.js';
7
+ export * from './StorageProvider.js';
21
8
  export * from './WriteCheckpointAPI.js';
9
+
package/src/sync/sync.ts CHANGED
@@ -318,9 +318,18 @@ async function* bucketDataBatch(request: BucketDataRequest): AsyncGenerator<Buck
318
318
  // Send the object as is, will most likely be encoded as a BSON document
319
319
  send_data = { data: r };
320
320
  } else if (raw_data) {
321
- // Data is a raw string - we can use the more efficient JSON.stringify.
321
+ /**
322
+ * Data is a raw string - we can use the more efficient JSON.stringify.
323
+ * FIXME: ensure that the data is actually a string
324
+ */
322
325
  const response: util.StreamingSyncData = {
323
- data: r
326
+ data: {
327
+ ...r,
328
+ data: r.data.map((entry) => ({
329
+ ...entry,
330
+ data: typeof entry.data == 'string' ? entry.data : JSONBig.stringify(entry.data)
331
+ }))
332
+ }
324
333
  };
325
334
  send_data = JSON.stringify(response);
326
335
  } else {
@@ -375,7 +384,7 @@ function transformLegacyResponse(bucketData: util.SyncBucketData): any {
375
384
  data: bucketData.data.map((entry) => {
376
385
  return {
377
386
  ...entry,
378
- data: entry.data == null ? null : new JsonContainer(entry.data as string),
387
+ data: entry.data == null ? null : typeof entry.data == 'string' ? new JsonContainer(entry.data) : entry.data,
379
388
  checksum: BigInt(entry.checksum)
380
389
  };
381
390
  })
@@ -1,6 +1,8 @@
1
- import { LifeCycledSystem, ServiceIdentifier, container } from '@powersync/lib-services-framework';
1
+ import { LifeCycledSystem, MigrationManager, ServiceIdentifier, container } from '@powersync/lib-services-framework';
2
2
 
3
+ import { framework } from '../index.js';
3
4
  import * as metrics from '../metrics/Metrics.js';
5
+ import { PowerSyncMigrationManager } from '../migrations/PowerSyncMigrationManager.js';
4
6
  import * as replication from '../replication/replication-index.js';
5
7
  import * as routes from '../routes/routes-index.js';
6
8
  import * as storage from '../storage/storage-index.js';
@@ -13,6 +15,7 @@ export interface ServiceContext {
13
15
  replicationEngine: replication.ReplicationEngine | null;
14
16
  routerEngine: routes.RouterEngine | null;
15
17
  storageEngine: storage.StorageEngine;
18
+ migrations: PowerSyncMigrationManager;
16
19
  }
17
20
 
18
21
  /**
@@ -30,13 +33,19 @@ export class ServiceContextContainer implements ServiceContext {
30
33
  this.storageEngine = new storage.StorageEngine({
31
34
  configuration
32
35
  });
36
+
37
+ const migrationManager = new MigrationManager();
38
+ container.register(framework.ContainerImplementation.MIGRATION_MANAGER, migrationManager);
39
+
40
+ this.lifeCycleEngine.withLifecycle(migrationManager, {
41
+ // Migrations should be executed before the system starts
42
+ start: () => migrationManager[Symbol.asyncDispose]()
43
+ });
44
+
33
45
  this.lifeCycleEngine.withLifecycle(this.storageEngine, {
34
46
  start: (storageEngine) => storageEngine.start(),
35
47
  stop: (storageEngine) => storageEngine.shutDown()
36
48
  });
37
-
38
- // Mongo storage is available as an option by default TODO: Consider moving this to a Mongo Storage Module
39
- this.storageEngine.registerProvider(new storage.MongoStorageProvider());
40
49
  }
41
50
 
42
51
  get replicationEngine(): replication.ReplicationEngine | null {
@@ -51,6 +60,10 @@ export class ServiceContextContainer implements ServiceContext {
51
60
  return container.getOptional(metrics.Metrics);
52
61
  }
53
62
 
63
+ get migrations(): PowerSyncMigrationManager {
64
+ return container.getImplementation(framework.ContainerImplementation.MIGRATION_MANAGER);
65
+ }
66
+
54
67
  /**
55
68
  * Allows for registering core and generic implementations of services/helpers.
56
69
  * This uses the framework container under the hood.
@@ -65,9 +65,27 @@ export class CompoundConfigCollector {
65
65
 
66
66
  const inputKeys = baseConfig.client_auth?.jwks?.keys ?? [];
67
67
  const staticCollector = await auth.StaticKeyCollector.importKeys(inputKeys);
68
-
69
68
  collectors.add(staticCollector);
70
69
 
70
+ if (baseConfig.client_auth?.supabase && baseConfig.client_auth?.supabase_jwt_secret != null) {
71
+ // This replaces the old SupabaseKeyCollector, with a statically-configured key.
72
+ // You can get the same effect with manual HS256 key configuration, but this
73
+ // makes the config simpler.
74
+ // We also a custom audience ("authenticated"), increased max lifetime (1 week),
75
+ // and auto base64-url-encode the key.
76
+ collectors.add(
77
+ await auth.StaticSupabaseKeyCollector.importKeys([
78
+ {
79
+ kty: 'oct',
80
+ alg: 'HS256',
81
+ // In this case, the key is not base64-encoded yet.
82
+ k: Buffer.from(baseConfig.client_auth.supabase_jwt_secret, 'utf8').toString('base64url'),
83
+ kid: undefined // Wildcard kid - any kid can match
84
+ }
85
+ ])
86
+ );
87
+ }
88
+
71
89
  let jwks_uris = baseConfig.client_auth?.jwks_uri ?? [];
72
90
  if (typeof jwks_uris == 'string') {
73
91
  jwks_uris = [jwks_uris];
@@ -30,8 +30,8 @@ export type SyncRulesConfig = {
30
30
 
31
31
  export type ResolvedPowerSyncConfig = {
32
32
  base_config: PowerSyncConfig;
33
- connections?: configFile.DataSourceConfig[];
34
- storage: configFile.StorageConfig;
33
+ connections?: configFile.GenericDataSourceConfig[];
34
+ storage: configFile.GenericStorageConfig;
35
35
  dev: {
36
36
  demo_auth: boolean;
37
37
  demo_password?: 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 } from './protocol-types.js';
5
+ import { BucketChecksum, OpId, OplogEntry } from './protocol-types.js';
6
6
 
7
7
  import * as storage from '../storage/storage-index.js';
8
8
 
@@ -144,3 +144,61 @@ export function checkpointUserId(user_id: string | undefined, client_id: string
144
144
  }
145
145
  return `${user_id}/${client_id}`;
146
146
  }
147
+
148
+ /**
149
+ * Reduce a bucket to the final state as stored on the client.
150
+ *
151
+ * This keeps the final state for each row as a PUT operation.
152
+ *
153
+ * All other operations are replaced with a single CLEAR operation,
154
+ * summing their checksums, and using a 0 as an op_id.
155
+ *
156
+ * This is the function $r(B)$, as described in /docs/bucket-properties.md.
157
+ *
158
+ * Used for tests.
159
+ */
160
+ export function reduceBucket(operations: OplogEntry[]) {
161
+ let rowState = new Map<string, OplogEntry>();
162
+ let otherChecksum = 0;
163
+
164
+ for (let op of operations) {
165
+ const key = rowKey(op);
166
+ if (op.op == 'PUT') {
167
+ const existing = rowState.get(key);
168
+ if (existing) {
169
+ otherChecksum = addChecksums(otherChecksum, existing.checksum as number);
170
+ }
171
+ rowState.set(key, op);
172
+ } else if (op.op == 'REMOVE') {
173
+ const existing = rowState.get(key);
174
+ if (existing) {
175
+ otherChecksum = addChecksums(otherChecksum, existing.checksum as number);
176
+ }
177
+ rowState.delete(key);
178
+ otherChecksum = addChecksums(otherChecksum, op.checksum as number);
179
+ } else if (op.op == 'CLEAR') {
180
+ rowState.clear();
181
+ otherChecksum = op.checksum as number;
182
+ } else if (op.op == 'MOVE') {
183
+ otherChecksum = addChecksums(otherChecksum, op.checksum as number);
184
+ } else {
185
+ throw new Error(`Unknown operation ${op.op}`);
186
+ }
187
+ }
188
+
189
+ const puts = [...rowState.values()].sort((a, b) => {
190
+ return Number(BigInt(a.op_id) - BigInt(b.op_id));
191
+ });
192
+
193
+ let finalState: OplogEntry[] = [
194
+ // Special operation to indiciate the checksum remainder
195
+ { op_id: '0', op: 'CLEAR', checksum: otherChecksum },
196
+ ...puts
197
+ ];
198
+
199
+ return finalState;
200
+ }
201
+
202
+ function rowKey(entry: OplogEntry) {
203
+ return `${entry.object_type}/${entry.object_id}/${entry.subkey}`;
204
+ }
@@ -1,13 +1,14 @@
1
- import { CachedKeyCollector } from '@/auth/CachedKeyCollector.js';
2
- import { KeyResult } from '@/auth/KeyCollector.js';
3
- import { KeySpec } from '@/auth/KeySpec.js';
4
- import { KeyStore } from '@/auth/KeyStore.js';
5
- import { RemoteJWKSCollector } from '@/auth/RemoteJWKSCollector.js';
6
- import { StaticKeyCollector } from '@/auth/StaticKeyCollector.js';
7
- import * as jose from 'jose';
8
1
  import { describe, expect, test } from 'vitest';
9
-
10
- const publicKey: jose.JWK = {
2
+ import { StaticKeyCollector } from '../../src/auth/StaticKeyCollector.js';
3
+ import * as jose from 'jose';
4
+ import { KeyStore } from '../../src/auth/KeyStore.js';
5
+ import { KeySpec } from '../../src/auth/KeySpec.js';
6
+ import { RemoteJWKSCollector } from '../../src/auth/RemoteJWKSCollector.js';
7
+ import { KeyResult } from '../../src/auth/KeyCollector.js';
8
+ import { CachedKeyCollector } from '../../src/auth/CachedKeyCollector.js';
9
+ import { JwtPayload } from '@/index.js';
10
+
11
+ const publicKeyRSA: jose.JWK = {
11
12
  use: 'sig',
12
13
  kty: 'RSA',
13
14
  e: 'AQAB',
@@ -29,6 +30,16 @@ const sharedKey2: jose.JWK = {
29
30
  k: Buffer.from('mysecret2', 'utf-8').toString('base64url')
30
31
  };
31
32
 
33
+ const privateKeyEdDSA: jose.JWK = {
34
+ use: 'sig',
35
+ kty: 'OKP',
36
+ crv: 'Ed25519',
37
+ kid: 'k2',
38
+ x: 'nfaqgxakPaiiEdAtRGrubgh_SQ1mr6gAUx3--N-ehvo',
39
+ d: 'wweBqMbTrME6oChSEMYAOyYzxsGisQb-C1t0XMjb_Ng',
40
+ alg: 'EdDSA'
41
+ };
42
+
32
43
  describe('JWT Auth', () => {
33
44
  test('KeyStore basics', async () => {
34
45
  const keys = await StaticKeyCollector.importKeys([sharedKey]);
@@ -86,20 +97,20 @@ describe('JWT Auth', () => {
86
97
  });
87
98
 
88
99
  test('Algorithm validation', async () => {
89
- const keys = await StaticKeyCollector.importKeys([publicKey]);
100
+ const keys = await StaticKeyCollector.importKeys([publicKeyRSA]);
90
101
  const store = new KeyStore(keys);
91
102
 
92
103
  // Bad attempt at signing token with rsa public key
93
104
  const spoofedKey: jose.JWK = {
94
105
  kty: 'oct',
95
- kid: publicKey.kid!,
106
+ kid: publicKeyRSA.kid!,
96
107
  alg: 'HS256',
97
- k: publicKey.n!
108
+ k: publicKeyRSA.n!
98
109
  };
99
110
  const signKey = (await jose.importJWK(spoofedKey)) as jose.KeyLike;
100
111
 
101
112
  const signedJwt = await new jose.SignJWT({})
102
- .setProtectedHeader({ alg: 'HS256', kid: publicKey.kid! })
113
+ .setProtectedHeader({ alg: 'HS256', kid: publicKeyRSA.kid! })
103
114
  .setSubject('f1')
104
115
  .setIssuedAt()
105
116
  .setIssuer('tester')
@@ -116,7 +127,7 @@ describe('JWT Auth', () => {
116
127
  });
117
128
 
118
129
  test('key selection for key with kid', async () => {
119
- const keys = await StaticKeyCollector.importKeys([publicKey, sharedKey, sharedKey2]);
130
+ const keys = await StaticKeyCollector.importKeys([publicKeyRSA, sharedKey, sharedKey2]);
120
131
  const store = new KeyStore(keys);
121
132
  const signKey = (await jose.importJWK(sharedKey)) as jose.KeyLike;
122
133
  const signKey2 = (await jose.importJWK(sharedKey2)) as jose.KeyLike;
@@ -296,30 +307,30 @@ describe('JWT Auth', () => {
296
307
 
297
308
  currentResponse = Promise.resolve({
298
309
  errors: [],
299
- keys: [await KeySpec.importKey(publicKey)]
310
+ keys: [await KeySpec.importKey(publicKeyRSA)]
300
311
  });
301
312
 
302
313
  let key = (await cached.getKeys()).keys[0];
303
- expect(key.kid).toEqual(publicKey.kid!);
314
+ expect(key.kid).toEqual(publicKeyRSA.kid!);
304
315
 
305
316
  currentResponse = undefined as any;
306
317
 
307
318
  key = (await cached.getKeys()).keys[0];
308
- expect(key.kid).toEqual(publicKey.kid!);
319
+ expect(key.kid).toEqual(publicKeyRSA.kid!);
309
320
 
310
321
  cached.addTimeForTests(301_000);
311
322
  currentResponse = Promise.reject('refresh failed');
312
323
 
313
324
  // Uses the promise, refreshes in the background
314
325
  let response = await cached.getKeys();
315
- expect(response.keys[0].kid).toEqual(publicKey.kid!);
326
+ expect(response.keys[0].kid).toEqual(publicKeyRSA.kid!);
316
327
  expect(response.errors).toEqual([]);
317
328
 
318
329
  // Wait for refresh to finish
319
330
  await cached.addTimeForTests(0);
320
331
  response = await cached.getKeys();
321
332
  // Still have the cached key, but also have the error
322
- expect(response.keys[0].kid).toEqual(publicKey.kid!);
333
+ expect(response.keys[0].kid).toEqual(publicKeyRSA.kid!);
323
334
  expect(response.errors[0].message).toMatch('Failed to fetch');
324
335
 
325
336
  await cached.addTimeForTests(3601_000);
@@ -331,12 +342,34 @@ describe('JWT Auth', () => {
331
342
 
332
343
  currentResponse = Promise.resolve({
333
344
  errors: [],
334
- keys: [await KeySpec.importKey(publicKey)]
345
+ keys: [await KeySpec.importKey(publicKeyRSA)]
335
346
  });
336
347
 
337
348
  // After a delay, we can refresh again
338
349
  await cached.addTimeForTests(30_000);
339
350
  key = (await cached.getKeys()).keys[0];
340
- expect(key.kid).toEqual(publicKey.kid!);
351
+ expect(key.kid).toEqual(publicKeyRSA.kid!);
352
+ });
353
+
354
+ test('signing with EdDSA', async () => {
355
+ const keys = await StaticKeyCollector.importKeys([privateKeyEdDSA]);
356
+ const store = new KeyStore(keys);
357
+ const signKey = (await jose.importJWK(privateKeyEdDSA)) as jose.KeyLike;
358
+
359
+ const signedJwt = await new jose.SignJWT({ claim: 'test-claim' })
360
+ .setProtectedHeader({ alg: 'EdDSA', kid: 'k2' })
361
+ .setSubject('f1')
362
+ .setIssuedAt()
363
+ .setIssuer('tester')
364
+ .setAudience('tests')
365
+ .setExpirationTime('5m')
366
+ .sign(signKey);
367
+
368
+ const verified = (await store.verifyJwt(signedJwt, {
369
+ defaultAudiences: ['tests'],
370
+ maxAge: '6m'
371
+ })) as JwtPayload & { claim: string };
372
+
373
+ expect(verified.claim).toEqual('test-claim');
341
374
  });
342
375
  });
package/test/src/env.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { utils } from '@powersync/lib-services-framework';
2
2
 
3
3
  export const env = utils.collectEnvironmentVariables({
4
- MONGO_TEST_URL: utils.type.string.default('mongodb://localhost:27017/powersync_test'),
5
4
  CI: utils.type.boolean.default('false')
6
5
  });