Skip to content

Feature/sdk reporting #316

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 181 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
181 commits
Select commit Hold shift + click to select a range
7546215
emiiter engine
EspressoTrip-v2 Jul 14, 2025
7eb90c4
Merge branch 'main' into feature/sdk-reporting
EspressoTrip-v2 Jul 14, 2025
edee67a
emitter changes
EspressoTrip-v2 Jul 14, 2025
fea7be8
chnages based on feedback
EspressoTrip-v2 Jul 14, 2025
0f603cb
refactoring and some renaming
EspressoTrip-v2 Jul 14, 2025
ef6cfd1
controller addition handler
EspressoTrip-v2 Jul 14, 2025
65c2f5b
type fix
EspressoTrip-v2 Jul 14, 2025
777d426
dev packages
EspressoTrip-v2 Jul 14, 2025
f6370a6
forgot lockfile
EspressoTrip-v2 Jul 14, 2025
79c9a5d
reverted dev packages
EspressoTrip-v2 Jul 15, 2025
4685dd3
lockfile dammit
EspressoTrip-v2 Jul 15, 2025
dd29d5f
testing
EspressoTrip-v2 Jul 15, 2025
cbdcaaf
test emitter
EspressoTrip-v2 Jul 15, 2025
fd6b080
test
EspressoTrip-v2 Jul 15, 2025
2846a61
added emiiter to socket route
EspressoTrip-v2 Jul 15, 2025
93bf85a
fixed binding
EspressoTrip-v2 Jul 15, 2025
1e12209
removed log
EspressoTrip-v2 Jul 15, 2025
499d98b
report storage initial
EspressoTrip-v2 Jul 15, 2025
44209ff
sdk scape
EspressoTrip-v2 Jul 15, 2025
063e446
reworked the emitter engine
EspressoTrip-v2 Jul 16, 2025
e3e7aab
alterations to types eventemitter
EspressoTrip-v2 Jul 16, 2025
5359d09
list events chnage
EspressoTrip-v2 Jul 16, 2025
ce5092b
changed document type mongo
EspressoTrip-v2 Jul 16, 2025
5ff2da5
changed document type mongo
EspressoTrip-v2 Jul 16, 2025
0657ffe
changed document type mongo
EspressoTrip-v2 Jul 16, 2025
388ab2f
changed document type mongo
EspressoTrip-v2 Jul 16, 2025
af06f64
chnaged date imps events
EspressoTrip-v2 Jul 16, 2025
50eb198
chnaged jwt exp
EspressoTrip-v2 Jul 16, 2025
119f5c4
Merge branch 'main' into feature/sdk-reporting
EspressoTrip-v2 Jul 16, 2025
ec9ce6f
ffs
EspressoTrip-v2 Jul 16, 2025
8a017f8
dev packages are desynced
EspressoTrip-v2 Jul 16, 2025
63b6f67
test disconnect event
EspressoTrip-v2 Jul 16, 2025
d5429c7
disconnect event
EspressoTrip-v2 Jul 16, 2025
83d4280
removed accidental key in event
EspressoTrip-v2 Jul 16, 2025
8f74f27
changes to update logic sdk
EspressoTrip-v2 Jul 21, 2025
db58d24
Merge branch 'main' into feature/sdk-reporting
EspressoTrip-v2 Jul 21, 2025
f1d187b
chnageste bump
EspressoTrip-v2 Jul 21, 2025
5280565
dev packages issue
EspressoTrip-v2 Jul 21, 2025
8cbbd35
dev packages
EspressoTrip-v2 Jul 21, 2025
a700ec9
chnages to mongo storage and package bumps
EspressoTrip-v2 Jul 21, 2025
72398c2
unset disconnect on connect
EspressoTrip-v2 Jul 21, 2025
842e621
change document sdk
EspressoTrip-v2 Jul 21, 2025
12ac832
deisconnect chnages
EspressoTrip-v2 Jul 21, 2025
6dfe510
connection query
EspressoTrip-v2 Jul 21, 2025
f3c356a
chnaged query for sdk data
EspressoTrip-v2 Jul 21, 2025
617fc06
removed the disconnect time
EspressoTrip-v2 Jul 21, 2025
bb9dfd7
chnaged disconnect report to use userdata
EspressoTrip-v2 Jul 21, 2025
9a4094e
fix
EspressoTrip-v2 Jul 21, 2025
644de71
chnaged sdk scrape and added sdk delete event
EspressoTrip-v2 Jul 21, 2025
7d93f69
change disconnect mongo update
EspressoTrip-v2 Jul 21, 2025
efd5600
wip
EspressoTrip-v2 Jul 21, 2025
64e397f
wip
EspressoTrip-v2 Jul 21, 2025
87e98c9
create new doc only if older than a day
EspressoTrip-v2 Jul 22, 2025
2fd9181
Merge branch 'main' into feature/sdk-reporting
EspressoTrip-v2 Jul 22, 2025
1ee1d39
wip
EspressoTrip-v2 Jul 22, 2025
fda348c
wip
EspressoTrip-v2 Jul 22, 2025
cb46003
duh
EspressoTrip-v2 Jul 22, 2025
b21770f
scrape data
EspressoTrip-v2 Jul 22, 2025
06a1859
wip
EspressoTrip-v2 Jul 22, 2025
d20451d
timespan logging
EspressoTrip-v2 Jul 22, 2025
453ec72
fix day query bug
EspressoTrip-v2 Jul 22, 2025
ae49da8
remove facet
EspressoTrip-v2 Jul 22, 2025
b123e43
dates
EspressoTrip-v2 Jul 22, 2025
eb7c4c2
wip
EspressoTrip-v2 Jul 22, 2025
46dd4af
wip
EspressoTrip-v2 Jul 22, 2025
d56753e
unique sdks
EspressoTrip-v2 Jul 22, 2025
5f59578
wip
EspressoTrip-v2 Jul 22, 2025
bb742da
sdk
EspressoTrip-v2 Jul 22, 2025
28efea4
sdk
EspressoTrip-v2 Jul 22, 2025
a4645b9
wip
EspressoTrip-v2 Jul 22, 2025
bc18198
wip
EspressoTrip-v2 Jul 22, 2025
6e1c1ef
list connections and some helpers
EspressoTrip-v2 Jul 23, 2025
83972c6
corrections timeframes
EspressoTrip-v2 Jul 23, 2025
fcc0468
testing
EspressoTrip-v2 Jul 23, 2025
486a094
scrapeSdk interval
EspressoTrip-v2 Jul 23, 2025
58fdb79
change to response
EspressoTrip-v2 Jul 23, 2025
70f1408
change to response
EspressoTrip-v2 Jul 23, 2025
5ded122
testing date ranges
EspressoTrip-v2 Jul 23, 2025
668b0d8
custom start range list connections
EspressoTrip-v2 Jul 23, 2025
a2cf616
start date
EspressoTrip-v2 Jul 23, 2025
2e6cb41
custom date validation
EspressoTrip-v2 Jul 23, 2025
c808e97
date start validation
EspressoTrip-v2 Jul 23, 2025
e7e1457
testing
EspressoTrip-v2 Jul 23, 2025
9ccf251
test
EspressoTrip-v2 Jul 23, 2025
0b1fadd
refactoring
EspressoTrip-v2 Jul 23, 2025
4ea15e6
date bug
EspressoTrip-v2 Jul 23, 2025
3735ab4
fixes krep in bugs
EspressoTrip-v2 Jul 23, 2025
c9088a4
added seperate filter for delete
EspressoTrip-v2 Jul 24, 2025
e2f7a7e
indexes
EspressoTrip-v2 Jul 24, 2025
28914bf
check document scan result
EspressoTrip-v2 Jul 24, 2025
51bbcc4
remove explain
EspressoTrip-v2 Jul 24, 2025
4c4144e
Merge branch 'main' into feature/sdk-reporting
EspressoTrip-v2 Jul 24, 2025
db23db6
end date to list connections
EspressoTrip-v2 Jul 24, 2025
f67746d
removed a log
EspressoTrip-v2 Jul 24, 2025
9797bad
postgres imp WIP
EspressoTrip-v2 Jul 29, 2025
73d8829
Merge branch 'main' into feature/sdk-reporting
EspressoTrip-v2 Jul 29, 2025
4682a74
tests
EspressoTrip-v2 Jul 29, 2025
f46bede
timestamp casting
EspressoTrip-v2 Jul 29, 2025
8d194f1
casting issues
EspressoTrip-v2 Jul 29, 2025
29b4b59
fix incorrect type
EspressoTrip-v2 Jul 29, 2025
a741f41
iso issues
EspressoTrip-v2 Jul 29, 2025
75c6692
testing
EspressoTrip-v2 Jul 29, 2025
70eac9c
testing
EspressoTrip-v2 Jul 29, 2025
732e1d2
transaction
EspressoTrip-v2 Jul 29, 2025
8899f9e
added logs
EspressoTrip-v2 Jul 29, 2025
42ddacf
pg-wire frustrations
EspressoTrip-v2 Jul 29, 2025
85d6e6b
pg-wire frustrations
EspressoTrip-v2 Jul 29, 2025
052ca63
pg-wire frustrations
EspressoTrip-v2 Jul 29, 2025
d4118ac
pg-wire frustrations
EspressoTrip-v2 Jul 29, 2025
2c1eb1b
pg-wire frustrations
EspressoTrip-v2 Jul 29, 2025
3f8cec5
pg-wire frustrations
EspressoTrip-v2 Jul 29, 2025
f824035
pg-wire frustrations
EspressoTrip-v2 Jul 29, 2025
27eb8f4
pg-wire frustrations
EspressoTrip-v2 Jul 29, 2025
02a5036
change the sql query to test
EspressoTrip-v2 Jul 30, 2025
c8c38df
testing
EspressoTrip-v2 Jul 30, 2025
d039559
testing
EspressoTrip-v2 Jul 30, 2025
aefa0f9
testing
EspressoTrip-v2 Jul 30, 2025
e096409
testing
EspressoTrip-v2 Jul 30, 2025
7526ab2
ts-codecs
EspressoTrip-v2 Jul 30, 2025
f1244fd
ts-codecs
EspressoTrip-v2 Jul 30, 2025
f88544f
ts-codecs
EspressoTrip-v2 Jul 30, 2025
6b5b58b
ts-codecs
EspressoTrip-v2 Jul 30, 2025
792b299
ts-codecs
EspressoTrip-v2 Jul 30, 2025
caf0c26
ts-codecs
EspressoTrip-v2 Jul 30, 2025
b2d5e9e
fixed sdk scrape query
EspressoTrip-v2 Jul 30, 2025
e538cdf
checking delete old data
EspressoTrip-v2 Jul 30, 2025
f385a50
checking delete old data
EspressoTrip-v2 Jul 30, 2025
8bed767
deleted row logging
EspressoTrip-v2 Jul 30, 2025
551edf2
seperated migration for sdk
EspressoTrip-v2 Jul 30, 2025
cabd182
seperated migration for sdk
EspressoTrip-v2 Jul 30, 2025
79043fa
added redundancy for events
EspressoTrip-v2 Jul 30, 2025
45d17df
better log
EspressoTrip-v2 Jul 30, 2025
c7cd9b1
oops
EspressoTrip-v2 Jul 30, 2025
2b9695c
clean up
EspressoTrip-v2 Jul 30, 2025
2d34818
Merge branch 'main' into feature/sdk-reporting
EspressoTrip-v2 Jul 30, 2025
2ff73da
fixing a bug
EspressoTrip-v2 Jul 30, 2025
0c3a853
fixing a bug
EspressoTrip-v2 Jul 30, 2025
8277c8e
removed query
EspressoTrip-v2 Jul 30, 2025
4a930e9
removed query
EspressoTrip-v2 Jul 30, 2025
ac40bca
removed query
EspressoTrip-v2 Jul 30, 2025
5020173
trying to simplify the update
EspressoTrip-v2 Jul 30, 2025
c2b84d2
trying to simplify the update
EspressoTrip-v2 Jul 30, 2025
fc82f14
tests tests
EspressoTrip-v2 Jul 31, 2025
a9d453f
postgres tests WIP
EspressoTrip-v2 Jul 31, 2025
7baf97b
removed command
EspressoTrip-v2 Jul 31, 2025
65120ce
application name conflict report storage
EspressoTrip-v2 Jul 31, 2025
da94778
checking concurrency issues in tests
EspressoTrip-v2 Jul 31, 2025
de57ca0
test chnages to tests
EspressoTrip-v2 Jul 31, 2025
05006be
completed postgres tests
EspressoTrip-v2 Aug 1, 2025
794a099
mongo tests report
EspressoTrip-v2 Aug 4, 2025
9b7fde8
seperated the migrations for sdk, problems testing
EspressoTrip-v2 Aug 4, 2025
1af5e3a
okay then
EspressoTrip-v2 Aug 4, 2025
30d0efd
mongo db tests report storage
EspressoTrip-v2 Aug 4, 2025
10a0164
changed the disconnect report to use connect_at time for long running…
EspressoTrip-v2 Aug 4, 2025
51661d8
fixed connected at filter
EspressoTrip-v2 Aug 11, 2025
824ed2f
main merge
EspressoTrip-v2 Aug 11, 2025
92ddb08
SMerge branch 'main' into feature/sdk-reporting
EspressoTrip-v2 Aug 12, 2025
1a6ad40
cclean up some code for pr
EspressoTrip-v2 Aug 12, 2025
0fad466
version bump
EspressoTrip-v2 Aug 12, 2025
4ac4ed3
chnages to date ranges on scrapes, simplified
EspressoTrip-v2 Aug 13, 2025
877ce66
fixed list current connections payload
EspressoTrip-v2 Aug 13, 2025
37825aa
Merge branch 'main' into feature/sdk-reporting
EspressoTrip-v2 Aug 13, 2025
29a0845
fix migration indexes on down
EspressoTrip-v2 Aug 14, 2025
ea0c212
model document naming
EspressoTrip-v2 Aug 14, 2025
c1b6a0c
moved abstarct methods to the top
EspressoTrip-v2 Aug 14, 2025
5a9c5ce
PR review chnages
EspressoTrip-v2 Aug 14, 2025
4f0c77d
PR review changes
EspressoTrip-v2 Aug 14, 2025
8167d34
emitter PR chnages
EspressoTrip-v2 Aug 14, 2025
cbfa0cb
PR chnages
EspressoTrip-v2 Aug 18, 2025
fa5b872
PR changes as requested
EspressoTrip-v2 Aug 18, 2025
a2ad23e
Merge branch 'main' into feature/sdk-reporting
EspressoTrip-v2 Aug 18, 2025
a15ee2d
changed names to the agreed naming convention
EspressoTrip-v2 Aug 19, 2025
ed65508
chnages table names to match naming conventions
EspressoTrip-v2 Aug 20, 2025
9e9e91b
fixed tests
EspressoTrip-v2 Aug 20, 2025
f986049
removed index drop migrations
EspressoTrip-v2 Aug 21, 2025
fe2ef90
removed unused type
EspressoTrip-v2 Aug 21, 2025
7a6673e
changed date query
EspressoTrip-v2 Aug 21, 2025
4a0c4ca
added comments
EspressoTrip-v2 Aug 22, 2025
d5f2ae6
Merge branch 'main' into feature/sdk-reporting
EspressoTrip-v2 Aug 22, 2025
acd4bea
refactored test utils to its own file in postgres and mongo
EspressoTrip-v2 Aug 22, 2025
084287c
oops
EspressoTrip-v2 Aug 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/heavy-pianos-grin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@powersync/service-module-mongodb-storage': patch
'@powersync/service-core': patch
'@powersync/service-types': patch
---

sdk reporting
8 changes: 8 additions & 0 deletions .changeset/honest-sloths-smoke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@powersync/service-module-postgres-storage': patch
'@powersync/service-module-mongodb-storage': patch
'@powersync/service-core': patch
'@powersync/service-types': patch
---

Added sdk reporting to storage
13 changes: 13 additions & 0 deletions .changeset/smart-mugs-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@powersync/service-module-postgres-storage': patch
'@powersync/service-module-mongodb-storage': patch
'@powersync/service-module-postgres': patch
'@powersync/service-module-mongodb': patch
'@powersync/service-core': patch
'@powersync/service-module-mysql': patch
'@powersync/service-module-core': patch
'@powersync/lib-services-framework': patch
'@powersync/service-types': patch
---

Reporting mongo storage added to storage engine.
2 changes: 1 addition & 1 deletion .github/workflows/development_packages_release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on: workflow_dispatch

jobs:
publish_packages:
name: Publish Devevelopment Packages
name: Publish Development Packages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down
7 changes: 3 additions & 4 deletions libs/lib-services/src/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { ErrorReporter } from './alerts/definitions.js';
import { NoOpReporter } from './alerts/no-op-reporter.js';
import { MigrationManager } from './migrations/MigrationManager.js';
import {
ProbeModule,
TerminationHandler,
createInMemoryProbe,
createTerminationHandler
createTerminationHandler,
ProbeModule,
TerminationHandler
} from './signals/signals-index.js';

export enum ContainerImplementation {
Expand Down Expand Up @@ -47,7 +47,6 @@ export type Newable<T> = new (...args: never[]) => T;
* Identifier used to get and register implementations
*/
export type ServiceIdentifier<T = unknown> = string | symbol | Newable<T> | Abstract<T> | ContainerImplementation;

const DEFAULT_GENERATORS: ContainerImplementationDefaultGenerators = {
[ContainerImplementation.REPORTER]: () => NoOpReporter,
[ContainerImplementation.PROBES]: () => createInMemoryProbe(),
Expand Down
2 changes: 1 addition & 1 deletion modules/module-mongodb-storage/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@
"@powersync/lib-service-mongodb": "workspace:*",
"@powersync/lib-services-framework": "workspace:*",
"@powersync/service-core": "workspace:*",
"@powersync/service-types": "workspace:*",
"@powersync/service-jsonbig": "workspace:*",
"@powersync/service-sync-rules": "workspace:*",
"@powersync/service-types": "workspace:*",
"bson": "^6.10.3",
"ix": "^5.0.0",
"lru-cache": "^10.2.2",
Expand Down
1 change: 1 addition & 0 deletions modules/module-mongodb-storage/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * as storage from './storage/storage-index.js';

export * from './types/types.js';
export * as types from './types/types.js';
export * as utils from './utils/utils-index.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { migrations } from '@powersync/service-core';
import * as storage from '../../../storage/storage-index.js';
import { MongoStorageConfig } from '../../../types/types.js';

export const up: migrations.PowerSyncMigrationFunction = async (context) => {
const {
service_context: { configuration }
} = context;
const db = storage.createPowerSyncMongo(configuration.storage as MongoStorageConfig);

try {
await db.createConnectionReportingCollection();

await db.connection_report_events.createIndex(
{
connected_at: 1,
jwt_exp: 1,
disconnected_at: 1
},
{ name: 'connection_list_index' }
);

await db.connection_report_events.createIndex(
{
user_id: 1
},
{ name: 'connection_user_id_index' }
);
await db.connection_report_events.createIndex(
{
client_id: 1
},
{ name: 'connection_client_id_index' }
);
await db.connection_report_events.createIndex(
{
sdk: 1
},
{ name: 'connection_index' }
);
} finally {
await db.client.close();
}
};

export const down: migrations.PowerSyncMigrationFunction = async (context) => {
const {
service_context: { configuration }
} = context;

const db = storage.createPowerSyncMongo(configuration.storage as MongoStorageConfig);

try {
await db.db.dropCollection('connection_report_events');
} finally {
await db.client.close();
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { PowerSyncMongo } from './implementation/db.js';
import { SyncRuleDocument } from './implementation/models.js';
import { MongoPersistedSyncRulesContent } from './implementation/MongoPersistedSyncRulesContent.js';
import { MongoSyncBucketStorage } from './implementation/MongoSyncBucketStorage.js';
import { generateSlotName } from './implementation/util.js';
import { generateSlotName } from '../utils/util.js';

export class MongoBucketStorage
extends BaseObserver<storage.BucketStorageFactoryListener>
Expand Down
197 changes: 197 additions & 0 deletions modules/module-mongodb-storage/src/storage/MongoReportStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import { mongo } from '@powersync/lib-service-mongodb';
import { storage } from '@powersync/service-core';
import { event_types } from '@powersync/service-types';
import { PowerSyncMongo } from './implementation/db.js';
import { logger } from '@powersync/lib-services-framework';

export class MongoReportStorage implements storage.ReportStorage {
private readonly client: mongo.MongoClient;
public readonly db: PowerSyncMongo;

constructor(db: PowerSyncMongo) {
this.client = db.client;
this.db = db;
}
async deleteOldConnectionData(data: event_types.DeleteOldConnectionData): Promise<void> {
const { date } = data;
const result = await this.db.connection_report_events.deleteMany({
connected_at: { $lt: date },
$or: [
{ disconnected_at: { $exists: true } },
{ jwt_exp: { $lt: new Date() }, disconnected_at: { $exists: false } }
]
});
if (result.deletedCount > 0) {
logger.info(
`TTL from ${date.toISOString()}: ${result.deletedCount} MongoDB documents have been removed from connection_report_events.`
);
}
}

async getClientConnectionReports(
data: event_types.ClientConnectionReportRequest
): Promise<event_types.ClientConnectionReportResponse> {
const { start, end } = data;
const result = await this.db.connection_report_events
.aggregate<event_types.ClientConnectionReportResponse>([
{
$match: {
connected_at: { $lte: end, $gte: start }
}
},
this.connectionsFacetPipeline(),
this.connectionsProjectPipeline()
])
.toArray();
return result[0];
}

async reportClientConnection(data: event_types.ClientConnectionBucketData): Promise<void> {
const updateFilter = this.updateDocFilter(data.user_id, data.client_id!);
await this.db.connection_report_events.findOneAndUpdate(
updateFilter,
{
$set: data,
$unset: {
disconnected_at: ''
}
},
{
upsert: true
}
);
}
async reportClientDisconnection(data: event_types.ClientDisconnectionEventData): Promise<void> {
const { connected_at, user_id, client_id } = data;
await this.db.connection_report_events.findOneAndUpdate(
{
client_id,
user_id,
connected_at
Comment on lines +68 to +70
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as for postgres - we should use connection ids here, rather than assume this combination is unique.

},
{
$set: {
disconnected_at: data.disconnected_at
},
$unset: {
jwt_exp: ''
}
}
);
}
async getConnectedClients(
data: event_types.ClientConnectionsRequest
): Promise<event_types.ClientConnectionReportResponse> {
const timeframeFilter = this.listConnectionsDateRange(data);
const result = await this.db.connection_report_events
.aggregate<event_types.ClientConnectionReportResponse>([
{
$match: {
disconnected_at: { $exists: false },
jwt_exp: { $gt: new Date() },
...timeframeFilter
Comment on lines +90 to +92
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as commented on Postgres storage - we should clarify whether this is expected to only be for current connections, or whether the timeframe can be used to fetch connections in the past.

}
},
this.connectionsFacetPipeline(),
this.connectionsProjectPipeline()
])
.toArray();
return result[0];
}

async [Symbol.asyncDispose]() {
// No-op
}

private parseJsDate(date: Date) {
const year = date.getFullYear();
const month = date.getMonth();
const today = date.getDate();
const day = date.getDay();
return {
year,
month,
today,
day,
parsedDate: date
};
}

private connectionsFacetPipeline() {
return {
$facet: {
unique_users: [
{
$group: {
_id: '$user_id'
}
},
{
$count: 'count'
}
],
sdk_versions_array: [
{
$group: {
_id: '$sdk',
total: { $sum: 1 },
client_ids: { $addToSet: '$client_id' },
user_ids: { $addToSet: '$user_id' }
}
},
{
$project: {
_id: 0,
sdk: '$_id',
users: { $size: '$user_ids' },
clients: { $size: '$client_ids' }
}
},
{
$sort: {
sdk: 1
}
}
]
}
};
}

private connectionsProjectPipeline() {
return {
$project: {
users: { $ifNull: [{ $arrayElemAt: ['$unique_users.count', 0] }, 0] },
sdks: '$sdk_versions_array'
}
};
}

private updateDocFilter(userId: string, clientId: string) {
const { year, month, today } = this.parseJsDate(new Date());
const nextDay = today + 1;
return {
user_id: userId,
client_id: clientId,
connected_at: {
// Need to create a new date here to sett the time to 00:00:00
$gte: new Date(year, month, today),
$lt: new Date(year, month, nextDay)
}
Comment on lines +177 to +179
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This depends on the timezone of the server - not sure if that is what we want?

But I'm also wondering - do we specifically want this behavior ("if the connection event is within the same day the stored initial connection is updated")? Why not store these as separate connection events, and then handle any other logic in the aggregations?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dylan was concerned about storing the connections as logs in the ps service db, due to volume. Considering we are using per day, per week and per month, we only need day granularity. When a user refreshes the web sdk it actually causes a disconnect and a reconnect which would make multiple copies withing very a short period if they refresh a few times. So to reduce the redundant connections by the same user I did it this way. Which still gives us the minimal per day granularity.

The date objects get converted to UTC time by Mongo.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, we can keep it as daily connections for now - it looks like we can change that later if needed without too much effort.

The date objects get converted to UTC time by Mongo.

If we do keep daily values, we need to be very explicit about time zones. As is, if I run the server in UTC+2, the filter here would use 2025-08-21T22:00:00.000Z. If the reporting job happens to use a different timezone, it would not match up with the way it is persisted here.

Now in our hosted environment at least, it is likely that the service runs in the UTC timezone already, and this won't make a difference. But if we're relying on that, it is better to be explicit - use UTC timezones here (new Date(Date.UTC(year, month, today))), and document that UTC-based timestamps should be used when querying the data.

};
}

private listConnectionsDateRange(data: event_types.ClientConnectionsRequest) {
const { range } = data;
if (!range) {
return undefined;
}
const endDate = data.range?.end ? new Date(data.range.end) : new Date();
const startDate = new Date(range.start);
return {
connected_at: {
$lte: endDate,
$gte: startDate
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { MongoIdSequence } from './MongoIdSequence.js';
import { batchCreateCustomWriteCheckpoints } from './MongoWriteCheckpointAPI.js';
import { cacheKey, OperationBatch, RecordOperation } from './OperationBatch.js';
import { PersistedBatch } from './PersistedBatch.js';
import { idPrefixFilter } from './util.js';
import { idPrefixFilter } from '../../utils/util.js';

/**
* 15MB
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { POWERSYNC_VERSION, storage } from '@powersync/service-core';
import { MongoStorageConfig } from '../../types/types.js';
import { MongoBucketStorage } from '../MongoBucketStorage.js';
import { PowerSyncMongo } from './db.js';
import { MongoReportStorage } from '../MongoReportStorage.js';

export class MongoStorageProvider implements storage.BucketStorageProvider {
export class MongoStorageProvider implements storage.StorageProvider {
get type() {
return lib_mongo.MONGO_CONNECTION_TYPE;
}
Expand Down Expand Up @@ -37,15 +38,19 @@ export class MongoStorageProvider implements storage.BucketStorageProvider {
await client.connect();

const database = new PowerSyncMongo(client, { database: resolvedConfig.storage.database });
const factory = new MongoBucketStorage(database, {
const syncStorageFactory = new MongoBucketStorage(database, {
// TODO currently need the entire resolved config due to this
slot_name_prefix: resolvedConfig.slot_name_prefix
});

// Storage factory for reports
const reportStorageFactory = new MongoReportStorage(database);
return {
storage: factory,
storage: syncStorageFactory,
reportStorage: reportStorageFactory,
shutDown: async () => {
shuttingDown = true;
await factory[Symbol.asyncDispose]();
await syncStorageFactory[Symbol.asyncDispose]();
await client.close();
},
tearDown: () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { BucketDataDocument, BucketDataKey, BucketStateDocument, SourceKey, Sour
import { MongoBucketBatch } from './MongoBucketBatch.js';
import { MongoCompactor } from './MongoCompactor.js';
import { MongoWriteCheckpointAPI } from './MongoWriteCheckpointAPI.js';
import { idPrefixFilter, mapOpEntry, readSingleBatch, setSessionSnapshotTime } from './util.js';
import { idPrefixFilter, mapOpEntry, readSingleBatch, setSessionSnapshotTime } from '../../utils/util.js';
import { MongoParameterCompactor } from './MongoParameterCompactor.js';

export class MongoSyncBucketStorage
Expand Down
Loading