@powersync/service-module-mongodb-storage 0.0.0-dev-20250813080357 → 0.0.0-dev-20250818104041

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.
@@ -4,7 +4,7 @@ import { event_types } from '@powersync/service-types';
4
4
  import { PowerSyncMongo } from './implementation/db.js';
5
5
  import { logger } from '@powersync/lib-services-framework';
6
6
 
7
- export class MongoReportStorage implements storage.ReportStorageFactory {
7
+ export class MongoReportStorage implements storage.ReportStorage {
8
8
  private readonly client: mongo.MongoClient;
9
9
  public readonly db: PowerSyncMongo;
10
10
 
@@ -12,6 +12,92 @@ export class MongoReportStorage implements storage.ReportStorageFactory {
12
12
  this.client = db.client;
13
13
  this.db = db;
14
14
  }
15
+ async deleteOldSdkData(data: event_types.DeleteOldSdkData): Promise<void> {
16
+ const { date } = data;
17
+ const result = await this.db.sdk_report_events.deleteMany({
18
+ connected_at: { $lt: date },
19
+ $or: [
20
+ { disconnected_at: { $exists: true } },
21
+ { jwt_exp: { $lt: new Date() }, disconnected_at: { $exists: false } }
22
+ ]
23
+ });
24
+ if (result.deletedCount > 0) {
25
+ logger.info(
26
+ `TTL from ${date.toISOString()}: ${result.deletedCount} MongoDB documents have been removed from sdk_report_events.`
27
+ );
28
+ }
29
+ }
30
+
31
+ async scrapeSdkData(data: event_types.ScrapeSdkDataRequest): Promise<event_types.SdkConnections> {
32
+ const { start, end } = data;
33
+ const result = await this.db.sdk_report_events
34
+ .aggregate<event_types.SdkConnections>([
35
+ {
36
+ $match: {
37
+ connected_at: { $lte: end, $gte: start }
38
+ }
39
+ },
40
+ this.sdkFacetPipeline(),
41
+ this.sdkProjectPipeline()
42
+ ])
43
+ .toArray();
44
+ return result[0];
45
+ }
46
+
47
+ async reportSdkConnect(data: event_types.SdkConnectBucketData): Promise<void> {
48
+ const updateFilter = this.updateDocFilter(data.user_id, data.client_id!);
49
+ await this.db.sdk_report_events.findOneAndUpdate(
50
+ updateFilter,
51
+ {
52
+ $set: data,
53
+ $unset: {
54
+ disconnected_at: ''
55
+ }
56
+ },
57
+ {
58
+ upsert: true
59
+ }
60
+ );
61
+ }
62
+ async reportSdkDisconnect(data: event_types.SdkDisconnectEventData): Promise<void> {
63
+ const { connected_at, user_id, client_id } = data;
64
+ await this.db.sdk_report_events.findOneAndUpdate(
65
+ {
66
+ client_id,
67
+ user_id,
68
+ connected_at
69
+ },
70
+ {
71
+ $set: {
72
+ disconnected_at: data.disconnected_at
73
+ },
74
+ $unset: {
75
+ jwt_exp: ''
76
+ }
77
+ }
78
+ );
79
+ }
80
+ async listCurrentConnections(data: event_types.ListCurrentConnectionsRequest): Promise<event_types.SdkConnections> {
81
+ const timeframeFilter = this.listConnectionsDateRange(data);
82
+ const result = await this.db.sdk_report_events
83
+ .aggregate<event_types.SdkConnections>([
84
+ {
85
+ $match: {
86
+ disconnected_at: { $exists: false },
87
+ jwt_exp: { $gt: new Date() },
88
+ ...timeframeFilter
89
+ }
90
+ },
91
+ this.sdkFacetPipeline(),
92
+ this.sdkProjectPipeline()
93
+ ])
94
+ .toArray();
95
+ return result[0];
96
+ }
97
+
98
+ async [Symbol.asyncDispose]() {
99
+ // No-op
100
+ }
15
101
 
16
102
  private parseJsDate(date: Date) {
17
103
  const year = date.getFullYear();
@@ -82,7 +168,7 @@ export class MongoReportStorage implements storage.ReportStorageFactory {
82
168
  return {
83
169
  user_id: userId,
84
170
  client_id: clientId,
85
- connect_at: {
171
+ connected_at: {
86
172
  // Need to create a new date here to sett the time to 00:00:00
87
173
  $gte: new Date(year, month, today),
88
174
  $lt: new Date(year, month, nextDay)
@@ -98,96 +184,10 @@ export class MongoReportStorage implements storage.ReportStorageFactory {
98
184
  const endDate = data.range?.end ? new Date(data.range.end) : new Date();
99
185
  const startDate = new Date(range.start);
100
186
  return {
101
- connect_at: {
187
+ connected_at: {
102
188
  $lte: endDate,
103
189
  $gte: startDate
104
190
  }
105
191
  };
106
192
  }
107
-
108
- async deleteOldSdkData(data: event_types.DeleteOldSdkData): Promise<void> {
109
- const { date } = data;
110
- const result = await this.db.sdk_report_events.deleteMany({
111
- connect_at: { $lt: date },
112
- $or: [{ disconnect_at: { $exists: true } }, { jwt_exp: { $lt: new Date() }, disconnect_at: { $exists: false } }]
113
- });
114
- if (result.deletedCount > 0) {
115
- logger.info(
116
- `TTL from ${date.toISOString()}: ${result.deletedCount} MongoDB documents have been removed from sdk_report_events.`
117
- );
118
- }
119
- }
120
-
121
- async scrapeSdkData(data: event_types.ScrapeSdkDataRequest): Promise<event_types.ListCurrentConnections> {
122
- const { start, end } = data;
123
- const result = await this.db.sdk_report_events
124
- .aggregate<event_types.ListCurrentConnections>([
125
- {
126
- $match: {
127
- connect_at: { $lte: end, $gte: start }
128
- }
129
- },
130
- this.sdkFacetPipeline(),
131
- this.sdkProjectPipeline()
132
- ])
133
- .toArray();
134
- return result[0];
135
- }
136
-
137
- async reportSdkConnect(data: event_types.SdkConnectBucketData): Promise<void> {
138
- const updateFilter = this.updateDocFilter(data.user_id, data.client_id!);
139
- await this.db.sdk_report_events.findOneAndUpdate(
140
- updateFilter,
141
- {
142
- $set: data,
143
- $unset: {
144
- disconnect_at: ''
145
- }
146
- },
147
- {
148
- upsert: true
149
- }
150
- );
151
- }
152
- async reportSdkDisconnect(data: event_types.SdkDisconnectEventData): Promise<void> {
153
- const { connect_at, user_id, client_id } = data;
154
- await this.db.sdk_report_events.findOneAndUpdate(
155
- {
156
- client_id,
157
- user_id,
158
- connect_at
159
- },
160
- {
161
- $set: {
162
- disconnect_at: data.disconnect_at
163
- },
164
- $unset: {
165
- jwt_exp: ''
166
- }
167
- }
168
- );
169
- }
170
- async listCurrentConnections(
171
- data: event_types.ListCurrentConnectionsRequest
172
- ): Promise<event_types.ListCurrentConnections> {
173
- const timeframeFilter = this.listConnectionsDateRange(data);
174
- const result = await this.db.sdk_report_events
175
- .aggregate<event_types.ListCurrentConnections>([
176
- {
177
- $match: {
178
- disconnect_at: { $exists: false },
179
- jwt_exp: { $gt: new Date() },
180
- ...timeframeFilter
181
- }
182
- },
183
- this.sdkFacetPipeline(),
184
- this.sdkProjectPipeline()
185
- ])
186
- .toArray();
187
- return result[0];
188
- }
189
-
190
- async [Symbol.asyncDispose]() {
191
- // No-op
192
- }
193
193
  }
@@ -807,7 +807,7 @@ export class MongoBucketBatch
807
807
  }
808
808
 
809
809
  async keepalive(lsn: string): Promise<boolean> {
810
- if (this.last_checkpoint_lsn != null && lsn <= this.last_checkpoint_lsn) {
810
+ if (this.last_checkpoint_lsn != null && lsn < this.last_checkpoint_lsn) {
811
811
  // No-op
812
812
  return false;
813
813
  }
@@ -6,7 +6,7 @@ import { MongoBucketStorage } from '../MongoBucketStorage.js';
6
6
  import { PowerSyncMongo } from './db.js';
7
7
  import { MongoReportStorage } from '../MongoReportStorage.js';
8
8
 
9
- export class MongoStorageProvider implements storage.BucketStorageProvider {
9
+ export class MongoStorageProvider implements storage.StorageProvider {
10
10
  get type() {
11
11
  return lib_mongo.MONGO_CONNECTION_TYPE;
12
12
  }
@@ -12,7 +12,7 @@ import {
12
12
  CustomWriteCheckpointDocument,
13
13
  IdSequenceDocument,
14
14
  InstanceDocument,
15
- SdkConnectDocument,
15
+ SdkConnectEventDocument,
16
16
  SourceTableDocument,
17
17
  SyncRuleDocument,
18
18
  WriteCheckpointDocument
@@ -38,7 +38,7 @@ export class PowerSyncMongo {
38
38
  readonly locks: mongo.Collection<lib_mongo.locks.Lock>;
39
39
  readonly bucket_state: mongo.Collection<BucketStateDocument>;
40
40
  readonly checkpoint_events: mongo.Collection<CheckpointEventDocument>;
41
- readonly sdk_report_events: mongo.Collection<SdkConnectDocument>;
41
+ readonly sdk_report_events: mongo.Collection<SdkConnectEventDocument>;
42
42
 
43
43
  readonly client: mongo.MongoClient;
44
44
  readonly db: mongo.Db;
@@ -221,4 +221,4 @@ export interface InstanceDocument {
221
221
  _id: string;
222
222
  }
223
223
 
224
- export interface SdkConnectDocument extends event_types.SdkConnectDocument {}
224
+ export interface SdkConnectEventDocument extends event_types.SdkConnection {}
@@ -3,10 +3,10 @@ import { INITIALIZED_MONGO_REPORT_STORAGE_FACTORY } from './util.js';
3
3
  import { event_types } from '@powersync/service-types';
4
4
 
5
5
  function removeVolatileFields(
6
- sdks: event_types.SdkConnectDocument[]
7
- ): Partial<event_types.SdkConnectDocument & { _id: string }>[] {
8
- return sdks.map((sdk: Partial<event_types.SdkConnectDocument & { _id: string }>) => {
9
- const { _id, disconnect_at, connect_at, jwt_exp, ...rest } = sdk;
6
+ sdks: event_types.SdkConnection[]
7
+ ): Partial<event_types.SdkConnection & { _id: string }>[] {
8
+ return sdks.map((sdk: Partial<event_types.SdkConnection & { _id: string }>) => {
9
+ const { _id, disconnected_at, connected_at, jwt_exp, ...rest } = sdk;
10
10
  return {
11
11
  ...rest
12
12
  };
@@ -37,7 +37,7 @@ describe('SDK reporting storage', async () => {
37
37
  const user_one = {
38
38
  user_id: 'user_one',
39
39
  client_id: 'client_one',
40
- connect_at: now,
40
+ connected_at: now,
41
41
  sdk: 'powersync-dart/1.6.4',
42
42
  user_agent: 'powersync-dart/1.6.4 Dart (flutter-web) Chrome/128 android',
43
43
  jwt_exp: nowAdd5minutes
@@ -45,7 +45,7 @@ describe('SDK reporting storage', async () => {
45
45
  const user_two = {
46
46
  user_id: 'user_two',
47
47
  client_id: 'client_two',
48
- connect_at: nowLess5minutes,
48
+ connected_at: nowLess5minutes,
49
49
  sdk: 'powersync-js/1.21.1',
50
50
  user_agent: 'powersync-js/1.21.0 powersync-web Chromium/138 linux',
51
51
  jwt_exp: nowAdd5minutes
@@ -53,16 +53,16 @@ describe('SDK reporting storage', async () => {
53
53
  const user_three = {
54
54
  user_id: 'user_three',
55
55
  client_id: 'client_three',
56
- connect_at: yesterday,
56
+ connected_at: yesterday,
57
57
  sdk: 'powersync-js/1.21.2',
58
58
  user_agent: 'powersync-js/1.21.0 powersync-web Firefox/141 linux',
59
- disconnect_at: yesterday
59
+ disconnected_at: yesterday
60
60
  };
61
61
 
62
62
  const user_four = {
63
63
  user_id: 'user_four',
64
64
  client_id: 'client_four',
65
- connect_at: now,
65
+ connected_at: now,
66
66
  sdk: 'powersync-js/1.21.4',
67
67
  user_agent: 'powersync-js/1.21.0 powersync-web Firefox/141 linux',
68
68
  jwt_exp: nowLess5minutes
@@ -71,25 +71,25 @@ describe('SDK reporting storage', async () => {
71
71
  const user_week = {
72
72
  user_id: 'user_week',
73
73
  client_id: 'client_week',
74
- connect_at: weekAgo,
74
+ connected_at: weekAgo,
75
75
  sdk: 'powersync-js/1.24.5',
76
76
  user_agent: 'powersync-js/1.21.0 powersync-web Firefox/141 linux',
77
- disconnect_at: weekAgo
77
+ disconnected_at: weekAgo
78
78
  };
79
79
 
80
80
  const user_month = {
81
81
  user_id: 'user_month',
82
82
  client_id: 'client_month',
83
- connect_at: monthAgo,
83
+ connected_at: monthAgo,
84
84
  sdk: 'powersync-js/1.23.6',
85
85
  user_agent: 'powersync-js/1.23.0 powersync-web Firefox/141 linux',
86
- disconnect_at: monthAgo
86
+ disconnected_at: monthAgo
87
87
  };
88
88
 
89
89
  const user_expired = {
90
90
  user_id: 'user_expired',
91
91
  client_id: 'client_expired',
92
- connect_at: monthAgo,
92
+ connected_at: monthAgo,
93
93
  sdk: 'powersync-js/1.23.7',
94
94
  user_agent: 'powersync-js/1.23.0 powersync-web Firefox/141 linux',
95
95
  jwt_exp: monthAgo
@@ -180,7 +180,7 @@ describe('SDK reporting storage', async () => {
180
180
  const jwtExp = new Date(newConnectAt.getFullYear(), newConnectAt.getMonth(), newConnectAt.getDate() + 1);
181
181
  await factory.reportSdkConnect({
182
182
  sdk: user_one.sdk,
183
- connect_at: newConnectAt,
183
+ connected_at: newConnectAt,
184
184
  jwt_exp: jwtExp,
185
185
  client_id: user_one.client_id,
186
186
  user_id: user_one.user_id,
@@ -189,9 +189,9 @@ describe('SDK reporting storage', async () => {
189
189
 
190
190
  const sdk = await factory.db.sdk_report_events.find({ user_id: user_one.user_id }).toArray();
191
191
  expect(sdk).toHaveLength(1);
192
- expect(new Date(sdk[0].connect_at)).toEqual(newConnectAt);
192
+ expect(new Date(sdk[0].connected_at)).toEqual(newConnectAt);
193
193
  expect(new Date(sdk[0].jwt_exp!)).toEqual(jwtExp);
194
- expect(sdk[0].disconnect_at).toBeUndefined();
194
+ expect(sdk[0].disconnected_at).toBeUndefined();
195
195
  const cleaned = removeVolatileFields(sdk);
196
196
  expect(cleaned).toMatchSnapshot();
197
197
  });
@@ -207,17 +207,17 @@ describe('SDK reporting storage', async () => {
207
207
  const jwtExp = new Date(disconnectAt.getFullYear(), disconnectAt.getMonth(), disconnectAt.getDate() + 1);
208
208
 
209
209
  await factory.reportSdkDisconnect({
210
- disconnect_at: disconnectAt,
210
+ disconnected_at: disconnectAt,
211
211
  jwt_exp: jwtExp,
212
212
  client_id: user_three.client_id,
213
213
  user_id: user_three.user_id,
214
214
  user_agent: user_three.user_agent,
215
- connect_at: user_three.connect_at
215
+ connected_at: user_three.connected_at
216
216
  });
217
217
 
218
218
  const sdk = await factory.db.sdk_report_events.find({ user_id: user_three.user_id }).toArray();
219
219
  expect(sdk).toHaveLength(1);
220
- expect(new Date(sdk[0].disconnect_at!)).toEqual(disconnectAt);
220
+ expect(new Date(sdk[0].disconnected_at!)).toEqual(disconnectAt);
221
221
  const cleaned = removeVolatileFields(sdk);
222
222
  expect(cleaned).toMatchSnapshot();
223
223
  });
@@ -228,7 +228,7 @@ describe('SDK reporting storage', async () => {
228
228
 
229
229
  await factory.reportSdkConnect({
230
230
  sdk: user_week.sdk,
231
- connect_at: newConnectAt,
231
+ connected_at: newConnectAt,
232
232
  jwt_exp: jwtExp,
233
233
  client_id: user_week.client_id,
234
234
  user_id: user_week.user_id,