@powersync/service-module-postgres-storage 0.0.0-dev-20250829094737 → 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 (42) hide show
  1. package/CHANGELOG.md +18 -4
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/@types/migrations/scripts/1756282360128-connection-reporting.d.ts +3 -0
  4. package/dist/@types/storage/PostgresReportStorageFactory.d.ts +24 -0
  5. package/dist/@types/storage/PostgresStorageProvider.d.ts +1 -1
  6. package/dist/@types/storage/storage-index.d.ts +0 -1
  7. package/dist/@types/types/models/SdkReporting.d.ts +21 -0
  8. package/dist/@types/types/models/models-index.d.ts +1 -0
  9. package/dist/@types/{storage/PostgresTestStorageFactoryGenerator.d.ts → utils/test-utils.d.ts} +5 -3
  10. package/dist/@types/utils/utils-index.d.ts +1 -0
  11. package/dist/migrations/scripts/1684951997326-init.js +18 -0
  12. package/dist/migrations/scripts/1684951997326-init.js.map +1 -1
  13. package/dist/migrations/scripts/1756282360128-connection-reporting.js +107 -0
  14. package/dist/migrations/scripts/1756282360128-connection-reporting.js.map +1 -0
  15. package/dist/storage/PostgresReportStorageFactory.js +238 -0
  16. package/dist/storage/PostgresReportStorageFactory.js.map +1 -0
  17. package/dist/storage/PostgresStorageProvider.js +10 -1
  18. package/dist/storage/PostgresStorageProvider.js.map +1 -1
  19. package/dist/storage/storage-index.js +0 -1
  20. package/dist/storage/storage-index.js.map +1 -1
  21. package/dist/types/models/SdkReporting.js +17 -0
  22. package/dist/types/models/SdkReporting.js.map +1 -0
  23. package/dist/types/models/models-index.js +1 -0
  24. package/dist/types/models/models-index.js.map +1 -1
  25. package/dist/{storage/PostgresTestStorageFactoryGenerator.js → utils/test-utils.js} +22 -6
  26. package/dist/utils/test-utils.js.map +1 -0
  27. package/dist/utils/utils-index.js +1 -0
  28. package/dist/utils/utils-index.js.map +1 -1
  29. package/package.json +7 -7
  30. package/src/migrations/scripts/1684951997326-init.ts +22 -0
  31. package/src/migrations/scripts/1756282360128-connection-reporting.ts +41 -0
  32. package/src/storage/PostgresReportStorageFactory.ts +258 -0
  33. package/src/storage/PostgresStorageProvider.ts +13 -2
  34. package/src/storage/storage-index.ts +0 -1
  35. package/src/types/models/SdkReporting.ts +23 -0
  36. package/src/types/models/models-index.ts +1 -0
  37. package/src/{storage/PostgresTestStorageFactoryGenerator.ts → utils/test-utils.ts} +21 -5
  38. package/src/utils/utils-index.ts +1 -0
  39. package/test/src/__snapshots__/connection-report-storage.test.ts.snap +215 -0
  40. package/test/src/connection-report-storage.test.ts +233 -0
  41. package/test/src/util.ts +3 -6
  42. package/dist/storage/PostgresTestStorageFactoryGenerator.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"utils-index.js","sourceRoot":"","sources":["../../src/utils/utils-index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC"}
1
+ {"version":3,"file":"utils-index.js","sourceRoot":"","sources":["../../src/utils/utils-index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAC"}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@powersync/service-module-postgres-storage",
3
3
  "repository": "https://github.com/powersync-ja/powersync-service",
4
4
  "types": "dist/@types/index.d.ts",
5
- "version": "0.0.0-dev-20250829094737",
5
+ "version": "0.0.0-dev-20250901073220",
6
6
  "main": "dist/index.js",
7
7
  "license": "FSL-1.1-ALv2",
8
8
  "type": "module",
@@ -29,17 +29,17 @@
29
29
  "p-defer": "^4.0.1",
30
30
  "ts-codec": "^1.3.0",
31
31
  "uuid": "^11.1.0",
32
- "@powersync/lib-service-postgres": "0.4.7",
33
- "@powersync/lib-services-framework": "0.7.3",
34
- "@powersync/service-core": "0.0.0-dev-20250829094737",
32
+ "@powersync/lib-service-postgres": "0.0.0-dev-20250901073220",
33
+ "@powersync/lib-services-framework": "0.0.0-dev-20250901073220",
34
+ "@powersync/service-core": "0.0.0-dev-20250901073220",
35
+ "@powersync/service-types": "0.0.0-dev-20250901073220",
35
36
  "@powersync/service-jpgwire": "0.20.2",
36
37
  "@powersync/service-jsonbig": "0.17.11",
37
- "@powersync/service-sync-rules": "0.29.0",
38
- "@powersync/service-types": "0.13.0"
38
+ "@powersync/service-sync-rules": "0.29.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "typescript": "^5.7.3",
42
- "@powersync/service-core-tests": "0.0.0-dev-20250829094737"
42
+ "@powersync/service-core-tests": "0.0.0-dev-20250901073220"
43
43
  },
44
44
  "scripts": {
45
45
  "build": "tsc -b",
@@ -128,6 +128,28 @@ export const up: migrations.PowerSyncMigrationFunction = async (context) => {
128
128
  CONSTRAINT unique_user_sync PRIMARY KEY (user_id, sync_rules_id)
129
129
  );
130
130
  `.execute();
131
+ await db.sql`
132
+ CREATE TABLE connection_report_events (
133
+ id TEXT PRIMARY KEY,
134
+ user_agent TEXT NOT NULL,
135
+ client_id TEXT NOT NULL,
136
+ user_id TEXT NOT NULL,
137
+ sdk TEXT NOT NULL,
138
+ jwt_exp TIMESTAMP WITH TIME ZONE,
139
+ connected_at TIMESTAMP WITH TIME ZONE NOT NULL,
140
+ disconnected_at TIMESTAMP WITH TIME ZONE
141
+ )
142
+ `.execute();
143
+
144
+ await db.sql`
145
+ CREATE INDEX sdk_list_index ON connection_report_events (connected_at, jwt_exp, disconnected_at)
146
+ `.execute();
147
+
148
+ await db.sql`CREATE INDEX sdk_user_id_index ON connection_report_events (user_id)`.execute();
149
+
150
+ await db.sql`CREATE INDEX sdk_client_id_index ON connection_report_events (client_id)`.execute();
151
+
152
+ await db.sql`CREATE INDEX sdk_index ON connection_report_events (sdk)`.execute();
131
153
  });
132
154
  };
133
155
 
@@ -0,0 +1,41 @@
1
+ import { migrations } from '@powersync/service-core';
2
+ import { openMigrationDB } from '../migration-utils.js';
3
+
4
+ export const up: migrations.PowerSyncMigrationFunction = async (context) => {
5
+ const {
6
+ service_context: { configuration }
7
+ } = context;
8
+ await using client = openMigrationDB(configuration.storage);
9
+ await client.transaction(async (db) => {
10
+ await db.sql`
11
+ CREATE TABLE IF NOT EXISTS connection_report_events (
12
+ id TEXT PRIMARY KEY,
13
+ user_agent TEXT NOT NULL,
14
+ client_id TEXT NOT NULL,
15
+ user_id TEXT NOT NULL,
16
+ sdk TEXT NOT NULL,
17
+ jwt_exp TIMESTAMP WITH TIME ZONE,
18
+ connected_at TIMESTAMP WITH TIME ZONE NOT NULL,
19
+ disconnected_at TIMESTAMP WITH TIME ZONE
20
+ )
21
+ `.execute();
22
+
23
+ await db.sql`
24
+ CREATE INDEX IF NOT EXISTS sdk_list_index ON connection_report_events (connected_at, jwt_exp, disconnected_at)
25
+ `.execute();
26
+
27
+ await db.sql`CREATE INDEX IF NOT EXISTS sdk_user_id_index ON connection_report_events (user_id)`.execute();
28
+
29
+ await db.sql`CREATE INDEX IF NOT EXISTS sdk_client_id_index ON connection_report_events (client_id)`.execute();
30
+
31
+ await db.sql`CREATE INDEX IF NOT EXISTS sdk_index ON connection_report_events (sdk)`.execute();
32
+ });
33
+ };
34
+
35
+ export const down: migrations.PowerSyncMigrationFunction = async (context) => {
36
+ const {
37
+ service_context: { configuration }
38
+ } = context;
39
+ await using client = openMigrationDB(configuration.storage);
40
+ await client.sql`DROP TABLE IF EXISTS connection_report_events`.execute();
41
+ };
@@ -0,0 +1,258 @@
1
+ import { storage } from '@powersync/service-core';
2
+ import * as pg_wire from '@powersync/service-jpgwire';
3
+ import { event_types } from '@powersync/service-types';
4
+ import { v4 } from 'uuid';
5
+ import * as lib_postgres from '@powersync/lib-service-postgres';
6
+ import { NormalizedPostgresStorageConfig } from '../types/types.js';
7
+ import { SdkReporting, SdkReportingDecoded } from '../types/models/SdkReporting.js';
8
+ import { toInteger } from 'ix/util/tointeger.js';
9
+ import { logger } from '@powersync/lib-services-framework';
10
+ import { getStorageApplicationName } from '../utils/application-name.js';
11
+ import { STORAGE_SCHEMA_NAME } from '../utils/db.js';
12
+
13
+ export type PostgresReportStorageOptions = {
14
+ config: NormalizedPostgresStorageConfig;
15
+ };
16
+
17
+ export class PostgresReportStorageFactory implements storage.ReportStorage {
18
+ readonly db: lib_postgres.DatabaseClient;
19
+ constructor(protected options: PostgresReportStorageOptions) {
20
+ this.db = new lib_postgres.DatabaseClient({
21
+ config: options.config,
22
+ schema: STORAGE_SCHEMA_NAME,
23
+ applicationName: getStorageApplicationName()
24
+ });
25
+
26
+ this.db.registerListener({
27
+ connectionCreated: async (connection) => this.prepareStatements(connection)
28
+ });
29
+ }
30
+
31
+ private parseJsDate(date: Date) {
32
+ const year = date.getFullYear();
33
+ const month = date.getMonth();
34
+ const today = date.getDate();
35
+ const day = date.getDay();
36
+ return {
37
+ year,
38
+ month,
39
+ today,
40
+ day,
41
+ parsedDate: date
42
+ };
43
+ }
44
+
45
+ private mapListCurrentConnectionsResponse(
46
+ result: SdkReportingDecoded | null
47
+ ): event_types.ClientConnectionReportResponse {
48
+ if (!result) {
49
+ return {
50
+ users: 0,
51
+ sdks: []
52
+ };
53
+ }
54
+ return {
55
+ users: Number(result.users),
56
+ sdks: result.sdks?.data || []
57
+ };
58
+ }
59
+ private async listConnectionsQuery() {
60
+ return await this.db.sql`
61
+ WITH
62
+ filtered AS (
63
+ SELECT
64
+ *
65
+ FROM
66
+ connection_report_events
67
+ WHERE
68
+ disconnected_at IS NULL
69
+ AND jwt_exp > NOW()
70
+ ),
71
+ unique_users AS (
72
+ SELECT
73
+ COUNT(DISTINCT user_id) AS count
74
+ FROM
75
+ filtered
76
+ ),
77
+ sdk_versions_array AS (
78
+ SELECT
79
+ sdk,
80
+ COUNT(DISTINCT client_id) AS clients,
81
+ COUNT(DISTINCT user_id) AS users
82
+ FROM
83
+ filtered
84
+ GROUP BY
85
+ sdk
86
+ )
87
+ SELECT
88
+ (
89
+ SELECT
90
+ COALESCE(count, 0)
91
+ FROM
92
+ unique_users
93
+ ) AS users,
94
+ (
95
+ SELECT
96
+ JSON_AGG(ROW_TO_JSON(s))
97
+ FROM
98
+ sdk_versions_array s
99
+ ) AS sdks;
100
+ `
101
+ .decoded(SdkReporting)
102
+ .first();
103
+ }
104
+
105
+ private updateTableFilter() {
106
+ const { year, month, today } = this.parseJsDate(new Date());
107
+ const nextDay = today + 1;
108
+ return {
109
+ gte: new Date(year, month, today).toISOString(),
110
+ lt: new Date(year, month, nextDay).toISOString()
111
+ };
112
+ }
113
+
114
+ async reportClientConnection(data: event_types.ClientConnectionBucketData): Promise<void> {
115
+ const { sdk, connected_at, user_id, user_agent, jwt_exp, client_id } = data;
116
+ const connectIsoString = connected_at.toISOString();
117
+ const jwtExpIsoString = jwt_exp.toISOString();
118
+ const { gte, lt } = this.updateTableFilter();
119
+ const uuid = v4();
120
+ const result = await this.db.sql`
121
+ UPDATE connection_report_events
122
+ SET
123
+ connected_at = ${{ type: 1184, value: connectIsoString }},
124
+ sdk = ${{ type: 'varchar', value: sdk }},
125
+ user_agent = ${{ type: 'varchar', value: user_agent }},
126
+ jwt_exp = ${{ type: 1184, value: jwtExpIsoString }},
127
+ disconnected_at = NULL
128
+ WHERE
129
+ user_id = ${{ type: 'varchar', value: user_id }}
130
+ AND client_id = ${{ type: 'varchar', value: client_id }}
131
+ AND connected_at >= ${{ type: 1184, value: gte }}
132
+ AND connected_at < ${{ type: 1184, value: lt }};
133
+ `.execute();
134
+ if (result.results[1].status === 'UPDATE 0') {
135
+ await this.db.sql`
136
+ INSERT INTO
137
+ connection_report_events (
138
+ user_id,
139
+ client_id,
140
+ connected_at,
141
+ sdk,
142
+ user_agent,
143
+ jwt_exp,
144
+ id
145
+ )
146
+ VALUES
147
+ (
148
+ ${{ type: 'varchar', value: user_id }},
149
+ ${{ type: 'varchar', value: client_id }},
150
+ ${{ type: 1184, value: connectIsoString }},
151
+ ${{ type: 'varchar', value: sdk }},
152
+ ${{ type: 'varchar', value: user_agent }},
153
+ ${{ type: 1184, value: jwtExpIsoString }},
154
+ ${{ type: 'varchar', value: uuid }}
155
+ )
156
+ `.execute();
157
+ }
158
+ }
159
+ async reportClientDisconnection(data: event_types.ClientDisconnectionEventData): Promise<void> {
160
+ const { user_id, client_id, disconnected_at, connected_at } = data;
161
+ const disconnectIsoString = disconnected_at.toISOString();
162
+ const connectIsoString = connected_at.toISOString();
163
+ await this.db.sql`
164
+ UPDATE connection_report_events
165
+ SET
166
+ disconnected_at = ${{ type: 1184, value: disconnectIsoString }},
167
+ jwt_exp = NULL
168
+ WHERE
169
+ user_id = ${{ type: 'varchar', value: user_id }}
170
+ AND client_id = ${{ type: 'varchar', value: client_id }}
171
+ AND connected_at = ${{ type: 1184, value: connectIsoString }}
172
+ `.execute();
173
+ }
174
+ async getConnectedClients(): Promise<event_types.ClientConnectionReportResponse> {
175
+ const result = await this.listConnectionsQuery();
176
+ return this.mapListCurrentConnectionsResponse(result);
177
+ }
178
+
179
+ async getClientConnectionReports(
180
+ data: event_types.ClientConnectionReportRequest
181
+ ): Promise<event_types.ClientConnectionReportResponse> {
182
+ const { start, end } = data;
183
+ const result = await this.db.sql`
184
+ WITH
185
+ filtered AS (
186
+ SELECT
187
+ *
188
+ FROM
189
+ connection_report_events
190
+ WHERE
191
+ connected_at >= ${{ type: 1184, value: start.toISOString() }}
192
+ AND connected_at <= ${{ type: 1184, value: end.toISOString() }}
193
+ ),
194
+ unique_users AS (
195
+ SELECT
196
+ COUNT(DISTINCT user_id) AS count
197
+ FROM
198
+ filtered
199
+ ),
200
+ sdk_versions_array AS (
201
+ SELECT
202
+ sdk,
203
+ COUNT(DISTINCT client_id) AS clients,
204
+ COUNT(DISTINCT user_id) AS users
205
+ FROM
206
+ filtered
207
+ GROUP BY
208
+ sdk
209
+ )
210
+ SELECT
211
+ (
212
+ SELECT
213
+ COALESCE(count, 0)
214
+ FROM
215
+ unique_users
216
+ ) AS users,
217
+ (
218
+ SELECT
219
+ JSON_AGG(ROW_TO_JSON(s))
220
+ FROM
221
+ sdk_versions_array s
222
+ ) AS sdks;
223
+ `
224
+ .decoded(SdkReporting)
225
+ .first();
226
+ return this.mapListCurrentConnectionsResponse(result);
227
+ }
228
+ async deleteOldConnectionData(data: event_types.DeleteOldConnectionData): Promise<void> {
229
+ const { date } = data;
230
+ const result = await this.db.sql`
231
+ DELETE FROM connection_report_events
232
+ WHERE
233
+ connected_at < ${{ type: 1184, value: date.toISOString() }}
234
+ AND (
235
+ disconnected_at IS NOT NULL
236
+ OR (
237
+ jwt_exp < NOW()
238
+ AND disconnected_at IS NULL
239
+ )
240
+ );
241
+ `.execute();
242
+ const deletedRows = toInteger(result.results[1].status.split(' ')[1] || '0');
243
+ if (deletedRows > 0) {
244
+ logger.info(
245
+ `TTL from ${date.toISOString()}: ${deletedRows} PostgresSQL rows have been removed from connection_report_events.`
246
+ );
247
+ }
248
+ }
249
+
250
+ async [Symbol.asyncDispose]() {
251
+ await this.db[Symbol.asyncDispose]();
252
+ }
253
+
254
+ async prepareStatements(connection: pg_wire.PgConnection) {
255
+ // It should be possible to prepare statements for some common operations here.
256
+ // This has not been implemented yet.
257
+ }
258
+ }
@@ -5,8 +5,9 @@ import { storage } from '@powersync/service-core';
5
5
  import { isPostgresStorageConfig, normalizePostgresStorageConfig, PostgresStorageConfig } from '../types/types.js';
6
6
  import { dropTables } from '../utils/db.js';
7
7
  import { PostgresBucketStorageFactory } from './PostgresBucketStorageFactory.js';
8
+ import { PostgresReportStorageFactory } from './PostgresReportStorageFactory.js';
8
9
 
9
- export class PostgresStorageProvider implements storage.BucketStorageProvider {
10
+ export class PostgresStorageProvider implements storage.StorageProvider {
10
11
  get type() {
11
12
  return lib_postgres.POSTGRES_CONNECTION_TYPE;
12
13
  }
@@ -28,13 +29,23 @@ export class PostgresStorageProvider implements storage.BucketStorageProvider {
28
29
  config: normalizedConfig,
29
30
  slot_name_prefix: options.resolvedConfig.slot_name_prefix
30
31
  });
32
+
33
+ const reportStorageFactory = new PostgresReportStorageFactory({
34
+ config: normalizedConfig
35
+ });
36
+
31
37
  return {
38
+ reportStorage: reportStorageFactory,
32
39
  storage: storageFactory,
33
- shutDown: async () => storageFactory.db[Symbol.asyncDispose](),
40
+ shutDown: async () => {
41
+ await storageFactory.db[Symbol.asyncDispose]();
42
+ await reportStorageFactory.db[Symbol.asyncDispose]();
43
+ },
34
44
  tearDown: async () => {
35
45
  logger.info(`Tearing down Postgres storage: ${normalizedConfig.database}...`);
36
46
  await dropTables(storageFactory.db);
37
47
  await storageFactory.db[Symbol.asyncDispose]();
48
+ await reportStorageFactory.db[Symbol.asyncDispose]();
38
49
  return true;
39
50
  }
40
51
  } satisfies storage.ActiveStorage;
@@ -2,4 +2,3 @@ export * from './PostgresBucketStorageFactory.js';
2
2
  export * from './PostgresCompactor.js';
3
3
  export * from './PostgresStorageProvider.js';
4
4
  export * from './PostgresSyncRulesStorage.js';
5
- export * from './PostgresTestStorageFactoryGenerator.js';
@@ -0,0 +1,23 @@
1
+ import * as t from 'ts-codec';
2
+ import { bigint, jsonb } from '../codecs.js';
3
+
4
+ export const Sdks = t.object({
5
+ sdk: t.string,
6
+ clients: t.number,
7
+ users: t.number
8
+ });
9
+
10
+ export type Sdks = t.Encoded<typeof Sdks>;
11
+
12
+ export const SdkReporting = t.object({
13
+ users: bigint,
14
+ sdks: t
15
+ .object({
16
+ data: jsonb<Sdks[]>(t.array(Sdks))
17
+ })
18
+ .optional()
19
+ .or(t.Null)
20
+ });
21
+
22
+ export type SdkReporting = t.Encoded<typeof SdkReporting>;
23
+ export type SdkReportingDecoded = t.Decoded<typeof SdkReporting>;
@@ -8,3 +8,4 @@ export * from './Migration.js';
8
8
  export * from './SourceTable.js';
9
9
  export * from './SyncRules.js';
10
10
  export * from './WriteCheckpoint.js';
11
+ export * from './SdkReporting.js';
@@ -1,7 +1,8 @@
1
1
  import { framework, PowerSyncMigrationManager, ServiceContext, TestStorageOptions } from '@powersync/service-core';
2
2
  import { PostgresMigrationAgent } from '../migrations/PostgresMigrationAgent.js';
3
3
  import { normalizePostgresStorageConfig, PostgresStorageConfigDecoded } from '../types/types.js';
4
- import { PostgresBucketStorageFactory } from './PostgresBucketStorageFactory.js';
4
+ import { PostgresReportStorageFactory } from '../storage/PostgresReportStorageFactory.js';
5
+ import { PostgresBucketStorageFactory } from '../storage/PostgresBucketStorageFactory.js';
5
6
 
6
7
  export type PostgresTestStorageOptions = {
7
8
  url: string;
@@ -12,7 +13,7 @@ export type PostgresTestStorageOptions = {
12
13
  migrationAgent?: (config: PostgresStorageConfigDecoded) => PostgresMigrationAgent;
13
14
  };
14
15
 
15
- export const postgresTestSetup = (factoryOptions: PostgresTestStorageOptions) => {
16
+ export function postgresTestSetup(factoryOptions: PostgresTestStorageOptions) {
16
17
  const BASE_CONFIG = {
17
18
  type: 'postgresql' as const,
18
19
  uri: factoryOptions.url,
@@ -48,6 +49,21 @@ export const postgresTestSetup = (factoryOptions: PostgresTestStorageOptions) =>
48
49
  };
49
50
 
50
51
  return {
52
+ reportFactory: async (options?: TestStorageOptions) => {
53
+ try {
54
+ if (!options?.doNotClear) {
55
+ await migrate(framework.migrations.Direction.Up);
56
+ }
57
+
58
+ return new PostgresReportStorageFactory({
59
+ config: TEST_CONNECTION_OPTIONS
60
+ });
61
+ } catch (ex) {
62
+ // Vitest does not display these errors nicely when using the `await using` syntx
63
+ console.error(ex, ex.cause);
64
+ throw ex;
65
+ }
66
+ },
51
67
  factory: async (options?: TestStorageOptions) => {
52
68
  try {
53
69
  if (!options?.doNotClear) {
@@ -66,8 +82,8 @@ export const postgresTestSetup = (factoryOptions: PostgresTestStorageOptions) =>
66
82
  },
67
83
  migrate
68
84
  };
69
- };
85
+ }
70
86
 
71
- export const PostgresTestStorageFactoryGenerator = (factoryOptions: PostgresTestStorageOptions) => {
87
+ export function postgresTestStorageFactoryGenerator(factoryOptions: PostgresTestStorageOptions) {
72
88
  return postgresTestSetup(factoryOptions).factory;
73
- };
89
+ }
@@ -2,3 +2,4 @@ export * from './bson.js';
2
2
  export * from './bucket-data.js';
3
3
  export * from './db.js';
4
4
  export * from './ts-codec.js';
5
+ export * as test_utils from './test-utils.js';