@powersync/service-module-mongodb-storage 0.0.0-dev-20250828134335 → 0.0.0-dev-20250901073220

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 (69) hide show
  1. package/CHANGELOG.md +22 -5
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.js +1 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/migrations/db/migrations/1752661449910-connection-reporting.d.ts +3 -0
  6. package/dist/migrations/db/migrations/1752661449910-connection-reporting.js +36 -0
  7. package/dist/migrations/db/migrations/1752661449910-connection-reporting.js.map +1 -0
  8. package/dist/storage/MongoBucketStorage.js +1 -1
  9. package/dist/storage/MongoBucketStorage.js.map +1 -1
  10. package/dist/storage/MongoReportStorage.d.ts +18 -0
  11. package/dist/storage/MongoReportStorage.js +154 -0
  12. package/dist/storage/MongoReportStorage.js.map +1 -0
  13. package/dist/storage/implementation/MongoBucketBatch.js +1 -1
  14. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  15. package/dist/storage/implementation/MongoChecksums.d.ts +34 -0
  16. package/dist/storage/implementation/MongoChecksums.js +274 -0
  17. package/dist/storage/implementation/MongoChecksums.js.map +1 -0
  18. package/dist/storage/implementation/MongoCompactor.js +26 -29
  19. package/dist/storage/implementation/MongoCompactor.js.map +1 -1
  20. package/dist/storage/implementation/MongoStorageProvider.d.ts +1 -1
  21. package/dist/storage/implementation/MongoStorageProvider.js +7 -3
  22. package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
  23. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +2 -11
  24. package/dist/storage/implementation/MongoSyncBucketStorage.js +7 -207
  25. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  26. package/dist/storage/implementation/PersistedBatch.js +1 -1
  27. package/dist/storage/implementation/PersistedBatch.js.map +1 -1
  28. package/dist/storage/implementation/db.d.ts +6 -1
  29. package/dist/storage/implementation/db.js +16 -0
  30. package/dist/storage/implementation/db.js.map +1 -1
  31. package/dist/storage/implementation/models.d.ts +4 -1
  32. package/dist/storage/storage-index.d.ts +3 -2
  33. package/dist/storage/storage-index.js +3 -2
  34. package/dist/storage/storage-index.js.map +1 -1
  35. package/dist/utils/test-utils.d.ts +11 -0
  36. package/dist/utils/test-utils.js +40 -0
  37. package/dist/utils/test-utils.js.map +1 -0
  38. package/dist/{storage/implementation → utils}/util.d.ts +2 -34
  39. package/dist/{storage/implementation → utils}/util.js +0 -54
  40. package/dist/utils/util.js.map +1 -0
  41. package/dist/utils/utils-index.d.ts +2 -0
  42. package/dist/utils/utils-index.js +3 -0
  43. package/dist/utils/utils-index.js.map +1 -0
  44. package/package.json +7 -7
  45. package/src/index.ts +1 -0
  46. package/src/migrations/db/migrations/1752661449910-connection-reporting.ts +58 -0
  47. package/src/storage/MongoBucketStorage.ts +1 -1
  48. package/src/storage/MongoReportStorage.ts +177 -0
  49. package/src/storage/implementation/MongoBucketBatch.ts +1 -1
  50. package/src/storage/implementation/MongoChecksums.ts +320 -0
  51. package/src/storage/implementation/MongoCompactor.ts +56 -56
  52. package/src/storage/implementation/MongoStorageProvider.ts +9 -4
  53. package/src/storage/implementation/MongoSyncBucketStorage.ts +7 -255
  54. package/src/storage/implementation/PersistedBatch.ts +1 -1
  55. package/src/storage/implementation/db.ts +18 -0
  56. package/src/storage/implementation/models.ts +4 -1
  57. package/src/storage/storage-index.ts +3 -2
  58. package/src/utils/test-utils.ts +55 -0
  59. package/src/{storage/implementation → utils}/util.ts +2 -59
  60. package/src/utils/utils-index.ts +2 -0
  61. package/test/src/__snapshots__/connection-report-storage.test.ts.snap +215 -0
  62. package/test/src/connection-report-storage.test.ts +133 -0
  63. package/test/src/util.ts +6 -2
  64. package/tsconfig.tsbuildinfo +1 -1
  65. package/dist/storage/implementation/MongoTestStorageFactoryGenerator.d.ts +0 -7
  66. package/dist/storage/implementation/MongoTestStorageFactoryGenerator.js +0 -18
  67. package/dist/storage/implementation/MongoTestStorageFactoryGenerator.js.map +0 -1
  68. package/dist/storage/implementation/util.js.map +0 -1
  69. package/src/storage/implementation/MongoTestStorageFactoryGenerator.ts +0 -28
@@ -1,8 +1,7 @@
1
1
  import * as bson from 'bson';
2
2
  import { mongo } from '@powersync/lib-service-mongodb';
3
- import { PartialOrFullChecksum, storage, utils } from '@powersync/service-core';
4
- import { PowerSyncMongo } from './db.js';
5
- import { BucketDataDocument } from './models.js';
3
+ import { storage, utils } from '@powersync/service-core';
4
+ import { BucketDataDocument } from '../storage/implementation/models.js';
6
5
  export declare function idPrefixFilter<T>(prefix: Partial<T>, rest: (keyof T)[]): mongo.Condition<T>;
7
6
  export declare function generateSlotName(prefix: string, sync_rules_id: number): string;
8
7
  /**
@@ -22,35 +21,4 @@ export declare function readSingleBatch<T>(cursor: mongo.AbstractCursor<T>): Pro
22
21
  }>;
23
22
  export declare function mapOpEntry(row: BucketDataDocument): utils.OplogEntry;
24
23
  export declare function replicaIdToSubkey(table: bson.ObjectId, id: storage.ReplicaId): string;
25
- /**
26
- * Helper for unit tests
27
- */
28
- export declare const connectMongoForTests: (url: string, isCI: boolean) => PowerSyncMongo;
29
24
  export declare function setSessionSnapshotTime(session: mongo.ClientSession, time: bson.Timestamp): void;
30
- export declare const CHECKSUM_QUERY_GROUP_STAGE: {
31
- $group: {
32
- _id: string;
33
- checksum_total: {
34
- $sum: {
35
- $toLong: string;
36
- };
37
- };
38
- count: {
39
- $sum: number;
40
- };
41
- has_clear_op: {
42
- $max: {
43
- $cond: (number | {
44
- $eq: string[];
45
- })[];
46
- };
47
- };
48
- last_op: {
49
- $max: string;
50
- };
51
- };
52
- };
53
- /**
54
- * Convert output of CHECKSUM_QUERY_GROUP_STAGE into a checksum.
55
- */
56
- export declare function checksumFromAggregate(doc: bson.Document): PartialOrFullChecksum;
@@ -1,9 +1,7 @@
1
1
  import * as bson from 'bson';
2
2
  import * as crypto from 'crypto';
3
3
  import * as uuid from 'uuid';
4
- import { mongo } from '@powersync/lib-service-mongodb';
5
4
  import { storage, utils } from '@powersync/service-core';
6
- import { PowerSyncMongo } from './db.js';
7
5
  import { ServiceAssertionError } from '@powersync/lib-services-framework';
8
6
  export function idPrefixFilter(prefix, rest) {
9
7
  let filter = {
@@ -96,19 +94,6 @@ export function replicaIdToSubkey(table, id) {
96
94
  return uuid.v5(repr, utils.ID_NAMESPACE);
97
95
  }
98
96
  }
99
- /**
100
- * Helper for unit tests
101
- */
102
- export const connectMongoForTests = (url, isCI) => {
103
- // Short timeout for tests, to fail fast when the server is not available.
104
- // Slightly longer timeouts for CI, to avoid arbitrary test failures
105
- const client = new mongo.MongoClient(url, {
106
- connectTimeoutMS: isCI ? 15_000 : 5_000,
107
- socketTimeoutMS: isCI ? 15_000 : 5_000,
108
- serverSelectionTimeoutMS: isCI ? 15_000 : 2_500
109
- });
110
- return new PowerSyncMongo(client);
111
- };
112
97
  export function setSessionSnapshotTime(session, time) {
113
98
  // This is a workaround for the lack of direct support for snapshot reads in the MongoDB driver.
114
99
  if (!session.snapshotEnabled) {
@@ -121,43 +106,4 @@ export function setSessionSnapshotTime(session, time) {
121
106
  throw new ServiceAssertionError(`Session snapshotTime is already set`);
122
107
  }
123
108
  }
124
- export const CHECKSUM_QUERY_GROUP_STAGE = {
125
- $group: {
126
- _id: '$_id.b',
127
- // Historically, checksum may be stored as 'int' or 'double'.
128
- // More recently, this should be a 'long'.
129
- // $toLong ensures that we always sum it as a long, avoiding inaccuracies in the calculations.
130
- checksum_total: { $sum: { $toLong: '$checksum' } },
131
- count: { $sum: 1 },
132
- has_clear_op: {
133
- $max: {
134
- $cond: [{ $eq: ['$op', 'CLEAR'] }, 1, 0]
135
- }
136
- },
137
- last_op: { $max: '$_id.o' }
138
- }
139
- };
140
- /**
141
- * Convert output of CHECKSUM_QUERY_GROUP_STAGE into a checksum.
142
- */
143
- export function checksumFromAggregate(doc) {
144
- const partialChecksum = Number(BigInt(doc.checksum_total) & 0xffffffffn) & 0xffffffff;
145
- const bucket = doc._id;
146
- if (doc.has_clear_op == 1) {
147
- return {
148
- // full checksum - replaces any previous one
149
- bucket,
150
- checksum: partialChecksum,
151
- count: doc.count
152
- };
153
- }
154
- else {
155
- return {
156
- // partial checksum - is added to a previous one
157
- bucket,
158
- partialCount: doc.count,
159
- partialChecksum
160
- };
161
- }
162
- }
163
109
  //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/utils/util.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAG7B,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAG1E,MAAM,UAAU,cAAc,CAAI,MAAkB,EAAE,IAAiB;IACrE,IAAI,MAAM,GAAG;QACX,IAAI,EAAE;YACJ,GAAG,MAAM;SACH;QACR,GAAG,EAAE;YACH,GAAG,MAAM;SACH;KACT,CAAC;IAEF,KAAK,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;IACtC,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAc,EAAE,aAAqB;IACpE,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC1D,OAAO,GAAG,MAAM,GAAG,aAAa,IAAI,WAAW,EAAE,CAAC;AACpD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAI,MAA+B;IACtE,IAAI,CAAC;QACH,IAAI,IAAS,CAAC;QACd,IAAI,OAAO,GAAG,IAAI,CAAC;QACnB,2CAA2C;QAC3C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;QACtC,yCAAyC;QACzC,IAAI,GAAG,MAAM,CAAC,qBAAqB,EAAE,CAAC;QACtC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC;YACnC,0CAA0C;YAC1C,wEAAwE;YACxE,uEAAuE;YACvE,oCAAoC;YACpC,EAAE;YACF,4EAA4E;YAC5E,2DAA2D;YAC3D,gCAAgC;YAChC,OAAO,GAAG,KAAK,CAAC;QAClB,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAC3B,CAAC;YAAS,CAAC;QACT,iDAAiD;QACjD,uIAAuI;QACvI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAuB;IAChD,IAAI,GAAG,CAAC,EAAE,IAAI,KAAK,IAAI,GAAG,CAAC,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC1C,OAAO;YACL,KAAK,EAAE,KAAK,CAAC,sBAAsB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;YAC9C,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,WAAW,EAAE,GAAG,CAAC,KAAK;YACtB,SAAS,EAAE,GAAG,CAAC,MAAM;YACrB,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAC9B,MAAM,EAAE,iBAAiB,CAAC,GAAG,CAAC,YAAa,EAAE,GAAG,CAAC,UAAW,CAAC;YAC7D,IAAI,EAAE,GAAG,CAAC,IAAI;SACf,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,cAAc;QAEd,OAAO;YACL,KAAK,EAAE,KAAK,CAAC,sBAAsB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;YAC9C,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;SAC/B,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAoB,EAAE,EAAqB;IAC3E,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;QACvB,mDAAmD;QACnD,OAAO,GAAG,KAAK,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;IACtD,CAAC;SAAM,CAAC;QACN,oCAAoC;QACpC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;QAC3C,OAAO,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IAC3C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,OAA4B,EAAE,IAAoB;IACvF,gGAAgG;IAChG,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC;QAC7B,MAAM,IAAI,qBAAqB,CAAC,oCAAoC,CAAC,CAAC;IACxE,CAAC;IACD,IAAK,OAAe,CAAC,YAAY,IAAI,IAAI,EAAE,CAAC;QACzC,OAAe,CAAC,YAAY,GAAG,IAAI,CAAC;IACvC,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,qBAAqB,CAAC,qCAAqC,CAAC,CAAC;IACzE,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * as test_utils from './test-utils.js';
2
+ export * from './util.js';
@@ -0,0 +1,3 @@
1
+ export * as test_utils from './test-utils.js';
2
+ export * from './util.js';
3
+ //# sourceMappingURL=utils-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils-index.js","sourceRoot":"","sources":["../../src/utils/utils-index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAC;AAC9C,cAAc,WAAW,CAAC"}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@powersync/service-module-mongodb-storage",
3
3
  "repository": "https://github.com/powersync-ja/powersync-service",
4
4
  "types": "dist/index.d.ts",
5
- "version": "0.0.0-dev-20250828134335",
5
+ "version": "0.0.0-dev-20250901073220",
6
6
  "main": "dist/index.js",
7
7
  "license": "FSL-1.1-ALv2",
8
8
  "type": "module",
@@ -27,15 +27,15 @@
27
27
  "lru-cache": "^10.2.2",
28
28
  "ts-codec": "^1.3.0",
29
29
  "uuid": "^11.1.0",
30
- "@powersync/lib-service-mongodb": "0.0.0-dev-20250828134335",
31
- "@powersync/lib-services-framework": "0.7.3",
32
- "@powersync/service-core": "0.0.0-dev-20250828134335",
30
+ "@powersync/lib-service-mongodb": "0.0.0-dev-20250901073220",
31
+ "@powersync/lib-services-framework": "0.0.0-dev-20250901073220",
32
+ "@powersync/service-core": "0.0.0-dev-20250901073220",
33
+ "@powersync/service-types": "0.0.0-dev-20250901073220",
33
34
  "@powersync/service-jsonbig": "0.17.11",
34
- "@powersync/service-sync-rules": "0.29.0",
35
- "@powersync/service-types": "0.13.0"
35
+ "@powersync/service-sync-rules": "0.29.0"
36
36
  },
37
37
  "devDependencies": {
38
- "@powersync/service-core-tests": "0.0.0-dev-20250828134335"
38
+ "@powersync/service-core-tests": "0.0.0-dev-20250901073220"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "tsc -b",
package/src/index.ts CHANGED
@@ -5,3 +5,4 @@ export * as storage from './storage/storage-index.js';
5
5
 
6
6
  export * from './types/types.js';
7
7
  export * as types from './types/types.js';
8
+ export * as utils from './utils/utils-index.js';
@@ -0,0 +1,58 @@
1
+ import { migrations } from '@powersync/service-core';
2
+ import * as storage from '../../../storage/storage-index.js';
3
+ import { MongoStorageConfig } from '../../../types/types.js';
4
+
5
+ export const up: migrations.PowerSyncMigrationFunction = async (context) => {
6
+ const {
7
+ service_context: { configuration }
8
+ } = context;
9
+ const db = storage.createPowerSyncMongo(configuration.storage as MongoStorageConfig);
10
+
11
+ try {
12
+ await db.createConnectionReportingCollection();
13
+
14
+ await db.connection_report_events.createIndex(
15
+ {
16
+ connected_at: 1,
17
+ jwt_exp: 1,
18
+ disconnected_at: 1
19
+ },
20
+ { name: 'connection_list_index' }
21
+ );
22
+
23
+ await db.connection_report_events.createIndex(
24
+ {
25
+ user_id: 1
26
+ },
27
+ { name: 'connection_user_id_index' }
28
+ );
29
+ await db.connection_report_events.createIndex(
30
+ {
31
+ client_id: 1
32
+ },
33
+ { name: 'connection_client_id_index' }
34
+ );
35
+ await db.connection_report_events.createIndex(
36
+ {
37
+ sdk: 1
38
+ },
39
+ { name: 'connection_index' }
40
+ );
41
+ } finally {
42
+ await db.client.close();
43
+ }
44
+ };
45
+
46
+ export const down: migrations.PowerSyncMigrationFunction = async (context) => {
47
+ const {
48
+ service_context: { configuration }
49
+ } = context;
50
+
51
+ const db = storage.createPowerSyncMongo(configuration.storage as MongoStorageConfig);
52
+
53
+ try {
54
+ await db.db.dropCollection('connection_report_events');
55
+ } finally {
56
+ await db.client.close();
57
+ }
58
+ };
@@ -12,7 +12,7 @@ import { PowerSyncMongo } from './implementation/db.js';
12
12
  import { SyncRuleDocument } from './implementation/models.js';
13
13
  import { MongoPersistedSyncRulesContent } from './implementation/MongoPersistedSyncRulesContent.js';
14
14
  import { MongoSyncBucketStorage } from './implementation/MongoSyncBucketStorage.js';
15
- import { generateSlotName } from './implementation/util.js';
15
+ import { generateSlotName } from '../utils/util.js';
16
16
 
17
17
  export class MongoBucketStorage
18
18
  extends BaseObserver<storage.BucketStorageFactoryListener>
@@ -0,0 +1,177 @@
1
+ import { mongo } from '@powersync/lib-service-mongodb';
2
+ import { storage } from '@powersync/service-core';
3
+ import { event_types } from '@powersync/service-types';
4
+ import { PowerSyncMongo } from './implementation/db.js';
5
+ import { logger } from '@powersync/lib-services-framework';
6
+
7
+ export class MongoReportStorage implements storage.ReportStorage {
8
+ private readonly client: mongo.MongoClient;
9
+ public readonly db: PowerSyncMongo;
10
+
11
+ constructor(db: PowerSyncMongo) {
12
+ this.client = db.client;
13
+ this.db = db;
14
+ }
15
+ async deleteOldConnectionData(data: event_types.DeleteOldConnectionData): Promise<void> {
16
+ const { date } = data;
17
+ const result = await this.db.connection_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 connection_report_events.`
27
+ );
28
+ }
29
+ }
30
+
31
+ async getClientConnectionReports(
32
+ data: event_types.ClientConnectionReportRequest
33
+ ): Promise<event_types.ClientConnectionReportResponse> {
34
+ const { start, end } = data;
35
+ const result = await this.db.connection_report_events
36
+ .aggregate<event_types.ClientConnectionReportResponse>([
37
+ {
38
+ $match: {
39
+ connected_at: { $lte: end, $gte: start }
40
+ }
41
+ },
42
+ this.connectionsFacetPipeline(),
43
+ this.connectionsProjectPipeline()
44
+ ])
45
+ .toArray();
46
+ return result[0];
47
+ }
48
+
49
+ async reportClientConnection(data: event_types.ClientConnectionBucketData): Promise<void> {
50
+ const updateFilter = this.updateDocFilter(data.user_id, data.client_id!);
51
+ await this.db.connection_report_events.findOneAndUpdate(
52
+ updateFilter,
53
+ {
54
+ $set: data,
55
+ $unset: {
56
+ disconnected_at: ''
57
+ }
58
+ },
59
+ {
60
+ upsert: true
61
+ }
62
+ );
63
+ }
64
+ async reportClientDisconnection(data: event_types.ClientDisconnectionEventData): Promise<void> {
65
+ const { connected_at, user_id, client_id } = data;
66
+ await this.db.connection_report_events.findOneAndUpdate(
67
+ {
68
+ client_id,
69
+ user_id,
70
+ connected_at
71
+ },
72
+ {
73
+ $set: {
74
+ disconnected_at: data.disconnected_at
75
+ },
76
+ $unset: {
77
+ jwt_exp: ''
78
+ }
79
+ }
80
+ );
81
+ }
82
+ async getConnectedClients(): Promise<event_types.ClientConnectionReportResponse> {
83
+ const result = await this.db.connection_report_events
84
+ .aggregate<event_types.ClientConnectionReportResponse>([
85
+ {
86
+ $match: {
87
+ disconnected_at: { $exists: false },
88
+ jwt_exp: { $gt: new Date() }
89
+ }
90
+ },
91
+ this.connectionsFacetPipeline(),
92
+ this.connectionsProjectPipeline()
93
+ ])
94
+ .toArray();
95
+ return result[0];
96
+ }
97
+
98
+ async [Symbol.asyncDispose]() {
99
+ // No-op
100
+ }
101
+
102
+ private parseJsDate(date: Date) {
103
+ const year = date.getUTCFullYear();
104
+ const month = date.getUTCMonth();
105
+ const today = date.getUTCDate();
106
+ const day = date.getUTCDay();
107
+ return {
108
+ year,
109
+ month,
110
+ today,
111
+ day,
112
+ parsedDate: date
113
+ };
114
+ }
115
+
116
+ private connectionsFacetPipeline() {
117
+ return {
118
+ $facet: {
119
+ unique_users: [
120
+ {
121
+ $group: {
122
+ _id: '$user_id'
123
+ }
124
+ },
125
+ {
126
+ $count: 'count'
127
+ }
128
+ ],
129
+ sdk_versions_array: [
130
+ {
131
+ $group: {
132
+ _id: '$sdk',
133
+ total: { $sum: 1 },
134
+ client_ids: { $addToSet: '$client_id' },
135
+ user_ids: { $addToSet: '$user_id' }
136
+ }
137
+ },
138
+ {
139
+ $project: {
140
+ _id: 0,
141
+ sdk: '$_id',
142
+ users: { $size: '$user_ids' },
143
+ clients: { $size: '$client_ids' }
144
+ }
145
+ },
146
+ {
147
+ $sort: {
148
+ sdk: 1
149
+ }
150
+ }
151
+ ]
152
+ }
153
+ };
154
+ }
155
+
156
+ private connectionsProjectPipeline() {
157
+ return {
158
+ $project: {
159
+ users: { $ifNull: [{ $arrayElemAt: ['$unique_users.count', 0] }, 0] },
160
+ sdks: '$sdk_versions_array'
161
+ }
162
+ };
163
+ }
164
+
165
+ private updateDocFilter(userId: string, clientId: string) {
166
+ const { year, month, today } = this.parseJsDate(new Date());
167
+ const nextDay = today + 1;
168
+ return {
169
+ user_id: userId,
170
+ client_id: clientId,
171
+ connected_at: {
172
+ $gte: new Date(Date.UTC(year, month, today)),
173
+ $lt: new Date(Date.UTC(year, month, nextDay))
174
+ }
175
+ };
176
+ }
177
+ }
@@ -28,7 +28,7 @@ import { MongoIdSequence } from './MongoIdSequence.js';
28
28
  import { batchCreateCustomWriteCheckpoints } from './MongoWriteCheckpointAPI.js';
29
29
  import { cacheKey, OperationBatch, RecordOperation } from './OperationBatch.js';
30
30
  import { PersistedBatch } from './PersistedBatch.js';
31
- import { idPrefixFilter } from './util.js';
31
+ import { idPrefixFilter } from '../../utils/util.js';
32
32
 
33
33
  /**
34
34
  * 15MB