@powersync/service-module-mongodb-storage 0.0.0-dev-20250813075005 → 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.
- package/CHANGELOG.md +10 -7
- package/dist/migrations/db/migrations/1752661449910-sdk-reporting.js +11 -8
- package/dist/migrations/db/migrations/1752661449910-sdk-reporting.js.map +1 -1
- package/dist/storage/MongoReportStorage.d.ts +7 -7
- package/dist/storage/MongoReportStorage.js +78 -75
- package/dist/storage/MongoReportStorage.js.map +1 -1
- package/dist/storage/implementation/MongoBucketBatch.js +1 -1
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoStorageProvider.d.ts +1 -1
- package/dist/storage/implementation/db.d.ts +2 -2
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +1 -1
- package/package.json +8 -8
- package/src/migrations/db/migrations/1752661449910-sdk-reporting.ts +11 -8
- package/src/storage/MongoReportStorage.ts +91 -91
- package/src/storage/implementation/MongoBucketBatch.ts +1 -1
- package/src/storage/implementation/MongoStorageProvider.ts +1 -1
- package/src/storage/implementation/db.ts +2 -2
- package/src/storage/implementation/models.ts +1 -1
- package/test/src/sdk-report-storage.test.ts +24 -24
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -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.
|
|
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
|
-
|
|
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)
|
|
@@ -95,99 +181,13 @@ export class MongoReportStorage implements storage.ReportStorageFactory {
|
|
|
95
181
|
if (!range) {
|
|
96
182
|
return undefined;
|
|
97
183
|
}
|
|
98
|
-
const endDate = data.range?.
|
|
99
|
-
const startDate = new Date(range.
|
|
184
|
+
const endDate = data.range?.end ? new Date(data.range.end) : new Date();
|
|
185
|
+
const startDate = new Date(range.start);
|
|
100
186
|
return {
|
|
101
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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<
|
|
41
|
+
readonly sdk_report_events: mongo.Collection<SdkConnectEventDocument>;
|
|
42
42
|
|
|
43
43
|
readonly client: mongo.MongoClient;
|
|
44
44
|
readonly db: mongo.Db;
|
|
@@ -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.
|
|
7
|
-
): Partial<event_types.
|
|
8
|
-
return sdks.map((sdk: Partial<event_types.
|
|
9
|
-
const { _id,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -121,7 +121,7 @@ describe('SDK reporting storage', async () => {
|
|
|
121
121
|
it('Should show connected users with start range', async () => {
|
|
122
122
|
const current = await factory.listCurrentConnections({
|
|
123
123
|
range: {
|
|
124
|
-
|
|
124
|
+
start: new Date(
|
|
125
125
|
now.getFullYear(),
|
|
126
126
|
now.getMonth(),
|
|
127
127
|
now.getDate(),
|
|
@@ -135,8 +135,8 @@ describe('SDK reporting storage', async () => {
|
|
|
135
135
|
it('Should show connected users with start range and end range', async () => {
|
|
136
136
|
const current = await factory.listCurrentConnections({
|
|
137
137
|
range: {
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
end: nowLess5minutes.toISOString(),
|
|
139
|
+
start: new Date(
|
|
140
140
|
now.getFullYear(),
|
|
141
141
|
now.getMonth(),
|
|
142
142
|
now.getDate(),
|
|
@@ -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
|
-
|
|
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].
|
|
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].
|
|
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
|
-
|
|
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
|
-
|
|
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].
|
|
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
|
-
|
|
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,
|