@powersync/service-module-mongodb-storage 0.23.2 → 0.24.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 (99) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/LICENSE +201 -67
  3. package/dist/storage/MongoBucketStorage.d.ts +1 -0
  4. package/dist/storage/MongoBucketStorage.js +45 -83
  5. package/dist/storage/MongoBucketStorage.js.map +1 -1
  6. package/dist/storage/implementation/MongoBucketBatch.d.ts +60 -39
  7. package/dist/storage/implementation/MongoBucketBatch.js +123 -296
  8. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  9. package/dist/storage/implementation/MongoOpIdAllocator.d.ts +6 -1
  10. package/dist/storage/implementation/MongoOpIdAllocator.js +12 -5
  11. package/dist/storage/implementation/MongoOpIdAllocator.js.map +1 -1
  12. package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +7 -0
  13. package/dist/storage/implementation/MongoParsedSyncConfigSet.js +28 -2
  14. package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -1
  15. package/dist/storage/implementation/MongoReplicationOptions.d.ts +27 -0
  16. package/dist/storage/implementation/MongoReplicationOptions.js +26 -0
  17. package/dist/storage/implementation/MongoReplicationOptions.js.map +1 -0
  18. package/dist/storage/implementation/MongoSyncBucketStorage.js +2 -3
  19. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  20. package/dist/storage/implementation/MongoSyncRulesLock.d.ts +15 -2
  21. package/dist/storage/implementation/MongoSyncRulesLock.js +36 -14
  22. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
  23. package/dist/storage/implementation/common/BatchOperationBuffer.d.ts +79 -0
  24. package/dist/storage/implementation/common/BatchOperationBuffer.js +286 -0
  25. package/dist/storage/implementation/common/BatchOperationBuffer.js.map +1 -0
  26. package/dist/storage/implementation/common/PersistedBatch.d.ts +10 -103
  27. package/dist/storage/implementation/common/PersistedBatch.js +1 -291
  28. package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
  29. package/dist/storage/implementation/common/PreparedPublication.d.ts +43 -0
  30. package/dist/storage/implementation/common/PreparedPublication.js +2 -0
  31. package/dist/storage/implementation/common/PreparedPublication.js.map +1 -0
  32. package/dist/storage/implementation/common/SourceRecordStore.d.ts +0 -2
  33. package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +17 -13
  34. package/dist/storage/implementation/v1/MongoBucketBatchV1.js +259 -18
  35. package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
  36. package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +21 -15
  37. package/dist/storage/implementation/v1/PersistedBatchV1.js +58 -24
  38. package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -1
  39. package/dist/storage/implementation/v1/SourceRecordStoreV1.d.ts +0 -2
  40. package/dist/storage/implementation/v1/SourceRecordStoreV1.js +0 -3
  41. package/dist/storage/implementation/v1/SourceRecordStoreV1.js.map +1 -1
  42. package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +25 -14
  43. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +350 -23
  44. package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
  45. package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +1 -0
  46. package/dist/storage/implementation/v3/MongoCompactorV3.js +10 -4
  47. package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
  48. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +39 -28
  49. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
  50. package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +46 -24
  51. package/dist/storage/implementation/v3/PersistedBatchV3.js +159 -83
  52. package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
  53. package/dist/storage/implementation/v3/SourceRecordStoreV3.d.ts +12 -0
  54. package/dist/storage/implementation/v3/SourceRecordStoreV3.js +41 -13
  55. package/dist/storage/implementation/v3/SourceRecordStoreV3.js.map +1 -1
  56. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.d.ts +4 -0
  57. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +4 -0
  58. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -1
  59. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +27 -2
  60. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +61 -5
  61. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -1
  62. package/package.json +9 -10
  63. package/src/storage/MongoBucketStorage.ts +55 -122
  64. package/src/storage/implementation/MongoBucketBatch.ts +185 -392
  65. package/src/storage/implementation/MongoOpIdAllocator.ts +12 -5
  66. package/src/storage/implementation/MongoParsedSyncConfigSet.ts +40 -1
  67. package/src/storage/implementation/MongoReplicationOptions.ts +28 -0
  68. package/src/storage/implementation/MongoSyncBucketStorage.ts +2 -3
  69. package/src/storage/implementation/MongoSyncRulesLock.ts +39 -17
  70. package/src/storage/implementation/common/BatchOperationBuffer.ts +353 -0
  71. package/src/storage/implementation/common/PersistedBatch.ts +12 -390
  72. package/src/storage/implementation/common/PreparedPublication.ts +49 -0
  73. package/src/storage/implementation/common/SourceRecordStore.ts +0 -2
  74. package/src/storage/implementation/v1/MongoBucketBatchV1.ts +339 -27
  75. package/src/storage/implementation/v1/PersistedBatchV1.ts +74 -32
  76. package/src/storage/implementation/v1/SourceRecordStoreV1.ts +0 -5
  77. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +430 -32
  78. package/src/storage/implementation/v3/MongoCompactorV3.ts +13 -4
  79. package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +43 -29
  80. package/src/storage/implementation/v3/PersistedBatchV3.ts +209 -108
  81. package/src/storage/implementation/v3/SourceRecordStoreV3.ts +45 -15
  82. package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +9 -0
  83. package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +79 -5
  84. package/test/src/checkpoint_cleanup.test.ts +75 -0
  85. package/test/src/concurrent_writers.test.ts +127 -53
  86. package/test/src/helpers/PublicationCommandMonitor.ts +119 -0
  87. package/test/src/helpers/concurrentWriter.mjs +2 -2
  88. package/test/src/invalid_sync_config.test.ts +9 -0
  89. package/test/src/object_storage_lifecycle.test.ts +72 -2
  90. package/test/src/prepared_publication.test.ts +388 -0
  91. package/test/src/replication_preparation.test.ts +829 -0
  92. package/test/src/replication_publication_baseline.test.ts +203 -0
  93. package/test/src/replication_publication_batching.test.ts +212 -0
  94. package/test/src/replication_uploads.test.ts +393 -0
  95. package/test/src/source_table_config.test.ts +4 -4
  96. package/test/src/storage_object_storage_inline_threshold.test.ts +1 -1
  97. package/test/src/storage_s3_compaction_lifecycle.test.ts +93 -1
  98. package/test/src/storage_sync.test.ts +101 -2
  99. package/tsconfig.tsbuildinfo +1 -1
@@ -1,4 +1,5 @@
1
1
  import { MongoSyncBucketStorageV3 } from '@module/storage/implementation/v3/MongoSyncBucketStorageV3.js';
2
+ import { ReplicationAbortedError } from '@powersync/lib-services-framework';
2
3
  import {
3
4
  deserializeParameterLookup,
4
5
  JwtPayload,
@@ -7,7 +8,7 @@ import {
7
8
  updateSyncRulesFromYaml
8
9
  } from '@powersync/service-core';
9
10
  import { bucketRequest, getTestStorage, register, test_utils } from '@powersync/service-core-tests';
10
- import { RequestParameters, ScopedParameterLookup, SqlSyncRules } from '@powersync/service-sync-rules';
11
+ import { parseSyncConfig, RequestParameters, ScopedParameterLookup } from '@powersync/service-sync-rules';
11
12
  import * as bson from 'bson';
12
13
  import { describe, expect, test } from 'vitest';
13
14
  import { MongoBucketStorage } from '../../src/storage/MongoBucketStorage.js';
@@ -72,7 +73,7 @@ function objectIdGenerator(id: string) {
72
73
  * `ResolveTablesOptions.parsedSyncConfig`.
73
74
  */
74
75
  function parsedSyncConfigSetFor(yaml: string, storageVersion: number) {
75
- const parsed = SqlSyncRules.fromYaml(yaml, test_utils.PARSE_OPTIONS);
76
+ const parsed = parseSyncConfig(yaml, test_utils.PARSE_OPTIONS);
76
77
  // These fixtures are legacy Sync Rules, which always report a deprecation warning.
77
78
  expect(parsed.errors.filter((error) => error.type !== 'warning')).toEqual([]);
78
79
  return new MongoParsedSyncConfigSet(1, getMongoStorageConfig(storageVersion), 'test_slot', [
@@ -939,6 +940,70 @@ event_definitions:
939
940
  expect(deserializeParameterLookup(parameterEntry!.lookup)).toEqual(['shape-check']);
940
941
  });
941
942
 
943
+ test.runIf(storageVersion >= 3)('fetches parameter sets beyond the aggregation stage limit', async () => {
944
+ await using factory = await storageConfig.factory();
945
+ const syncRules = await factory.updateSyncRules(
946
+ updateSyncRulesFromYaml(
947
+ /* yaml */ `
948
+ # Parameter lookup batching regression fixture.
949
+ config:
950
+ edition: 3
951
+ streams:
952
+ by_owner:
953
+ auto_subscribe: true
954
+ query: |
955
+ SELECT * FROM test
956
+ WHERE id IN (SELECT owner_id FROM test WHERE id = auth.parameter('test'))
957
+ `,
958
+ { storageVersion }
959
+ )
960
+ );
961
+ const bucketStorage = await getTestStorage(factory, syncRules);
962
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
963
+ const sourceTable = await test_utils.resolveTestTable(writer, 'test', ['id'], INITIALIZED_MONGO_STORAGE_FACTORY);
964
+
965
+ for (const id of ['row-first', 'row-middle', 'row-last']) {
966
+ await writer.save({
967
+ sourceTable,
968
+ tag: storage.SaveOperationTag.INSERT,
969
+ after: { id, description: id, owner_id: `user-${id}` },
970
+ afterReplicaId: test_utils.rid(id)
971
+ });
972
+ }
973
+ await writer.markAllSnapshotDone('1/1');
974
+ await writer.commit('1/1');
975
+
976
+ const parsed = syncRules.syncConfigContent[0].parsed(test_utils.PARSE_OPTIONS);
977
+ const [parameterSource] = [...parsed.syncConfigs[0].config.bucketParameterLookupSources];
978
+ const scope = parsed.hydrationState.getParameterIndexLookupScope(parameterSource);
979
+
980
+ // MongoDB rejects aggregation pipelines with more than 1000 stages, and each
981
+ // lookup after the first adds a $unionWith stage (#873).
982
+ const lookups: ScopedParameterLookup[] = [];
983
+ for (let i = 0; i < 996; i++) {
984
+ let id = `missing-${i}`;
985
+ if (i == 0) {
986
+ id = 'row-first';
987
+ } else if (i == 900) {
988
+ id = 'row-middle';
989
+ } else if (i == 995) {
990
+ id = 'row-last';
991
+ }
992
+ lookups.push(ScopedParameterLookup.direct(scope, [id]));
993
+ }
994
+
995
+ const checkpoint = await bucketStorage.getCheckpoint();
996
+ const parameterSets = await checkpoint.getParameterSets(lookups, 1000);
997
+
998
+ expect(parameterSets.length).toEqual(3);
999
+ await expect(checkpoint.getParameterSets(lookups, 2)).rejects.toThrow('Too many parameter results');
1000
+ expect(await checkpoint.getParameterSets(lookups, 3)).toEqual(parameterSets);
1001
+ const rowsByKey = new Map(parameterSets.map((set) => [set.lookup.indexKey[0], set.rows]));
1002
+ expect(rowsByKey.get('row-first')).toEqual([{ '0': 'user-row-first' }]);
1003
+ expect(rowsByKey.get('row-middle')).toEqual([{ '0': 'user-row-middle' }]);
1004
+ expect(rowsByKey.get('row-last')).toEqual([{ '0': 'user-row-last' }]);
1005
+ });
1006
+
942
1007
  test.runIf(storageVersion >= 3)('replaces an existing deploying sync config', async () => {
943
1008
  await using factory = await storageConfig.factory();
944
1009
 
@@ -1343,6 +1408,22 @@ streams:
1343
1408
  expect(updatedActiveStorage.replicationStream.replicationJobId).toContain(
1344
1409
  (await factory.getActiveSyncConfig())!.content.syncConfigId
1345
1410
  );
1411
+
1412
+ // The full-stream writer still holds the old config's definition mapping.
1413
+ // Both metadata writes and the next checkpoint must exit so a replacement
1414
+ // job can load only live mappings.
1415
+ await expect(
1416
+ secondWriter.resolveTables({
1417
+ connection_id: 1,
1418
+ source: sourceDescriptor('todos', { objectId: 'todos-relation-after-activation' }),
1419
+ idGenerator: objectIdGenerator('6544e3899293153fa7b3835e')
1420
+ })
1421
+ ).rejects.toBeInstanceOf(ReplicationAbortedError);
1422
+ await expect(secondWriter.commit('3/1')).rejects.toBeInstanceOf(ReplicationAbortedError);
1423
+ const persistedStream = (await (factory as MongoBucketStorage).db.sync_rules.findOne({
1424
+ _id: first.replicationStreamId
1425
+ })) as ReplicationStreamDocumentV3;
1426
+ expect(persistedStream.resume_lsn).toBe('2/1');
1346
1427
  }
1347
1428
  );
1348
1429
 
@@ -1870,6 +1951,24 @@ streams:
1870
1951
  }
1871
1952
  );
1872
1953
 
1954
+ test.runIf(storageVersion >= 3)('storage metrics collect sizes across union batches', async () => {
1955
+ await using factory = await storageConfig.factory();
1956
+ const mongoFactory = factory as MongoBucketStorage;
1957
+ const before = await factory.getStorageMetrics();
1958
+ let expectedSize = 0;
1959
+ for (let index = 0; index < 101; index++) {
1960
+ const collection = mongoFactory.db.db.collection(`bucket_data_999_metrics_${index}`);
1961
+ await collection.insertOne({ value: 'x'.repeat(index + 1) });
1962
+ const [stats] = await collection.aggregate([{ $collStats: { storageStats: {} } }]).toArray();
1963
+ expectedSize += Number(stats.storageStats.size);
1964
+ }
1965
+
1966
+ const after = await factory.getStorageMetrics();
1967
+ expect(after.operations_size_bytes - before.operations_size_bytes).toBe(expectedSize);
1968
+ expect(after.parameters_size_bytes).toBe(before.parameters_size_bytes);
1969
+ expect(after.replication_size_bytes).toBe(before.replication_size_bytes);
1970
+ });
1971
+
1873
1972
  test.runIf(storageVersion < 3)('storage metrics include v1 current_data', async () => {
1874
1973
  await using factory = await storageConfig.factory();
1875
1974
  const syncRules = await factory.updateSyncRules(