@powersync/service-core 0.0.0-dev-20241128134723 → 0.0.0-dev-20241219110735

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 (188) hide show
  1. package/CHANGELOG.md +65 -4
  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/entry/commands/compact-action.js +15 -15
  8. package/dist/entry/commands/compact-action.js.map +1 -1
  9. package/dist/entry/commands/migrate-action.js +15 -4
  10. package/dist/entry/commands/migrate-action.js.map +1 -1
  11. package/dist/index.d.ts +1 -3
  12. package/dist/index.js +1 -3
  13. package/dist/index.js.map +1 -1
  14. package/dist/migrations/PowerSyncMigrationManager.d.ts +17 -0
  15. package/dist/migrations/PowerSyncMigrationManager.js +22 -0
  16. package/dist/migrations/PowerSyncMigrationManager.js.map +1 -0
  17. package/dist/migrations/ensure-automatic-migrations.d.ts +4 -0
  18. package/dist/migrations/ensure-automatic-migrations.js +14 -0
  19. package/dist/migrations/ensure-automatic-migrations.js.map +1 -0
  20. package/dist/migrations/migrations-index.d.ts +2 -3
  21. package/dist/migrations/migrations-index.js +2 -3
  22. package/dist/migrations/migrations-index.js.map +1 -1
  23. package/dist/routes/RouterEngine.js +2 -1
  24. package/dist/routes/RouterEngine.js.map +1 -1
  25. package/dist/routes/configure-fastify.d.ts +28 -28
  26. package/dist/routes/endpoints/admin.d.ts +24 -24
  27. package/dist/storage/BucketStorage.d.ts +41 -1
  28. package/dist/storage/BucketStorage.js +26 -0
  29. package/dist/storage/BucketStorage.js.map +1 -1
  30. package/dist/storage/storage-index.d.ts +2 -14
  31. package/dist/storage/storage-index.js +2 -14
  32. package/dist/storage/storage-index.js.map +1 -1
  33. package/dist/sync/sync.js +12 -3
  34. package/dist/sync/sync.js.map +1 -1
  35. package/dist/system/ServiceContext.d.ts +3 -0
  36. package/dist/system/ServiceContext.js +11 -3
  37. package/dist/system/ServiceContext.js.map +1 -1
  38. package/dist/util/config/types.d.ts +2 -2
  39. package/dist/util/utils.d.ts +14 -1
  40. package/dist/util/utils.js +56 -0
  41. package/dist/util/utils.js.map +1 -1
  42. package/package.json +6 -7
  43. package/src/auth/KeySpec.ts +12 -9
  44. package/src/auth/RemoteJWKSCollector.ts +2 -2
  45. package/src/entry/commands/compact-action.ts +20 -15
  46. package/src/entry/commands/migrate-action.ts +17 -4
  47. package/src/index.ts +1 -4
  48. package/src/migrations/PowerSyncMigrationManager.ts +43 -0
  49. package/src/migrations/ensure-automatic-migrations.ts +15 -0
  50. package/src/migrations/migrations-index.ts +2 -3
  51. package/src/routes/RouterEngine.ts +2 -1
  52. package/src/storage/BucketStorage.ts +44 -1
  53. package/src/storage/storage-index.ts +3 -15
  54. package/src/sync/sync.ts +12 -3
  55. package/src/system/ServiceContext.ts +17 -4
  56. package/src/util/config/types.ts +2 -2
  57. package/src/util/utils.ts +59 -1
  58. package/test/src/auth.test.ts +54 -21
  59. package/test/src/env.ts +0 -1
  60. package/tsconfig.tsbuildinfo +1 -1
  61. package/dist/db/db-index.d.ts +0 -1
  62. package/dist/db/db-index.js +0 -2
  63. package/dist/db/db-index.js.map +0 -1
  64. package/dist/db/mongo.d.ts +0 -35
  65. package/dist/db/mongo.js +0 -73
  66. package/dist/db/mongo.js.map +0 -1
  67. package/dist/locks/LockManager.d.ts +0 -10
  68. package/dist/locks/LockManager.js +0 -7
  69. package/dist/locks/LockManager.js.map +0 -1
  70. package/dist/locks/MongoLocks.d.ts +0 -36
  71. package/dist/locks/MongoLocks.js +0 -81
  72. package/dist/locks/MongoLocks.js.map +0 -1
  73. package/dist/locks/locks-index.d.ts +0 -2
  74. package/dist/locks/locks-index.js +0 -3
  75. package/dist/locks/locks-index.js.map +0 -1
  76. package/dist/migrations/db/migrations/1684951997326-init.d.ts +0 -3
  77. package/dist/migrations/db/migrations/1684951997326-init.js +0 -33
  78. package/dist/migrations/db/migrations/1684951997326-init.js.map +0 -1
  79. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.d.ts +0 -2
  80. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js +0 -5
  81. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js.map +0 -1
  82. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.d.ts +0 -3
  83. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js +0 -56
  84. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +0 -1
  85. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.d.ts +0 -3
  86. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js +0 -29
  87. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js.map +0 -1
  88. package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.d.ts +0 -3
  89. package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.js +0 -31
  90. package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.js.map +0 -1
  91. package/dist/migrations/definitions.d.ts +0 -18
  92. package/dist/migrations/definitions.js +0 -6
  93. package/dist/migrations/definitions.js.map +0 -1
  94. package/dist/migrations/executor.d.ts +0 -16
  95. package/dist/migrations/executor.js +0 -64
  96. package/dist/migrations/executor.js.map +0 -1
  97. package/dist/migrations/migrations.d.ts +0 -18
  98. package/dist/migrations/migrations.js +0 -110
  99. package/dist/migrations/migrations.js.map +0 -1
  100. package/dist/migrations/store/migration-store.d.ts +0 -11
  101. package/dist/migrations/store/migration-store.js +0 -46
  102. package/dist/migrations/store/migration-store.js.map +0 -1
  103. package/dist/storage/MongoBucketStorage.d.ts +0 -48
  104. package/dist/storage/MongoBucketStorage.js +0 -426
  105. package/dist/storage/MongoBucketStorage.js.map +0 -1
  106. package/dist/storage/mongo/MongoBucketBatch.d.ts +0 -67
  107. package/dist/storage/mongo/MongoBucketBatch.js +0 -643
  108. package/dist/storage/mongo/MongoBucketBatch.js.map +0 -1
  109. package/dist/storage/mongo/MongoCompactor.d.ts +0 -40
  110. package/dist/storage/mongo/MongoCompactor.js +0 -309
  111. package/dist/storage/mongo/MongoCompactor.js.map +0 -1
  112. package/dist/storage/mongo/MongoIdSequence.d.ts +0 -12
  113. package/dist/storage/mongo/MongoIdSequence.js +0 -21
  114. package/dist/storage/mongo/MongoIdSequence.js.map +0 -1
  115. package/dist/storage/mongo/MongoPersistedSyncRules.d.ts +0 -9
  116. package/dist/storage/mongo/MongoPersistedSyncRules.js +0 -9
  117. package/dist/storage/mongo/MongoPersistedSyncRules.js.map +0 -1
  118. package/dist/storage/mongo/MongoPersistedSyncRulesContent.d.ts +0 -20
  119. package/dist/storage/mongo/MongoPersistedSyncRulesContent.js +0 -26
  120. package/dist/storage/mongo/MongoPersistedSyncRulesContent.js.map +0 -1
  121. package/dist/storage/mongo/MongoStorageProvider.d.ts +0 -5
  122. package/dist/storage/mongo/MongoStorageProvider.js +0 -26
  123. package/dist/storage/mongo/MongoStorageProvider.js.map +0 -1
  124. package/dist/storage/mongo/MongoSyncBucketStorage.d.ts +0 -38
  125. package/dist/storage/mongo/MongoSyncBucketStorage.js +0 -531
  126. package/dist/storage/mongo/MongoSyncBucketStorage.js.map +0 -1
  127. package/dist/storage/mongo/MongoSyncRulesLock.d.ts +0 -16
  128. package/dist/storage/mongo/MongoSyncRulesLock.js +0 -65
  129. package/dist/storage/mongo/MongoSyncRulesLock.js.map +0 -1
  130. package/dist/storage/mongo/MongoWriteCheckpointAPI.d.ts +0 -20
  131. package/dist/storage/mongo/MongoWriteCheckpointAPI.js +0 -103
  132. package/dist/storage/mongo/MongoWriteCheckpointAPI.js.map +0 -1
  133. package/dist/storage/mongo/OperationBatch.d.ts +0 -35
  134. package/dist/storage/mongo/OperationBatch.js +0 -119
  135. package/dist/storage/mongo/OperationBatch.js.map +0 -1
  136. package/dist/storage/mongo/PersistedBatch.d.ts +0 -46
  137. package/dist/storage/mongo/PersistedBatch.js +0 -213
  138. package/dist/storage/mongo/PersistedBatch.js.map +0 -1
  139. package/dist/storage/mongo/config.d.ts +0 -19
  140. package/dist/storage/mongo/config.js +0 -26
  141. package/dist/storage/mongo/config.js.map +0 -1
  142. package/dist/storage/mongo/db.d.ts +0 -36
  143. package/dist/storage/mongo/db.js +0 -47
  144. package/dist/storage/mongo/db.js.map +0 -1
  145. package/dist/storage/mongo/models.d.ts +0 -156
  146. package/dist/storage/mongo/models.js +0 -27
  147. package/dist/storage/mongo/models.js.map +0 -1
  148. package/dist/storage/mongo/util.d.ts +0 -40
  149. package/dist/storage/mongo/util.js +0 -151
  150. package/dist/storage/mongo/util.js.map +0 -1
  151. package/src/db/db-index.ts +0 -1
  152. package/src/db/mongo.ts +0 -81
  153. package/src/locks/LockManager.ts +0 -16
  154. package/src/locks/MongoLocks.ts +0 -142
  155. package/src/locks/locks-index.ts +0 -2
  156. package/src/migrations/db/migrations/1684951997326-init.ts +0 -38
  157. package/src/migrations/db/migrations/1688556755264-initial-sync-rules.ts +0 -5
  158. package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +0 -102
  159. package/src/migrations/db/migrations/1711543888062-write-checkpoint-index.ts +0 -34
  160. package/src/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.ts +0 -37
  161. package/src/migrations/definitions.ts +0 -21
  162. package/src/migrations/executor.ts +0 -87
  163. package/src/migrations/migrations.ts +0 -142
  164. package/src/migrations/store/migration-store.ts +0 -63
  165. package/src/storage/MongoBucketStorage.ts +0 -540
  166. package/src/storage/mongo/MongoBucketBatch.ts +0 -841
  167. package/src/storage/mongo/MongoCompactor.ts +0 -392
  168. package/src/storage/mongo/MongoIdSequence.ts +0 -24
  169. package/src/storage/mongo/MongoPersistedSyncRules.ts +0 -16
  170. package/src/storage/mongo/MongoPersistedSyncRulesContent.ts +0 -50
  171. package/src/storage/mongo/MongoStorageProvider.ts +0 -31
  172. package/src/storage/mongo/MongoSyncBucketStorage.ts +0 -636
  173. package/src/storage/mongo/MongoSyncRulesLock.ts +0 -85
  174. package/src/storage/mongo/MongoWriteCheckpointAPI.ts +0 -151
  175. package/src/storage/mongo/OperationBatch.ts +0 -131
  176. package/src/storage/mongo/PersistedBatch.ts +0 -272
  177. package/src/storage/mongo/config.ts +0 -40
  178. package/src/storage/mongo/db.ts +0 -88
  179. package/src/storage/mongo/models.ts +0 -179
  180. package/src/storage/mongo/util.ts +0 -158
  181. package/test/src/__snapshots__/sync.test.ts.snap +0 -332
  182. package/test/src/bucket_validation.test.ts +0 -142
  183. package/test/src/bucket_validation.ts +0 -116
  184. package/test/src/compacting.test.ts +0 -295
  185. package/test/src/data_storage.test.ts +0 -1499
  186. package/test/src/stream_utils.ts +0 -42
  187. package/test/src/sync.test.ts +0 -511
  188. package/test/src/util.ts +0 -148
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
8
- "version": "0.0.0-dev-20241128134723",
8
+ "version": "0.0.0-dev-20241219110735",
9
9
  "main": "dist/index.js",
10
10
  "license": "FSL-1.1-Apache-2.0",
11
11
  "type": "module",
@@ -26,18 +26,17 @@
26
26
  "jose": "^4.15.1",
27
27
  "lodash": "^4.17.21",
28
28
  "lru-cache": "^10.2.2",
29
- "mongodb": "^6.7.0",
30
29
  "node-fetch": "^3.3.2",
31
- "ts-codec": "^1.2.2",
30
+ "ts-codec": "^1.3.0",
32
31
  "uri-js": "^4.4.1",
33
32
  "uuid": "^9.0.1",
34
33
  "winston": "^3.13.0",
35
34
  "yaml": "^2.3.2",
36
- "@powersync/lib-services-framework": "0.2.0",
35
+ "@powersync/lib-services-framework": "0.0.0-dev-20241219110735",
37
36
  "@powersync/service-jsonbig": "0.17.10",
38
- "@powersync/service-rsocket-router": "0.0.14",
39
- "@powersync/service-sync-rules": "0.21.0",
40
- "@powersync/service-types": "0.0.0-dev-20241128134723"
37
+ "@powersync/service-rsocket-router": "0.0.0-dev-20241219110735",
38
+ "@powersync/service-sync-rules": "0.0.0-dev-20241219110735",
39
+ "@powersync/service-types": "0.0.0-dev-20241219110735"
41
40
  },
42
41
  "devDependencies": {
43
42
  "@types/async": "^3.2.24",
@@ -2,7 +2,8 @@ import * as jose from 'jose';
2
2
 
3
3
  export const HS_ALGORITHMS = ['HS256', 'HS384', 'HS512'];
4
4
  export const RSA_ALGORITHMS = ['RS256', 'RS384', 'RS512'];
5
- export const SUPPORTED_ALGORITHMS = [...HS_ALGORITHMS, ...RSA_ALGORITHMS];
5
+ export const OKP_ALGORITHMS = ['EdDSA'];
6
+ export const SUPPORTED_ALGORITHMS = [...HS_ALGORITHMS, ...RSA_ALGORITHMS, ...OKP_ALGORITHMS];
6
7
 
7
8
  export interface KeyOptions {
8
9
  /**
@@ -38,17 +39,19 @@ export class KeySpec {
38
39
  return this.source.kid;
39
40
  }
40
41
 
41
- matchesAlgorithm(jwtAlg: string) {
42
+ matchesAlgorithm(jwtAlg: string): boolean {
42
43
  if (this.source.alg) {
43
- return jwtAlg == this.source.alg;
44
- } else if (this.source.kty == 'RSA') {
44
+ return jwtAlg === this.source.alg;
45
+ } else if (this.source.kty === 'RSA') {
45
46
  return RSA_ALGORITHMS.includes(jwtAlg);
46
- } else if (this.source.kty == 'oct') {
47
+ } else if (this.source.kty === 'oct') {
47
48
  return HS_ALGORITHMS.includes(jwtAlg);
48
- } else {
49
- // We don't support 'ec' yet
50
- return false;
49
+ } else if (this.source.kty === 'OKP') {
50
+ return OKP_ALGORITHMS.includes(jwtAlg);
51
51
  }
52
+
53
+ // 'EC' is unsupported
54
+ return false;
52
55
  }
53
56
 
54
57
  async isValidSignature(token: string): Promise<boolean> {
@@ -56,7 +59,7 @@ export class KeySpec {
56
59
  await jose.compactVerify(token, this.key);
57
60
  return true;
58
61
  } catch (e) {
59
- if (e.code == 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED') {
62
+ if (e.code === 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED') {
60
63
  return false;
61
64
  } else {
62
65
  // Token format error most likely
@@ -73,8 +73,8 @@ export class RemoteJWKSCollector implements KeyCollector {
73
73
 
74
74
  let keys: KeySpec[] = [];
75
75
  for (let keyData of data.keys) {
76
- if (keyData.kty != 'RSA') {
77
- // Only RSA public keys supported here
76
+ if (keyData.kty != 'RSA' && keyData.kty != 'OKP') {
77
+ // HS (oct) keys not allowed because they are symmetric
78
78
  continue;
79
79
  }
80
80
 
@@ -1,9 +1,11 @@
1
1
  import { Command } from 'commander';
2
2
 
3
- import { logger } from '@powersync/lib-services-framework';
3
+ import { container, logger } from '@powersync/lib-services-framework';
4
4
  import * as v8 from 'v8';
5
- import * as storage from '../../storage/storage-index.js';
5
+ import * as system from '../../system/system-index.js';
6
6
  import * as utils from '../../util/util-index.js';
7
+
8
+ import { modules } from '../../index.js';
7
9
  import { extractRunnerOptions, wrapConfigCommand } from './config-command.js';
8
10
 
9
11
  const COMMAND_NAME = 'compact';
@@ -29,23 +31,25 @@ export function registerCompactAction(program: Command) {
29
31
 
30
32
  return compactCommand.description('Compact storage').action(async (options) => {
31
33
  const buckets = options.buckets?.split(',');
32
- if (buckets != null) {
34
+ if (buckets == null) {
33
35
  logger.info('Compacting storage for all buckets...');
34
36
  } else {
35
- logger.info(`Compacting storage for ${buckets.join(', ')}...`);
37
+ logger.info(`Compacting storage for ${buckets?.join(', ')}...`);
36
38
  }
37
- const runnerConfig = extractRunnerOptions(options);
38
- const configuration = await utils.loadConfig(runnerConfig);
39
- logger.info('Successfully loaded configuration...');
40
- const { storage: storageConfig } = configuration;
39
+ const config = await utils.loadConfig(extractRunnerOptions(options));
40
+ const serviceContext = new system.ServiceContextContainer(config);
41
+
42
+ // Register modules in order to allow custom module compacting
43
+ const moduleManager = container.getImplementation(modules.ModuleManager);
44
+ await moduleManager.initialize(serviceContext);
45
+
41
46
  logger.info('Connecting to storage...');
42
- const psdb = storage.createPowerSyncMongo(storageConfig);
43
- const client = psdb.client;
44
- await client.connect();
47
+
45
48
  try {
46
- const bucketStorage = new storage.MongoBucketStorage(psdb, {
47
- slot_name_prefix: configuration.slot_name_prefix
48
- });
49
+ // Start the storage engine in order to create the appropriate BucketStorage
50
+ await serviceContext.lifeCycleEngine.start();
51
+ const bucketStorage = serviceContext.storageEngine.activeBucketStorage;
52
+
49
53
  const active = await bucketStorage.getActiveSyncRulesContent();
50
54
  if (active == null) {
51
55
  logger.info('No active instance to compact');
@@ -57,9 +61,10 @@ export function registerCompactAction(program: Command) {
57
61
  logger.info('Successfully compacted storage.');
58
62
  } catch (e) {
59
63
  logger.error(`Failed to compact: ${e.toString()}`);
64
+ // Indirectly triggers lifeCycleEngine.stop
60
65
  process.exit(1);
61
66
  } finally {
62
- await client.close();
67
+ // Indirectly triggers lifeCycleEngine.stop
63
68
  process.exit(0);
64
69
  }
65
70
  });
@@ -1,7 +1,9 @@
1
- import { logger } from '@powersync/lib-services-framework';
1
+ import { container, logger, migrations } from '@powersync/lib-services-framework';
2
2
  import { Command } from 'commander';
3
3
 
4
- import * as migrations from '../../migrations/migrations-index.js';
4
+ import * as modules from '../../modules/modules-index.js';
5
+ import * as system from '../../system/system-index.js';
6
+ import * as utils from '../../util/util-index.js';
5
7
  import { extractRunnerOptions, wrapConfigCommand } from './config-command.js';
6
8
 
7
9
  const COMMAND_NAME = 'migrate';
@@ -15,12 +17,23 @@ export function registerMigrationAction(program: Command) {
15
17
  .description('Run migrations')
16
18
  .argument('<direction>', 'Migration direction. `up` or `down`')
17
19
  .action(async (direction: migrations.Direction, options) => {
20
+ const config = await utils.loadConfig(extractRunnerOptions(options));
21
+ const serviceContext = new system.ServiceContextContainer(config);
22
+
23
+ // Register modules in order to allow custom module migrations
24
+ const moduleManager = container.getImplementation(modules.ModuleManager);
25
+ await moduleManager.initialize(serviceContext);
26
+
18
27
  try {
19
- await migrations.migrate({
28
+ await serviceContext.migrations.migrate({
20
29
  direction,
21
- runner_config: extractRunnerOptions(options)
30
+ // Give the migrations access to the service context
31
+ migrationContext: {
32
+ service_context: serviceContext
33
+ }
22
34
  });
23
35
 
36
+ await serviceContext.migrations[Symbol.asyncDispose]();
24
37
  process.exit(0);
25
38
  } catch (e) {
26
39
  logger.error(`Migration failure`, e);
package/src/index.ts CHANGED
@@ -6,9 +6,6 @@ export * as api from './api/api-index.js';
6
6
  export * from './auth/auth-index.js';
7
7
  export * as auth from './auth/auth-index.js';
8
8
 
9
- export * from './db/db-index.js';
10
- export * as db from './db/db-index.js';
11
-
12
9
  export * from './entry/entry-index.js';
13
10
  export * as entry from './entry/entry-index.js';
14
11
 
@@ -18,8 +15,8 @@ export * as framework from '@powersync/lib-services-framework';
18
15
  export * from './metrics/Metrics.js';
19
16
  export * as metrics from './metrics/Metrics.js';
20
17
 
18
+ export * from './migrations/migrations-index.js';
21
19
  export * as migrations from './migrations/migrations-index.js';
22
- export * from './migrations/migrations.js';
23
20
 
24
21
  export * from './modules/modules-index.js';
25
22
  export * as modules from './modules/modules-index.js';
@@ -0,0 +1,43 @@
1
+ import * as framework from '@powersync/lib-services-framework';
2
+ import fs from 'fs/promises';
3
+ import path from 'path';
4
+ import * as system from '../system/system-index.js';
5
+
6
+ /**
7
+ * PowerSync service migrations each have this context available to the `up` and `down` methods.
8
+ */
9
+ export interface PowerSyncMigrationContext {
10
+ service_context: system.ServiceContext;
11
+ }
12
+
13
+ export interface PowerSyncMigrationGenerics extends framework.MigrationAgentGenerics {
14
+ MIGRATION_CONTEXT: PowerSyncMigrationContext;
15
+ }
16
+
17
+ export type PowerSyncMigrationFunction = framework.MigrationFunction<PowerSyncMigrationContext>;
18
+
19
+ export abstract class AbstractPowerSyncMigrationAgent extends framework.AbstractMigrationAgent<PowerSyncMigrationGenerics> {
20
+ abstract getInternalScriptsDir(): string;
21
+
22
+ async loadInternalMigrations(): Promise<framework.Migration<PowerSyncMigrationContext>[]> {
23
+ const migrationsDir = this.getInternalScriptsDir();
24
+ const files = await fs.readdir(migrationsDir);
25
+ const migrations = files.filter((file) => {
26
+ // Vitest resolves ts files
27
+ return ['.js', '.ts'].includes(path.extname(file));
28
+ });
29
+
30
+ return await Promise.all(
31
+ migrations.map(async (migration) => {
32
+ const module = await import(path.resolve(migrationsDir, migration));
33
+ return {
34
+ name: path.basename(migration).replace(path.extname(migration), ''),
35
+ up: module.up,
36
+ down: module.down
37
+ };
38
+ })
39
+ );
40
+ }
41
+ }
42
+
43
+ export type PowerSyncMigrationManager = framework.MigrationManager<PowerSyncMigrationGenerics>;
@@ -0,0 +1,15 @@
1
+ import * as framework from '@powersync/lib-services-framework';
2
+ import * as system from '../system/system-index.js';
3
+
4
+ export const ensureAutomaticMigrations = async (options: { serviceContext: system.ServiceContext }) => {
5
+ const { serviceContext } = options;
6
+ if (serviceContext.configuration.migrations?.disable_auto_migration) {
7
+ return;
8
+ }
9
+ await serviceContext.migrations.migrate({
10
+ direction: framework.migrations.Direction.Up,
11
+ migrationContext: {
12
+ service_context: serviceContext
13
+ }
14
+ });
15
+ };
@@ -1,3 +1,2 @@
1
- export * from './definitions.js';
2
- export * from './executor.js';
3
- export * from './migrations.js';
1
+ export * from './ensure-automatic-migrations.js';
2
+ export * from './PowerSyncMigrationManager.js';
@@ -4,6 +4,7 @@ import * as api from '../api/api-index.js';
4
4
 
5
5
  import { ADMIN_ROUTES } from './endpoints/admin.js';
6
6
  import { CHECKPOINT_ROUTES } from './endpoints/checkpointing.js';
7
+ import { PROBES_ROUTES } from './endpoints/probes.js';
7
8
  import { syncStreamReactive } from './endpoints/socket-route.js';
8
9
  import { SYNC_RULES_ROUTES } from './endpoints/sync-rules.js';
9
10
  import { SYNC_STREAM_ROUTES } from './endpoints/sync-stream.js';
@@ -47,7 +48,7 @@ export class RouterEngine {
47
48
 
48
49
  // Default routes
49
50
  this.routes = {
50
- api_routes: [...ADMIN_ROUTES, ...CHECKPOINT_ROUTES, ...SYNC_RULES_ROUTES],
51
+ api_routes: [...ADMIN_ROUTES, ...CHECKPOINT_ROUTES, ...SYNC_RULES_ROUTES, ...PROBES_ROUTES],
51
52
  stream_routes: [...SYNC_STREAM_ROUTES],
52
53
  socket_routes: [syncStreamReactive]
53
54
  };
@@ -8,13 +8,49 @@ import {
8
8
  SqliteRow,
9
9
  ToastableSqliteRow
10
10
  } from '@powersync/service-sync-rules';
11
+ import { BSON } from 'bson';
11
12
  import * as util from '../util/util-index.js';
12
13
  import { ReplicationEventPayload } from './ReplicationEventPayload.js';
13
14
  import { SourceEntityDescriptor } from './SourceEntity.js';
14
15
  import { SourceTable } from './SourceTable.js';
15
- import { BatchedCustomWriteCheckpointOptions, ReplicaId } from './storage-index.js';
16
+ import { BatchedCustomWriteCheckpointOptions } from './storage-index.js';
16
17
  import { SyncStorageWriteCheckpointAPI } from './WriteCheckpointAPI.js';
17
18
 
19
+ /**
20
+ * Replica id uniquely identifying a row on the source database.
21
+ *
22
+ * Can be any value serializable to BSON.
23
+ *
24
+ * If the value is an entire document, the data serialized to a v5 UUID may be a good choice here.
25
+ */
26
+ export type ReplicaId = BSON.UUID | BSON.Document | any;
27
+
28
+ export enum SyncRuleState {
29
+ /**
30
+ * New sync rules - needs to be processed (initial replication).
31
+ *
32
+ * While multiple sets of sync rules _can_ be in PROCESSING,
33
+ * it's generally pointless, so we only keep one in that state.
34
+ */
35
+ PROCESSING = 'PROCESSING',
36
+
37
+ /**
38
+ * Sync rule processing is done, and can be used for sync.
39
+ *
40
+ * Only one set of sync rules should be in ACTIVE state.
41
+ */
42
+ ACTIVE = 'ACTIVE',
43
+ /**
44
+ * This state is used when the sync rules has been replaced,
45
+ * and replication is or should be stopped.
46
+ */
47
+ STOP = 'STOP',
48
+ /**
49
+ * After sync rules have been stopped, the data needs to be
50
+ * deleted. Once deleted, the state is TERMINATED.
51
+ */
52
+ TERMINATED = 'TERMINATED'
53
+ }
18
54
  export interface BucketStorageFactoryListener extends DisposableListener {
19
55
  syncStorageCreated: (storage: SyncRulesBucketStorage) => void;
20
56
  replicationEvent: (event: ReplicationEventPayload) => void;
@@ -211,6 +247,13 @@ export interface StartBatchOptions extends ParseSyncRulesOptions {
211
247
  * database, for example from MongoDB.
212
248
  */
213
249
  storeCurrentData: boolean;
250
+
251
+ /**
252
+ * Set to true for initial replication.
253
+ *
254
+ * This will avoid creating new operations for rows previously replicated.
255
+ */
256
+ skipExistingRows?: boolean;
214
257
  }
215
258
 
216
259
  export interface SyncRulesBucketStorageListener extends DisposableListener {
@@ -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.
@@ -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
+ }