@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
@@ -1,151 +0,0 @@
1
- import * as framework from '@powersync/lib-services-framework';
2
- import {
3
- CustomWriteCheckpointFilters,
4
- CustomWriteCheckpointOptions,
5
- LastWriteCheckpointFilters,
6
- ManagedWriteCheckpointFilters,
7
- ManagedWriteCheckpointOptions,
8
- WriteCheckpointAPI,
9
- WriteCheckpointMode
10
- } from '../WriteCheckpointAPI.js';
11
- import { PowerSyncMongo } from './db.js';
12
-
13
- export type MongoCheckpointAPIOptions = {
14
- db: PowerSyncMongo;
15
- mode: WriteCheckpointMode;
16
- };
17
-
18
- export class MongoWriteCheckpointAPI implements WriteCheckpointAPI {
19
- readonly db: PowerSyncMongo;
20
- private _mode: WriteCheckpointMode;
21
-
22
- constructor(options: MongoCheckpointAPIOptions) {
23
- this.db = options.db;
24
- this._mode = options.mode;
25
- }
26
-
27
- get writeCheckpointMode() {
28
- return this._mode;
29
- }
30
-
31
- setWriteCheckpointMode(mode: WriteCheckpointMode): void {
32
- this._mode = mode;
33
- }
34
-
35
- async batchCreateCustomWriteCheckpoints(checkpoints: CustomWriteCheckpointOptions[]): Promise<void> {
36
- return batchCreateCustomWriteCheckpoints(this.db, checkpoints);
37
- }
38
-
39
- async createCustomWriteCheckpoint(options: CustomWriteCheckpointOptions): Promise<bigint> {
40
- if (this.writeCheckpointMode !== WriteCheckpointMode.CUSTOM) {
41
- throw new framework.errors.ValidationError(
42
- `Creating a custom Write Checkpoint when the current Write Checkpoint mode is set to "${this.writeCheckpointMode}"`
43
- );
44
- }
45
-
46
- const { checkpoint, user_id, sync_rules_id } = options;
47
- const doc = await this.db.custom_write_checkpoints.findOneAndUpdate(
48
- {
49
- user_id: user_id,
50
- sync_rules_id
51
- },
52
- {
53
- $set: {
54
- checkpoint
55
- }
56
- },
57
- { upsert: true, returnDocument: 'after' }
58
- );
59
- return doc!.checkpoint;
60
- }
61
-
62
- async createManagedWriteCheckpoint(checkpoint: ManagedWriteCheckpointOptions): Promise<bigint> {
63
- if (this.writeCheckpointMode !== WriteCheckpointMode.MANAGED) {
64
- throw new framework.errors.ValidationError(
65
- `Attempting to create a managed Write Checkpoint when the current Write Checkpoint mode is set to "${this.writeCheckpointMode}"`
66
- );
67
- }
68
-
69
- const { user_id, heads: lsns } = checkpoint;
70
- const doc = await this.db.write_checkpoints.findOneAndUpdate(
71
- {
72
- user_id: user_id
73
- },
74
- {
75
- $set: {
76
- lsns
77
- },
78
- $inc: {
79
- client_id: 1n
80
- }
81
- },
82
- { upsert: true, returnDocument: 'after' }
83
- );
84
- return doc!.client_id;
85
- }
86
-
87
- async lastWriteCheckpoint(filters: LastWriteCheckpointFilters): Promise<bigint | null> {
88
- switch (this.writeCheckpointMode) {
89
- case WriteCheckpointMode.CUSTOM:
90
- if (false == 'sync_rules_id' in filters) {
91
- throw new framework.errors.ValidationError(`Sync rules ID is required for custom Write Checkpoint filtering`);
92
- }
93
- return this.lastCustomWriteCheckpoint(filters);
94
- case WriteCheckpointMode.MANAGED:
95
- if (false == 'heads' in filters) {
96
- throw new framework.errors.ValidationError(
97
- `Replication HEAD is required for managed Write Checkpoint filtering`
98
- );
99
- }
100
- return this.lastManagedWriteCheckpoint(filters);
101
- }
102
- }
103
-
104
- protected async lastCustomWriteCheckpoint(filters: CustomWriteCheckpointFilters) {
105
- const { user_id, sync_rules_id } = filters;
106
- const lastWriteCheckpoint = await this.db.custom_write_checkpoints.findOne({
107
- user_id,
108
- sync_rules_id
109
- });
110
- return lastWriteCheckpoint?.checkpoint ?? null;
111
- }
112
-
113
- protected async lastManagedWriteCheckpoint(filters: ManagedWriteCheckpointFilters) {
114
- const { user_id, heads } = filters;
115
- // TODO: support multiple heads when we need to support multiple connections
116
- const lsn = heads['1'];
117
- if (lsn == null) {
118
- // Can happen if we haven't replicated anything yet.
119
- return null;
120
- }
121
- const lastWriteCheckpoint = await this.db.write_checkpoints.findOne({
122
- user_id: user_id,
123
- 'lsns.1': { $lte: lsn }
124
- });
125
- return lastWriteCheckpoint?.client_id ?? null;
126
- }
127
- }
128
-
129
- export async function batchCreateCustomWriteCheckpoints(
130
- db: PowerSyncMongo,
131
- checkpoints: CustomWriteCheckpointOptions[]
132
- ): Promise<void> {
133
- if (!checkpoints.length) {
134
- return;
135
- }
136
-
137
- await db.custom_write_checkpoints.bulkWrite(
138
- checkpoints.map((checkpointOptions) => ({
139
- updateOne: {
140
- filter: { user_id: checkpointOptions.user_id, sync_rules_id: checkpointOptions.sync_rules_id },
141
- update: {
142
- $set: {
143
- checkpoint: checkpointOptions.checkpoint,
144
- sync_rules_id: checkpointOptions.sync_rules_id
145
- }
146
- },
147
- upsert: true
148
- }
149
- }))
150
- );
151
- }
@@ -1,131 +0,0 @@
1
- import { ToastableSqliteRow } from '@powersync/service-sync-rules';
2
- import * as bson from 'bson';
3
-
4
- import { SaveOptions } from '../BucketStorage.js';
5
- import { isUUID } from './util.js';
6
- import { ReplicaId } from './models.js';
7
-
8
- /**
9
- * Maximum number of operations in a batch.
10
- */
11
- const MAX_BATCH_COUNT = 2000;
12
-
13
- /**
14
- * Maximum size of operations in the batch (estimated).
15
- */
16
- const MAX_RECORD_BATCH_SIZE = 5_000_000;
17
-
18
- /**
19
- * Maximum size of size of current_data documents we lookup at a time.
20
- */
21
- const MAX_CURRENT_DATA_BATCH_SIZE = 16_000_000;
22
-
23
- /**
24
- * Batch of input operations.
25
- *
26
- * We accumulate operations up to MAX_RECORD_BATCH_SIZE,
27
- * then further split into sub-batches if MAX_CURRENT_DATA_BATCH_SIZE is exceeded.
28
- */
29
- export class OperationBatch {
30
- batch: RecordOperation[] = [];
31
- currentSize: number = 0;
32
-
33
- get length() {
34
- return this.batch.length;
35
- }
36
-
37
- push(op: RecordOperation) {
38
- this.batch.push(op);
39
- this.currentSize += op.estimatedSize;
40
- }
41
-
42
- shouldFlush() {
43
- return this.batch.length >= MAX_BATCH_COUNT || this.currentSize > MAX_RECORD_BATCH_SIZE;
44
- }
45
-
46
- /**
47
- *
48
- * @param sizes Map of source key to estimated size of the current_data document, or undefined if current_data is not persisted.
49
- *
50
- */
51
- *batched(sizes: Map<string, number> | undefined): Generator<RecordOperation[]> {
52
- if (sizes == null) {
53
- yield this.batch;
54
- return;
55
- }
56
- let currentBatch: RecordOperation[] = [];
57
- let currentBatchSize = 0;
58
- for (let op of this.batch) {
59
- const key = op.internalBeforeKey;
60
- const size = sizes.get(key) ?? 0;
61
- if (currentBatchSize + size > MAX_CURRENT_DATA_BATCH_SIZE && currentBatch.length > 0) {
62
- yield currentBatch;
63
- currentBatch = [];
64
- currentBatchSize = 0;
65
- }
66
- currentBatchSize += size;
67
- currentBatch.push(op);
68
- }
69
- if (currentBatch.length > 0) {
70
- yield currentBatch;
71
- }
72
- }
73
- }
74
-
75
- export class RecordOperation {
76
- public readonly afterId: ReplicaId | null;
77
- public readonly beforeId: ReplicaId;
78
- public readonly internalBeforeKey: string;
79
- public readonly internalAfterKey: string | null;
80
- public readonly estimatedSize: number;
81
-
82
- constructor(public readonly record: SaveOptions) {
83
- const afterId = record.afterReplicaId ?? null;
84
- const beforeId = record.beforeReplicaId ?? record.afterReplicaId;
85
- this.afterId = afterId;
86
- this.beforeId = beforeId;
87
- this.internalBeforeKey = cacheKey(record.sourceTable.id, beforeId);
88
- this.internalAfterKey = afterId ? cacheKey(record.sourceTable.id, afterId) : null;
89
-
90
- this.estimatedSize = estimateRowSize(record.before) + estimateRowSize(record.after);
91
- }
92
- }
93
-
94
- /**
95
- * In-memory cache key - must not be persisted.
96
- */
97
- export function cacheKey(table: bson.ObjectId, id: ReplicaId) {
98
- if (isUUID(id)) {
99
- return `${table.toHexString()}.${id.toHexString()}`;
100
- } else if (typeof id == 'string') {
101
- return `${table.toHexString()}.${id}`;
102
- } else {
103
- return `${table.toHexString()}.${(bson.serialize({ id: id }) as Buffer).toString('base64')}`;
104
- }
105
- }
106
-
107
- /**
108
- * Estimate in-memory size of row.
109
- */
110
- function estimateRowSize(record: ToastableSqliteRow | undefined) {
111
- if (record == null) {
112
- return 12;
113
- }
114
- let size = 0;
115
- for (let [key, value] of Object.entries(record)) {
116
- size += 12 + key.length;
117
- // number | string | null | bigint | Uint8Array
118
- if (value == null) {
119
- size += 4;
120
- } else if (typeof value == 'number') {
121
- size += 8;
122
- } else if (typeof value == 'bigint') {
123
- size += 8;
124
- } else if (typeof value == 'string') {
125
- size += value.length;
126
- } else if (value instanceof Uint8Array) {
127
- size += value.byteLength;
128
- }
129
- }
130
- return size;
131
- }
@@ -1,272 +0,0 @@
1
- import { JSONBig } from '@powersync/service-jsonbig';
2
- import { EvaluatedParameters, EvaluatedRow } from '@powersync/service-sync-rules';
3
- import * as bson from 'bson';
4
- import * as mongo from 'mongodb';
5
-
6
- import * as util from '../../util/util-index.js';
7
- import { SourceTable } from '../SourceTable.js';
8
- import { currentBucketKey } from './MongoBucketBatch.js';
9
- import { MongoIdSequence } from './MongoIdSequence.js';
10
- import { PowerSyncMongo } from './db.js';
11
- import {
12
- BucketDataDocument,
13
- BucketParameterDocument,
14
- CurrentBucket,
15
- CurrentDataDocument,
16
- SourceKey,
17
- ReplicaId
18
- } from './models.js';
19
- import { replicaIdToSubkey, serializeLookup } from './util.js';
20
- import { logger } from '@powersync/lib-services-framework';
21
-
22
- /**
23
- * Maximum size of operations we write in a single transaction.
24
- *
25
- * It's tricky to find the exact limit, but from experience, over 100MB
26
- * can cause an error:
27
- * > transaction is too large and will not fit in the storage engine cache
28
- *
29
- * Additionally, unbounded size here can balloon our memory usage in some edge
30
- * cases.
31
- *
32
- * When we reach this threshold, we commit the transaction and start a new one.
33
- */
34
- const MAX_TRANSACTION_BATCH_SIZE = 30_000_000;
35
-
36
- /**
37
- * Keeps track of bulkwrite operations within a transaction.
38
- *
39
- * There may be multiple of these batches per transaction, but it may not span
40
- * multiple transactions.
41
- */
42
- export class PersistedBatch {
43
- bucketData: mongo.AnyBulkWriteOperation<BucketDataDocument>[] = [];
44
- bucketParameters: mongo.AnyBulkWriteOperation<BucketParameterDocument>[] = [];
45
- currentData: mongo.AnyBulkWriteOperation<CurrentDataDocument>[] = [];
46
-
47
- /**
48
- * For debug logging only.
49
- */
50
- debugLastOpId: bigint | null = null;
51
-
52
- /**
53
- * Very rough estimate of transaction size.
54
- */
55
- currentSize = 0;
56
-
57
- constructor(
58
- private group_id: number,
59
- writtenSize: number
60
- ) {
61
- this.currentSize = writtenSize;
62
- }
63
-
64
- saveBucketData(options: {
65
- op_seq: MongoIdSequence;
66
- sourceKey: ReplicaId;
67
- table: SourceTable;
68
- evaluated: EvaluatedRow[];
69
- before_buckets: CurrentBucket[];
70
- }) {
71
- const remaining_buckets = new Map<string, CurrentBucket>();
72
- for (let b of options.before_buckets) {
73
- const key = currentBucketKey(b);
74
- remaining_buckets.set(key, b);
75
- }
76
-
77
- const dchecksum = util.hashDelete(replicaIdToSubkey(options.table.id, options.sourceKey));
78
-
79
- for (let k of options.evaluated) {
80
- const key = currentBucketKey(k);
81
- remaining_buckets.delete(key);
82
-
83
- // INSERT
84
- const recordData = JSONBig.stringify(k.data);
85
- const checksum = util.hashData(k.table, k.id, recordData);
86
- this.currentSize += recordData.length + 200;
87
-
88
- const op_id = options.op_seq.next();
89
- this.debugLastOpId = op_id;
90
-
91
- this.bucketData.push({
92
- insertOne: {
93
- document: {
94
- _id: {
95
- g: this.group_id,
96
- b: k.bucket,
97
- o: op_id
98
- },
99
- op: 'PUT',
100
- source_table: options.table.id,
101
- source_key: options.sourceKey,
102
- table: k.table,
103
- row_id: k.id,
104
- checksum: checksum,
105
- data: recordData
106
- }
107
- }
108
- });
109
- }
110
-
111
- for (let bd of remaining_buckets.values()) {
112
- // REMOVE
113
-
114
- const op_id = options.op_seq.next();
115
- this.debugLastOpId = op_id;
116
-
117
- this.bucketData.push({
118
- insertOne: {
119
- document: {
120
- _id: {
121
- g: this.group_id,
122
- b: bd.bucket,
123
- o: op_id
124
- },
125
- op: 'REMOVE',
126
- source_table: options.table.id,
127
- source_key: options.sourceKey,
128
- table: bd.table,
129
- row_id: bd.id,
130
- checksum: dchecksum,
131
- data: null
132
- }
133
- }
134
- });
135
- this.currentSize += 200;
136
- }
137
- }
138
-
139
- saveParameterData(data: {
140
- op_seq: MongoIdSequence;
141
- sourceKey: ReplicaId;
142
- sourceTable: SourceTable;
143
- evaluated: EvaluatedParameters[];
144
- existing_lookups: bson.Binary[];
145
- }) {
146
- // This is similar to saving bucket data.
147
- // A key difference is that we don't need to keep the history intact.
148
- // We do need to keep track of recent history though - enough that we can get consistent data for any specific checkpoint.
149
- // Instead of storing per bucket id, we store per "lookup".
150
- // A key difference is that we don't need to store or keep track of anything per-bucket - the entire record is
151
- // either persisted or removed.
152
- // We also don't need to keep history intact.
153
- const { sourceTable, sourceKey, evaluated } = data;
154
-
155
- const remaining_lookups = new Map<string, bson.Binary>();
156
- for (let l of data.existing_lookups) {
157
- remaining_lookups.set(l.toString('base64'), l);
158
- }
159
-
160
- // 1. Insert new entries
161
- for (let result of evaluated) {
162
- const binLookup = serializeLookup(result.lookup);
163
- const hex = binLookup.toString('base64');
164
- remaining_lookups.delete(hex);
165
-
166
- const op_id = data.op_seq.next();
167
- this.debugLastOpId = op_id;
168
- this.bucketParameters.push({
169
- insertOne: {
170
- document: {
171
- _id: op_id,
172
- key: {
173
- g: this.group_id,
174
- t: sourceTable.id,
175
- k: sourceKey
176
- },
177
- lookup: binLookup,
178
- bucket_parameters: result.bucket_parameters
179
- }
180
- }
181
- });
182
-
183
- this.currentSize += 200;
184
- }
185
-
186
- // 2. "REMOVE" entries for any lookup not touched.
187
- for (let lookup of remaining_lookups.values()) {
188
- const op_id = data.op_seq.next();
189
- this.debugLastOpId = op_id;
190
- this.bucketParameters.push({
191
- insertOne: {
192
- document: {
193
- _id: op_id,
194
- key: {
195
- g: this.group_id,
196
- t: sourceTable.id,
197
- k: sourceKey
198
- },
199
- lookup: lookup,
200
- bucket_parameters: []
201
- }
202
- }
203
- });
204
-
205
- this.currentSize += 200;
206
- }
207
- }
208
-
209
- deleteCurrentData(id: SourceKey) {
210
- const op: mongo.AnyBulkWriteOperation<CurrentDataDocument> = {
211
- deleteOne: {
212
- filter: { _id: id }
213
- }
214
- };
215
- this.currentData.push(op);
216
- this.currentSize += 50;
217
- }
218
-
219
- upsertCurrentData(id: SourceKey, values: Partial<CurrentDataDocument>) {
220
- const op: mongo.AnyBulkWriteOperation<CurrentDataDocument> = {
221
- updateOne: {
222
- filter: { _id: id },
223
- update: {
224
- $set: values
225
- },
226
- upsert: true
227
- }
228
- };
229
- this.currentData.push(op);
230
- this.currentSize += (values.data?.length() ?? 0) + 100;
231
- }
232
-
233
- shouldFlushTransaction() {
234
- return this.currentSize >= MAX_TRANSACTION_BATCH_SIZE;
235
- }
236
-
237
- async flush(db: PowerSyncMongo, session: mongo.ClientSession) {
238
- if (this.bucketData.length > 0) {
239
- await db.bucket_data.bulkWrite(this.bucketData, {
240
- session,
241
- // inserts only - order doesn't matter
242
- ordered: false
243
- });
244
- }
245
- if (this.bucketParameters.length > 0) {
246
- await db.bucket_parameters.bulkWrite(this.bucketParameters, {
247
- session,
248
- // inserts only - order doesn't matter
249
- ordered: false
250
- });
251
- }
252
- if (this.currentData.length > 0) {
253
- await db.current_data.bulkWrite(this.currentData, {
254
- session,
255
- // may update and delete data within the same batch - order matters
256
- ordered: true
257
- });
258
- }
259
-
260
- logger.info(
261
- `powersync_${this.group_id} Flushed ${this.bucketData.length} + ${this.bucketParameters.length} + ${
262
- this.currentData.length
263
- } updates, ${Math.round(this.currentSize / 1024)}kb. Last op_id: ${this.debugLastOpId}`
264
- );
265
-
266
- this.bucketData = [];
267
- this.bucketParameters = [];
268
- this.currentData = [];
269
- this.currentSize = 0;
270
- this.debugLastOpId = null;
271
- }
272
- }
@@ -1,40 +0,0 @@
1
- import * as urijs from 'uri-js';
2
-
3
- export interface MongoConnectionConfig {
4
- uri: string;
5
- username?: string;
6
- password?: string;
7
- database?: string;
8
- }
9
-
10
- /**
11
- * Validate and normalize connection options.
12
- *
13
- * Returns destructured options.
14
- *
15
- * For use by both storage and mongo module.
16
- */
17
- export function normalizeMongoConfig(options: MongoConnectionConfig) {
18
- let uri = urijs.parse(options.uri);
19
-
20
- const database = options.database ?? uri.path?.substring(1) ?? '';
21
-
22
- const userInfo = uri.userinfo?.split(':');
23
-
24
- const username = options.username ?? userInfo?.[0];
25
- const password = options.password ?? userInfo?.[1];
26
-
27
- if (database == '') {
28
- throw new Error(`database required`);
29
- }
30
-
31
- delete uri.userinfo;
32
-
33
- return {
34
- uri: urijs.serialize(uri),
35
- database,
36
-
37
- username,
38
- password
39
- };
40
- }
@@ -1,88 +0,0 @@
1
- import * as mongo from 'mongodb';
2
-
3
- import { configFile } from '@powersync/service-types';
4
- import * as db from '../../db/db-index.js';
5
- import * as locks from '../../locks/locks-index.js';
6
- import {
7
- BucketDataDocument,
8
- BucketParameterDocument,
9
- CurrentDataDocument,
10
- CustomWriteCheckpointDocument,
11
- IdSequenceDocument,
12
- InstanceDocument,
13
- SourceTableDocument,
14
- SyncRuleDocument,
15
- WriteCheckpointDocument
16
- } from './models.js';
17
- import { BSON_DESERIALIZE_OPTIONS } from './util.js';
18
-
19
- export interface PowerSyncMongoOptions {
20
- /**
21
- * Optional - uses the database from the MongoClient connection URI if not specified.
22
- */
23
- database?: string;
24
- }
25
-
26
- export function createPowerSyncMongo(config: configFile.PowerSyncConfig['storage']) {
27
- return new PowerSyncMongo(db.mongo.createMongoClient(config), { database: config.database });
28
- }
29
-
30
- export class PowerSyncMongo {
31
- readonly current_data: mongo.Collection<CurrentDataDocument>;
32
- readonly bucket_data: mongo.Collection<BucketDataDocument>;
33
- readonly bucket_parameters: mongo.Collection<BucketParameterDocument>;
34
- readonly op_id_sequence: mongo.Collection<IdSequenceDocument>;
35
- readonly sync_rules: mongo.Collection<SyncRuleDocument>;
36
- readonly source_tables: mongo.Collection<SourceTableDocument>;
37
- readonly custom_write_checkpoints: mongo.Collection<CustomWriteCheckpointDocument>;
38
- readonly write_checkpoints: mongo.Collection<WriteCheckpointDocument>;
39
- readonly instance: mongo.Collection<InstanceDocument>;
40
- readonly locks: mongo.Collection<locks.Lock>;
41
-
42
- readonly client: mongo.MongoClient;
43
- readonly db: mongo.Db;
44
-
45
- constructor(client: mongo.MongoClient, options?: PowerSyncMongoOptions) {
46
- this.client = client;
47
-
48
- const db = client.db(options?.database, {
49
- ...BSON_DESERIALIZE_OPTIONS
50
- });
51
- this.db = db;
52
-
53
- this.current_data = db.collection<CurrentDataDocument>('current_data');
54
- this.bucket_data = db.collection('bucket_data');
55
- this.bucket_parameters = db.collection('bucket_parameters');
56
- this.op_id_sequence = db.collection('op_id_sequence');
57
- this.sync_rules = db.collection('sync_rules');
58
- this.source_tables = db.collection('source_tables');
59
- this.custom_write_checkpoints = db.collection('custom_write_checkpoints');
60
- this.write_checkpoints = db.collection('write_checkpoints');
61
- this.instance = db.collection('instance');
62
- this.locks = this.db.collection('locks');
63
- }
64
-
65
- /**
66
- * Clear all collections.
67
- */
68
- async clear() {
69
- await this.current_data.deleteMany({});
70
- await this.bucket_data.deleteMany({});
71
- await this.bucket_parameters.deleteMany({});
72
- await this.op_id_sequence.deleteMany({});
73
- await this.sync_rules.deleteMany({});
74
- await this.source_tables.deleteMany({});
75
- await this.write_checkpoints.deleteMany({});
76
- await this.instance.deleteOne({});
77
- await this.locks.deleteMany({});
78
- }
79
-
80
- /**
81
- * Drop the entire database.
82
- *
83
- * Primarily for tests.
84
- */
85
- async drop() {
86
- await this.db.dropDatabase();
87
- }
88
- }