@seedcord/plugins 0.3.3 → 0.4.1

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.d.ts CHANGED
@@ -1,59 +1,45 @@
1
- import { Plugin, Core, Logger } from 'seedcord';
1
+ import { Plugin, Core, Logger, TypedOmit, TypedConstructor as TypedConstructor$1 } from 'seedcord';
2
+ import mongoose, { Mongoose } from 'mongoose';
2
3
  import { TypedConstructor } from '@seedcord/types';
3
- import mongoose from 'mongoose';
4
4
  import { Constructor } from 'type-fest';
5
+ import { PoolConfig, Pool } from 'pg';
6
+ import { KyselyConfig, Kysely, NO_MIGRATIONS, Migration, MigrationInfo } from 'kysely';
7
+
8
+ /**
9
+ * Configuration options for MongoDB connection and service loading.
10
+ */
11
+ interface MongoOptions {
12
+ /** Directory path containing database service classes */
13
+ dir: string;
14
+ /** MongoDB connection URI */
15
+ uri: string;
16
+ /** Database name to use */
17
+ name: string;
18
+ }
5
19
 
6
20
  /**
7
21
  * Registry of available database services.
8
22
  *
9
23
  * This interface can be augmented via declaration merging to add
10
- * type-safe service definitions when using the \@DatabaseService and \@DatabaseModel decorator.
24
+ * type-safe service definitions when using the \@RegisterMongoService and \@RegisterMongoModel decorator.
11
25
  *
12
26
  * @example
13
27
  * ```typescript
14
- * declare module 'seedcord' {
15
- * interface Services {
16
- * 'user': UserService;
17
- * 'guild': GuildService;
28
+ * declare module '@seedcord/plugins' {
29
+ * interface MongoServices {
30
+ * 'user': Users;
31
+ * 'guild': Guilds;
18
32
  * }
19
33
  * }
20
34
  * ```
21
35
  */
22
- interface Services {
36
+ interface MongoServices {
23
37
  }
24
38
  /**
25
39
  * Helper type to extract service keys from the Services interface.
26
40
  */
27
- type ServiceKeys = keyof Services;
28
-
29
- /**
30
- * Basic document interface with MongoDB ObjectId field.
31
- *
32
- * Represents the minimal structure of a MongoDB document
33
- * with the required `_id` field.
34
- */
35
- interface IDocument {
36
- /** MongoDB document identifier */
37
- _id: string;
38
- }
39
- /**
40
- * Helper type to extract the type of a document that extends IDocument.
41
- *
42
- * @typeParam Doc - The document type extending IDocument
43
- */
44
- type TypeOfIDocument<Doc extends IDocument = IDocument> = Doc;
41
+ type MongoServiceKeys = keyof MongoServices;
45
42
 
46
- /**
47
- * Configuration options for MongoDB connection and service loading.
48
- */
49
- interface MongoOptions {
50
- /** Directory path containing database service classes */
51
- dir: string;
52
- /** MongoDB connection URI */
53
- uri: string;
54
- /** Database name to use */
55
- name: string;
56
- }
57
43
  /**
58
44
  * MongoDB integration plugin for Seedcord.
59
45
  *
@@ -68,9 +54,11 @@ declare class Mongo extends Plugin {
68
54
  private readonly uri;
69
55
  /**
70
56
  * Map of all loaded services.
71
- * Keys come from `@DatabaseService('key')`
57
+ * Keys come from `@RegisterMongoService('key')`
72
58
  */
73
- readonly services: Services;
59
+ readonly services: MongoServices;
60
+ /** Exposed Mongoose instance once `init` completes. */
61
+ connection: Mongoose;
74
62
  constructor(core: Core, options: MongoOptions);
75
63
  init(): Promise<void>;
76
64
  stop(): Promise<void>;
@@ -78,9 +66,31 @@ declare class Mongo extends Plugin {
78
66
  private disconnect;
79
67
  private loadServices;
80
68
  private isServiceClass;
81
- _register<SKey extends keyof Services>(key: SKey, instance: Services[SKey]): void;
69
+ /**
70
+ * Register hook used by decorated services.
71
+ *
72
+ * @internal
73
+ */
74
+ _register<SKey extends keyof MongoServices>(key: SKey, instance: MongoServices[SKey]): void;
82
75
  }
83
76
 
77
+ /**
78
+ * Basic document interface with MongoDB ObjectId field.
79
+ *
80
+ * Represents the minimal structure of a MongoDB document
81
+ * with the required `_id` field.
82
+ */
83
+ interface MongoDocument {
84
+ /** MongoDB document identifier */
85
+ _id: string;
86
+ }
87
+ /**
88
+ * Helper type to extract the type of a document that extends MongoDocument.
89
+ *
90
+ * @typeParam Doc - The document type extending MongoDocument
91
+ */
92
+ type MongoDocumentType<Doc extends MongoDocument = MongoDocument> = Doc;
93
+
84
94
  /**
85
95
  * Base class for MongoDB service layers
86
96
  *
@@ -90,9 +100,9 @@ declare class Mongo extends Plugin {
90
100
  * @typeParam Doc - The document type this service manages
91
101
  * @example
92
102
  * ```typescript
93
- * \@DatabaseService('users')
103
+ * \@RegisterMongoService('users')
94
104
  * export class Users extends MongoService<IUser> {
95
- * \@DatabaseModel('users')
105
+ * \@RegisterMongoModel('users')
96
106
  * public static schema = new mongoose.Schema<IUser>({
97
107
  * username: { type: String, required: true, unique: true }
98
108
  * });
@@ -104,36 +114,15 @@ declare class Mongo extends Plugin {
104
114
  * }
105
115
  * ```
106
116
  */
107
- declare abstract class MongoService<Doc extends IDocument = IDocument> {
117
+ declare abstract class MongoService<Doc extends MongoDocument = MongoDocument> {
108
118
  protected readonly db: Mongo;
109
119
  protected readonly core: Core;
110
120
  readonly model: mongoose.Model<Doc>;
111
121
  constructor(db: Mongo, core: Core);
112
122
  }
113
- /** Constructor type for MongoService classes */
123
+ /** Constructor type for {@link MongoService} classes */
114
124
  type MongoServiceConstructor = TypedConstructor<typeof MongoService>;
115
125
 
116
- /**
117
- * Catches and wraps database operation errors.
118
- *
119
- * Automatically wraps non-CustomError exceptions in DatabaseError instances
120
- * with UUID tracking. Should be applied to database service methods.
121
- *
122
- * @typeParam TypeReturn - The return type of the decorated method
123
- * @param errorMessage - Message to include when wrapping errors
124
- * @decorator
125
- * @example
126
- * ```typescript
127
- * class UserService extends MongoService<IUser> {
128
- * \@DBCatchable('Failed to find user')
129
- * async findById(id: string) {
130
- * return this.model.findById(id);
131
- * }
132
- * }
133
- * ```
134
- */
135
- declare function DBCatchable<TypeReturn>(errorMessage: string): (_target: unknown, _propertyKey: string, descriptor: TypedPropertyDescriptor<(...args: any[]) => Promise<TypeReturn>>) => void;
136
-
137
126
  declare const ModelMetadataKey: unique symbol;
138
127
  /**
139
128
  * Associates a Mongoose model with a database service
@@ -147,16 +136,16 @@ declare const ModelMetadataKey: unique symbol;
147
136
  * @decorator
148
137
  * @example
149
138
  * ```typescript
150
- * \@DatabaseService('users')
139
+ * \@RegisterMongoService('users')
151
140
  * export class Users extends MongoService<IUser> {
152
- * \@DatabaseModel('users')
141
+ * \@RegisterMongoModel('users')
153
142
  * public static schema = new mongoose.Schema<IUser>({
154
143
  * username: { type: String, required: true, unique: true }
155
144
  * });
156
145
  * }
157
146
  * ```
158
147
  */
159
- declare function DatabaseModel<TService extends ServiceKeys>(collection: TService): <SchemaObj extends Record<KeyOfSchema, mongoose.Schema>, KeyOfSchema extends keyof SchemaObj & (string | symbol)>(target: SchemaObj, propertyKey: KeyOfSchema) => void;
148
+ declare function RegisterMongoModel<TService extends MongoServiceKeys>(collection: TService): <SchemaObj extends Record<KeyOfSchema, mongoose.Schema>, KeyOfSchema extends keyof SchemaObj & (string | symbol)>(target: SchemaObj, propertyKey: KeyOfSchema) => void;
160
149
 
161
150
  declare const ServiceMetadataKey: unique symbol;
162
151
  /**
@@ -170,14 +159,374 @@ declare const ServiceMetadataKey: unique symbol;
170
159
  * @decorator
171
160
  * @example
172
161
  * ```typescript
173
- * \@DatabaseService('users')
162
+ * \@RegisterMongoService('users')
174
163
  * export class Users<Doc extends IUser = IUser> extends MongoService<Doc> {
175
164
  * // Some code
176
165
  * }
177
166
  * ```
178
167
  */
179
- declare function DatabaseService<TService extends ServiceKeys>(key: TService): <DatabaseCtor extends Constructor<unknown> & {
168
+ declare function RegisterMongoService<TService extends MongoServiceKeys>(key: TService): <DatabaseCtor extends Constructor<unknown> & {
180
169
  prototype: MongoService;
181
170
  }>(ctor: DatabaseCtor) => void;
182
171
 
183
- export { DBCatchable, DatabaseModel, DatabaseService, type IDocument, ModelMetadataKey, Mongo, type MongoOptions, MongoService, type MongoServiceConstructor, type ServiceKeys, ServiceMetadataKey, type Services, type TypeOfIDocument };
172
+ /**
173
+ * Handles ensuring the target Postgres database exists, creating it if necessary.
174
+ */
175
+ declare class KpgDatabaseBootstrapper {
176
+ private readonly logger;
177
+ private static readonly ADMIN_DB;
178
+ private static readonly DATABASE_EXISTS_SQL;
179
+ constructor(logger: Logger);
180
+ resolveDatabaseName(config: PoolConfig): string | null;
181
+ resolveDatabaseFromPool(pool: Pool): string | null;
182
+ ensure(baseConfig: PoolConfig): Promise<void>;
183
+ private buildAdminConfig;
184
+ private databaseExists;
185
+ private createDatabase;
186
+ private static parseDatabaseName;
187
+ private static applyDatabaseToConnectionString;
188
+ private static escapeIdentifier;
189
+ }
190
+
191
+ /**
192
+ * Options that describe where migrations live and how the migrator should
193
+ * behave.
194
+ */
195
+ interface KpgMigrationsOptions {
196
+ /** Directory path, single file path, or array of migration files */
197
+ readonly path: string | string[];
198
+ /** Allow running migrations even if new ones are inserted out of order */
199
+ readonly allowUnorderedMigrations?: boolean;
200
+ /** Custom table name used to track executed migrations */
201
+ readonly migrationTableName?: string;
202
+ /** Custom lock table name used by the migrator */
203
+ readonly migrationLockTableName?: string;
204
+ /** Schema that contains the migration bookkeeping tables */
205
+ readonly migrationTableSchema?: string;
206
+ /** Comparator that determines execution order for migrations */
207
+ readonly nameComparator?: (nameA: string, nameB: string) => number;
208
+ /** Behavior when the plugin connects. `true`/`undefined` runs to latest. */
209
+ readonly onStartup?: boolean | MigrationOptions;
210
+ }
211
+ /**
212
+ * Configuration options for Postgres connection and service discovery.
213
+ */
214
+ interface KpgOptions {
215
+ /** Directory containing service classes. Make sure file(s)/folder(s) are built to `.js` in dist and aren't merged into a single file. */
216
+ readonly dir: string;
217
+ /** Migration settings */
218
+ readonly migrations: KpgMigrationsOptions;
219
+ /** Optional existing Pool instance or configuration overrides */
220
+ readonly pool?: Pool | PoolConfig;
221
+ /** Optional connection string used when a pool config is provided */
222
+ readonly connectionString?: string;
223
+ /** Optional SQL statements executed for each new connection */
224
+ readonly onConnectSQL?: string[];
225
+ /** Force using insecure SSL*/
226
+ readonly forceInsecureSSL?: boolean;
227
+ /** Kysely config (excludes dialect because it's Postgres for this plugin) */
228
+ readonly kysely?: TypedOmit<KyselyConfig, 'dialect'>;
229
+ }
230
+
231
+ /**
232
+ * Type representing a migration function (either `up` or `down`).
233
+ *
234
+ * @internal
235
+ */
236
+ type MigrationFn<TKey extends keyof Migration> = NonNullable<Migration[TKey]>;
237
+ /**
238
+ * Module exporting both `up` and `down` migration functions.
239
+ *
240
+ * @internal
241
+ */
242
+ interface MigrationModule {
243
+ up: MigrationFn<'up'>;
244
+ down: MigrationFn<'down'>;
245
+ }
246
+ /**
247
+ * Target migration identifier used to indicate no migrations should be run. Uses Kysely's built-in `NO_MIGRATIONS` constant.
248
+ *
249
+ * @internal
250
+ */
251
+ type MigrationTarget = string | typeof NO_MIGRATIONS;
252
+ /**
253
+ * Behavior configuration for migrations that should run automatically when a
254
+ * database connection is established.
255
+ */
256
+ interface MigrationOptions {
257
+ /** Optional target migration to reach. Defaults to latest if omitted. */
258
+ readonly target?: MigrationTarget;
259
+ /** Direction to move along the migration timeline. Defaults to `latest`. */
260
+ readonly direction?: 'latest' | 'up' | 'down';
261
+ /** Number of steps to apply when direction is `up` or `down`. */
262
+ readonly steps?: number;
263
+ }
264
+ /**
265
+ * Behavior configuration for step-based migrations.
266
+ */
267
+ interface StepMigrationOptions {
268
+ /** Number of steps to apply when direction is `up` or `down`. */
269
+ readonly steps?: number | undefined;
270
+ }
271
+ /**
272
+ * Context provided to the migration manager for performing migrations.
273
+ *
274
+ * @internal
275
+ */
276
+ interface MigrationManagerContext<Database extends object> {
277
+ readonly db: Kysely<Database>;
278
+ readonly logger: Logger;
279
+ readonly config: KpgMigrationsOptions;
280
+ readonly baseDir: string;
281
+ }
282
+
283
+ /**
284
+ * Migration tooling for KyselyPg.
285
+ */
286
+ declare class KpgMigrationManager<Database extends object> {
287
+ private readonly ctx;
288
+ constructor(ctx: MigrationManagerContext<Database>);
289
+ migrate(options?: MigrationOptions): Promise<void>;
290
+ migrateUp(options?: StepMigrationOptions): Promise<void>;
291
+ migrateDown(options?: StepMigrationOptions): Promise<void>;
292
+ listMigrations(): Promise<readonly MigrationInfo[]>;
293
+ private runMigration;
294
+ private runStepwise;
295
+ private createMigrator;
296
+ private getMigrationProvider;
297
+ private createModuleProvider;
298
+ private logMigrationFiles;
299
+ private logPreparedMigrations;
300
+ private logMigrationResults;
301
+ private relativePath;
302
+ private resolvePath;
303
+ private handleMigrationError;
304
+ private isMigrationModule;
305
+ }
306
+
307
+ /**
308
+ * Namespace interface that gets augmented by individual service packages.
309
+ *
310
+ * This interface can be augmented via declaration merging to add
311
+ * type-safe service definitions when using the `@RegisterKpgService` decorator.
312
+ *
313
+ * @example
314
+ * ```typescript
315
+ * declare module '@seedcord/plugins' {
316
+ * interface KpgServices {
317
+ * 'users': Users;
318
+ * }
319
+ * }
320
+ * ```
321
+ */
322
+ interface KpgServices {
323
+ }
324
+ /**
325
+ * Union of all registered service keys.
326
+ */
327
+ type KpgServiceKeys = keyof KpgServices;
328
+ /**
329
+ * Fallback database shape used when no services have extended the base interface.
330
+ *
331
+ * @internal
332
+ */
333
+ type DefaultKpgDatabase = Record<string, unknown>;
334
+ /**
335
+ * Fallback service type used while no concrete services are registered.
336
+ *
337
+ * @internal
338
+ */
339
+ type DefaultKpgService = KpgService<DefaultKpgDatabase, string>;
340
+ /**
341
+ * Either a real service from `KpgServices` or the default service placeholder.
342
+ *
343
+ * @internal
344
+ */
345
+ type AnyKpgService = [KpgServiceKeys] extends [never] ? DefaultKpgService : KpgServices[KpgServiceKeys];
346
+
347
+ /**
348
+ * Postgres plugin using Kysely.
349
+ *
350
+ * Handles setting up the connection pool, applying migrations, and
351
+ * registering decorated services so they can be resolved from the core.
352
+ */
353
+ declare class KyselyPg<Database extends object> extends Plugin {
354
+ readonly core: Core;
355
+ private readonly options;
356
+ readonly logger: Logger;
357
+ private isInitialised;
358
+ /** Exposed Kysely instance once `init` completes. */
359
+ connection: Kysely<Database>;
360
+ private pool;
361
+ private migrationManager;
362
+ private readonly serviceRegistry;
363
+ private readonly databaseBootstrapper;
364
+ private databaseName;
365
+ /**
366
+ * Map of all services registered with the plugin, keyed by their decorator name.
367
+ */
368
+ get services(): KpgServices;
369
+ constructor(core: Core, options: KpgOptions);
370
+ /**
371
+ * Connects to Postgres, runs any startup migrations, and loads decorated services.
372
+ *
373
+ * Safe to call multiple times; subsequent calls exit early.
374
+ */
375
+ init(): Promise<void>;
376
+ /**
377
+ * Tears down the connection pool and clears the migration manager reference.
378
+ */
379
+ stop(): Promise<void>;
380
+ private connect;
381
+ private disconnect;
382
+ /**
383
+ * Runs migrations using the supplied options or defaults to `latest`.
384
+ *
385
+ * @param options - Target migration or direction overrides
386
+ */
387
+ migrate(options?: MigrationOptions): Promise<void>;
388
+ /**
389
+ * Runs a single upwards migration step unless a custom count is provided.
390
+ *
391
+ * @param options - Optional configuration for step-based execution
392
+ */
393
+ migrateUp(options?: StepMigrationOptions): Promise<void>;
394
+ /**
395
+ * Runs a single downwards migration step unless a custom count is provided.
396
+ *
397
+ * @param options - Optional configuration for step-based execution
398
+ */
399
+ migrateDown(options?: StepMigrationOptions): Promise<void>;
400
+ /**
401
+ * Lists every migration the manager knows about along with its execution state.
402
+ */
403
+ listMigrations(): Promise<readonly MigrationInfo[]>;
404
+ /**
405
+ * Lists unapplied migrations.
406
+ */
407
+ listPendingMigrations(): Promise<MigrationInfo[]>;
408
+ private getMigrationManager;
409
+ /**
410
+ * Register hook used by decorated services.
411
+ *
412
+ * @internal
413
+ */
414
+ _register(key: KpgServiceKeys, instance: AnyKpgService): void;
415
+ private resolvePool;
416
+ private createPoolConfig;
417
+ private registerOnConnectStatements;
418
+ private testPoolConnection;
419
+ }
420
+
421
+ /**
422
+ * Base class for KyselyPg services.
423
+ *
424
+ * Provides a small, typed shim around the shared Kysely instance and ensures
425
+ * that subclasses have been decorated with `@RegisterKpgService`.
426
+ *
427
+ * @typeParam Database - The database shape used by Kysely (tables as keys).
428
+ * @typeParam TTable - The specific table key from `Database` this service works with.
429
+ *
430
+ * @example
431
+ * ```typescript
432
+ * \@RegisterKpgService('users')
433
+ * export class UsersService extends KpgService<ImportedDatabaseInterface, 'users'> {
434
+ * public async findById(id: string) {
435
+ * return this.entity
436
+ * .selectFrom(this.table)
437
+ * .selectAll().where('id', '=', id)
438
+ * .executeTakeFirst();
439
+ * }
440
+ * }
441
+ *
442
+ * // Usage inside handlers:
443
+ * const user = await this.core.db.services.users.findById('abc');
444
+ * ```
445
+ */
446
+ declare abstract class KpgService<Database extends object, TTable extends keyof Database & string> {
447
+ protected readonly kysely: KyselyPg<Database>;
448
+ protected readonly core: Core;
449
+ readonly table: TTable;
450
+ constructor(kysely: KyselyPg<Database>, core: Core);
451
+ /**
452
+ * Shared Kysely instance used to interact with the Postgres database.
453
+ */
454
+ get db(): Kysely<Database>;
455
+ }
456
+ /** Constructor type for {@link KpgService} classes */
457
+ type KyselyServiceConstructor<Database extends object = object> = TypedConstructor$1<typeof KpgService<Database, keyof Database & string>>;
458
+
459
+ /**
460
+ * Discovers and registers Postgres services for the plugin.
461
+ */
462
+ declare class KpgServiceRegistry<Database extends object> {
463
+ private readonly plugin;
464
+ private readonly core;
465
+ private readonly logger;
466
+ private readonly services;
467
+ constructor(plugin: KyselyPg<Database>, core: Core, logger: Logger);
468
+ get map(): KpgServices;
469
+ register(key: KpgServiceKeys, instance: AnyKpgService): void;
470
+ loadFromDirectory(dir: string): Promise<void>;
471
+ private isServiceClass;
472
+ }
473
+
474
+ /**
475
+ * Extra configuration supplied to `@RegisterKpgService`.
476
+ */
477
+ interface KpgServiceRegistrationOptions {
478
+ /** Optional override for the table name exposed via the service. Defaults to the provided key. */
479
+ table?: string;
480
+ }
481
+
482
+ declare const PgServiceMetadataKey: unique symbol;
483
+ declare const PgTableMetadataKey: unique symbol;
484
+ /**
485
+ *
486
+ * Registers a Kysely PG service with the specified key and options.
487
+ *
488
+ * Associates a service class with a key for dependency injection.
489
+ * The service becomes available via `core.db.services[key]`.
490
+ *
491
+ * @typeParam TKey - The service key type
492
+ * @param key - Service key for registration and type-safe access
493
+ * @param options - Additional registration options
494
+ * @decorator
495
+ * @example
496
+ * ```typescript
497
+ * \@RegisterKpgService('users', { table: 'app_users' })
498
+ * export class UsersService extends KpgService<{ users: IUser }, 'users'> {
499
+ * // Some code
500
+ * }
501
+ * ```
502
+ *
503
+ * @see {@link KpgService}
504
+ */
505
+ declare function RegisterKpgService<TKey extends KpgServiceKeys>(key: TKey, options?: KpgServiceRegistrationOptions): <Ctor extends Constructor<KpgServices[TKey]>>(ctor: Ctor) => void;
506
+
507
+ /**
508
+ * Catches and wraps database operation errors.
509
+ *
510
+ * Wraps non-CustomError exceptions in DatabaseError instances
511
+ * with UUID tracking. Should be applied to database service methods.
512
+ *
513
+ * @typeParam TypeReturn - The return type of the decorated method
514
+ * @param errorMessage - Message to include when wrapping errors
515
+ * @decorator
516
+ * @example
517
+ * ```typescript
518
+ * class UserService extends MongoService<IUser> {
519
+ * \@WrapDatabaseError('Failed to find user')
520
+ * async findById(id: string) {
521
+ * return this.model.findById(id);
522
+ * }
523
+ * }
524
+ * ```
525
+ *
526
+ * @see {@link DatabaseError}
527
+ * @see {@link CustomError}
528
+ * @see {@link MongoService}
529
+ */
530
+ declare function WrapDatabaseError<TypeReturn>(errorMessage: string): (_target: unknown, _propertyKey: string, descriptor: TypedPropertyDescriptor<(...args: any[]) => Promise<TypeReturn>>) => void;
531
+
532
+ export { type AnyKpgService, type DefaultKpgDatabase, type DefaultKpgService, KpgDatabaseBootstrapper, KpgMigrationManager, type KpgMigrationsOptions, type KpgOptions, KpgService, type KpgServiceKeys, type KpgServiceRegistrationOptions, KpgServiceRegistry, type KpgServices, KyselyPg, type KyselyServiceConstructor, type MigrationFn, type MigrationManagerContext, type MigrationModule, type MigrationOptions, type MigrationTarget, ModelMetadataKey, Mongo, type MongoDocument, type MongoDocumentType, MongoService, type MongoServiceConstructor, type MongoServiceKeys, type MongoServices, PgServiceMetadataKey, PgTableMetadataKey, RegisterKpgService, RegisterMongoModel, RegisterMongoService, ServiceMetadataKey, type StepMigrationOptions, WrapDatabaseError };