diff --git a/.changeset/slow-hounds-walk.md b/.changeset/slow-hounds-walk.md index 1745e4389..31d556b20 100644 --- a/.changeset/slow-hounds-walk.md +++ b/.changeset/slow-hounds-walk.md @@ -1,10 +1,12 @@ --- '@powersync/service-module-mongodb-storage': patch +'@powersync/service-module-postgres-storage': patch '@powersync/service-module-mongodb': patch +'@powersync/service-module-postgres': patch '@powersync/service-module-mysql': patch '@powersync/lib-service-mongodb': patch -'@powersync/service-core': patch -'@powersync/service-image': patch +'@powersync/service-core': minor +'@powersync/service-image': minor --- -Add 'powersync' as the app name for MongoDB and MySQL connections. +Add 'powersync' or 'powersync-storage' as the app name for database connections. diff --git a/libs/lib-mongodb/src/db/mongo.ts b/libs/lib-mongodb/src/db/mongo.ts index ddeaf39dc..059c4facd 100644 --- a/libs/lib-mongodb/src/db/mongo.ts +++ b/libs/lib-mongodb/src/db/mongo.ts @@ -29,7 +29,8 @@ export const MONGO_OPERATION_TIMEOUT_MS = 30_000; export const MONGO_CLEAR_OPERATION_TIMEOUT_MS = 5_000; export interface MongoConnectionOptions { - maxPoolSize: number; + maxPoolSize?: number; + powersyncVersion?: string; } /** @@ -50,7 +51,12 @@ export function createMongoClient(config: BaseMongoConfigDecoded, options?: Mong serverSelectionTimeoutMS: 30_000, // Identify the client - appName: 'powersync-storage', + appName: options?.powersyncVersion ? `powersync-storage ${options.powersyncVersion}` : 'powersync-storage', + driverInfo: { + // This is merged with the node driver info. + name: 'powersync-storage', + version: options?.powersyncVersion + }, lookup: normalized.lookup, diff --git a/libs/lib-postgres/src/db/connection/ConnectionSlot.ts b/libs/lib-postgres/src/db/connection/ConnectionSlot.ts index 00670fa8f..a048fb453 100644 --- a/libs/lib-postgres/src/db/connection/ConnectionSlot.ts +++ b/libs/lib-postgres/src/db/connection/ConnectionSlot.ts @@ -19,6 +19,7 @@ export type ConnectionLease = { export type ConnectionSlotOptions = { config: pgwire.NormalizedConnectionConfig; notificationChannels?: string[]; + applicationName: string; }; export const MAX_CONNECTION_ATTEMPTS = 5; @@ -46,7 +47,10 @@ export class ConnectionSlot extends framework.BaseObserver l.connectionCreated?.(connection)); diff --git a/libs/lib-postgres/src/db/connection/DatabaseClient.ts b/libs/lib-postgres/src/db/connection/DatabaseClient.ts index 2ed576bc0..60cd47fa6 100644 --- a/libs/lib-postgres/src/db/connection/DatabaseClient.ts +++ b/libs/lib-postgres/src/db/connection/DatabaseClient.ts @@ -15,6 +15,8 @@ export type DatabaseClientOptions = { * Notification channels to listen to. */ notificationChannels?: string[]; + + applicationName: string; }; export type DatabaseClientListener = NotificationListener & { @@ -43,12 +45,17 @@ export class DatabaseClient extends AbstractPostgresConnection { // Only listen to notifications on a single (the first) connection const notificationChannels = index == 0 ? options.notificationChannels : []; - const slot = new ConnectionSlot({ config: options.config, notificationChannels }); + const slot = new ConnectionSlot({ + config: options.config, + notificationChannels, + applicationName: options.applicationName + }); slot.registerListener({ connectionAvailable: () => this.processConnectionQueue(), connectionError: (ex) => this.handleConnectionError(ex), diff --git a/modules/module-mongodb-storage/src/storage/implementation/MongoStorageProvider.ts b/modules/module-mongodb-storage/src/storage/implementation/MongoStorageProvider.ts index 6fd5f3648..75333edb1 100644 --- a/modules/module-mongodb-storage/src/storage/implementation/MongoStorageProvider.ts +++ b/modules/module-mongodb-storage/src/storage/implementation/MongoStorageProvider.ts @@ -1,6 +1,6 @@ import * as lib_mongo from '@powersync/lib-service-mongodb'; import { ErrorCode, logger, ServiceAssertionError, ServiceError } from '@powersync/lib-services-framework'; -import { storage } from '@powersync/service-core'; +import { POWERSYNC_VERSION, storage } from '@powersync/service-core'; import { MongoStorageConfig } from '../../types/types.js'; import { MongoBucketStorage } from '../MongoBucketStorage.js'; import { PowerSyncMongo } from './db.js'; @@ -23,6 +23,7 @@ export class MongoStorageProvider implements storage.BucketStorageProvider { const decodedConfig = MongoStorageConfig.decode(storage as any); const client = lib_mongo.db.createMongoClient(decodedConfig, { + powersyncVersion: POWERSYNC_VERSION, maxPoolSize: resolvedConfig.storage.max_pool_size ?? 8 }); diff --git a/modules/module-mongodb-storage/src/storage/implementation/db.ts b/modules/module-mongodb-storage/src/storage/implementation/db.ts index 694da1b75..dc5f4738e 100644 --- a/modules/module-mongodb-storage/src/storage/implementation/db.ts +++ b/modules/module-mongodb-storage/src/storage/implementation/db.ts @@ -1,6 +1,6 @@ import * as lib_mongo from '@powersync/lib-service-mongodb'; import { mongo } from '@powersync/lib-service-mongodb'; -import { storage } from '@powersync/service-core'; +import { POWERSYNC_VERSION, storage } from '@powersync/service-core'; import { MongoStorageConfig } from '../../types/types.js'; import { @@ -130,5 +130,11 @@ export class PowerSyncMongo { } export function createPowerSyncMongo(config: MongoStorageConfig, options?: lib_mongo.MongoConnectionOptions) { - return new PowerSyncMongo(lib_mongo.createMongoClient(config, options), { database: config.database }); + return new PowerSyncMongo( + lib_mongo.createMongoClient(config, { + powersyncVersion: POWERSYNC_VERSION, + ...options + }), + { database: config.database } + ); } diff --git a/modules/module-mongodb-storage/src/storage/implementation/util.ts b/modules/module-mongodb-storage/src/storage/implementation/util.ts index 2c9a05d53..84e94466b 100644 --- a/modules/module-mongodb-storage/src/storage/implementation/util.ts +++ b/modules/module-mongodb-storage/src/storage/implementation/util.ts @@ -104,20 +104,13 @@ export function replicaIdToSubkey(table: bson.ObjectId, id: storage.ReplicaId): } } -/** - * Helper function for creating a MongoDB client from consumers of this package - */ -const createMongoClient = (url: string, options?: mongo.MongoClientOptions) => { - return new mongo.MongoClient(url, options); -}; - /** * Helper for unit tests */ export const connectMongoForTests = (url: string, isCI: boolean) => { // Short timeout for tests, to fail fast when the server is not available. // Slightly longer timeouts for CI, to avoid arbitrary test failures - const client = createMongoClient(url, { + const client = new mongo.MongoClient(url, { connectTimeoutMS: isCI ? 15_000 : 5_000, socketTimeoutMS: isCI ? 15_000 : 5_000, serverSelectionTimeoutMS: isCI ? 15_000 : 2_500 diff --git a/modules/module-mongodb/src/replication/MongoManager.ts b/modules/module-mongodb/src/replication/MongoManager.ts index 3742406ba..b66878f4b 100644 --- a/modules/module-mongodb/src/replication/MongoManager.ts +++ b/modules/module-mongodb/src/replication/MongoManager.ts @@ -1,7 +1,7 @@ import { mongo } from '@powersync/lib-service-mongodb'; import { NormalizedMongoConnectionConfig } from '../types/types.js'; -import { BSON_DESERIALIZE_DATA_OPTIONS } from '@powersync/service-core'; +import { BSON_DESERIALIZE_DATA_OPTIONS, POWERSYNC_VERSION } from '@powersync/service-core'; /** * Manage a MongoDB source database connection. @@ -30,7 +30,12 @@ export class MongoManager { serverSelectionTimeoutMS: 30_000, // Identify the client - appName: 'powersync', + appName: `powersync ${POWERSYNC_VERSION}`, + driverInfo: { + // This is merged with the node driver info. + name: 'powersync', + version: POWERSYNC_VERSION + }, // Avoid too many connections: // 1. It can overwhelm the source database. diff --git a/modules/module-mysql/src/replication/BinLogReplicationJob.ts b/modules/module-mysql/src/replication/BinLogReplicationJob.ts index 24a07a49a..d97aa5281 100644 --- a/modules/module-mysql/src/replication/BinLogReplicationJob.ts +++ b/modules/module-mysql/src/replication/BinLogReplicationJob.ts @@ -1,5 +1,5 @@ import { container, logger as defaultLogger } from '@powersync/lib-services-framework'; -import { replication } from '@powersync/service-core'; +import { POWERSYNC_VERSION, replication } from '@powersync/service-core'; import { BinlogConfigurationError, BinLogStream } from './BinLogStream.js'; import { MySQLConnectionManagerFactory } from './MySQLConnectionManagerFactory.js'; @@ -61,7 +61,8 @@ export class BinLogReplicationJob extends replication.AbstractReplicationJob { // https://dev.mysql.com/doc/refman/8.0/en/performance-schema-connection-attribute-tables.html // These do not appear to be supported by Zongji yet, so we only specify it here. // Query using `select * from performance_schema.session_connect_attrs`. - program_name: 'powersync' + program_name: 'powersync', + program_version: POWERSYNC_VERSION // _client_name and _client_version is specified by the driver } diff --git a/modules/module-postgres-storage/src/migrations/PostgresMigrationAgent.ts b/modules/module-postgres-storage/src/migrations/PostgresMigrationAgent.ts index ba6831d06..6d3819525 100644 --- a/modules/module-postgres-storage/src/migrations/PostgresMigrationAgent.ts +++ b/modules/module-postgres-storage/src/migrations/PostgresMigrationAgent.ts @@ -6,6 +6,7 @@ import { fileURLToPath } from 'url'; import { normalizePostgresStorageConfig, PostgresStorageConfigDecoded } from '../types/types.js'; +import { getStorageApplicationName } from '../utils/application-name.js'; import { STORAGE_SCHEMA_NAME } from '../utils/db.js'; import { PostgresMigrationStore } from './PostgresMigrationStore.js'; @@ -25,7 +26,8 @@ export class PostgresMigrationAgent extends migrations.AbstractPowerSyncMigratio this.db = new lib_postgres.DatabaseClient({ config: normalizePostgresStorageConfig(config), - schema: STORAGE_SCHEMA_NAME + schema: STORAGE_SCHEMA_NAME, + applicationName: getStorageApplicationName() }); this.store = new PostgresMigrationStore({ db: this.db diff --git a/modules/module-postgres-storage/src/migrations/migration-utils.ts b/modules/module-postgres-storage/src/migrations/migration-utils.ts index 09c595272..0c62d83e8 100644 --- a/modules/module-postgres-storage/src/migrations/migration-utils.ts +++ b/modules/module-postgres-storage/src/migrations/migration-utils.ts @@ -3,6 +3,7 @@ import { configFile } from '@powersync/service-types'; import { isPostgresStorageConfig, normalizePostgresStorageConfig, PostgresStorageConfig } from '../types/types.js'; import { STORAGE_SCHEMA_NAME } from '../utils/db.js'; import { ServiceAssertionError } from '@powersync/lib-services-framework'; +import { getStorageApplicationName } from '../utils/application-name.js'; export const openMigrationDB = (config: configFile.BaseStorageConfig) => { if (!isPostgresStorageConfig(config)) { @@ -10,6 +11,7 @@ export const openMigrationDB = (config: configFile.BaseStorageConfig) => { } return new lib_postgres.DatabaseClient({ config: normalizePostgresStorageConfig(PostgresStorageConfig.decode(config)), - schema: STORAGE_SCHEMA_NAME + schema: STORAGE_SCHEMA_NAME, + applicationName: getStorageApplicationName() }); }; diff --git a/modules/module-postgres-storage/src/storage/PostgresBucketStorageFactory.ts b/modules/module-postgres-storage/src/storage/PostgresBucketStorageFactory.ts index cec373475..85fbe4dad 100644 --- a/modules/module-postgres-storage/src/storage/PostgresBucketStorageFactory.ts +++ b/modules/module-postgres-storage/src/storage/PostgresBucketStorageFactory.ts @@ -12,6 +12,7 @@ import { NOTIFICATION_CHANNEL, STORAGE_SCHEMA_NAME } from '../utils/db.js'; import { notifySyncRulesUpdate } from './batch/PostgresBucketBatch.js'; import { PostgresSyncRulesStorage } from './PostgresSyncRulesStorage.js'; import { PostgresPersistedSyncRulesContent } from './sync-rules/PostgresPersistedSyncRulesContent.js'; +import { getStorageApplicationName } from '../utils/application-name.js'; export type PostgresBucketStorageOptions = { config: NormalizedPostgresStorageConfig; @@ -32,7 +33,8 @@ export class PostgresBucketStorageFactory this.db = new lib_postgres.DatabaseClient({ config: options.config, schema: STORAGE_SCHEMA_NAME, - notificationChannels: [NOTIFICATION_CHANNEL] + notificationChannels: [NOTIFICATION_CHANNEL], + applicationName: getStorageApplicationName() }); this.slot_name_prefix = options.slot_name_prefix; diff --git a/modules/module-postgres-storage/src/utils/application-name.ts b/modules/module-postgres-storage/src/utils/application-name.ts new file mode 100644 index 000000000..69ed67ee3 --- /dev/null +++ b/modules/module-postgres-storage/src/utils/application-name.ts @@ -0,0 +1,8 @@ +import { POWERSYNC_VERSION } from '@powersync/service-core'; + +/** + * Name for postgres application_name, for bucket storage connections. + */ +export function getStorageApplicationName() { + return `powersync-storage/${POWERSYNC_VERSION}`; +} diff --git a/modules/module-postgres/src/api/PostgresRouteAPIAdapter.ts b/modules/module-postgres/src/api/PostgresRouteAPIAdapter.ts index 634ece0f3..fc9324686 100644 --- a/modules/module-postgres/src/api/PostgresRouteAPIAdapter.ts +++ b/modules/module-postgres/src/api/PostgresRouteAPIAdapter.ts @@ -8,6 +8,7 @@ import * as replication_utils from '../replication/replication-utils.js'; import { getDebugTableInfo } from '../replication/replication-utils.js'; import { KEEPALIVE_STATEMENT, PUBLICATION_NAME } from '../replication/WalStream.js'; import * as types from '../types/types.js'; +import { getApplicationName } from '../utils/application-name.js'; export class PostgresRouteAPIAdapter implements api.RouteAPI { connectionTag: string; @@ -16,7 +17,8 @@ export class PostgresRouteAPIAdapter implements api.RouteAPI { static withConfig(config: types.ResolvedConnectionConfig) { const pool = pgwire.connectPgWirePool(config, { - idleTimeout: 30_000 + idleTimeout: 30_000, + applicationName: getApplicationName() }); return new PostgresRouteAPIAdapter(pool, config.tag, config); } diff --git a/modules/module-postgres/src/auth/SupabaseKeyCollector.ts b/modules/module-postgres/src/auth/SupabaseKeyCollector.ts index 291b648ba..bee0a11ce 100644 --- a/modules/module-postgres/src/auth/SupabaseKeyCollector.ts +++ b/modules/module-postgres/src/auth/SupabaseKeyCollector.ts @@ -5,6 +5,7 @@ import * as jose from 'jose'; import * as types from '../types/types.js'; import { AuthorizationError, ErrorCode } from '@powersync/lib-services-framework'; +import { getApplicationName } from '../utils/application-name.js'; /** * Fetches key from the Supabase database. @@ -28,7 +29,8 @@ export class SupabaseKeyCollector implements auth.KeyCollector { // limit to a single connection, and close the connection shortly // after using it. idleTimeout: 5_000, - maxSize: 1 + maxSize: 1, + applicationName: getApplicationName() }); } diff --git a/modules/module-postgres/src/module/PostgresModule.ts b/modules/module-postgres/src/module/PostgresModule.ts index 7dfb06107..41e7b00e2 100644 --- a/modules/module-postgres/src/module/PostgresModule.ts +++ b/modules/module-postgres/src/module/PostgresModule.ts @@ -20,6 +20,7 @@ import * as types from '../types/types.js'; import { PostgresConnectionConfig } from '../types/types.js'; import { baseUri, NormalizedBasePostgresConnectionConfig } from '@powersync/lib-service-postgres'; import { ReplicationMetric } from '@powersync/service-types'; +import { getApplicationName } from '../utils/application-name.js'; export class PostgresModule extends replication.ReplicationModule { constructor() { @@ -88,7 +89,8 @@ export class PostgresModule extends replication.ReplicationModule { - const p = pgwire.connectPgWire(this.options, { type: 'replication' }); + const p = pgwire.connectPgWire(this.options, { type: 'replication', applicationName: getApplicationName() }); this.connectionPromises.push(p); return await p; } @@ -51,7 +52,7 @@ export class PgManager { * This connection must not be shared between multiple async contexts. */ async snapshotConnection(): Promise { - const p = pgwire.connectPgWire(this.options, { type: 'standard' }); + const p = pgwire.connectPgWire(this.options, { type: 'standard', applicationName: getApplicationName() }); this.connectionPromises.push(p); const connection = await p; diff --git a/modules/module-postgres/src/replication/WalStreamReplicationJob.ts b/modules/module-postgres/src/replication/WalStreamReplicationJob.ts index fe97aaa8a..a9d38b5b5 100644 --- a/modules/module-postgres/src/replication/WalStreamReplicationJob.ts +++ b/modules/module-postgres/src/replication/WalStreamReplicationJob.ts @@ -4,6 +4,7 @@ import { MissingReplicationSlotError, sendKeepAlive, WalStream } from './WalStre import { replication } from '@powersync/service-core'; import { ConnectionManagerFactory } from './ConnectionManagerFactory.js'; +import { getApplicationName } from '../utils/application-name.js'; export interface WalStreamReplicationJobOptions extends replication.AbstractReplicationJobOptions { connectionFactory: ConnectionManagerFactory; @@ -21,7 +22,8 @@ export class WalStreamReplicationJob extends replication.AbstractReplicationJob this.connectionManager = this.connectionFactory.create({ // Pool connections are only used intermittently. idleTimeout: 30_000, - maxSize: 2 + maxSize: 2, + applicationName: getApplicationName() }); } @@ -87,7 +89,8 @@ export class WalStreamReplicationJob extends replication.AbstractReplicationJob const connectionManager = this.connectionFactory.create({ // Pool connections are only used intermittently. idleTimeout: 30_000, - maxSize: 2 + maxSize: 2, + applicationName: getApplicationName() }); try { await this.rateLimiter?.waitUntilAllowed({ signal: this.abortController.signal }); diff --git a/modules/module-postgres/src/replication/WalStreamReplicator.ts b/modules/module-postgres/src/replication/WalStreamReplicator.ts index 2d8c482c6..d922280bc 100644 --- a/modules/module-postgres/src/replication/WalStreamReplicator.ts +++ b/modules/module-postgres/src/replication/WalStreamReplicator.ts @@ -1,8 +1,9 @@ import { replication, storage } from '@powersync/service-core'; +import { PostgresModule } from '../module/PostgresModule.js'; +import { getApplicationName } from '../utils/application-name.js'; import { ConnectionManagerFactory } from './ConnectionManagerFactory.js'; import { cleanUpReplicationSlot } from './replication-utils.js'; import { WalStreamReplicationJob } from './WalStreamReplicationJob.js'; -import { PostgresModule } from '../module/PostgresModule.js'; export interface WalStreamReplicatorOptions extends replication.AbstractReplicatorOptions { connectionFactory: ConnectionManagerFactory; @@ -29,6 +30,7 @@ export class WalStreamReplicator extends replication.AbstractReplicator { const connectionManager = this.connectionFactory.create({ + applicationName: getApplicationName(), idleTimeout: 30_000, maxSize: 1 }); diff --git a/modules/module-postgres/src/utils/application-name.ts b/modules/module-postgres/src/utils/application-name.ts new file mode 100644 index 000000000..d7ccbde4e --- /dev/null +++ b/modules/module-postgres/src/utils/application-name.ts @@ -0,0 +1,8 @@ +import { POWERSYNC_VERSION } from '@powersync/service-core'; + +/** + * application_name for PostgreSQL connections to the source database + */ +export function getApplicationName() { + return `powersync/${POWERSYNC_VERSION}`; +} diff --git a/modules/module-postgres/src/utils/populate_test_data_worker.ts b/modules/module-postgres/src/utils/populate_test_data_worker.ts index 5fd161103..72ea44dfb 100644 --- a/modules/module-postgres/src/utils/populate_test_data_worker.ts +++ b/modules/module-postgres/src/utils/populate_test_data_worker.ts @@ -29,7 +29,10 @@ if (isMainThread || parentPort == null) { async function populateDataInner(options: PopulateDataOptions) { // Dedicated connection so we can release the memory easily - const initialDb = await pgwire.connectPgWire(options.connection, { type: 'standard' }); + const initialDb = await pgwire.connectPgWire(options.connection, { + type: 'standard', + applicationName: 'powersync-tests' + }); const largeDescription = crypto.randomBytes(options.size / 2).toString('hex'); let operation_count = 0; for (let i = 0; i < options.num_transactions; i++) { diff --git a/modules/module-postgres/test/src/util.ts b/modules/module-postgres/test/src/util.ts index 30d106858..7a75db622 100644 --- a/modules/module-postgres/test/src/util.ts +++ b/modules/module-postgres/test/src/util.ts @@ -62,7 +62,7 @@ export async function clearTestDb(db: pgwire.PgClient) { } export async function connectPgWire(type?: 'replication' | 'standard') { - const db = await pgwire.connectPgWire(TEST_CONNECTION_OPTIONS, { type }); + const db = await pgwire.connectPgWire(TEST_CONNECTION_OPTIONS, { type, applicationName: 'powersync-tests' }); return db; } diff --git a/packages/jpgwire/src/util.ts b/packages/jpgwire/src/util.ts index 731d7eb6e..625cc5a60 100644 --- a/packages/jpgwire/src/util.ts +++ b/packages/jpgwire/src/util.ts @@ -88,9 +88,12 @@ export function makeTlsOptions(options: PgWireConnectionOptions): false | tls.Co } } -export async function connectPgWire(config: PgWireConnectionOptions, options?: { type?: 'standard' | 'replication' }) { +export async function connectPgWire( + config: PgWireConnectionOptions, + options?: { type?: 'standard' | 'replication'; applicationName: string } +) { let connectionOptions: Mutable = { - application_name: 'PowerSync', + application_name: options?.applicationName ?? 'powersync', // tlsOptions below contains the original hostname hostname: config.resolved_ip ?? config.hostname, @@ -160,6 +163,8 @@ export interface PgPoolOptions { * Idle timeout in ms before a connection is closed. */ idleTimeout?: number | undefined; + + applicationName?: string; } /** @@ -172,7 +177,7 @@ export function connectPgWirePool(config: PgWireConnectionOptions, options?: PgP const maxSize = options?.maxSize ?? 5; let connectionOptions: Mutable = { - application_name: 'PowerSync', + application_name: options?.applicationName ?? 'powersync', // tlsOptions below contains the original hostname hostname: config.resolved_ip ?? config.hostname, diff --git a/packages/service-core/src/util/util-index.ts b/packages/service-core/src/util/util-index.ts index 8e19e2461..acdd09c4a 100644 --- a/packages/service-core/src/util/util-index.ts +++ b/packages/service-core/src/util/util-index.ts @@ -6,6 +6,7 @@ export * from './protocol-types.js'; export * from './secs.js'; export * from './utils.js'; export * from './checkpointing.js'; +export * from './version.js'; export * from './config.js'; export * from './config/compound-config-collector.js'; diff --git a/packages/service-core/src/util/version.ts b/packages/service-core/src/util/version.ts new file mode 100644 index 000000000..80f28a59e --- /dev/null +++ b/packages/service-core/src/util/version.ts @@ -0,0 +1,3 @@ +import pkg from '../../package.json' with { type: 'json' }; + +export const POWERSYNC_VERSION = pkg.version; diff --git a/service/src/util/version.ts b/service/src/util/version.ts index ecbdcebf8..538b40f5b 100644 --- a/service/src/util/version.ts +++ b/service/src/util/version.ts @@ -1,9 +1,9 @@ import { logger } from '@powersync/lib-services-framework'; -import pkg from '@powersync/service-core/package.json' with { type: 'json' }; +import { POWERSYNC_VERSION } from '@powersync/service-core'; export function logBooting(runner: string) { - const version = pkg.version; + const version = POWERSYNC_VERSION; const edition = 'Open Edition'; logger.info(`Booting PowerSync Service v${version}, ${runner}, ${edition}`, { version, edition, runner }); }