@powersync/service-module-mongodb 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 (137) hide show
  1. package/CHANGELOG.md +69 -4
  2. package/dist/db/db-index.d.ts +1 -0
  3. package/dist/db/db-index.js +2 -0
  4. package/dist/db/db-index.js.map +1 -0
  5. package/dist/db/mongo.d.ts +35 -0
  6. package/dist/db/mongo.js +73 -0
  7. package/dist/db/mongo.js.map +1 -0
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.js +2 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/locks/MonogLocks.d.ts +36 -0
  12. package/dist/locks/MonogLocks.js +83 -0
  13. package/dist/locks/MonogLocks.js.map +1 -0
  14. package/dist/migrations/MonogMigrationAgent.d.ts +12 -0
  15. package/dist/migrations/MonogMigrationAgent.js +25 -0
  16. package/dist/migrations/MonogMigrationAgent.js.map +1 -0
  17. package/dist/migrations/db/migrations/1684951997326-init.d.ts +3 -0
  18. package/dist/migrations/db/migrations/1684951997326-init.js +30 -0
  19. package/dist/migrations/db/migrations/1684951997326-init.js.map +1 -0
  20. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.d.ts +2 -0
  21. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js +5 -0
  22. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js.map +1 -0
  23. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.d.ts +3 -0
  24. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js +54 -0
  25. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +1 -0
  26. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.d.ts +3 -0
  27. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js +26 -0
  28. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js.map +1 -0
  29. package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.d.ts +3 -0
  30. package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.js +28 -0
  31. package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.js.map +1 -0
  32. package/dist/migrations/mongo-migration-store.d.ts +7 -0
  33. package/dist/migrations/mongo-migration-store.js +49 -0
  34. package/dist/migrations/mongo-migration-store.js.map +1 -0
  35. package/dist/module/MongoModule.js +15 -4
  36. package/dist/module/MongoModule.js.map +1 -1
  37. package/dist/replication/MongoManager.d.ts +1 -1
  38. package/dist/replication/MongoManager.js +3 -2
  39. package/dist/replication/MongoManager.js.map +1 -1
  40. package/dist/storage/MongoBucketStorage.d.ts +48 -0
  41. package/dist/storage/MongoBucketStorage.js +425 -0
  42. package/dist/storage/MongoBucketStorage.js.map +1 -0
  43. package/dist/storage/implementation/MongoBucketBatch.d.ts +72 -0
  44. package/dist/storage/implementation/MongoBucketBatch.js +681 -0
  45. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -0
  46. package/dist/storage/implementation/MongoCompactor.d.ts +40 -0
  47. package/dist/storage/implementation/MongoCompactor.js +310 -0
  48. package/dist/storage/implementation/MongoCompactor.js.map +1 -0
  49. package/dist/storage/implementation/MongoIdSequence.d.ts +12 -0
  50. package/dist/storage/implementation/MongoIdSequence.js +21 -0
  51. package/dist/storage/implementation/MongoIdSequence.js.map +1 -0
  52. package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +9 -0
  53. package/dist/storage/implementation/MongoPersistedSyncRules.js +9 -0
  54. package/dist/storage/implementation/MongoPersistedSyncRules.js.map +1 -0
  55. package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +20 -0
  56. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +26 -0
  57. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +1 -0
  58. package/dist/storage/implementation/MongoStorageProvider.d.ts +6 -0
  59. package/dist/storage/implementation/MongoStorageProvider.js +34 -0
  60. package/dist/storage/implementation/MongoStorageProvider.js.map +1 -0
  61. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +36 -0
  62. package/dist/storage/implementation/MongoSyncBucketStorage.js +529 -0
  63. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -0
  64. package/dist/storage/implementation/MongoSyncRulesLock.d.ts +16 -0
  65. package/dist/storage/implementation/MongoSyncRulesLock.js +65 -0
  66. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -0
  67. package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +20 -0
  68. package/dist/storage/implementation/MongoWriteCheckpointAPI.js +104 -0
  69. package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -0
  70. package/dist/storage/implementation/OperationBatch.d.ts +34 -0
  71. package/dist/storage/implementation/OperationBatch.js +119 -0
  72. package/dist/storage/implementation/OperationBatch.js.map +1 -0
  73. package/dist/storage/implementation/PersistedBatch.d.ts +46 -0
  74. package/dist/storage/implementation/PersistedBatch.js +223 -0
  75. package/dist/storage/implementation/PersistedBatch.js.map +1 -0
  76. package/dist/storage/implementation/config.d.ts +19 -0
  77. package/dist/storage/implementation/config.js +26 -0
  78. package/dist/storage/implementation/config.js.map +1 -0
  79. package/dist/storage/implementation/db.d.ts +36 -0
  80. package/dist/storage/implementation/db.js +47 -0
  81. package/dist/storage/implementation/db.js.map +1 -0
  82. package/dist/storage/implementation/models.d.ts +139 -0
  83. package/dist/storage/implementation/models.js +2 -0
  84. package/dist/storage/implementation/models.js.map +1 -0
  85. package/dist/storage/implementation/util.d.ts +58 -0
  86. package/dist/storage/implementation/util.js +196 -0
  87. package/dist/storage/implementation/util.js.map +1 -0
  88. package/dist/storage/storage-index.d.ts +14 -0
  89. package/dist/storage/storage-index.js +15 -0
  90. package/dist/storage/storage-index.js.map +1 -0
  91. package/dist/types/types.d.ts +3 -0
  92. package/dist/types/types.js +4 -1
  93. package/dist/types/types.js.map +1 -1
  94. package/package.json +11 -8
  95. package/src/db/db-index.ts +1 -0
  96. package/src/db/mongo.ts +81 -0
  97. package/src/index.ts +4 -0
  98. package/src/locks/MonogLocks.ts +147 -0
  99. package/src/migrations/MonogMigrationAgent.ts +39 -0
  100. package/src/migrations/db/migrations/1684951997326-init.ts +39 -0
  101. package/src/migrations/db/migrations/1688556755264-initial-sync-rules.ts +5 -0
  102. package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +105 -0
  103. package/src/migrations/db/migrations/1711543888062-write-checkpoint-index.ts +38 -0
  104. package/src/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.ts +40 -0
  105. package/src/migrations/mongo-migration-store.ts +62 -0
  106. package/src/module/MongoModule.ts +18 -4
  107. package/src/replication/MongoManager.ts +6 -2
  108. package/src/storage/MongoBucketStorage.ts +530 -0
  109. package/src/storage/implementation/MongoBucketBatch.ts +893 -0
  110. package/src/storage/implementation/MongoCompactor.ts +392 -0
  111. package/src/storage/implementation/MongoIdSequence.ts +24 -0
  112. package/src/storage/implementation/MongoPersistedSyncRules.ts +16 -0
  113. package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +49 -0
  114. package/src/storage/implementation/MongoStorageProvider.ts +42 -0
  115. package/src/storage/implementation/MongoSyncBucketStorage.ts +612 -0
  116. package/src/storage/implementation/MongoSyncRulesLock.ts +88 -0
  117. package/src/storage/implementation/MongoWriteCheckpointAPI.ts +146 -0
  118. package/src/storage/implementation/OperationBatch.ts +130 -0
  119. package/src/storage/implementation/PersistedBatch.ts +283 -0
  120. package/src/storage/implementation/config.ts +40 -0
  121. package/src/storage/implementation/db.ts +88 -0
  122. package/src/storage/implementation/models.ts +160 -0
  123. package/src/storage/implementation/util.ts +209 -0
  124. package/src/storage/storage-index.ts +14 -0
  125. package/src/types/types.ts +8 -1
  126. package/test/src/__snapshots__/storage_sync.test.ts.snap +332 -0
  127. package/test/src/change_stream.test.ts +34 -33
  128. package/test/src/change_stream_utils.ts +6 -6
  129. package/test/src/env.ts +1 -0
  130. package/test/src/slow_tests.test.ts +4 -4
  131. package/test/src/storage.test.ts +7 -0
  132. package/test/src/storage_compacting.test.ts +6 -0
  133. package/test/src/storage_sync.test.ts +113 -0
  134. package/test/src/util.ts +20 -7
  135. package/test/tsconfig.json +4 -0
  136. package/tsconfig.tsbuildinfo +1 -1
  137. package/vitest.config.ts +1 -1
@@ -0,0 +1,47 @@
1
+ import * as db from '../../db/db-index.js';
2
+ import { BSON_DESERIALIZE_OPTIONS } from './util.js';
3
+ export function createPowerSyncMongo(config) {
4
+ return new PowerSyncMongo(db.mongo.createMongoClient(config), { database: config.database });
5
+ }
6
+ export class PowerSyncMongo {
7
+ constructor(client, options) {
8
+ this.client = client;
9
+ const db = client.db(options?.database, {
10
+ ...BSON_DESERIALIZE_OPTIONS
11
+ });
12
+ this.db = db;
13
+ this.current_data = db.collection('current_data');
14
+ this.bucket_data = db.collection('bucket_data');
15
+ this.bucket_parameters = db.collection('bucket_parameters');
16
+ this.op_id_sequence = db.collection('op_id_sequence');
17
+ this.sync_rules = db.collection('sync_rules');
18
+ this.source_tables = db.collection('source_tables');
19
+ this.custom_write_checkpoints = db.collection('custom_write_checkpoints');
20
+ this.write_checkpoints = db.collection('write_checkpoints');
21
+ this.instance = db.collection('instance');
22
+ this.locks = this.db.collection('locks');
23
+ }
24
+ /**
25
+ * Clear all collections.
26
+ */
27
+ async clear() {
28
+ await this.current_data.deleteMany({});
29
+ await this.bucket_data.deleteMany({});
30
+ await this.bucket_parameters.deleteMany({});
31
+ await this.op_id_sequence.deleteMany({});
32
+ await this.sync_rules.deleteMany({});
33
+ await this.source_tables.deleteMany({});
34
+ await this.write_checkpoints.deleteMany({});
35
+ await this.instance.deleteOne({});
36
+ await this.locks.deleteMany({});
37
+ }
38
+ /**
39
+ * Drop the entire database.
40
+ *
41
+ * Primarily for tests.
42
+ */
43
+ async drop() {
44
+ await this.db.dropDatabase();
45
+ }
46
+ }
47
+ //# sourceMappingURL=db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../../../src/storage/implementation/db.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAa3C,OAAO,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AASrD,MAAM,UAAU,oBAAoB,CAAC,MAAqC;IACxE,OAAO,IAAI,cAAc,CAAC,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;AAC/F,CAAC;AAED,MAAM,OAAO,cAAc;IAezB,YAAY,MAAyB,EAAE,OAA+B;QACpE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QAErB,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE;YACtC,GAAG,wBAAwB;SAC5B,CAAC,CAAC;QACH,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QAEb,IAAI,CAAC,YAAY,GAAG,EAAE,CAAC,UAAU,CAAsB,cAAc,CAAC,CAAC;QACvE,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;QAChD,IAAI,CAAC,iBAAiB,GAAG,EAAE,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAC;QAC5D,IAAI,CAAC,cAAc,GAAG,EAAE,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;QACtD,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;QAC9C,IAAI,CAAC,aAAa,GAAG,EAAE,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;QACpD,IAAI,CAAC,wBAAwB,GAAG,EAAE,CAAC,UAAU,CAAC,0BAA0B,CAAC,CAAC;QAC1E,IAAI,CAAC,iBAAiB,GAAG,EAAE,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAC;QAC5D,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;QAC1C,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAC3C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACvC,MAAM,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACtC,MAAM,IAAI,CAAC,iBAAiB,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAC5C,MAAM,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACzC,MAAM,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACrC,MAAM,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACxC,MAAM,IAAI,CAAC,iBAAiB,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAC5C,MAAM,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QAClC,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IAClC,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,IAAI;QACR,MAAM,IAAI,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC;IAC/B,CAAC;CACF"}
@@ -0,0 +1,139 @@
1
+ import { storage } from '@powersync/service-core';
2
+ import { SqliteJsonValue } from '@powersync/service-sync-rules';
3
+ import * as bson from 'bson';
4
+ /**
5
+ * Replica id uniquely identifying a row on the source database.
6
+ *
7
+ * Can be any value serializable to BSON.
8
+ *
9
+ * If the value is an entire document, the data serialized to a v5 UUID may be a good choice here.
10
+ */
11
+ export type ReplicaId = bson.UUID | bson.Document | any;
12
+ export interface SourceKey {
13
+ /** group_id */
14
+ g: number;
15
+ /** source table id */
16
+ t: bson.ObjectId;
17
+ /** source key */
18
+ k: ReplicaId;
19
+ }
20
+ export interface CurrentDataDocument {
21
+ _id: SourceKey;
22
+ data: bson.Binary;
23
+ buckets: CurrentBucket[];
24
+ lookups: bson.Binary[];
25
+ }
26
+ export interface CurrentBucket {
27
+ bucket: string;
28
+ table: string;
29
+ id: string;
30
+ }
31
+ export interface BucketParameterDocument {
32
+ _id: bigint;
33
+ key: SourceKey;
34
+ lookup: bson.Binary;
35
+ bucket_parameters: Record<string, SqliteJsonValue>[];
36
+ }
37
+ export interface BucketDataKey {
38
+ /** group_id */
39
+ g: number;
40
+ /** bucket name */
41
+ b: string;
42
+ /** op_id */
43
+ o: bigint;
44
+ }
45
+ export interface BucketDataDocument {
46
+ _id: BucketDataKey;
47
+ op: OpType;
48
+ source_table?: bson.ObjectId;
49
+ source_key?: storage.ReplicaId;
50
+ table?: string;
51
+ row_id?: string;
52
+ checksum: number;
53
+ data: string | null;
54
+ target_op?: bigint | null;
55
+ }
56
+ export type OpType = 'PUT' | 'REMOVE' | 'MOVE' | 'CLEAR';
57
+ export interface SourceTableDocument {
58
+ _id: bson.ObjectId;
59
+ group_id: number;
60
+ connection_id: number;
61
+ relation_id: number | string | undefined;
62
+ schema_name: string;
63
+ table_name: string;
64
+ replica_id_columns: string[] | null;
65
+ replica_id_columns2: {
66
+ name: string;
67
+ type_oid?: number;
68
+ type?: string;
69
+ }[] | undefined;
70
+ snapshot_done: boolean | undefined;
71
+ }
72
+ export interface IdSequenceDocument {
73
+ _id: string;
74
+ op_id: bigint;
75
+ }
76
+ export interface SyncRuleDocument {
77
+ _id: number;
78
+ state: storage.SyncRuleState;
79
+ /**
80
+ * True if initial snapshot has been replicated.
81
+ *
82
+ * Can only be false if state == PROCESSING.
83
+ */
84
+ snapshot_done: boolean;
85
+ /**
86
+ * The last consistent checkpoint.
87
+ *
88
+ * There may be higher OpIds used in the database if we're in the middle of replicating a large transaction.
89
+ */
90
+ last_checkpoint: bigint | null;
91
+ /**
92
+ * The LSN associated with the last consistent checkpoint.
93
+ */
94
+ last_checkpoint_lsn: string | null;
95
+ /**
96
+ * If set, no new checkpoints may be created < this value.
97
+ */
98
+ no_checkpoint_before: string | null;
99
+ /**
100
+ * Goes together with no_checkpoint_before.
101
+ *
102
+ * If a keepalive is triggered that creates the checkpoint > no_checkpoint_before,
103
+ * then the checkpoint must be equal to this keepalive_op.
104
+ */
105
+ keepalive_op: string | null;
106
+ slot_name: string | null;
107
+ /**
108
+ * Last time we persisted a checkpoint.
109
+ *
110
+ * This may be old if no data is incoming.
111
+ */
112
+ last_checkpoint_ts: Date | null;
113
+ /**
114
+ * Last time we persisted a checkpoint or keepalive.
115
+ *
116
+ * This should stay fairly current while replicating.
117
+ */
118
+ last_keepalive_ts: Date | null;
119
+ /**
120
+ * If an error is stopping replication, it will be stored here.
121
+ */
122
+ last_fatal_error: string | null;
123
+ content: string;
124
+ }
125
+ export interface CustomWriteCheckpointDocument {
126
+ _id: bson.ObjectId;
127
+ user_id: string;
128
+ checkpoint: bigint;
129
+ sync_rules_id: number;
130
+ }
131
+ export interface WriteCheckpointDocument {
132
+ _id: bson.ObjectId;
133
+ user_id: string;
134
+ lsns: Record<string, string>;
135
+ client_id: bigint;
136
+ }
137
+ export interface InstanceDocument {
138
+ _id: string;
139
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.js","sourceRoot":"","sources":["../../../src/storage/implementation/models.ts"],"names":[],"mappings":""}
@@ -0,0 +1,58 @@
1
+ import { storage, utils } from '@powersync/service-core';
2
+ import { SqliteJsonValue } from '@powersync/service-sync-rules';
3
+ import * as bson from 'bson';
4
+ import * as mongo from 'mongodb';
5
+ import { BucketDataDocument } from './models.js';
6
+ /**
7
+ * Lookup serialization must be number-agnostic. I.e. normalize numbers, instead of preserving numbers.
8
+ * @param lookup
9
+ */
10
+ export declare function serializeLookup(lookup: SqliteJsonValue[]): bson.Binary;
11
+ export declare function idPrefixFilter<T>(prefix: Partial<T>, rest: (keyof T)[]): mongo.Condition<T>;
12
+ export declare function generateSlotName(prefix: string, sync_rules_id: number): string;
13
+ /**
14
+ * Read a single batch of data from a cursor, then close it.
15
+ *
16
+ * We do our best to avoid MongoDB fetching any more data than this single batch.
17
+ *
18
+ * This is similar to using `singleBatch: true` in find options.
19
+ * However, that makes `has_more` detection very difficult, since the cursor is always closed
20
+ * after the first batch. Instead, we do a workaround to only fetch a single batch below.
21
+ *
22
+ * For this to be effective, set batchSize = limit in the find command.
23
+ */
24
+ export declare function readSingleBatch<T>(cursor: mongo.FindCursor<T>): Promise<{
25
+ data: T[];
26
+ hasMore: boolean;
27
+ }>;
28
+ export declare const BSON_DESERIALIZE_OPTIONS: bson.DeserializeOptions;
29
+ export declare function mapOpEntry(row: BucketDataDocument): utils.OplogEntry;
30
+ /**
31
+ * Returns true if two ReplicaId values are the same (serializes to the same BSON value).
32
+ */
33
+ export declare function replicaIdEquals(a: storage.ReplicaId, b: storage.ReplicaId): boolean;
34
+ export declare function replicaIdToSubkey(table: bson.ObjectId, id: storage.ReplicaId): string;
35
+ /**
36
+ * True if this is a bson.UUID.
37
+ *
38
+ * Works even with multiple copies of the bson package.
39
+ */
40
+ export declare function isUUID(value: any): value is bson.UUID;
41
+ /**
42
+ * Helper function for creating a MongoDB client from consumers of this package
43
+ */
44
+ export declare const createMongoClient: (url: string, options?: mongo.MongoClientOptions) => mongo.MongoClient;
45
+ /**
46
+ * MongoDB bulkWrite internally splits the operations into batches
47
+ * so that no batch exceeds 16MB. However, there are cases where
48
+ * the batch size is very close to 16MB, where additional metadata
49
+ * on the server pushes it over the limit, resulting in this error
50
+ * from the server:
51
+ *
52
+ * > MongoBulkWriteError: BSONObj size: 16814023 (0x1008FC7) is invalid. Size must be between 0 and 16793600(16MB) First element: insert: "bucket_data"
53
+ *
54
+ * We work around the issue by doing our own batching, limiting the
55
+ * batch size to 15MB. This does add additional overhead with
56
+ * BSON.calculateObjectSize.
57
+ */
58
+ export declare function safeBulkWrite<T extends mongo.Document>(collection: mongo.Collection<T>, operations: mongo.AnyBulkWriteOperation<T>[], options: mongo.BulkWriteOptions): Promise<void>;
@@ -0,0 +1,196 @@
1
+ import { utils } from '@powersync/service-core';
2
+ import * as bson from 'bson';
3
+ import * as crypto from 'crypto';
4
+ import * as mongo from 'mongodb';
5
+ import * as uuid from 'uuid';
6
+ /**
7
+ * Lookup serialization must be number-agnostic. I.e. normalize numbers, instead of preserving numbers.
8
+ * @param lookup
9
+ */
10
+ export function serializeLookup(lookup) {
11
+ const normalized = lookup.map((value) => {
12
+ if (typeof value == 'number' && Number.isInteger(value)) {
13
+ return BigInt(value);
14
+ }
15
+ else {
16
+ return value;
17
+ }
18
+ });
19
+ return new bson.Binary(bson.serialize({ l: normalized }));
20
+ }
21
+ export function idPrefixFilter(prefix, rest) {
22
+ let filter = {
23
+ $gte: {
24
+ ...prefix
25
+ },
26
+ $lt: {
27
+ ...prefix
28
+ }
29
+ };
30
+ for (let key of rest) {
31
+ filter.$gte[key] = new bson.MinKey();
32
+ filter.$lt[key] = new bson.MaxKey();
33
+ }
34
+ return filter;
35
+ }
36
+ export function generateSlotName(prefix, sync_rules_id) {
37
+ const slot_suffix = crypto.randomBytes(2).toString('hex');
38
+ return `${prefix}${sync_rules_id}_${slot_suffix}`;
39
+ }
40
+ /**
41
+ * Read a single batch of data from a cursor, then close it.
42
+ *
43
+ * We do our best to avoid MongoDB fetching any more data than this single batch.
44
+ *
45
+ * This is similar to using `singleBatch: true` in find options.
46
+ * However, that makes `has_more` detection very difficult, since the cursor is always closed
47
+ * after the first batch. Instead, we do a workaround to only fetch a single batch below.
48
+ *
49
+ * For this to be effective, set batchSize = limit in the find command.
50
+ */
51
+ export async function readSingleBatch(cursor) {
52
+ try {
53
+ let data;
54
+ let hasMore = true;
55
+ // Let MongoDB load the first batch of data
56
+ const hasAny = await cursor.hasNext();
57
+ // Now it's in memory, and we can read it
58
+ data = cursor.readBufferedDocuments();
59
+ if (!hasAny || cursor.id?.isZero()) {
60
+ // A zero id means the cursor is exhaused.
61
+ // No results (hasAny == false) means even this batch doesn't have data.
62
+ // This should similar results as `await cursor.hasNext()`, but without
63
+ // actually fetching the next batch.
64
+ //
65
+ // Note that it is safe (but slightly inefficient) to return `hasMore: true`
66
+ // without there being more data, as long as the next batch
67
+ // will return `hasMore: false`.
68
+ hasMore = false;
69
+ }
70
+ return { data, hasMore };
71
+ }
72
+ finally {
73
+ // Match the from the cursor iterator logic here:
74
+ // https://github.com/mongodb/node-mongodb-native/blob/e02534e7d1c627bf50b85ca39f5995dbf165ad44/src/cursor/abstract_cursor.ts#L327-L331
75
+ if (!cursor.closed) {
76
+ await cursor.close();
77
+ }
78
+ }
79
+ }
80
+ export const BSON_DESERIALIZE_OPTIONS = {
81
+ // use bigint instead of Long
82
+ useBigInt64: true
83
+ };
84
+ export function mapOpEntry(row) {
85
+ if (row.op == 'PUT' || row.op == 'REMOVE') {
86
+ return {
87
+ op_id: utils.timestampToOpId(row._id.o),
88
+ op: row.op,
89
+ object_type: row.table,
90
+ object_id: row.row_id,
91
+ checksum: Number(row.checksum),
92
+ subkey: replicaIdToSubkey(row.source_table, row.source_key),
93
+ data: row.data
94
+ };
95
+ }
96
+ else {
97
+ // MOVE, CLEAR
98
+ return {
99
+ op_id: utils.timestampToOpId(row._id.o),
100
+ op: row.op,
101
+ checksum: Number(row.checksum)
102
+ };
103
+ }
104
+ }
105
+ /**
106
+ * Returns true if two ReplicaId values are the same (serializes to the same BSON value).
107
+ */
108
+ export function replicaIdEquals(a, b) {
109
+ if (a === b) {
110
+ return true;
111
+ }
112
+ else if (typeof a == 'string' && typeof b == 'string') {
113
+ return a == b;
114
+ }
115
+ else if (isUUID(a) && isUUID(b)) {
116
+ return a.equals(b);
117
+ }
118
+ else if (a == null && b == null) {
119
+ return true;
120
+ }
121
+ else if ((b == null && a != null) || (a == null && b != null)) {
122
+ return false;
123
+ }
124
+ else {
125
+ // There are many possible primitive values, this covers them all
126
+ return bson.serialize({ id: a }).equals(bson.serialize({ id: b }));
127
+ }
128
+ }
129
+ export function replicaIdToSubkey(table, id) {
130
+ if (isUUID(id)) {
131
+ // Special case for UUID for backwards-compatiblity
132
+ return `${table.toHexString()}/${id.toHexString()}`;
133
+ }
134
+ else {
135
+ // Hashed UUID from the table and id
136
+ const repr = bson.serialize({ table, id });
137
+ return uuid.v5(repr, utils.ID_NAMESPACE);
138
+ }
139
+ }
140
+ /**
141
+ * True if this is a bson.UUID.
142
+ *
143
+ * Works even with multiple copies of the bson package.
144
+ */
145
+ export function isUUID(value) {
146
+ if (value == null || typeof value != 'object') {
147
+ return false;
148
+ }
149
+ const uuid = value;
150
+ return uuid._bsontype == 'Binary' && uuid.sub_type == bson.Binary.SUBTYPE_UUID;
151
+ }
152
+ /**
153
+ * Helper function for creating a MongoDB client from consumers of this package
154
+ */
155
+ export const createMongoClient = (url, options) => {
156
+ return new mongo.MongoClient(url, options);
157
+ };
158
+ /**
159
+ * MongoDB bulkWrite internally splits the operations into batches
160
+ * so that no batch exceeds 16MB. However, there are cases where
161
+ * the batch size is very close to 16MB, where additional metadata
162
+ * on the server pushes it over the limit, resulting in this error
163
+ * from the server:
164
+ *
165
+ * > MongoBulkWriteError: BSONObj size: 16814023 (0x1008FC7) is invalid. Size must be between 0 and 16793600(16MB) First element: insert: "bucket_data"
166
+ *
167
+ * We work around the issue by doing our own batching, limiting the
168
+ * batch size to 15MB. This does add additional overhead with
169
+ * BSON.calculateObjectSize.
170
+ */
171
+ export async function safeBulkWrite(collection, operations, options) {
172
+ // Must be below 16MB.
173
+ // We could probably go a little closer, but 15MB is a safe threshold.
174
+ const BULK_WRITE_LIMIT = 15 * 1024 * 1024;
175
+ let batch = [];
176
+ let currentSize = 0;
177
+ // Estimated overhead per operation, should be smaller in reality.
178
+ const keySize = 8;
179
+ for (let op of operations) {
180
+ const bsonSize = mongo.BSON.calculateObjectSize(op, {
181
+ checkKeys: false,
182
+ ignoreUndefined: true
183
+ }) + keySize;
184
+ if (batch.length > 0 && currentSize + bsonSize > BULK_WRITE_LIMIT) {
185
+ await collection.bulkWrite(batch, options);
186
+ currentSize = 0;
187
+ batch = [];
188
+ }
189
+ batch.push(op);
190
+ currentSize += bsonSize;
191
+ }
192
+ if (batch.length > 0) {
193
+ await collection.bulkWrite(batch, options);
194
+ }
195
+ }
196
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../../../src/storage/implementation/util.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export * from './implementation/config.js';
2
+ export * from './implementation/db.js';
3
+ export * from './implementation/models.js';
4
+ export * from './implementation/MongoBucketBatch.js';
5
+ export * from './implementation/MongoIdSequence.js';
6
+ export * from './implementation/MongoPersistedSyncRules.js';
7
+ export * from './implementation/MongoPersistedSyncRulesContent.js';
8
+ export * from './implementation/MongoStorageProvider.js';
9
+ export * from './implementation/MongoSyncBucketStorage.js';
10
+ export * from './implementation/MongoSyncRulesLock.js';
11
+ export * from './implementation/OperationBatch.js';
12
+ export * from './implementation/PersistedBatch.js';
13
+ export * from './implementation/util.js';
14
+ export * from './MongoBucketStorage.js';
@@ -0,0 +1,15 @@
1
+ export * from './implementation/config.js';
2
+ export * from './implementation/db.js';
3
+ export * from './implementation/models.js';
4
+ export * from './implementation/MongoBucketBatch.js';
5
+ export * from './implementation/MongoIdSequence.js';
6
+ export * from './implementation/MongoPersistedSyncRules.js';
7
+ export * from './implementation/MongoPersistedSyncRulesContent.js';
8
+ export * from './implementation/MongoStorageProvider.js';
9
+ export * from './implementation/MongoSyncBucketStorage.js';
10
+ export * from './implementation/MongoSyncRulesLock.js';
11
+ export * from './implementation/OperationBatch.js';
12
+ export * from './implementation/PersistedBatch.js';
13
+ export * from './implementation/util.js';
14
+ export * from './MongoBucketStorage.js';
15
+ //# sourceMappingURL=storage-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-index.js","sourceRoot":"","sources":["../../src/storage/storage-index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sCAAsC,CAAC;AACrD,cAAc,qCAAqC,CAAC;AACpD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,oDAAoD,CAAC;AACnE,cAAc,0CAA0C,CAAC;AACzD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,wCAAwC,CAAC;AACvD,cAAc,oCAAoC,CAAC;AACnD,cAAc,oCAAoC,CAAC;AACnD,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC"}
@@ -1,3 +1,5 @@
1
+ import * as service_types from '@powersync/service-types';
2
+ import { MongoStorageConfig } from '@powersync/service-types/src/config/PowerSyncConfig.js';
1
3
  import * as t from 'ts-codec';
2
4
  export declare const MONGO_CONNECTION_TYPE: "mongodb";
3
5
  export declare enum PostImagesOption {
@@ -84,3 +86,4 @@ export declare function normalizeConnectionConfig(options: MongoConnectionConfig
84
86
  * Only contains hostname, port, database.
85
87
  */
86
88
  export declare function baseUri(options: NormalizedMongoConnectionConfig): string;
89
+ export declare function isMongoStorageConfig(config: service_types.configFile.GenericStorageConfig): config is MongoStorageConfig;
@@ -1,6 +1,6 @@
1
- import { normalizeMongoConfig } from '@powersync/service-core';
2
1
  import * as service_types from '@powersync/service-types';
3
2
  import * as t from 'ts-codec';
3
+ import { MONGO_STORAGE_TYPE, normalizeMongoConfig } from '../storage/storage-index.js';
4
4
  export const MONGO_CONNECTION_TYPE = 'mongodb';
5
5
  export var PostImagesOption;
6
6
  (function (PostImagesOption) {
@@ -70,4 +70,7 @@ export function normalizeConnectionConfig(options) {
70
70
  export function baseUri(options) {
71
71
  return options.uri;
72
72
  }
73
+ export function isMongoStorageConfig(config) {
74
+ return config.type == MONGO_STORAGE_TYPE;
75
+ }
73
76
  //# sourceMappingURL=types.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,aAAa,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,CAAC,MAAM,UAAU,CAAC;AAE9B,MAAM,CAAC,MAAM,qBAAqB,GAAG,SAAkB,CAAC;AAExD,MAAM,CAAN,IAAY,gBAkCX;AAlCD,WAAY,gBAAgB;IAC1B;;;;;;;OAOG;IACH,+BAAW,CAAA;IAEX;;;;;;;OAOG;IACH,qDAAiC,CAAA;IAEjC;;;;;;;;;;;OAWG;IACH,2CAAuB,CAAA;AACzB,CAAC,EAlCW,gBAAgB,KAAhB,gBAAgB,QAkC3B;AAeD,MAAM,CAAC,MAAM,qBAAqB,GAAG,aAAa,CAAC,UAAU,CAAC,gBAAgB,CAAC,GAAG,CAChF,CAAC,CAAC,MAAM,CAAC;IACP,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,qBAAqB,CAAC;IACtC,2FAA2F;IAC3F,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE;IACvB,8FAA8F;IAC9F,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE;IACxB,GAAG,EAAE,CAAC,CAAC,MAAM;IACb,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE;IAC7B,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE;IAC7B,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE;IAE7B,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ,EAAE;CACpG,CAAC,CACH,CAAC;AAYF;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CAAC,OAA8B;IACtE,MAAM,IAAI,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAE3C,OAAO;QACL,GAAG,IAAI;QACP,EAAE,EAAE,OAAO,CAAC,EAAE,IAAI,SAAS;QAC3B,GAAG,EAAE,OAAO,CAAC,GAAG,IAAI,SAAS;QAC7B,UAAU,EAAG,OAAO,CAAC,WAA4C,IAAI,gBAAgB,CAAC,GAAG;KAC1F,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,OAAwC;IAC9D,OAAO,OAAO,CAAC,GAAG,CAAC;AACrB,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,aAAa,MAAM,0BAA0B,CAAC;AAE1D,OAAO,KAAK,CAAC,MAAM,UAAU,CAAC;AAC9B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAEvF,MAAM,CAAC,MAAM,qBAAqB,GAAG,SAAkB,CAAC;AAExD,MAAM,CAAN,IAAY,gBAkCX;AAlCD,WAAY,gBAAgB;IAC1B;;;;;;;OAOG;IACH,+BAAW,CAAA;IAEX;;;;;;;OAOG;IACH,qDAAiC,CAAA;IAEjC;;;;;;;;;;;OAWG;IACH,2CAAuB,CAAA;AACzB,CAAC,EAlCW,gBAAgB,KAAhB,gBAAgB,QAkC3B;AAeD,MAAM,CAAC,MAAM,qBAAqB,GAAG,aAAa,CAAC,UAAU,CAAC,gBAAgB,CAAC,GAAG,CAChF,CAAC,CAAC,MAAM,CAAC;IACP,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,qBAAqB,CAAC;IACtC,2FAA2F;IAC3F,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE;IACvB,8FAA8F;IAC9F,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE;IACxB,GAAG,EAAE,CAAC,CAAC,MAAM;IACb,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE;IAC7B,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE;IAC7B,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE;IAE7B,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ,EAAE;CACpG,CAAC,CACH,CAAC;AAYF;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CAAC,OAA8B;IACtE,MAAM,IAAI,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAE3C,OAAO;QACL,GAAG,IAAI;QACP,EAAE,EAAE,OAAO,CAAC,EAAE,IAAI,SAAS;QAC3B,GAAG,EAAE,OAAO,CAAC,GAAG,IAAI,SAAS;QAC7B,UAAU,EAAG,OAAO,CAAC,WAA4C,IAAI,gBAAgB,CAAC,GAAG;KAC1F,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,OAAwC;IAC9D,OAAO,OAAO,CAAC,GAAG,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,MAAqD;IAErD,OAAO,MAAM,CAAC,IAAI,IAAI,kBAAkB,CAAC;AAC3C,CAAC"}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@powersync/service-module-mongodb",
3
3
  "repository": "https://github.com/powersync-ja/powersync-service",
4
4
  "types": "dist/index.d.ts",
5
- "version": "0.0.0-dev-20241128134723",
5
+ "version": "0.0.0-dev-20241219110735",
6
6
  "main": "dist/index.js",
7
7
  "license": "FSL-1.1-Apache-2.0",
8
8
  "type": "module",
@@ -22,18 +22,21 @@
22
22
  }
23
23
  },
24
24
  "dependencies": {
25
- "mongodb": "^6.7.0",
26
- "ts-codec": "^1.2.2",
25
+ "mongodb": "^6.11.0",
26
+ "ts-codec": "^1.3.0",
27
+ "ix": "^5.0.0",
28
+ "lru-cache": "^10.2.2",
27
29
  "uuid": "^9.0.1",
28
30
  "uri-js": "^4.4.1",
29
- "@powersync/lib-services-framework": "0.2.0",
30
- "@powersync/service-core": "0.0.0-dev-20241128134723",
31
+ "@powersync/lib-services-framework": "0.0.0-dev-20241219110735",
32
+ "@powersync/service-core": "0.0.0-dev-20241219110735",
31
33
  "@powersync/service-jsonbig": "0.17.10",
32
- "@powersync/service-sync-rules": "0.21.0",
33
- "@powersync/service-types": "0.0.0-dev-20241128134723"
34
+ "@powersync/service-sync-rules": "0.0.0-dev-20241219110735",
35
+ "@powersync/service-types": "0.0.0-dev-20241219110735"
34
36
  },
35
37
  "devDependencies": {
36
- "@types/uuid": "^9.0.4"
38
+ "@types/uuid": "^9.0.4",
39
+ "@powersync/service-core-tests": "0.0.0-dev-20241219110735"
37
40
  },
38
41
  "scripts": {
39
42
  "build": "tsc -b",
@@ -0,0 +1 @@
1
+ export * as mongo from './mongo.js';
@@ -0,0 +1,81 @@
1
+ import * as mongo from 'mongodb';
2
+ import * as timers from 'timers/promises';
3
+
4
+ import { configFile } from '@powersync/service-types';
5
+ import { normalizeMongoConfig } from '../storage/storage-index.js';
6
+
7
+ /**
8
+ * Time for new connection to timeout.
9
+ */
10
+ export const MONGO_CONNECT_TIMEOUT_MS = 10_000;
11
+
12
+ /**
13
+ * Time for individual requests to timeout the socket.
14
+ */
15
+ export const MONGO_SOCKET_TIMEOUT_MS = 60_000;
16
+
17
+ /**
18
+ * Time for individual requests to timeout the operation.
19
+ *
20
+ * This is time spent on the cursor, not total time.
21
+ *
22
+ * Must be less than MONGO_SOCKET_TIMEOUT_MS to ensure proper error handling.
23
+ */
24
+ export const MONGO_OPERATION_TIMEOUT_MS = 30_000;
25
+
26
+ /**
27
+ * Same as above, but specifically for clear operations.
28
+ *
29
+ * These are retried when reaching the timeout.
30
+ */
31
+ export const MONGO_CLEAR_OPERATION_TIMEOUT_MS = 5_000;
32
+
33
+ export function createMongoClient(config: configFile.MongoStorageConfig) {
34
+ const normalized = normalizeMongoConfig(config);
35
+ return new mongo.MongoClient(normalized.uri, {
36
+ auth: {
37
+ username: normalized.username,
38
+ password: normalized.password
39
+ },
40
+ // Time for connection to timeout
41
+ connectTimeoutMS: MONGO_CONNECT_TIMEOUT_MS,
42
+ // Time for individual requests to timeout
43
+ socketTimeoutMS: MONGO_SOCKET_TIMEOUT_MS,
44
+ // How long to wait for new primary selection
45
+ serverSelectionTimeoutMS: 30_000,
46
+
47
+ // Avoid too many connections:
48
+ // 1. It can overwhelm the source database.
49
+ // 2. Processing too many queries in parallel can cause the process to run out of memory.
50
+ maxPoolSize: 8,
51
+
52
+ maxConnecting: 3,
53
+ maxIdleTimeMS: 60_000
54
+ });
55
+ }
56
+
57
+ /**
58
+ * Wait up to a minute for authentication errors to resolve.
59
+ *
60
+ * There can be a delay between an Atlas user being created, and that user being
61
+ * available on the database cluster. This works around it.
62
+ *
63
+ * This is specifically relevant for migrations and teardown - other parts of the stack
64
+ * can generate handle these failures and just retry or restart.
65
+ */
66
+ export async function waitForAuth(db: mongo.Db) {
67
+ const start = Date.now();
68
+ while (Date.now() - start < 60_000) {
69
+ try {
70
+ await db.command({ ping: 1 });
71
+ // Success
72
+ break;
73
+ } catch (e) {
74
+ if (e.codeName == 'AuthenticationFailed') {
75
+ await timers.setTimeout(1_000);
76
+ continue;
77
+ }
78
+ throw e;
79
+ }
80
+ }
81
+ }
package/src/index.ts CHANGED
@@ -1 +1,5 @@
1
1
  export * from './module/MongoModule.js';
2
+
3
+ export * as db from './db/db-index.js';
4
+ export * as storage from './storage/storage-index.js';
5
+