@seedcord/plugins 0.4.6 → 0.6.0-next.0

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/dist/index.cjs CHANGED
@@ -1,814 +1,1004 @@
1
- 'use strict';
2
-
3
- require('reflect-metadata');
4
- var chalk3 = require('chalk');
5
- var envapt = require('envapt');
6
- var mongoose = require('mongoose');
7
- var seedcord = require('seedcord');
8
- var pg = require('pg');
9
- var fs = require('fs');
10
- var path = require('path');
11
- var url = require('url');
12
- var util = require('util');
13
- var utils = require('@seedcord/utils');
14
- var kysely = require('kysely');
15
-
16
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
12
+ key = keys[i];
13
+ if (!__hasOwnProp.call(to, key) && key !== except) {
14
+ __defProp(to, key, {
15
+ get: ((k) => from[k]).bind(null, key),
16
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
17
+ });
18
+ }
19
+ }
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
24
+ value: mod,
25
+ enumerable: true
26
+ }) : target, mod));
17
27
 
18
- var chalk3__default = /*#__PURE__*/_interopDefault(chalk3);
19
- var mongoose__default = /*#__PURE__*/_interopDefault(mongoose);
20
- var path__default = /*#__PURE__*/_interopDefault(path);
28
+ //#endregion
29
+ require("reflect-metadata");
30
+ let _seedcord_services_internal = require("@seedcord/services/internal");
31
+ let chalk = require("chalk");
32
+ chalk = __toESM(chalk, 1);
33
+ let envapt = require("envapt");
34
+ let mongoose = require("mongoose");
35
+ mongoose = __toESM(mongoose, 1);
36
+ let seedcord = require("seedcord");
37
+ let kysely = require("kysely");
38
+ let pg = require("pg");
39
+ let node_fs = require("node:fs");
40
+ let node_path = require("node:path");
41
+ node_path = __toESM(node_path, 1);
42
+ let node_url = require("node:url");
43
+ let node_util = require("node:util");
44
+ let _seedcord_utils = require("@seedcord/utils");
45
+ let kysely_migration = require("kysely/migration");
46
+ let seedcord_internal = require("seedcord/internal");
47
+ let node_crypto = require("node:crypto");
48
+ let _seedcord_services = require("@seedcord/services");
21
49
 
22
- var __defProp = Object.defineProperty;
23
- var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
50
+ //#region src/mongo/decorators/RegisterMongoModel.ts
51
+ const ModelMetadataKey = Symbol("db:model");
52
+ /**
53
+ * Associates a Mongoose model with a database service
54
+ *
55
+ * Creates a Mongoose model from the decorated static schema property and stores it
56
+ * for service registration. The model becomes available as `this.model` in the service.
57
+ * Must be applied to a `public static schema` property in the service class.
58
+ *
59
+ * @typeParam TService - The service key type
60
+ * @param collection - Collection name for the Mongoose model
61
+ * @decorator
62
+ * @example
63
+ * ```typescript
64
+ * \@RegisterMongoService('users')
65
+ * export class Users extends MongoService<IUser> {
66
+ * \@RegisterMongoModel('users')
67
+ * public static schema = new mongoose.Schema<IUser>({
68
+ * username: { type: String, required: true, unique: true }
69
+ * });
70
+ * }
71
+ * ```
72
+ */
73
+ function RegisterMongoModel(collection) {
74
+ return (target, propertyKey) => {
75
+ const schema = target[propertyKey];
76
+ const name = String(collection);
77
+ const model = mongoose.default.model(name, schema);
78
+ Reflect.defineMetadata(ModelMetadataKey, model, target);
79
+ };
80
+ }
24
81
 
25
- // src/mongo/decorators/RegisterMongoService.ts
26
- var ServiceMetadataKey = Symbol("db:serviceKey");
82
+ //#endregion
83
+ //#region src/mongo/decorators/RegisterMongoService.ts
84
+ const ServiceMetadataKey = Symbol("db:serviceKey");
85
+ /**
86
+ * Registers a database service with a typed key
87
+ *
88
+ * Associates a service class with a key for dependency injection.
89
+ * The service becomes available via `core.db.services[key]`.
90
+ *
91
+ * @typeParam TService - The service key type
92
+ * @param key - Service key for registration and type-safe access
93
+ * @decorator
94
+ * @example
95
+ * ```typescript
96
+ * \@RegisterMongoService('users')
97
+ * export class Users<Doc extends IUser = IUser> extends MongoService<Doc> {
98
+ * // Some code
99
+ * }
100
+ * ```
101
+ */
27
102
  function RegisterMongoService(key) {
28
- return (ctor) => {
29
- Reflect.defineMetadata(ServiceMetadataKey, key, ctor);
30
- };
31
- }
32
- __name(RegisterMongoService, "RegisterMongoService");
33
- var ModelMetadataKey = Symbol("db:model");
34
- function RegisterMongoModel(collection) {
35
- return (target, propertyKey) => {
36
- const schema = target[propertyKey];
37
- const name = String(collection);
38
- const model = mongoose__default.default.model(name, schema);
39
- Reflect.defineMetadata(ModelMetadataKey, model, target);
40
- };
103
+ return (ctor) => {
104
+ Reflect.defineMetadata(ServiceMetadataKey, key, ctor);
105
+ };
41
106
  }
42
- __name(RegisterMongoModel, "RegisterMongoModel");
43
107
 
44
- // src/mongo/MongoService.ts
108
+ //#endregion
109
+ //#region src/mongo/MongoService.ts
110
+ /**
111
+ * Base class for MongoDB service layers
112
+ *
113
+ * Provides typed access to MongoDB collections through Mongoose models.
114
+ * Services are automatically registered with the Mongo plugin when instantiated.
115
+ *
116
+ * @typeParam Doc - The document type this service manages
117
+ * @example
118
+ * ```typescript
119
+ * \@RegisterMongoService('users')
120
+ * export class Users extends MongoService<IUser> {
121
+ * \@RegisterMongoModel('users')
122
+ * public static schema = new mongoose.Schema<IUser>({
123
+ * username: { type: String, required: true, unique: true }
124
+ * });
125
+ *
126
+ * // Custom methods here
127
+ * public async findByUsername(username: string) {
128
+ * return this.model.findOne({ username });
129
+ * }
130
+ * }
131
+ * ```
132
+ */
45
133
  var MongoService = class {
46
- static {
47
- __name(this, "MongoService");
48
- }
49
- db;
50
- core;
51
- model;
52
- constructor(db, core) {
53
- this.db = db;
54
- this.core = core;
55
- const ctor = this.constructor;
56
- const key = Reflect.getMetadata(ServiceMetadataKey, ctor);
57
- if (!key) {
58
- throw new seedcord.SeedcordError(seedcord.SeedcordErrorCode.PluginMongoServiceDecoratorMissing, [
59
- ctor.name
60
- ]);
61
- }
62
- const model = Reflect.getMetadata(ModelMetadataKey, ctor);
63
- if (!model) {
64
- throw new seedcord.SeedcordError(seedcord.SeedcordErrorCode.PluginMongoModelDecoratorMissing, [
65
- ctor.name
66
- ]);
67
- }
68
- this.model = model;
69
- db._register(key, this);
70
- }
134
+ db;
135
+ core;
136
+ model;
137
+ constructor(db, core) {
138
+ this.db = db;
139
+ this.core = core;
140
+ const ctor = this.constructor;
141
+ const key = Reflect.getMetadata(ServiceMetadataKey, ctor);
142
+ if (!key) throw new _seedcord_services_internal.SeedcordError(seedcord.SeedcordErrorCode.PluginMongoServiceDecoratorMissing, [ctor.name]);
143
+ const model = Reflect.getMetadata(ModelMetadataKey, ctor);
144
+ if (!model) throw new _seedcord_services_internal.SeedcordError(seedcord.SeedcordErrorCode.PluginMongoModelDecoratorMissing, [ctor.name]);
145
+ this.model = model;
146
+ db._register(key, this);
147
+ }
71
148
  };
72
149
 
73
- // src/mongo/Mongo.ts
150
+ //#endregion
151
+ //#region src/mongo/Mongo.ts
152
+ /**
153
+ * MongoDB integration plugin for Seedcord.
154
+ *
155
+ * Manages MongoDB connections, service loading, and provides type-safe
156
+ * access to database services through service registration decorators.
157
+ */
74
158
  var Mongo = class extends seedcord.Plugin {
75
- static {
76
- __name(this, "Mongo");
77
- }
78
- core;
79
- options;
80
- logger = new seedcord.Logger("Mongo");
81
- isInitialised = false;
82
- uri;
83
- /**
84
- * Map of all loaded services.
85
- * Keys come from `@RegisterMongoService('key')`
86
- */
87
- services = {};
88
- constructor(core, options) {
89
- super(core), this.core = core, this.options = options;
90
- this.uri = options.uri;
91
- this.core.shutdown.addTask(seedcord.ShutdownPhase.ExternalResources, "stop-database", async () => await this.stop(), this.options.timeout);
92
- }
93
- async init() {
94
- if (this.isInitialised) return;
95
- this.isInitialised = true;
96
- await this.connect();
97
- await this.loadServices();
98
- }
99
- async stop() {
100
- await this.disconnect();
101
- }
102
- async connect() {
103
- this.connection = await mongoose__default.default.connect(this.uri, {
104
- dbName: this.options.name,
105
- ...envapt.Envapter.isProduction && {
106
- tls: true,
107
- ssl: true
108
- },
109
- ...seedcord.keepDefined(this.options.connectionOptions ?? {})
110
- }).then((conn) => {
111
- this.logger.info(chalk3__default.default.green.bold(`Connected to MongoDB: ${chalk3__default.default.magenta.bold(conn.connection.name)}`));
112
- return conn;
113
- }).catch((err) => {
114
- throw new seedcord.SeedcordError(seedcord.SeedcordErrorCode.PluginMongoConnectionFailed, [
115
- this.options.name
116
- ], {
117
- cause: err
118
- });
119
- });
120
- }
121
- async disconnect() {
122
- await this.connection.disconnect().then(() => this.logger.info(chalk3__default.default.red.bold("Disconnected from MongoDB"))).catch((err) => this.logger.error(`Could not disconnect from MongoDB: ${err.message}`));
123
- }
124
- async loadServices() {
125
- const servicesDir = this.options.dir;
126
- this.logger.info(chalk3__default.default.bold(servicesDir));
127
- await seedcord.traverseDirectory(servicesDir, (_full, rel, mod) => {
128
- for (const Service of Object.values(mod)) {
129
- if (this.isServiceClass(Service)) {
130
- const instance = new Service(this, this.core);
131
- this.logger.info(`${chalk3__default.default.italic("Registered")} ${chalk3__default.default.bold.yellow(instance.constructor.name)} from ${chalk3__default.default.gray(rel)}`);
132
- }
133
- }
134
- }, this.logger);
135
- this.logger.info(`${chalk3__default.default.bold.green("Loaded")}: ${chalk3__default.default.magenta(Object.keys(this.services).length)} services`);
136
- }
137
- isServiceClass(obj) {
138
- return typeof obj === "function" && obj.prototype instanceof MongoService && Reflect.hasMetadata(ServiceMetadataKey, obj);
139
- }
140
- /**
141
- * Register hook used by decorated services.
142
- *
143
- * @internal
144
- */
145
- _register(key, instance) {
146
- this.services[key] = instance;
147
- }
148
- };
149
- var KpgDatabaseBootstrapper = class _KpgDatabaseBootstrapper {
150
- static {
151
- __name(this, "KpgDatabaseBootstrapper");
152
- }
153
- logger;
154
- static ADMIN_DB = "postgres";
155
- static DATABASE_EXISTS_SQL = 'SELECT EXISTS (SELECT 1 FROM pg_database WHERE datname = $1) AS "exists"';
156
- constructor(logger) {
157
- this.logger = logger;
158
- }
159
- resolveDatabaseName(config) {
160
- return _KpgDatabaseBootstrapper.parseDatabaseName(config);
161
- }
162
- resolveDatabaseFromPool(pool) {
163
- const config = {};
164
- const { options } = pool;
165
- if (typeof options.database === "string") {
166
- config.database = options.database;
167
- }
168
- if (typeof options.connectionString === "string") {
169
- config.connectionString = options.connectionString;
170
- }
171
- return this.resolveDatabaseName(config);
172
- }
173
- async ensure(baseConfig) {
174
- const targetDb = this.resolveDatabaseName(baseConfig);
175
- if (!targetDb) {
176
- this.logger.info(chalk3__default.default.gray("Skipping database existence check (no database specified)."));
177
- return;
178
- }
179
- if (targetDb === _KpgDatabaseBootstrapper.ADMIN_DB) {
180
- this.logger.info(chalk3__default.default.gray("Target database is postgres; skipping creation."));
181
- return;
182
- }
183
- const adminConfig = this.buildAdminConfig(baseConfig);
184
- if (!adminConfig) {
185
- this.logger.warn(`Unable to derive admin connection when ensuring database ${targetDb}`);
186
- return;
187
- }
188
- this.logger.info(chalk3__default.default.gray(`Ensuring database ${chalk3__default.default.yellow(targetDb)} exists...`));
189
- const adminPool = new pg.Pool(adminConfig);
190
- try {
191
- const exists = await this.databaseExists(adminPool, targetDb);
192
- if (exists) {
193
- this.logger.info(chalk3__default.default.gray(`Database ${chalk3__default.default.yellow(targetDb)} already exists.`));
194
- return;
195
- }
196
- await this.createDatabase(adminPool, targetDb);
197
- this.logger.info(chalk3__default.default.green(`Created database ${chalk3__default.default.bold(targetDb)}.`));
198
- } catch (error) {
199
- const err = error instanceof Error ? error : new Error(String(error));
200
- this.logger.error(`Failed to ensure database ${targetDb}: ${err.message}`);
201
- throw err;
202
- } finally {
203
- await adminPool.end();
204
- }
205
- }
206
- buildAdminConfig(baseConfig) {
207
- const adminConfig = {
208
- ...baseConfig
209
- };
210
- const { connectionString } = adminConfig;
211
- if (connectionString) {
212
- const connection = _KpgDatabaseBootstrapper.applyDatabaseToConnectionString(connectionString, _KpgDatabaseBootstrapper.ADMIN_DB);
213
- if (!connection) return null;
214
- adminConfig.connectionString = connection;
215
- }
216
- adminConfig.database = _KpgDatabaseBootstrapper.ADMIN_DB;
217
- return adminConfig;
218
- }
219
- async databaseExists(pool, database) {
220
- const client = await pool.connect();
221
- try {
222
- const { rows } = await client.query(_KpgDatabaseBootstrapper.DATABASE_EXISTS_SQL, [
223
- database
224
- ]);
225
- return Boolean(rows[0]?.exists);
226
- } finally {
227
- client.release();
228
- }
229
- }
230
- async createDatabase(pool, database) {
231
- const client = await pool.connect();
232
- try {
233
- const createSql = `CREATE DATABASE ${_KpgDatabaseBootstrapper.escapeIdentifier(database)}`;
234
- await client.query(createSql);
235
- } finally {
236
- client.release();
237
- }
238
- }
239
- static parseDatabaseName(config) {
240
- if (typeof config.database === "string" && config.database.trim().length > 0) {
241
- return config.database.trim();
242
- }
243
- const connectionString = config.connectionString;
244
- if (!connectionString) return null;
245
- try {
246
- const url = new URL(connectionString);
247
- const pathname = url.pathname.replace(/^\//, "");
248
- if (!pathname) return null;
249
- const [candidate] = pathname.split("/");
250
- return candidate ? decodeURIComponent(candidate) : null;
251
- } catch {
252
- return null;
253
- }
254
- }
255
- static applyDatabaseToConnectionString(connectionString, database) {
256
- try {
257
- const url = new URL(connectionString);
258
- url.pathname = `/${encodeURIComponent(database)}`;
259
- return url.toString();
260
- } catch {
261
- return null;
262
- }
263
- }
264
- static escapeIdentifier(identifier) {
265
- return `"${identifier.replace(/"/g, '""')}"`;
266
- }
267
- };
268
- var KpgMigrationManager = class {
269
- static {
270
- __name(this, "KpgMigrationManager");
271
- }
272
- ctx;
273
- constructor(ctx) {
274
- this.ctx = ctx;
275
- }
276
- async migrate(options) {
277
- const { target, direction = "latest", steps } = options ?? {};
278
- if (typeof target !== "undefined") {
279
- const label = target === kysely.NO_MIGRATIONS ? "NO_MIGRATIONS" : target;
280
- await this.runMigration((migrator) => migrator.migrateTo(target), `Migrating to ${chalk3__default.default.yellow(label)}...`);
281
- return;
282
- }
283
- switch (direction) {
284
- case "latest":
285
- await this.runMigration((migrator) => migrator.migrateToLatest());
286
- return;
287
- case "up":
288
- case "down": {
289
- const stepCount = steps ?? 1;
290
- if (!Number.isInteger(stepCount) || stepCount < 0) {
291
- throw new seedcord.SeedcordRangeError(seedcord.SeedcordErrorCode.PluginKpgInvalidStepCount);
292
- }
293
- if (stepCount === 0) {
294
- this.logMigrationResults([]);
295
- return;
296
- }
297
- const runner = direction === "up" ? (migrator) => migrator.migrateUp() : (migrator) => migrator.migrateDown();
298
- await this.runStepwise(stepCount, direction, runner);
299
- return;
300
- }
301
- default:
302
- throw new seedcord.SeedcordError(seedcord.SeedcordErrorCode.PluginKpgUnknownDirection, [
303
- direction
304
- ]);
305
- }
306
- }
307
- async migrateUp(options) {
308
- if (typeof options?.steps === "undefined") {
309
- await this.migrate({
310
- direction: "up"
311
- });
312
- return;
313
- }
314
- await this.migrate({
315
- direction: "up",
316
- steps: options.steps
317
- });
318
- }
319
- async migrateDown(options) {
320
- if (typeof options?.steps === "undefined") {
321
- await this.migrate({
322
- direction: "down"
323
- });
324
- return;
325
- }
326
- await this.migrate({
327
- direction: "down",
328
- steps: options.steps
329
- });
330
- }
331
- async listMigrations() {
332
- const migrator = await this.createMigrator();
333
- return migrator.getMigrations();
334
- }
335
- async runMigration(runner, runningMessage = "Running migrations...") {
336
- this.ctx.logger.info(chalk3__default.default.gray("Preparing migrations..."));
337
- const migrator = await this.createMigrator();
338
- this.ctx.logger.info(chalk3__default.default.gray(runningMessage));
339
- const { error, results } = await runner(migrator);
340
- this.logMigrationResults(results ?? []);
341
- if (error) {
342
- this.handleMigrationError(error);
343
- }
344
- }
345
- async runStepwise(steps, direction, runner) {
346
- this.ctx.logger.info(chalk3__default.default.gray("Preparing migrations..."));
347
- const migrator = await this.createMigrator();
348
- const directionLabel = direction === "up" ? "Running" : "Reverting";
349
- const countLabel = steps === 1 ? "one migration" : `${chalk3__default.default.yellow(String(steps))} migrations`;
350
- this.ctx.logger.info(chalk3__default.default.gray(`${directionLabel} ${countLabel}...`));
351
- const aggregated = [];
352
- let encounteredError;
353
- for (let index = 0; index < steps; index += 1) {
354
- const { error, results } = await runner(migrator);
355
- if (results?.length) {
356
- aggregated.push(...results);
357
- }
358
- if (error) {
359
- encounteredError = error;
360
- break;
361
- }
362
- if (!results?.length) {
363
- break;
364
- }
365
- }
366
- this.logMigrationResults(aggregated);
367
- if (encounteredError) {
368
- this.handleMigrationError(encounteredError);
369
- }
370
- }
371
- async createMigrator() {
372
- const provider = await this.getMigrationProvider();
373
- const { config } = this.ctx;
374
- return new kysely.Migrator({
375
- db: this.ctx.db,
376
- provider,
377
- allowUnorderedMigrations: config.allowUnorderedMigrations ?? false,
378
- ...utils.keepDefined(config, "migrationTableName", "migrationLockTableName", "migrationTableSchema")
379
- });
380
- }
381
- async getMigrationProvider() {
382
- const { path: target } = this.ctx.config;
383
- const resolvedTarget = Array.isArray(target) ? target.map((entry) => this.resolvePath(entry)) : this.resolvePath(target);
384
- if (Array.isArray(resolvedTarget)) {
385
- this.logMigrationFiles(resolvedTarget);
386
- return this.createModuleProvider(resolvedTarget);
387
- }
388
- let migrationStat;
389
- try {
390
- migrationStat = await fs.promises.stat(resolvedTarget);
391
- } catch {
392
- migrationStat = void 0;
393
- }
394
- if (migrationStat?.isDirectory()) {
395
- const directory = this.relativePath(resolvedTarget);
396
- this.ctx.logger.info(chalk3__default.default.gray(`Loading migrations directory ${chalk3__default.default.yellow(directory)}`));
397
- return new kysely.FileMigrationProvider({
398
- fs: fs.promises,
399
- path: path__default.default,
400
- migrationFolder: resolvedTarget
401
- });
402
- }
403
- if (migrationStat?.isFile() ?? true) {
404
- this.logMigrationFiles([
405
- resolvedTarget
406
- ]);
407
- return this.createModuleProvider([
408
- resolvedTarget
409
- ]);
410
- }
411
- const label = Array.isArray(target) ? target.join(", ") : target;
412
- throw new seedcord.SeedcordError(seedcord.SeedcordErrorCode.PluginKpgUnresolvedMigrationsPath, [
413
- label
414
- ]);
415
- }
416
- async createModuleProvider(files) {
417
- if (files.length === 0) {
418
- throw new seedcord.SeedcordError(seedcord.SeedcordErrorCode.PluginKpgNoMigrationFiles);
419
- }
420
- const comparator = this.ctx.config.nameComparator ?? ((nameA, nameB) => nameA.localeCompare(nameB));
421
- const entries = await Promise.all(files.map(async (filePath) => {
422
- const moduleUrl = url.pathToFileURL(filePath).href;
423
- const mod = await import(moduleUrl);
424
- if (!this.isMigrationModule(mod)) {
425
- throw new seedcord.SeedcordError(seedcord.SeedcordErrorCode.PluginKpgInvalidMigrationModule, [
426
- filePath
427
- ]);
428
- }
429
- const { up, down } = mod;
430
- const name = path__default.default.basename(filePath);
431
- const migration = {
432
- async up(db) {
433
- await up(db);
434
- },
435
- async down(db) {
436
- await down(db);
437
- }
438
- };
439
- return [
440
- name,
441
- migration
442
- ];
443
- }));
444
- const sorted = entries.sort(([a], [b]) => comparator(a, b));
445
- this.logPreparedMigrations(sorted);
446
- return {
447
- getMigrations: /* @__PURE__ */ __name(() => Promise.resolve(Object.fromEntries(sorted)), "getMigrations")
448
- };
449
- }
450
- logMigrationFiles(files) {
451
- if (!files.length) return;
452
- this.ctx.logger.info("Loading migration file(s):");
453
- for (const file of files) {
454
- this.ctx.logger.info(`\u2192 ${chalk3__default.default.yellow(this.relativePath(file))}`);
455
- }
456
- }
457
- logPreparedMigrations(entries) {
458
- if (!entries.length) return;
459
- this.ctx.logger.info("Prepared migrations:");
460
- for (const [name] of entries) {
461
- this.ctx.logger.info(`\u2192 ${chalk3__default.default.green(name)}`);
462
- }
463
- }
464
- logMigrationResults(results) {
465
- if (!results.length) {
466
- this.ctx.logger.info(chalk3__default.default.gray("No migrations executed."));
467
- return;
468
- }
469
- this.ctx.logger.info("Migration results:");
470
- for (const result of results) {
471
- if (result.status === "Success") {
472
- this.ctx.logger.info(`${chalk3__default.default.green("\u2713")} ${chalk3__default.default.bold(result.migrationName)}`);
473
- continue;
474
- }
475
- if (result.status === "Error") {
476
- this.ctx.logger.error(`${chalk3__default.default.red("\u2717")} ${chalk3__default.default.bold(result.migrationName)}`);
477
- continue;
478
- }
479
- this.ctx.logger.info(`${chalk3__default.default.yellow("\u2022")} ${chalk3__default.default.bold(result.migrationName)} ${chalk3__default.default.gray("(skipped)")}`);
480
- }
481
- }
482
- relativePath(filePath) {
483
- const relative = path__default.default.relative(this.ctx.baseDir, filePath);
484
- return relative.startsWith("..") ? filePath : relative;
485
- }
486
- resolvePath(target) {
487
- if (path__default.default.isAbsolute(target)) return target;
488
- return path__default.default.resolve(this.ctx.baseDir, target);
489
- }
490
- handleMigrationError(error) {
491
- const message = error instanceof Error ? error.message : util.inspect(error);
492
- this.ctx.logger.error(`Migration failure: ${message}`);
493
- if (error instanceof Error) {
494
- throw error;
495
- }
496
- throw new seedcord.SeedcordError(seedcord.SeedcordErrorCode.PluginKpgNonErrorFailure, [
497
- message
498
- ]);
499
- }
500
- isMigrationModule(value) {
501
- if (!value || typeof value !== "object") return false;
502
- if (!("up" in value) || !("down" in value)) return false;
503
- const { up, down } = value;
504
- return typeof up === "function" && typeof down === "function";
505
- }
159
+ core;
160
+ options;
161
+ logger = new seedcord.Logger("Mongo");
162
+ isInitialised = false;
163
+ servicesReady = false;
164
+ uri;
165
+ _services = {};
166
+ /**
167
+ * Map of all loaded services. Keys come from `@RegisterMongoService('key')`.
168
+ *
169
+ * @throws A {@link SeedcordError} if accessed before the plugin finishes initializing (e.g. from
170
+ * a plugin that starts in an earlier phase).
171
+ */
172
+ get services() {
173
+ if (!this.servicesReady) throw new _seedcord_services_internal.SeedcordError(seedcord.SeedcordErrorCode.PluginMongoServicesNotReady);
174
+ return this._services;
175
+ }
176
+ hmrHandler;
177
+ constructor(core, options) {
178
+ super(core);
179
+ this.core = core;
180
+ this.options = options;
181
+ this.uri = options.uri;
182
+ this.core.shutdown.addTask(seedcord.ShutdownPhase.ExternalResources, "stop-database", async () => await this.stop(), this.options.timeout);
183
+ if (!envapt.Envapter.isDevelopment) return;
184
+ this.hmrHandler = new seedcord.HmrModuleHandler({
185
+ handlersDir: this.options.dir,
186
+ isHandler: this.isServiceClass.bind(this),
187
+ registerHandler: this.initializeService.bind(this),
188
+ unregisterHandler: this.unregister.bind(this),
189
+ getArtifacts: this.getArtifacts.bind(this),
190
+ logger: this.logger,
191
+ name: "Mongo"
192
+ });
193
+ }
194
+ getArtifacts(ctor) {
195
+ const key = Reflect.getMetadata(ServiceMetadataKey, ctor);
196
+ const model = Reflect.getMetadata(ModelMetadataKey, ctor);
197
+ return {
198
+ ...key ? { key } : {},
199
+ ...model?.modelName ? { modelName: model.modelName } : {}
200
+ };
201
+ }
202
+ /** @internal For use in dev mode */
203
+ async onHmr(event) {
204
+ await this.hmrHandler?.handle(event);
205
+ }
206
+ async init() {
207
+ if (this.isInitialised) return;
208
+ this.isInitialised = true;
209
+ await this.connect();
210
+ await this.loadServices();
211
+ this.servicesReady = true;
212
+ }
213
+ async stop() {
214
+ await this.disconnect();
215
+ }
216
+ async connect() {
217
+ this.clearModels();
218
+ this.connection = await mongoose.default.connect(this.uri, {
219
+ dbName: this.options.name,
220
+ ...envapt.Envapter.isProduction && {
221
+ tls: true,
222
+ ssl: true
223
+ },
224
+ ...(0, seedcord.keepDefined)(this.options.connectionOptions ?? {})
225
+ }).then((conn) => {
226
+ this.logger.info(chalk.default.green.bold(`Connected to MongoDB: ${chalk.default.magenta.bold(conn.connection.name)}`));
227
+ return conn;
228
+ }).catch((err) => {
229
+ throw new _seedcord_services_internal.SeedcordError(seedcord.SeedcordErrorCode.PluginMongoConnectionFailed, [this.options.name], { cause: err });
230
+ });
231
+ }
232
+ clearModels() {
233
+ const modelNames = Object.keys(mongoose.default.models);
234
+ if (modelNames.length > 0) {
235
+ this.logger.debug(`Clearing ${modelNames.length} mongoose models`);
236
+ for (const name of modelNames) mongoose.default.deleteModel(name);
237
+ }
238
+ }
239
+ async disconnect() {
240
+ this.clearModels();
241
+ if (!this.connection) return;
242
+ await this.connection.disconnect().then(() => this.logger.info(chalk.default.red.bold("Disconnected from MongoDB"))).catch((err) => {
243
+ this.logger.error(`Could not disconnect from MongoDB: ${err.message}`);
244
+ throw new _seedcord_services_internal.SeedcordError(seedcord.SeedcordErrorCode.PluginMongoDisconnectFailed, { cause: err });
245
+ });
246
+ }
247
+ async loadServices() {
248
+ const servicesDir = this.options.dir;
249
+ this.logger.info(chalk.default.bold(servicesDir));
250
+ await (0, seedcord.traverseDirectory)(servicesDir, (fullPath, rel, mod) => {
251
+ for (const Service of Object.values(mod)) if (this.isServiceClass(Service)) {
252
+ this.initializeService(Service, rel);
253
+ this.hmrHandler?.trackHandler(fullPath, Service);
254
+ }
255
+ }, this.logger);
256
+ this.logger.utils.list([`${chalk.default.magenta(Object.keys(this._services).length)} services`], chalk.default.bold.green("Loaded"));
257
+ }
258
+ initializeService(Service, relativePath) {
259
+ const instance = new Service(this, this.core);
260
+ this.logger.utils.registration(instance.constructor.name, relativePath);
261
+ }
262
+ isServiceClass(obj) {
263
+ return typeof obj === "function" && obj.prototype instanceof MongoService && Reflect.hasMetadata(ServiceMetadataKey, obj);
264
+ }
265
+ /**
266
+ * Register hook used by decorated services.
267
+ *
268
+ * @internal
269
+ */
270
+ _register(key, instance) {
271
+ this._services[key] = instance;
272
+ }
273
+ unregister(Service, artifacts) {
274
+ const key = artifacts?.key ?? Reflect.getMetadata(ServiceMetadataKey, Service);
275
+ const modelName = artifacts?.modelName ?? Reflect.getMetadata(ModelMetadataKey, Service)?.modelName;
276
+ if (key && this._services[key]) delete this._services[key];
277
+ if (modelName) mongoose.default.deleteModel(modelName);
278
+ }
506
279
  };
507
280
 
508
- // src/kysely-pg/decorators/RegisterKpgService.ts
509
- var PgServiceMetadataKey = Symbol("db:pgServiceKey");
510
- var PgTableMetadataKey = Symbol("db:pgTable");
281
+ //#endregion
282
+ //#region src/kysely-pg/decorators/RegisterKpgService.ts
283
+ const PgServiceMetadataKey = Symbol("db:pgServiceKey");
284
+ const PgTableMetadataKey = Symbol("db:pgTable");
285
+ /**
286
+ *
287
+ * Registers a Kysely PG service with the specified key and options.
288
+ *
289
+ * Associates a service class with a key for dependency injection.
290
+ * The service becomes available via `core.db.services[key]`.
291
+ *
292
+ * @typeParam TKey - The service key type
293
+ * @param key - Service key for registration and type-safe access
294
+ * @param options - Additional registration options
295
+ * @decorator
296
+ * @example
297
+ * ```typescript
298
+ * \@RegisterKpgService('users', { table: 'app_users' })
299
+ * export class UsersService extends KpgService<{ users: IUser }, 'users'> {
300
+ * // Some code
301
+ * }
302
+ * ```
303
+ *
304
+ * @see {@link KpgService}
305
+ */
511
306
  function RegisterKpgService(key, options) {
512
- return (ctor) => {
513
- Reflect.defineMetadata(PgServiceMetadataKey, key, ctor);
514
- const tableName = options?.table ?? String(key);
515
- Reflect.defineMetadata(PgTableMetadataKey, tableName, ctor);
516
- };
307
+ return (ctor) => {
308
+ Reflect.defineMetadata(PgServiceMetadataKey, key, ctor);
309
+ const tableName = options?.table ?? String(key);
310
+ Reflect.defineMetadata(PgTableMetadataKey, tableName, ctor);
311
+ };
517
312
  }
518
- __name(RegisterKpgService, "RegisterKpgService");
519
313
 
520
- // src/kysely-pg/KpgService.ts
314
+ //#endregion
315
+ //#region src/kysely-pg/KpgService.ts
316
+ /**
317
+ * Base class for KyselyPg services.
318
+ *
319
+ * Provides a small, typed shim around the shared Kysely instance and ensures
320
+ * that subclasses have been decorated with `@RegisterKpgService`.
321
+ *
322
+ * @typeParam Database - The database shape used by Kysely (tables as keys).
323
+ * @typeParam TTable - The specific table key from `Database` this service works with.
324
+ *
325
+ * @example
326
+ * ```typescript
327
+ * \@RegisterKpgService('users')
328
+ * export class UsersService extends KpgService<ImportedDatabaseInterface, 'users'> {
329
+ * public async findById(id: string) {
330
+ * return this.entity
331
+ * .selectFrom(this.table)
332
+ * .selectAll().where('id', '=', id)
333
+ * .executeTakeFirst();
334
+ * }
335
+ * }
336
+ *
337
+ * // Usage inside handlers:
338
+ * const user = await this.core.db.services.users.findById('abc');
339
+ * ```
340
+ */
521
341
  var KpgService = class {
522
- static {
523
- __name(this, "KpgService");
524
- }
525
- kysely;
526
- core;
527
- table;
528
- constructor(kysely, core) {
529
- this.kysely = kysely;
530
- this.core = core;
531
- const ctor = this.constructor;
532
- const key = Reflect.getMetadata(PgServiceMetadataKey, ctor);
533
- if (!key) {
534
- throw new seedcord.SeedcordError(seedcord.SeedcordErrorCode.PluginKpgServiceDecoratorMissing, [
535
- ctor.name
536
- ]);
537
- }
538
- const table = Reflect.getMetadata(PgTableMetadataKey, ctor);
539
- if (!table) {
540
- throw new seedcord.SeedcordError(seedcord.SeedcordErrorCode.PluginKpgServiceTableMissing, [
541
- ctor.name
542
- ]);
543
- }
544
- this.table = table;
545
- this.kysely._register(key, this);
546
- }
547
- /**
548
- * Shared Kysely instance used to interact with the Postgres database.
549
- */
550
- get db() {
551
- return this.kysely.connection;
552
- }
342
+ kysely;
343
+ core;
344
+ table;
345
+ constructor(kysely, core) {
346
+ this.kysely = kysely;
347
+ this.core = core;
348
+ const ctor = this.constructor;
349
+ const key = Reflect.getMetadata(PgServiceMetadataKey, ctor);
350
+ if (!key) throw new _seedcord_services_internal.SeedcordError(seedcord.SeedcordErrorCode.PluginKpgServiceDecoratorMissing, [ctor.name]);
351
+ const table = Reflect.getMetadata(PgTableMetadataKey, ctor);
352
+ if (!table) throw new _seedcord_services_internal.SeedcordError(seedcord.SeedcordErrorCode.PluginKpgServiceTableMissing, [ctor.name]);
353
+ this.table = table;
354
+ this.kysely._register(key, this);
355
+ }
356
+ /**
357
+ * Shared Kysely instance used to interact with the Postgres database.
358
+ */
359
+ get db() {
360
+ return this.kysely.connection;
361
+ }
553
362
  };
363
+
364
+ //#endregion
365
+ //#region src/kysely-pg/KpgDatabaseBootstrapper.ts
366
+ /**
367
+ * Ensures the target Postgres database exists, creating it if missing.
368
+ */
369
+ var KpgDatabaseBootstrapper = class KpgDatabaseBootstrapper {
370
+ logger;
371
+ static ADMIN_DB = "postgres";
372
+ static DATABASE_EXISTS_SQL = "SELECT EXISTS (SELECT 1 FROM pg_database WHERE datname = $1) AS \"exists\"";
373
+ constructor(logger) {
374
+ this.logger = logger;
375
+ }
376
+ resolveDatabaseName(config) {
377
+ return KpgDatabaseBootstrapper.parseDatabaseName(config);
378
+ }
379
+ resolveDatabaseFromPool(pool) {
380
+ const config = {};
381
+ const { options } = pool;
382
+ if (typeof options.database === "string") config.database = options.database;
383
+ if (typeof options.connectionString === "string") config.connectionString = options.connectionString;
384
+ return this.resolveDatabaseName(config);
385
+ }
386
+ async ensure(baseConfig) {
387
+ const targetDb = this.resolveDatabaseName(baseConfig);
388
+ if (!targetDb) {
389
+ this.logger.info(chalk.default.gray("Skipping database existence check (no database specified)."));
390
+ return;
391
+ }
392
+ if (targetDb === KpgDatabaseBootstrapper.ADMIN_DB) {
393
+ this.logger.info(chalk.default.gray("Target database is postgres; skipping creation."));
394
+ return;
395
+ }
396
+ const adminConfig = this.buildAdminConfig(baseConfig);
397
+ if (!adminConfig) {
398
+ this.logger.warn(`Unable to derive admin connection when ensuring database ${targetDb}`);
399
+ return;
400
+ }
401
+ this.logger.info(chalk.default.gray(`Ensuring database ${chalk.default.yellow(targetDb)} exists...`));
402
+ const adminPool = new pg.Pool(adminConfig);
403
+ try {
404
+ if (await this.databaseExists(adminPool, targetDb)) {
405
+ this.logger.info(chalk.default.gray(`Database ${chalk.default.yellow(targetDb)} already exists.`));
406
+ return;
407
+ }
408
+ await this.createDatabase(adminPool, targetDb);
409
+ this.logger.info(chalk.default.green(`Created database ${chalk.default.bold(targetDb)}.`));
410
+ } catch (error) {
411
+ const err = error instanceof Error ? error : new Error(String(error));
412
+ this.logger.error(`Failed to ensure database ${targetDb}: ${err.message}`);
413
+ throw err;
414
+ } finally {
415
+ await adminPool.end();
416
+ }
417
+ }
418
+ buildAdminConfig(baseConfig) {
419
+ const adminConfig = { ...baseConfig };
420
+ const { connectionString } = adminConfig;
421
+ if (connectionString) {
422
+ const connection = KpgDatabaseBootstrapper.applyDatabaseToConnectionString(connectionString, KpgDatabaseBootstrapper.ADMIN_DB);
423
+ if (!connection) return null;
424
+ adminConfig.connectionString = connection;
425
+ }
426
+ adminConfig.database = KpgDatabaseBootstrapper.ADMIN_DB;
427
+ return adminConfig;
428
+ }
429
+ async databaseExists(pool, database) {
430
+ const client = await pool.connect();
431
+ try {
432
+ const { rows } = await client.query(KpgDatabaseBootstrapper.DATABASE_EXISTS_SQL, [database]);
433
+ return Boolean(rows[0]?.exists);
434
+ } finally {
435
+ client.release();
436
+ }
437
+ }
438
+ async createDatabase(pool, database) {
439
+ const client = await pool.connect();
440
+ try {
441
+ const createSql = `CREATE DATABASE ${KpgDatabaseBootstrapper.escapeIdentifier(database)}`;
442
+ await client.query(createSql);
443
+ } finally {
444
+ client.release();
445
+ }
446
+ }
447
+ static parseDatabaseName(config) {
448
+ if (typeof config.database === "string" && config.database.trim().length > 0) return config.database.trim();
449
+ const connectionString = config.connectionString;
450
+ if (!connectionString) return null;
451
+ try {
452
+ const pathname = new URL(connectionString).pathname.replace(/^\//, "");
453
+ if (!pathname) return null;
454
+ const [candidate] = pathname.split("/");
455
+ return candidate ? decodeURIComponent(candidate) : null;
456
+ } catch {
457
+ return null;
458
+ }
459
+ }
460
+ static applyDatabaseToConnectionString(connectionString, database) {
461
+ try {
462
+ const url = new URL(connectionString);
463
+ url.pathname = `/${encodeURIComponent(database)}`;
464
+ return url.toString();
465
+ } catch {
466
+ return null;
467
+ }
468
+ }
469
+ static escapeIdentifier(identifier) {
470
+ return `"${identifier.replace(/"/g, "\"\"")}"`;
471
+ }
472
+ };
473
+
474
+ //#endregion
475
+ //#region src/kysely-pg/KpgMigrationManager.ts
476
+ /**
477
+ * Migration tooling for KyselyPg.
478
+ *
479
+ * @sealed
480
+ */
481
+ var KpgMigrationManager = class {
482
+ ctx;
483
+ constructor(ctx) {
484
+ this.ctx = ctx;
485
+ }
486
+ async migrate(options) {
487
+ const { target, direction = "latest", steps } = options ?? {};
488
+ if (typeof target !== "undefined") {
489
+ const label = target === kysely_migration.NO_MIGRATIONS ? "NO_MIGRATIONS" : target;
490
+ await this.runMigration((migrator) => migrator.migrateTo(target), `Migrating to ${chalk.default.yellow(label)}...`);
491
+ return;
492
+ }
493
+ switch (direction) {
494
+ case "latest":
495
+ await this.runMigration((migrator) => migrator.migrateToLatest());
496
+ return;
497
+ case "up":
498
+ case "down": {
499
+ const stepCount = steps ?? 1;
500
+ if (!Number.isInteger(stepCount) || stepCount < 0) throw new _seedcord_services_internal.SeedcordRangeError(seedcord.SeedcordErrorCode.PluginKpgInvalidStepCount);
501
+ if (stepCount === 0) {
502
+ this.logMigrationResults([]);
503
+ return;
504
+ }
505
+ const runner = direction === "up" ? (migrator) => migrator.migrateUp() : (migrator) => migrator.migrateDown();
506
+ await this.runStepwise(stepCount, direction, runner);
507
+ return;
508
+ }
509
+ default: throw new _seedcord_services_internal.SeedcordError(seedcord.SeedcordErrorCode.PluginKpgUnknownDirection, [direction]);
510
+ }
511
+ }
512
+ async migrateUp(options) {
513
+ if (typeof options?.steps === "undefined") {
514
+ await this.migrate({ direction: "up" });
515
+ return;
516
+ }
517
+ await this.migrate({
518
+ direction: "up",
519
+ steps: options.steps
520
+ });
521
+ }
522
+ async migrateDown(options) {
523
+ if (typeof options?.steps === "undefined") {
524
+ await this.migrate({ direction: "down" });
525
+ return;
526
+ }
527
+ await this.migrate({
528
+ direction: "down",
529
+ steps: options.steps
530
+ });
531
+ }
532
+ async listMigrations() {
533
+ return (await this.createMigrator()).getMigrations();
534
+ }
535
+ async runMigration(runner, runningMessage = "Running migrations...") {
536
+ this.ctx.logger.info(chalk.default.gray("Preparing migrations..."));
537
+ const migrator = await this.createMigrator();
538
+ this.ctx.logger.info(chalk.default.gray(runningMessage));
539
+ const { error, results } = await runner(migrator);
540
+ this.logMigrationResults(results ?? []);
541
+ if (error) this.handleMigrationError(error);
542
+ }
543
+ async runStepwise(steps, direction, runner) {
544
+ this.ctx.logger.info(chalk.default.gray("Preparing migrations..."));
545
+ const migrator = await this.createMigrator();
546
+ const directionLabel = direction === "up" ? "Running" : "Reverting";
547
+ const countLabel = steps === 1 ? "one migration" : `${chalk.default.yellow(String(steps))} migrations`;
548
+ this.ctx.logger.info(chalk.default.gray(`${directionLabel} ${countLabel}...`));
549
+ const aggregated = [];
550
+ let encounteredError;
551
+ for (let index = 0; index < steps; index += 1) {
552
+ const { error, results } = await runner(migrator);
553
+ if (results?.length) aggregated.push(...results);
554
+ if (error) {
555
+ encounteredError = error;
556
+ break;
557
+ }
558
+ if (!results?.length) break;
559
+ }
560
+ this.logMigrationResults(aggregated);
561
+ if (encounteredError) this.handleMigrationError(encounteredError);
562
+ }
563
+ async createMigrator() {
564
+ const provider = await this.getMigrationProvider();
565
+ const { config } = this.ctx;
566
+ return new kysely_migration.Migrator({
567
+ db: this.ctx.db,
568
+ provider,
569
+ allowUnorderedMigrations: config.allowUnorderedMigrations ?? false,
570
+ ...(0, _seedcord_utils.keepDefined)(config, "migrationTableName", "migrationLockTableName", "migrationTableSchema")
571
+ });
572
+ }
573
+ async getMigrationProvider() {
574
+ const { path: target } = this.ctx.config;
575
+ const resolvedTarget = Array.isArray(target) ? target.map((entry) => this.resolvePath(entry)) : this.resolvePath(target);
576
+ if (Array.isArray(resolvedTarget)) {
577
+ this.logMigrationFiles(resolvedTarget);
578
+ return this.createModuleProvider(resolvedTarget);
579
+ }
580
+ let migrationStat;
581
+ try {
582
+ migrationStat = await node_fs.promises.stat(resolvedTarget);
583
+ } catch {
584
+ migrationStat = void 0;
585
+ }
586
+ if (migrationStat?.isDirectory()) {
587
+ const directory = this.relativePath(resolvedTarget);
588
+ this.ctx.logger.info(chalk.default.gray(`Loading migrations directory ${chalk.default.yellow(directory)}`));
589
+ return new kysely_migration.FileMigrationProvider({
590
+ fs: node_fs.promises,
591
+ path: node_path.default,
592
+ migrationFolder: resolvedTarget
593
+ });
594
+ }
595
+ if (migrationStat?.isFile() ?? true) {
596
+ this.logMigrationFiles([resolvedTarget]);
597
+ return this.createModuleProvider([resolvedTarget]);
598
+ }
599
+ const label = Array.isArray(target) ? target.join(", ") : target;
600
+ throw new _seedcord_services_internal.SeedcordError(seedcord.SeedcordErrorCode.PluginKpgUnresolvedMigrationsPath, [label]);
601
+ }
602
+ async createModuleProvider(files) {
603
+ if (files.length === 0) throw new _seedcord_services_internal.SeedcordError(seedcord.SeedcordErrorCode.PluginKpgNoMigrationFiles);
604
+ const comparator = this.ctx.config.nameComparator ?? ((nameA, nameB) => nameA.localeCompare(nameB));
605
+ const sorted = (await Promise.all(files.map(async (filePath) => {
606
+ const mod = await import((0, node_url.pathToFileURL)(filePath).href);
607
+ if (!this.isMigrationModule(mod)) throw new _seedcord_services_internal.SeedcordError(seedcord.SeedcordErrorCode.PluginKpgInvalidMigrationModule, [filePath]);
608
+ const { up, down } = mod;
609
+ return [node_path.default.basename(filePath), {
610
+ async up(db) {
611
+ await up(db);
612
+ },
613
+ async down(db) {
614
+ await down(db);
615
+ }
616
+ }];
617
+ }))).sort(([a], [b]) => comparator(a, b));
618
+ this.logPreparedMigrations(sorted);
619
+ return { getMigrations: () => Promise.resolve(Object.fromEntries(sorted)) };
620
+ }
621
+ logMigrationFiles(files) {
622
+ if (!files.length) return;
623
+ this.ctx.logger.info("Loading migration file(s):");
624
+ for (const file of files) this.ctx.logger.utils.item(`${chalk.default.yellow(this.relativePath(file))}`);
625
+ }
626
+ logPreparedMigrations(entries) {
627
+ if (!entries.length) return;
628
+ this.ctx.logger.info("Prepared migrations:");
629
+ for (const [name] of entries) this.ctx.logger.utils.item(`${chalk.default.green(name)}`);
630
+ }
631
+ logMigrationResults(results) {
632
+ if (!results.length) {
633
+ this.ctx.logger.info(chalk.default.gray("No migrations executed."));
634
+ return;
635
+ }
636
+ this.ctx.logger.info("Migration results:");
637
+ for (const result of results) {
638
+ if (result.status === "Success") {
639
+ this.ctx.logger.info(`${chalk.default.green("✓")} ${chalk.default.bold(result.migrationName)}`);
640
+ continue;
641
+ }
642
+ if (result.status === "Error") {
643
+ this.ctx.logger.error(`${chalk.default.red("✗")} ${chalk.default.bold(result.migrationName)}`);
644
+ continue;
645
+ }
646
+ this.ctx.logger.info(`${chalk.default.yellow("•")} ${chalk.default.bold(result.migrationName)} ${chalk.default.gray("(skipped)")}`);
647
+ }
648
+ }
649
+ relativePath(filePath) {
650
+ const relative = node_path.default.relative(this.ctx.baseDir, filePath);
651
+ return relative.startsWith("..") ? filePath : relative;
652
+ }
653
+ resolvePath(target) {
654
+ if (node_path.default.isAbsolute(target)) return target;
655
+ return node_path.default.resolve(this.ctx.baseDir, target);
656
+ }
657
+ handleMigrationError(error) {
658
+ const message = error instanceof Error ? error.message : (0, node_util.inspect)(error);
659
+ this.ctx.logger.error(`Migration failure: ${message}`);
660
+ if (error instanceof Error) throw error;
661
+ throw new _seedcord_services_internal.SeedcordError(seedcord.SeedcordErrorCode.PluginKpgNonErrorFailure, [message]);
662
+ }
663
+ isMigrationModule(value) {
664
+ if (!value || typeof value !== "object") return false;
665
+ if (!("up" in value) || !("down" in value)) return false;
666
+ const { up, down } = value;
667
+ return typeof up === "function" && typeof down === "function";
668
+ }
669
+ };
670
+
671
+ //#endregion
672
+ //#region src/kysely-pg/KpgServiceRegistry.ts
673
+ /**
674
+ * Discovers and registers Postgres services for the plugin.
675
+ */
554
676
  var KpgServiceRegistry = class {
555
- static {
556
- __name(this, "KpgServiceRegistry");
557
- }
558
- plugin;
559
- core;
560
- logger;
561
- services = /* @__PURE__ */ Object.create(null);
562
- constructor(plugin, core, logger) {
563
- this.plugin = plugin;
564
- this.core = core;
565
- this.logger = logger;
566
- }
567
- get map() {
568
- return this.services;
569
- }
570
- register(key, instance) {
571
- this.services[key] = instance;
572
- }
573
- async loadFromDirectory(dir) {
574
- this.logger.info(chalk3__default.default.bold(dir));
575
- await seedcord.traverseDirectory(dir, (_full, rel, mod) => {
576
- for (const Service of Object.values(mod)) {
577
- if (this.isServiceClass(Service)) {
578
- const instance = new Service(this.plugin, this.core);
579
- this.logger.info(`${chalk3__default.default.italic("Registered")} ${chalk3__default.default.bold.yellow(instance.constructor.name)} from ${chalk3__default.default.gray(rel)}`);
580
- }
581
- }
582
- }, this.logger);
583
- this.logger.info(`${chalk3__default.default.bold.green("Loaded")}: ${chalk3__default.default.magenta(Object.keys(this.services).length)} services`);
584
- }
585
- isServiceClass(obj) {
586
- return typeof obj === "function" && obj.prototype instanceof KpgService && Reflect.hasMetadata(PgServiceMetadataKey, obj);
587
- }
677
+ plugin;
678
+ core;
679
+ logger;
680
+ services = Object.create(null);
681
+ constructor(plugin, core, logger) {
682
+ this.plugin = plugin;
683
+ this.core = core;
684
+ this.logger = logger;
685
+ }
686
+ get map() {
687
+ return this.services;
688
+ }
689
+ register(key, instance) {
690
+ this.services[key] = instance;
691
+ }
692
+ async loadFromDirectory(dir) {
693
+ this.logger.info(chalk.default.bold(dir));
694
+ await (0, seedcord.traverseDirectory)(dir, (fullPath, rel, mod) => {
695
+ for (const Service of Object.values(mod)) if (this.isServiceClass(Service)) {
696
+ this.initializeService(Service, rel);
697
+ this.plugin.trackServiceFile(fullPath, Service);
698
+ }
699
+ }, this.logger);
700
+ this.logger.utils.list([`${chalk.default.magenta(Object.keys(this.services).length)} services`], chalk.default.bold.green("Loaded"));
701
+ }
702
+ unregister(Service, artifacts) {
703
+ const key = artifacts?.key ?? Reflect.getMetadata(PgServiceMetadataKey, Service);
704
+ if (key && this.services[key]) delete this.services[key];
705
+ }
706
+ initializeService(Service, relativePath) {
707
+ const instance = new Service(this.plugin, this.core);
708
+ this.logger.utils.registration(instance.constructor.name, relativePath);
709
+ }
710
+ isServiceClass(obj) {
711
+ return typeof obj === "function" && obj.prototype instanceof KpgService && Reflect.hasMetadata(PgServiceMetadataKey, obj);
712
+ }
588
713
  };
714
+
715
+ //#endregion
716
+ //#region src/kysely-pg/KyselyPg.ts
717
+ /**
718
+ * Postgres plugin using Kysely.
719
+ *
720
+ * Handles setting up the connection pool, applying migrations, and
721
+ * registering decorated services so they can be resolved from the core.
722
+ */
589
723
  var KyselyPg = class extends seedcord.Plugin {
590
- static {
591
- __name(this, "KyselyPg");
592
- }
593
- core;
594
- options;
595
- logger = new seedcord.Logger("KyselyPg");
596
- isInitialised = false;
597
- pool = null;
598
- migrationManager = null;
599
- serviceRegistry;
600
- databaseBootstrapper;
601
- databaseName = null;
602
- /**
603
- * Map of all services registered with the plugin, keyed by their decorator name.
604
- */
605
- get services() {
606
- return this.serviceRegistry.map;
607
- }
608
- constructor(core, options) {
609
- super(core), this.core = core, this.options = options;
610
- this.serviceRegistry = new KpgServiceRegistry(this, core, this.logger);
611
- this.databaseBootstrapper = new KpgDatabaseBootstrapper(this.logger);
612
- this.core.shutdown.addTask(seedcord.ShutdownPhase.ExternalResources, "stop-kyselypg", async () => await this.stop(), this.options.timeout);
613
- }
614
- /**
615
- * Connects to Postgres, runs any startup migrations, and loads decorated services.
616
- *
617
- * Safe to call multiple times; subsequent calls exit early.
618
- */
619
- async init() {
620
- if (this.isInitialised) return;
621
- this.isInitialised = true;
622
- await this.connect();
623
- const startupConfig = this.options.migrations.onStartup;
624
- if (startupConfig !== false) {
625
- if (startupConfig && typeof startupConfig !== "boolean") {
626
- await this.migrate(startupConfig);
627
- } else {
628
- await this.migrate();
629
- }
630
- }
631
- await this.serviceRegistry.loadFromDirectory(this.options.dir);
632
- }
633
- /**
634
- * Tears down the connection pool and clears the migration manager reference.
635
- */
636
- async stop() {
637
- await this.disconnect();
638
- }
639
- async connect() {
640
- const pool = await this.resolvePool();
641
- this.pool = pool;
642
- this.registerOnConnectStatements(pool, this.options.onConnectSQL);
643
- try {
644
- await this.testPoolConnection(pool);
645
- this.connection = new kysely.Kysely({
646
- dialect: new kysely.PostgresDialect({
647
- pool
648
- }),
649
- ...seedcord.keepDefined(this.options.kysely ?? {})
650
- });
651
- this.migrationManager = new KpgMigrationManager({
652
- db: this.connection,
653
- logger: this.logger,
654
- config: this.options.migrations,
655
- baseDir: process.cwd()
656
- });
657
- const dbLabel = this.databaseName ?? "unknown";
658
- this.logger.info(`Connected to Postgres database ${chalk3__default.default.bold.magenta(dbLabel)}`);
659
- } catch (err) {
660
- const error = err instanceof Error ? err : new Error(String(err));
661
- this.logger.error(`Could not connect to Postgres: ${error.message}`);
662
- throw error;
663
- }
664
- }
665
- async disconnect() {
666
- const pool = this.pool;
667
- if (!pool) return;
668
- this.pool = null;
669
- this.migrationManager = null;
670
- this.logger.info(chalk3__default.default.gray("Closing Postgres pool."));
671
- await pool.end().catch((err) => {
672
- this.logger.error(`Could not close pg pool: ${err.message}`);
673
- });
674
- this.logger.info(chalk3__default.default.red.bold("Disconnected from Postgres"));
675
- }
676
- /**
677
- * Runs migrations using the supplied options or defaults to `latest`.
678
- *
679
- * @param options - Target migration or direction overrides
680
- */
681
- async migrate(options) {
682
- await this.getMigrationManager().migrate(options);
683
- }
684
- /**
685
- * Runs a single upwards migration step unless a custom count is provided.
686
- *
687
- * @param options - Optional configuration for step-based execution
688
- */
689
- async migrateUp(options) {
690
- await this.getMigrationManager().migrateUp(options);
691
- }
692
- /**
693
- * Runs a single downwards migration step unless a custom count is provided.
694
- *
695
- * @param options - Optional configuration for step-based execution
696
- */
697
- async migrateDown(options) {
698
- await this.getMigrationManager().migrateDown(options);
699
- }
700
- /**
701
- * Lists every migration the manager knows about along with its execution state.
702
- */
703
- listMigrations() {
704
- return this.getMigrationManager().listMigrations();
705
- }
706
- /**
707
- * Lists unapplied migrations.
708
- */
709
- async listPendingMigrations() {
710
- const all = await this.listMigrations();
711
- return all.filter((m) => !m.executedAt);
712
- }
713
- getMigrationManager() {
714
- if (this.migrationManager) return this.migrationManager;
715
- const manager = new KpgMigrationManager({
716
- db: this.connection,
717
- logger: this.logger,
718
- config: this.options.migrations,
719
- baseDir: process.cwd()
720
- });
721
- this.migrationManager = manager;
722
- return manager;
723
- }
724
- /**
725
- * Register hook used by decorated services.
726
- *
727
- * @internal
728
- */
729
- _register(key, instance) {
730
- this.serviceRegistry.register(key, instance);
731
- }
732
- async resolvePool() {
733
- const { pool: providedPool, connectionString } = this.options;
734
- if (providedPool instanceof pg.Pool) {
735
- this.logger.info(chalk3__default.default.gray("Reusing provided Postgres pool instance."));
736
- this.databaseName = this.databaseBootstrapper.resolveDatabaseFromPool(providedPool);
737
- return providedPool;
738
- }
739
- const baseConfig = this.createPoolConfig(providedPool, connectionString);
740
- await this.databaseBootstrapper.ensure(baseConfig);
741
- this.databaseName = this.databaseBootstrapper.resolveDatabaseName(baseConfig);
742
- this.logger.info(chalk3__default.default.gray("Creating new Postgres pool."));
743
- return new pg.Pool(baseConfig);
744
- }
745
- createPoolConfig(poolConfig, connectionString) {
746
- const config = poolConfig ? {
747
- ...poolConfig
748
- } : {};
749
- if (connectionString) {
750
- config.connectionString = connectionString;
751
- }
752
- if (this.options.forceInsecureSSL) {
753
- config.ssl = {
754
- rejectUnauthorized: false
755
- };
756
- }
757
- return config;
758
- }
759
- registerOnConnectStatements(pool, statements) {
760
- if (!statements?.length) return;
761
- const queuedStatements = [
762
- ...statements
763
- ];
764
- pool.on("connect", (client) => {
765
- void (async () => {
766
- for (const sql of queuedStatements) {
767
- await client.query(sql);
768
- }
769
- })();
770
- });
771
- }
772
- async testPoolConnection(pool) {
773
- const client = await pool.connect();
774
- client.release();
775
- }
724
+ core;
725
+ options;
726
+ logger = new seedcord.Logger("KyselyPg");
727
+ isInitialised = false;
728
+ servicesReady = false;
729
+ pool = null;
730
+ onConnectHandler = null;
731
+ migrationManager = null;
732
+ serviceRegistry;
733
+ databaseBootstrapper;
734
+ databaseName = null;
735
+ hmrHandler;
736
+ /**
737
+ * Map of all services registered with the plugin, keyed by their decorator name.
738
+ */
739
+ get services() {
740
+ if (!this.servicesReady) throw new _seedcord_services_internal.SeedcordError(seedcord.SeedcordErrorCode.PluginKpgServicesNotReady);
741
+ return this.serviceRegistry.map;
742
+ }
743
+ constructor(core, options) {
744
+ super(core);
745
+ this.core = core;
746
+ this.options = options;
747
+ this.serviceRegistry = new KpgServiceRegistry(this, core, this.logger);
748
+ this.databaseBootstrapper = new KpgDatabaseBootstrapper(this.logger);
749
+ this.core.shutdown.addTask(seedcord.ShutdownPhase.ExternalResources, "stop-kyselypg", async () => await this.stop(), this.options.timeout);
750
+ if (!envapt.Envapter.isDevelopment) return;
751
+ const relPaths = this.options.migrations.path;
752
+ super.registerCriticalFiles(Array.isArray(relPaths) ? relPaths : [relPaths]);
753
+ this.hmrHandler = new seedcord.HmrModuleHandler({
754
+ handlersDir: this.options.dir,
755
+ isHandler: this.serviceRegistry.isServiceClass.bind(this.serviceRegistry),
756
+ registerHandler: this.serviceRegistry.initializeService.bind(this.serviceRegistry),
757
+ unregisterHandler: this.serviceRegistry.unregister.bind(this.serviceRegistry),
758
+ getArtifacts: this.getArtifacts.bind(this),
759
+ logger: this.logger,
760
+ name: "KyselyPg"
761
+ });
762
+ }
763
+ getArtifacts(ctor) {
764
+ const key = Reflect.getMetadata(PgServiceMetadataKey, ctor);
765
+ return key ? { key } : {};
766
+ }
767
+ /** @internal For use in dev mode */
768
+ async onHmr(event) {
769
+ await this.hmrHandler?.handle(event);
770
+ }
771
+ /**
772
+ * Connects to Postgres, runs any startup migrations, and loads decorated services.
773
+ *
774
+ * Safe to call multiple times; subsequent calls exit early.
775
+ */
776
+ async init() {
777
+ if (this.isInitialised) return;
778
+ this.isInitialised = true;
779
+ await this.connect();
780
+ const startupConfig = this.options.migrations.onStartup;
781
+ if (startupConfig !== false) if (startupConfig && typeof startupConfig !== "boolean") await this.migrate(startupConfig);
782
+ else await this.migrate();
783
+ await this.serviceRegistry.loadFromDirectory(this.options.dir);
784
+ this.servicesReady = true;
785
+ }
786
+ /**
787
+ * Tears down the connection pool and clears the migration manager reference.
788
+ */
789
+ async stop() {
790
+ await this.disconnect();
791
+ }
792
+ async connect() {
793
+ const pool = await this.resolvePool();
794
+ this.pool = pool;
795
+ this.registerOnConnectStatements(pool, this.options.onConnectSQL);
796
+ try {
797
+ await this.testPoolConnection(pool);
798
+ this.connection = new kysely.Kysely({
799
+ dialect: new kysely.PostgresDialect({ pool }),
800
+ ...(0, seedcord.keepDefined)(this.options.kysely ?? {})
801
+ });
802
+ this.migrationManager = new KpgMigrationManager({
803
+ db: this.connection,
804
+ logger: this.logger,
805
+ config: this.options.migrations,
806
+ baseDir: process.cwd()
807
+ });
808
+ const dbLabel = this.databaseName ?? "unknown";
809
+ this.logger.info(`Connected to Postgres database ${chalk.default.bold.magenta(dbLabel)}`);
810
+ } catch (err) {
811
+ const error = err instanceof Error ? err : new Error(String(err));
812
+ this.logger.error(`Could not connect to Postgres: ${error.message}`);
813
+ throw error;
814
+ }
815
+ }
816
+ async disconnect() {
817
+ const pool = this.pool;
818
+ if (!pool) return;
819
+ if (this.onConnectHandler) {
820
+ pool.removeListener("connect", this.onConnectHandler);
821
+ this.onConnectHandler = null;
822
+ }
823
+ this.pool = null;
824
+ this.migrationManager = null;
825
+ this.logger.info(chalk.default.gray("Closing Postgres pool."));
826
+ await pool.end().catch((err) => {
827
+ this.logger.error(`Could not close pg pool: ${err.message}`);
828
+ throw new _seedcord_services_internal.SeedcordError(seedcord.SeedcordErrorCode.PluginKpgDisconnectFailed, { cause: err });
829
+ });
830
+ this.logger.info(chalk.default.red.bold("Disconnected from Postgres"));
831
+ }
832
+ /**
833
+ * Runs migrations using the supplied options or defaults to `latest`.
834
+ *
835
+ * @param options - Target migration or direction overrides
836
+ */
837
+ async migrate(options) {
838
+ await this.getMigrationManager().migrate(options);
839
+ }
840
+ /**
841
+ * Runs a single upwards migration step unless a custom count is provided.
842
+ *
843
+ * @param options - Optional configuration for step-based execution
844
+ */
845
+ async migrateUp(options) {
846
+ await this.getMigrationManager().migrateUp(options);
847
+ }
848
+ /**
849
+ * Runs a single downwards migration step unless a custom count is provided.
850
+ *
851
+ * @param options - Optional configuration for step-based execution
852
+ */
853
+ async migrateDown(options) {
854
+ await this.getMigrationManager().migrateDown(options);
855
+ }
856
+ /**
857
+ * Lists every migration registered with the manager along with its execution state.
858
+ */
859
+ listMigrations() {
860
+ return this.getMigrationManager().listMigrations();
861
+ }
862
+ /**
863
+ * Lists unapplied migrations.
864
+ */
865
+ async listPendingMigrations() {
866
+ return (await this.listMigrations()).filter((m) => !m.executedAt);
867
+ }
868
+ getMigrationManager() {
869
+ if (this.migrationManager) return this.migrationManager;
870
+ const manager = new KpgMigrationManager({
871
+ db: this.connection,
872
+ logger: this.logger,
873
+ config: this.options.migrations,
874
+ baseDir: process.cwd()
875
+ });
876
+ this.migrationManager = manager;
877
+ return manager;
878
+ }
879
+ /**
880
+ * Register hook used by decorated services.
881
+ *
882
+ * @internal
883
+ */
884
+ _register(key, instance) {
885
+ this.serviceRegistry.register(key, instance);
886
+ }
887
+ /**
888
+ * Tracks a service file with the HMR handler so dev reloads can swap it. No-op outside dev.
889
+ *
890
+ * @internal Lets {@link KpgServiceRegistry} reach the dev-only HMR handler without poking a
891
+ * private field.
892
+ */
893
+ trackServiceFile(filePath, ctor) {
894
+ this.hmrHandler?.trackHandler(filePath, ctor);
895
+ }
896
+ async resolvePool() {
897
+ const { pool: providedPool, connectionString } = this.options;
898
+ if (providedPool instanceof pg.Pool) {
899
+ this.logger.info(chalk.default.gray("Reusing provided Postgres pool instance."));
900
+ this.databaseName = this.databaseBootstrapper.resolveDatabaseFromPool(providedPool);
901
+ return providedPool;
902
+ }
903
+ const baseConfig = this.createPoolConfig(providedPool, connectionString);
904
+ await this.databaseBootstrapper.ensure(baseConfig);
905
+ this.databaseName = this.databaseBootstrapper.resolveDatabaseName(baseConfig);
906
+ this.logger.info(chalk.default.gray("Creating new Postgres pool."));
907
+ return new pg.Pool(baseConfig);
908
+ }
909
+ createPoolConfig(poolConfig, connectionString) {
910
+ const config = poolConfig ? { ...poolConfig } : {};
911
+ if (connectionString) config.connectionString = connectionString;
912
+ if (this.options.forceInsecureSSL) config.ssl = { rejectUnauthorized: false };
913
+ return config;
914
+ }
915
+ registerOnConnectStatements(pool, statements) {
916
+ if (!statements?.length) return;
917
+ const queuedStatements = [...statements];
918
+ const handler = (client) => {
919
+ (async () => {
920
+ for (const sql of queuedStatements) await client.query(sql);
921
+ })().catch((err) => this.logger.error("Failed to run onConnect SQL", err));
922
+ };
923
+ this.onConnectHandler = handler;
924
+ pool.on("connect", handler);
925
+ }
926
+ async testPoolConnection(pool) {
927
+ (await pool.connect()).release();
928
+ }
776
929
  };
930
+
931
+ //#endregion
932
+ //#region src/shared/throwDatabaseError.ts
933
+ const logger = new _seedcord_services.Logger("DatabaseError");
934
+ /**
935
+ * Wraps an unknown error in a {@link DatabaseError} with a generated UUID for correlation, then
936
+ * throws it. Used by `@WrapDatabaseError` to normalize raw database failures.
937
+ *
938
+ * @param error - The original error or value
939
+ * @param message - Fallback message used when `error` is not an `Error`
940
+ * @throws A {@link DatabaseError} carrying the message and a fresh UUID
941
+ *
942
+ * @internal
943
+ */
944
+ function throwDatabaseError(error, message) {
945
+ logger.error("Throwing DatabaseError", error instanceof Error ? error.name : String(error));
946
+ throw new seedcord_internal.DatabaseError(error instanceof Error ? error.message : message, (0, node_crypto.randomUUID)());
947
+ }
948
+
949
+ //#endregion
950
+ //#region src/shared/WrapDatabaseError.ts
951
+ /**
952
+ * Catches and wraps database operation errors.
953
+ *
954
+ * Wraps non-CustomError exceptions in DatabaseError instances
955
+ * with UUID tracking. Should be applied to database service methods.
956
+ *
957
+ * @typeParam TypeReturn - The return type of the decorated method
958
+ * @param errorMessage - Message to include when wrapping errors
959
+ * @decorator
960
+ * @example
961
+ * ```typescript
962
+ * class UserService extends MongoService<IUser> {
963
+ * \@WrapDatabaseError('Failed to find user')
964
+ * async findById(id: string) {
965
+ * return this.model.findById(id);
966
+ * }
967
+ * }
968
+ * ```
969
+ *
970
+ * @see {@link DatabaseError}
971
+ * @see {@link CustomError}
972
+ * @see {@link MongoService}
973
+ */
777
974
  function WrapDatabaseError(errorMessage) {
778
- return function(_target, _propertyKey, descriptor) {
779
- const originalMethod = descriptor.value;
780
- descriptor.value = async function(...args) {
781
- if (!originalMethod) {
782
- throw new seedcord.SeedcordError(seedcord.SeedcordErrorCode.DecoratorMethodNotFound);
783
- }
784
- try {
785
- return await originalMethod.apply(this, args);
786
- } catch (error) {
787
- if (!(error instanceof seedcord.CustomError)) {
788
- seedcord.throwCustomError(error, errorMessage, seedcord.DatabaseError);
789
- } else {
790
- throw error;
791
- }
792
- }
793
- };
794
- };
975
+ return function(_target, _propertyKey, descriptor) {
976
+ const originalMethod = descriptor.value;
977
+ descriptor.value = async function(...args) {
978
+ if (!originalMethod) throw new seedcord_internal.SeedcordError(seedcord.SeedcordErrorCode.DecoratorMethodNotFound);
979
+ try {
980
+ return await originalMethod.apply(this, args);
981
+ } catch (error) {
982
+ if (!(error instanceof seedcord.CustomError)) throwDatabaseError(error, errorMessage);
983
+ else throw error;
984
+ }
985
+ };
986
+ };
795
987
  }
796
- __name(WrapDatabaseError, "WrapDatabaseError");
797
988
 
798
- exports.KpgDatabaseBootstrapper = KpgDatabaseBootstrapper;
799
- exports.KpgMigrationManager = KpgMigrationManager;
989
+ //#endregion
990
+ //#region src/index.ts
991
+ /** Package version */
992
+ const version = "0.6.0-next.0";
993
+
994
+ //#endregion
800
995
  exports.KpgService = KpgService;
801
- exports.KpgServiceRegistry = KpgServiceRegistry;
802
996
  exports.KyselyPg = KyselyPg;
803
- exports.ModelMetadataKey = ModelMetadataKey;
804
997
  exports.Mongo = Mongo;
805
998
  exports.MongoService = MongoService;
806
- exports.PgServiceMetadataKey = PgServiceMetadataKey;
807
- exports.PgTableMetadataKey = PgTableMetadataKey;
808
999
  exports.RegisterKpgService = RegisterKpgService;
809
1000
  exports.RegisterMongoModel = RegisterMongoModel;
810
1001
  exports.RegisterMongoService = RegisterMongoService;
811
- exports.ServiceMetadataKey = ServiceMetadataKey;
812
1002
  exports.WrapDatabaseError = WrapDatabaseError;
813
- //# sourceMappingURL=index.cjs.map
1003
+ exports.version = version;
814
1004
  //# sourceMappingURL=index.cjs.map