@powersync/service-core 0.12.2 → 0.14.0

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 (198) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/auth/KeySpec.d.ts +1 -0
  3. package/dist/auth/KeySpec.js +5 -2
  4. package/dist/auth/KeySpec.js.map +1 -1
  5. package/dist/auth/RemoteJWKSCollector.js +1 -1
  6. package/dist/auth/RemoteJWKSCollector.js.map +1 -1
  7. package/dist/entry/commands/compact-action.js +14 -14
  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 +21 -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/configure-fastify.d.ts +12 -12
  24. package/dist/routes/endpoints/admin.d.ts +24 -24
  25. package/dist/routes/endpoints/probes.d.ts +1 -1
  26. package/dist/routes/endpoints/probes.js +5 -5
  27. package/dist/routes/endpoints/probes.js.map +1 -1
  28. package/dist/storage/BucketStorage.d.ts +49 -1
  29. package/dist/storage/BucketStorage.js +26 -0
  30. package/dist/storage/BucketStorage.js.map +1 -1
  31. package/dist/storage/SourceTable.d.ts +4 -0
  32. package/dist/storage/SourceTable.js +4 -0
  33. package/dist/storage/SourceTable.js.map +1 -1
  34. package/dist/storage/bson.d.ts +24 -0
  35. package/dist/storage/bson.js +73 -0
  36. package/dist/storage/bson.js.map +1 -0
  37. package/dist/storage/storage-index.d.ts +3 -14
  38. package/dist/storage/storage-index.js +3 -14
  39. package/dist/storage/storage-index.js.map +1 -1
  40. package/dist/sync/sync.js +3 -1
  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/types.d.ts +2 -2
  46. package/dist/util/utils.d.ts +13 -1
  47. package/dist/util/utils.js +20 -1
  48. package/dist/util/utils.js.map +1 -1
  49. package/package.json +7 -8
  50. package/src/auth/KeySpec.ts +5 -3
  51. package/src/auth/RemoteJWKSCollector.ts +1 -1
  52. package/src/entry/commands/compact-action.ts +19 -14
  53. package/src/entry/commands/migrate-action.ts +17 -4
  54. package/src/index.ts +1 -4
  55. package/src/migrations/PowerSyncMigrationManager.ts +42 -0
  56. package/src/migrations/ensure-automatic-migrations.ts +15 -0
  57. package/src/migrations/migrations-index.ts +2 -3
  58. package/src/routes/endpoints/probes.ts +6 -6
  59. package/src/storage/BucketStorage.ts +53 -1
  60. package/src/storage/SourceTable.ts +4 -0
  61. package/src/storage/bson.ts +78 -0
  62. package/src/storage/storage-index.ts +3 -15
  63. package/src/sync/sync.ts +3 -1
  64. package/src/system/ServiceContext.ts +17 -4
  65. package/src/util/config/types.ts +2 -2
  66. package/src/util/utils.ts +21 -1
  67. package/test/src/auth.test.ts +33 -0
  68. package/test/src/env.ts +0 -1
  69. package/test/src/routes/probes.test.ts +6 -1
  70. package/tsconfig.tsbuildinfo +1 -1
  71. package/dist/db/db-index.d.ts +0 -1
  72. package/dist/db/db-index.js +0 -2
  73. package/dist/db/db-index.js.map +0 -1
  74. package/dist/db/mongo.d.ts +0 -35
  75. package/dist/db/mongo.js +0 -73
  76. package/dist/db/mongo.js.map +0 -1
  77. package/dist/locks/LockManager.d.ts +0 -10
  78. package/dist/locks/LockManager.js +0 -7
  79. package/dist/locks/LockManager.js.map +0 -1
  80. package/dist/locks/MongoLocks.d.ts +0 -36
  81. package/dist/locks/MongoLocks.js +0 -81
  82. package/dist/locks/MongoLocks.js.map +0 -1
  83. package/dist/locks/locks-index.d.ts +0 -2
  84. package/dist/locks/locks-index.js +0 -3
  85. package/dist/locks/locks-index.js.map +0 -1
  86. package/dist/migrations/db/migrations/1684951997326-init.d.ts +0 -3
  87. package/dist/migrations/db/migrations/1684951997326-init.js +0 -33
  88. package/dist/migrations/db/migrations/1684951997326-init.js.map +0 -1
  89. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.d.ts +0 -2
  90. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js +0 -5
  91. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js.map +0 -1
  92. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.d.ts +0 -3
  93. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js +0 -56
  94. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +0 -1
  95. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.d.ts +0 -3
  96. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js +0 -29
  97. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js.map +0 -1
  98. package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.d.ts +0 -3
  99. package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.js +0 -31
  100. package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.js.map +0 -1
  101. package/dist/migrations/definitions.d.ts +0 -18
  102. package/dist/migrations/definitions.js +0 -6
  103. package/dist/migrations/definitions.js.map +0 -1
  104. package/dist/migrations/executor.d.ts +0 -16
  105. package/dist/migrations/executor.js +0 -64
  106. package/dist/migrations/executor.js.map +0 -1
  107. package/dist/migrations/migrations.d.ts +0 -18
  108. package/dist/migrations/migrations.js +0 -110
  109. package/dist/migrations/migrations.js.map +0 -1
  110. package/dist/migrations/store/migration-store.d.ts +0 -11
  111. package/dist/migrations/store/migration-store.js +0 -46
  112. package/dist/migrations/store/migration-store.js.map +0 -1
  113. package/dist/storage/MongoBucketStorage.d.ts +0 -48
  114. package/dist/storage/MongoBucketStorage.js +0 -427
  115. package/dist/storage/MongoBucketStorage.js.map +0 -1
  116. package/dist/storage/mongo/MongoBucketBatch.d.ts +0 -74
  117. package/dist/storage/mongo/MongoBucketBatch.js +0 -683
  118. package/dist/storage/mongo/MongoBucketBatch.js.map +0 -1
  119. package/dist/storage/mongo/MongoCompactor.d.ts +0 -40
  120. package/dist/storage/mongo/MongoCompactor.js +0 -310
  121. package/dist/storage/mongo/MongoCompactor.js.map +0 -1
  122. package/dist/storage/mongo/MongoIdSequence.d.ts +0 -12
  123. package/dist/storage/mongo/MongoIdSequence.js +0 -21
  124. package/dist/storage/mongo/MongoIdSequence.js.map +0 -1
  125. package/dist/storage/mongo/MongoPersistedSyncRules.d.ts +0 -9
  126. package/dist/storage/mongo/MongoPersistedSyncRules.js +0 -9
  127. package/dist/storage/mongo/MongoPersistedSyncRules.js.map +0 -1
  128. package/dist/storage/mongo/MongoPersistedSyncRulesContent.d.ts +0 -20
  129. package/dist/storage/mongo/MongoPersistedSyncRulesContent.js +0 -26
  130. package/dist/storage/mongo/MongoPersistedSyncRulesContent.js.map +0 -1
  131. package/dist/storage/mongo/MongoStorageProvider.d.ts +0 -5
  132. package/dist/storage/mongo/MongoStorageProvider.js +0 -26
  133. package/dist/storage/mongo/MongoStorageProvider.js.map +0 -1
  134. package/dist/storage/mongo/MongoSyncBucketStorage.d.ts +0 -38
  135. package/dist/storage/mongo/MongoSyncBucketStorage.js +0 -534
  136. package/dist/storage/mongo/MongoSyncBucketStorage.js.map +0 -1
  137. package/dist/storage/mongo/MongoSyncRulesLock.d.ts +0 -16
  138. package/dist/storage/mongo/MongoSyncRulesLock.js +0 -65
  139. package/dist/storage/mongo/MongoSyncRulesLock.js.map +0 -1
  140. package/dist/storage/mongo/MongoWriteCheckpointAPI.d.ts +0 -20
  141. package/dist/storage/mongo/MongoWriteCheckpointAPI.js +0 -104
  142. package/dist/storage/mongo/MongoWriteCheckpointAPI.js.map +0 -1
  143. package/dist/storage/mongo/OperationBatch.d.ts +0 -35
  144. package/dist/storage/mongo/OperationBatch.js +0 -119
  145. package/dist/storage/mongo/OperationBatch.js.map +0 -1
  146. package/dist/storage/mongo/PersistedBatch.d.ts +0 -46
  147. package/dist/storage/mongo/PersistedBatch.js +0 -223
  148. package/dist/storage/mongo/PersistedBatch.js.map +0 -1
  149. package/dist/storage/mongo/config.d.ts +0 -19
  150. package/dist/storage/mongo/config.js +0 -26
  151. package/dist/storage/mongo/config.js.map +0 -1
  152. package/dist/storage/mongo/db.d.ts +0 -36
  153. package/dist/storage/mongo/db.js +0 -47
  154. package/dist/storage/mongo/db.js.map +0 -1
  155. package/dist/storage/mongo/models.d.ts +0 -163
  156. package/dist/storage/mongo/models.js +0 -27
  157. package/dist/storage/mongo/models.js.map +0 -1
  158. package/dist/storage/mongo/util.d.ts +0 -54
  159. package/dist/storage/mongo/util.js +0 -190
  160. package/dist/storage/mongo/util.js.map +0 -1
  161. package/src/db/db-index.ts +0 -1
  162. package/src/db/mongo.ts +0 -81
  163. package/src/locks/LockManager.ts +0 -16
  164. package/src/locks/MongoLocks.ts +0 -142
  165. package/src/locks/locks-index.ts +0 -2
  166. package/src/migrations/db/migrations/1684951997326-init.ts +0 -38
  167. package/src/migrations/db/migrations/1688556755264-initial-sync-rules.ts +0 -5
  168. package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +0 -102
  169. package/src/migrations/db/migrations/1711543888062-write-checkpoint-index.ts +0 -34
  170. package/src/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.ts +0 -37
  171. package/src/migrations/definitions.ts +0 -21
  172. package/src/migrations/executor.ts +0 -87
  173. package/src/migrations/migrations.ts +0 -142
  174. package/src/migrations/store/migration-store.ts +0 -63
  175. package/src/storage/MongoBucketStorage.ts +0 -541
  176. package/src/storage/mongo/MongoBucketBatch.ts +0 -900
  177. package/src/storage/mongo/MongoCompactor.ts +0 -393
  178. package/src/storage/mongo/MongoIdSequence.ts +0 -24
  179. package/src/storage/mongo/MongoPersistedSyncRules.ts +0 -16
  180. package/src/storage/mongo/MongoPersistedSyncRulesContent.ts +0 -50
  181. package/src/storage/mongo/MongoStorageProvider.ts +0 -31
  182. package/src/storage/mongo/MongoSyncBucketStorage.ts +0 -640
  183. package/src/storage/mongo/MongoSyncRulesLock.ts +0 -85
  184. package/src/storage/mongo/MongoWriteCheckpointAPI.ts +0 -154
  185. package/src/storage/mongo/OperationBatch.ts +0 -131
  186. package/src/storage/mongo/PersistedBatch.ts +0 -285
  187. package/src/storage/mongo/config.ts +0 -40
  188. package/src/storage/mongo/db.ts +0 -88
  189. package/src/storage/mongo/models.ts +0 -187
  190. package/src/storage/mongo/util.ts +0 -203
  191. package/test/src/__snapshots__/sync.test.ts.snap +0 -332
  192. package/test/src/bucket_validation.test.ts +0 -143
  193. package/test/src/bucket_validation.ts +0 -60
  194. package/test/src/compacting.test.ts +0 -295
  195. package/test/src/data_storage.test.ts +0 -1569
  196. package/test/src/stream_utils.ts +0 -42
  197. package/test/src/sync.test.ts +0 -511
  198. package/test/src/util.ts +0 -150
@@ -1,541 +0,0 @@
1
- import { SqlSyncRules } from '@powersync/service-sync-rules';
2
- import { wrapWithAbort } from 'ix/asynciterable/operators/withabort.js';
3
- import { LRUCache } from 'lru-cache/min';
4
- import * as mongo from 'mongodb';
5
- import * as timers from 'timers/promises';
6
-
7
- import * as locks from '../locks/locks-index.js';
8
- import * as sync from '../sync/sync-index.js';
9
- import * as util from '../util/util-index.js';
10
-
11
- import { DisposableObserver, logger } from '@powersync/lib-services-framework';
12
- import { v4 as uuid } from 'uuid';
13
- import {
14
- ActiveCheckpoint,
15
- BucketStorageFactory,
16
- BucketStorageFactoryListener,
17
- ParseSyncRulesOptions,
18
- PersistedSyncRules,
19
- PersistedSyncRulesContent,
20
- StorageMetrics,
21
- UpdateSyncRulesOptions,
22
- WriteCheckpoint
23
- } from './BucketStorage.js';
24
- import { PowerSyncMongo } from './mongo/db.js';
25
- import { SyncRuleDocument, SyncRuleState } from './mongo/models.js';
26
- import { MongoPersistedSyncRulesContent } from './mongo/MongoPersistedSyncRulesContent.js';
27
- import { MongoSyncBucketStorage } from './mongo/MongoSyncBucketStorage.js';
28
- import { generateSlotName } from './mongo/util.js';
29
-
30
- export class MongoBucketStorage
31
- extends DisposableObserver<BucketStorageFactoryListener>
32
- implements BucketStorageFactory
33
- {
34
- private readonly client: mongo.MongoClient;
35
- private readonly session: mongo.ClientSession;
36
- // TODO: This is still Postgres specific and needs to be reworked
37
- public readonly slot_name_prefix: string;
38
-
39
- private readonly storageCache = new LRUCache<number, MongoSyncBucketStorage>({
40
- max: 3,
41
- fetchMethod: async (id) => {
42
- const doc2 = await this.db.sync_rules.findOne(
43
- {
44
- _id: id
45
- },
46
- { limit: 1 }
47
- );
48
- if (doc2 == null) {
49
- // Deleted in the meantime?
50
- return undefined;
51
- }
52
- const rules = new MongoPersistedSyncRulesContent(this.db, doc2);
53
- return this.getInstance(rules);
54
- },
55
- dispose: (storage) => {
56
- storage[Symbol.dispose]();
57
- }
58
- });
59
-
60
- public readonly db: PowerSyncMongo;
61
-
62
- constructor(
63
- db: PowerSyncMongo,
64
- options: {
65
- slot_name_prefix: string;
66
- }
67
- ) {
68
- super();
69
- this.client = db.client;
70
- this.db = db;
71
- this.session = this.client.startSession();
72
- this.slot_name_prefix = options.slot_name_prefix;
73
- }
74
-
75
- getInstance(options: PersistedSyncRulesContent): MongoSyncBucketStorage {
76
- let { id, slot_name } = options;
77
- if ((typeof id as any) == 'bigint') {
78
- id = Number(id);
79
- }
80
- const storage = new MongoSyncBucketStorage(this, id, options, slot_name);
81
- this.iterateListeners((cb) => cb.syncStorageCreated?.(storage));
82
- storage.registerListener({
83
- batchStarted: (batch) => {
84
- // This nested listener will be automatically disposed when the storage is disposed
85
- batch.registerManagedListener(storage, {
86
- replicationEvent: (payload) => this.iterateListeners((cb) => cb.replicationEvent?.(payload))
87
- });
88
- }
89
- });
90
- return storage;
91
- }
92
-
93
- async configureSyncRules(sync_rules: string, options?: { lock?: boolean }) {
94
- const next = await this.getNextSyncRulesContent();
95
- const active = await this.getActiveSyncRulesContent();
96
-
97
- if (next?.sync_rules_content == sync_rules) {
98
- logger.info('Sync rules from configuration unchanged');
99
- return { updated: false };
100
- } else if (next == null && active?.sync_rules_content == sync_rules) {
101
- logger.info('Sync rules from configuration unchanged');
102
- return { updated: false };
103
- } else {
104
- logger.info('Sync rules updated from configuration');
105
- const persisted_sync_rules = await this.updateSyncRules({
106
- content: sync_rules,
107
- lock: options?.lock
108
- });
109
- return { updated: true, persisted_sync_rules, lock: persisted_sync_rules.current_lock ?? undefined };
110
- }
111
- }
112
-
113
- async slotRemoved(slot_name: string) {
114
- const next = await this.getNextSyncRulesContent();
115
- const active = await this.getActiveSyncRulesContent();
116
-
117
- // In both the below cases, we create a new sync rules instance.
118
- // The current one will continue erroring until the next one has finished processing.
119
- // TODO: Update
120
- if (next != null && next.slot_name == slot_name) {
121
- // We need to redo the "next" sync rules
122
- await this.updateSyncRules({
123
- content: next.sync_rules_content
124
- });
125
- // Pro-actively stop replicating
126
- await this.db.sync_rules.updateOne(
127
- {
128
- _id: next.id,
129
- state: SyncRuleState.PROCESSING
130
- },
131
- {
132
- $set: {
133
- state: SyncRuleState.STOP
134
- }
135
- }
136
- );
137
- } else if (next == null && active?.slot_name == slot_name) {
138
- // Slot removed for "active" sync rules, while there is no "next" one.
139
- await this.updateSyncRules({
140
- content: active.sync_rules_content
141
- });
142
-
143
- // Pro-actively stop replicating
144
- await this.db.sync_rules.updateOne(
145
- {
146
- _id: active.id,
147
- state: SyncRuleState.ACTIVE
148
- },
149
- {
150
- $set: {
151
- state: SyncRuleState.STOP
152
- }
153
- }
154
- );
155
- }
156
- }
157
-
158
- async updateSyncRules(options: UpdateSyncRulesOptions): Promise<MongoPersistedSyncRulesContent> {
159
- // Parse and validate before applying any changes
160
- const parsed = SqlSyncRules.fromYaml(options.content, {
161
- // No schema-based validation at this point
162
- schema: undefined,
163
- defaultSchema: 'not_applicable', // Not needed for validation
164
- throwOnError: true
165
- });
166
-
167
- let rules: MongoPersistedSyncRulesContent | undefined = undefined;
168
-
169
- await this.session.withTransaction(async () => {
170
- // Only have a single set of sync rules with PROCESSING.
171
- await this.db.sync_rules.updateMany(
172
- {
173
- state: SyncRuleState.PROCESSING
174
- },
175
- { $set: { state: SyncRuleState.STOP } }
176
- );
177
-
178
- const id_doc = await this.db.op_id_sequence.findOneAndUpdate(
179
- {
180
- _id: 'sync_rules'
181
- },
182
- {
183
- $inc: {
184
- op_id: 1n
185
- }
186
- },
187
- {
188
- upsert: true,
189
- returnDocument: 'after'
190
- }
191
- );
192
-
193
- const id = Number(id_doc!.op_id);
194
- const slot_name = generateSlotName(this.slot_name_prefix, id);
195
-
196
- const doc: SyncRuleDocument = {
197
- _id: id,
198
- content: options.content,
199
- last_checkpoint: null,
200
- last_checkpoint_lsn: null,
201
- no_checkpoint_before: null,
202
- keepalive_op: null,
203
- snapshot_done: false,
204
- state: SyncRuleState.PROCESSING,
205
- slot_name: slot_name,
206
- last_checkpoint_ts: null,
207
- last_fatal_error: null,
208
- last_keepalive_ts: null
209
- };
210
- await this.db.sync_rules.insertOne(doc);
211
- rules = new MongoPersistedSyncRulesContent(this.db, doc);
212
- if (options.lock) {
213
- const lock = await rules.lock();
214
- }
215
- });
216
-
217
- return rules!;
218
- }
219
-
220
- async getActiveSyncRulesContent(): Promise<MongoPersistedSyncRulesContent | null> {
221
- const doc = await this.db.sync_rules.findOne(
222
- {
223
- state: SyncRuleState.ACTIVE
224
- },
225
- { sort: { _id: -1 }, limit: 1 }
226
- );
227
- if (doc == null) {
228
- return null;
229
- }
230
-
231
- return new MongoPersistedSyncRulesContent(this.db, doc);
232
- }
233
-
234
- async getActiveSyncRules(options: ParseSyncRulesOptions): Promise<PersistedSyncRules | null> {
235
- const content = await this.getActiveSyncRulesContent();
236
- return content?.parsed(options) ?? null;
237
- }
238
-
239
- async getNextSyncRulesContent(): Promise<MongoPersistedSyncRulesContent | null> {
240
- const doc = await this.db.sync_rules.findOne(
241
- {
242
- state: SyncRuleState.PROCESSING
243
- },
244
- { sort: { _id: -1 }, limit: 1 }
245
- );
246
- if (doc == null) {
247
- return null;
248
- }
249
-
250
- return new MongoPersistedSyncRulesContent(this.db, doc);
251
- }
252
-
253
- async getNextSyncRules(options: ParseSyncRulesOptions): Promise<PersistedSyncRules | null> {
254
- const content = await this.getNextSyncRulesContent();
255
- return content?.parsed(options) ?? null;
256
- }
257
-
258
- async getReplicatingSyncRules(): Promise<PersistedSyncRulesContent[]> {
259
- const docs = await this.db.sync_rules
260
- .find({
261
- $or: [{ state: SyncRuleState.ACTIVE }, { state: SyncRuleState.PROCESSING }]
262
- })
263
- .toArray();
264
-
265
- return docs.map((doc) => {
266
- return new MongoPersistedSyncRulesContent(this.db, doc);
267
- });
268
- }
269
-
270
- async getStoppedSyncRules(): Promise<PersistedSyncRulesContent[]> {
271
- const docs = await this.db.sync_rules
272
- .find({
273
- state: SyncRuleState.STOP
274
- })
275
- .toArray();
276
-
277
- return docs.map((doc) => {
278
- return new MongoPersistedSyncRulesContent(this.db, doc);
279
- });
280
- }
281
-
282
- async getActiveCheckpoint(): Promise<ActiveCheckpoint> {
283
- const doc = await this.db.sync_rules.findOne(
284
- {
285
- state: SyncRuleState.ACTIVE
286
- },
287
- {
288
- sort: { _id: -1 },
289
- limit: 1,
290
- projection: { _id: 1, last_checkpoint: 1, last_checkpoint_lsn: 1 }
291
- }
292
- );
293
-
294
- return this.makeActiveCheckpoint(doc);
295
- }
296
-
297
- async getStorageMetrics(): Promise<StorageMetrics> {
298
- const ignoreNotExiting = (e: unknown) => {
299
- if (e instanceof mongo.MongoServerError && e.codeName == 'NamespaceNotFound') {
300
- // Collection doesn't exist - return 0
301
- return [{ storageStats: { size: 0 } }];
302
- } else {
303
- return Promise.reject(e);
304
- }
305
- };
306
-
307
- const active_sync_rules = await this.getActiveSyncRules({ defaultSchema: 'public' });
308
- if (active_sync_rules == null) {
309
- return {
310
- operations_size_bytes: 0,
311
- parameters_size_bytes: 0,
312
- replication_size_bytes: 0
313
- };
314
- }
315
- const operations_aggregate = await this.db.bucket_data
316
-
317
- .aggregate([
318
- {
319
- $collStats: {
320
- storageStats: {}
321
- }
322
- }
323
- ])
324
- .toArray()
325
- .catch(ignoreNotExiting);
326
-
327
- const parameters_aggregate = await this.db.bucket_parameters
328
- .aggregate([
329
- {
330
- $collStats: {
331
- storageStats: {}
332
- }
333
- }
334
- ])
335
- .toArray()
336
- .catch(ignoreNotExiting);
337
-
338
- const replication_aggregate = await this.db.current_data
339
- .aggregate([
340
- {
341
- $collStats: {
342
- storageStats: {}
343
- }
344
- }
345
- ])
346
- .toArray()
347
- .catch(ignoreNotExiting);
348
-
349
- return {
350
- operations_size_bytes: operations_aggregate[0].storageStats.size,
351
- parameters_size_bytes: parameters_aggregate[0].storageStats.size,
352
- replication_size_bytes: replication_aggregate[0].storageStats.size
353
- };
354
- }
355
-
356
- async getPowerSyncInstanceId(): Promise<string> {
357
- let instance = await this.db.instance.findOne({
358
- _id: { $exists: true }
359
- });
360
-
361
- if (!instance) {
362
- const manager = locks.createMongoLockManager(this.db.locks, {
363
- name: `instance-id-insertion-lock`
364
- });
365
-
366
- await manager.lock(async () => {
367
- await this.db.instance.insertOne({
368
- _id: uuid()
369
- });
370
- });
371
-
372
- instance = await this.db.instance.findOne({
373
- _id: { $exists: true }
374
- });
375
- }
376
-
377
- return instance!._id;
378
- }
379
-
380
- private makeActiveCheckpoint(doc: SyncRuleDocument | null) {
381
- return {
382
- checkpoint: util.timestampToOpId(doc?.last_checkpoint ?? 0n),
383
- lsn: doc?.last_checkpoint_lsn ?? null,
384
- hasSyncRules() {
385
- return doc != null;
386
- },
387
- getBucketStorage: async () => {
388
- if (doc == null) {
389
- return null;
390
- }
391
- return (await this.storageCache.fetch(doc._id)) ?? null;
392
- }
393
- } satisfies ActiveCheckpoint;
394
- }
395
-
396
- /**
397
- * Instance-wide watch on the latest available checkpoint (op_id + lsn).
398
- */
399
- private async *watchActiveCheckpoint(signal: AbortSignal): AsyncIterable<ActiveCheckpoint> {
400
- const pipeline: mongo.Document[] = [
401
- {
402
- $match: {
403
- 'fullDocument.state': 'ACTIVE',
404
- operationType: { $in: ['insert', 'update'] }
405
- }
406
- },
407
- {
408
- $project: {
409
- operationType: 1,
410
- 'fullDocument._id': 1,
411
- 'fullDocument.last_checkpoint': 1,
412
- 'fullDocument.last_checkpoint_lsn': 1
413
- }
414
- }
415
- ];
416
-
417
- // Use this form instead of (doc: SyncRuleDocument | null = null),
418
- // otherwise we get weird "doc: never" issues.
419
- let doc = null as SyncRuleDocument | null;
420
- let clusterTime = null as mongo.Timestamp | null;
421
-
422
- await this.client.withSession(async (session) => {
423
- doc = await this.db.sync_rules.findOne(
424
- {
425
- state: SyncRuleState.ACTIVE
426
- },
427
- {
428
- session,
429
- sort: { _id: -1 },
430
- limit: 1,
431
- projection: {
432
- _id: 1,
433
- last_checkpoint: 1,
434
- last_checkpoint_lsn: 1
435
- }
436
- }
437
- );
438
- const time = session.clusterTime?.clusterTime ?? null;
439
- clusterTime = time;
440
- });
441
- if (clusterTime == null) {
442
- throw new Error('Could not get clusterTime');
443
- }
444
-
445
- if (signal.aborted) {
446
- return;
447
- }
448
-
449
- if (doc) {
450
- yield this.makeActiveCheckpoint(doc);
451
- }
452
-
453
- const stream = this.db.sync_rules.watch(pipeline, {
454
- fullDocument: 'updateLookup',
455
- // Start at the cluster time where we got the initial doc, to make sure
456
- // we don't skip any updates.
457
- // This may result in the first operation being a duplicate, but we filter
458
- // it out anyway.
459
- startAtOperationTime: clusterTime
460
- });
461
-
462
- signal.addEventListener(
463
- 'abort',
464
- () => {
465
- stream.close();
466
- },
467
- { once: true }
468
- );
469
-
470
- let lastOp: ActiveCheckpoint | null = null;
471
-
472
- for await (const update of stream.stream()) {
473
- if (signal.aborted) {
474
- break;
475
- }
476
- if (update.operationType != 'insert' && update.operationType != 'update') {
477
- continue;
478
- }
479
- const doc = update.fullDocument!;
480
- if (doc == null) {
481
- continue;
482
- }
483
-
484
- const op = this.makeActiveCheckpoint(doc);
485
- // Check for LSN / checkpoint changes - ignore other metadata changes
486
- if (lastOp == null || op.lsn != lastOp.lsn || op.checkpoint != lastOp.checkpoint) {
487
- lastOp = op;
488
- yield op;
489
- }
490
- }
491
- }
492
-
493
- // Nothing is done here until a subscriber starts to iterate
494
- private readonly sharedIter = new sync.BroadcastIterable((signal) => {
495
- return this.watchActiveCheckpoint(signal);
496
- });
497
-
498
- /**
499
- * User-specific watch on the latest checkpoint and/or write checkpoint.
500
- */
501
- async *watchWriteCheckpoint(user_id: string, signal: AbortSignal): AsyncIterable<WriteCheckpoint> {
502
- let lastCheckpoint: util.OpId | null = null;
503
- let lastWriteCheckpoint: bigint | null = null;
504
-
505
- const iter = wrapWithAbort(this.sharedIter, signal);
506
- for await (const cp of iter) {
507
- const { checkpoint, lsn } = cp;
508
-
509
- // lsn changes are not important by itself.
510
- // What is important is:
511
- // 1. checkpoint (op_id) changes.
512
- // 2. write checkpoint changes for the specific user
513
- const bucketStorage = await cp.getBucketStorage();
514
- if (!bucketStorage) {
515
- continue;
516
- }
517
-
518
- const lsnFilters: Record<string, string> = lsn ? { 1: lsn } : {};
519
-
520
- const currentWriteCheckpoint = await bucketStorage.lastWriteCheckpoint({
521
- user_id,
522
- heads: {
523
- ...lsnFilters
524
- }
525
- });
526
-
527
- if (currentWriteCheckpoint == lastWriteCheckpoint && checkpoint == lastCheckpoint) {
528
- // No change - wait for next one
529
- // In some cases, many LSNs may be produced in a short time.
530
- // Add a delay to throttle the write checkpoint lookup a bit.
531
- await timers.setTimeout(20 + 10 * Math.random());
532
- continue;
533
- }
534
-
535
- lastWriteCheckpoint = currentWriteCheckpoint;
536
- lastCheckpoint = checkpoint;
537
-
538
- yield { base: cp, writeCheckpoint: currentWriteCheckpoint };
539
- }
540
- }
541
- }