@goodie-ts/kysely 0.4.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.
Files changed (38) hide show
  1. package/README.md +87 -0
  2. package/dist/abstract-migration.d.ts +29 -0
  3. package/dist/abstract-migration.d.ts.map +1 -0
  4. package/dist/abstract-migration.js +26 -0
  5. package/dist/abstract-migration.js.map +1 -0
  6. package/dist/crud-repository.d.ts +40 -0
  7. package/dist/crud-repository.d.ts.map +1 -0
  8. package/dist/crud-repository.js +64 -0
  9. package/dist/crud-repository.js.map +1 -0
  10. package/dist/decorators/migration.d.ts +18 -0
  11. package/dist/decorators/migration.d.ts.map +1 -0
  12. package/dist/decorators/migration.js +24 -0
  13. package/dist/decorators/migration.js.map +1 -0
  14. package/dist/decorators/transactional.d.ts +16 -0
  15. package/dist/decorators/transactional.d.ts.map +1 -0
  16. package/dist/decorators/transactional.js +14 -0
  17. package/dist/decorators/transactional.js.map +1 -0
  18. package/dist/index.d.ts +11 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +9 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/kysely-transformer-plugin.d.ts +32 -0
  23. package/dist/kysely-transformer-plugin.d.ts.map +1 -0
  24. package/dist/kysely-transformer-plugin.js +315 -0
  25. package/dist/kysely-transformer-plugin.js.map +1 -0
  26. package/dist/migration-runner.d.ts +16 -0
  27. package/dist/migration-runner.d.ts.map +1 -0
  28. package/dist/migration-runner.js +45 -0
  29. package/dist/migration-runner.js.map +1 -0
  30. package/dist/transaction-manager.d.ts +59 -0
  31. package/dist/transaction-manager.d.ts.map +1 -0
  32. package/dist/transaction-manager.js +132 -0
  33. package/dist/transaction-manager.js.map +1 -0
  34. package/dist/transactional-interceptor.d.ts +14 -0
  35. package/dist/transactional-interceptor.d.ts.map +1 -0
  36. package/dist/transactional-interceptor.js +24 -0
  37. package/dist/transactional-interceptor.js.map +1 -0
  38. package/package.json +51 -0
package/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # @goodie-ts/kysely
2
+
3
+ [Kysely](https://kysely.dev/) integration for [goodie-ts](https://github.com/GOOD-Code-ApS/goodie) — declarative transactions, auto-wired migrations, and a CRUD repository base class.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @goodie-ts/kysely kysely
9
+ ```
10
+
11
+ ## Overview
12
+
13
+ Provides `@Transactional` for declarative transaction management, `@Migration` for auto-discovered database migrations, and `CrudRepository<T>` for common CRUD operations. All backed by `TransactionManager` which uses `AsyncLocalStorage` for transaction propagation.
14
+
15
+ ## Decorators
16
+
17
+ | Decorator | Target | Description |
18
+ |-----------|--------|-------------|
19
+ | `@Transactional({ propagation? })` | method | Wraps method in a database transaction |
20
+ | `@Migration('name')` | class | Marks a class as a migration (sorted by name) |
21
+
22
+ ## Usage
23
+
24
+ ```typescript
25
+ import { Singleton } from '@goodie-ts/decorators';
26
+ import { Transactional, CrudRepository, TransactionManager } from '@goodie-ts/kysely';
27
+
28
+ @Singleton()
29
+ class TodoRepository extends CrudRepository<Todo> {
30
+ constructor(transactionManager: TransactionManager) {
31
+ super('todos', transactionManager);
32
+ }
33
+ }
34
+
35
+ @Singleton()
36
+ class TodoService {
37
+ constructor(private repo: TodoRepository) {}
38
+
39
+ @Transactional()
40
+ async createMany(titles: string[]) {
41
+ for (const title of titles) {
42
+ await this.repo.save({ title, completed: false });
43
+ }
44
+ // All-or-nothing: rolls back on error
45
+ }
46
+ }
47
+ ```
48
+
49
+ ## Migrations
50
+
51
+ ```typescript
52
+ import { Migration, AbstractMigration } from '@goodie-ts/kysely';
53
+ import type { Kysely } from 'kysely';
54
+
55
+ @Migration('001_create_todos')
56
+ class CreateTodosTable extends AbstractMigration {
57
+ async up(db: Kysely<any>) {
58
+ await db.schema.createTable('todos')
59
+ .addColumn('id', 'uuid', c => c.primaryKey().defaultTo(sql`gen_random_uuid()`))
60
+ .addColumn('title', 'text', c => c.notNull())
61
+ .execute();
62
+ }
63
+ }
64
+ ```
65
+
66
+ Migrations run automatically at startup via `MigrationRunner` (`@PostConstruct`), sorted by name.
67
+
68
+ ## Vite Plugin Setup
69
+
70
+ ```typescript
71
+ import { diPlugin } from '@goodie-ts/vite-plugin';
72
+ import { createKyselyPlugin } from '@goodie-ts/kysely';
73
+
74
+ export default defineConfig({
75
+ plugins: [
76
+ diPlugin({
77
+ plugins: [createKyselyPlugin({ database: 'Database' })],
78
+ }),
79
+ ],
80
+ });
81
+ ```
82
+
83
+ The `database` option specifies the class name of your Kysely wrapper (a `@Singleton` with a `.kysely` property).
84
+
85
+ ## License
86
+
87
+ [MIT](https://github.com/GOOD-Code-ApS/goodie/blob/main/LICENSE)
@@ -0,0 +1,29 @@
1
+ import type { Kysely } from 'kysely';
2
+ /**
3
+ * Abstract base class for Kysely migrations.
4
+ *
5
+ * Extend this class and decorate with `@Migration('name')` to define a
6
+ * migration that is auto-discovered and executed by the MigrationRunner
7
+ * at application startup.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * @Migration('001_create_users')
12
+ * export class CreateUsersTable extends AbstractMigration {
13
+ * async up(db: Kysely<any>) {
14
+ * await db.schema.createTable('users')
15
+ * .addColumn('id', 'uuid', c => c.primaryKey())
16
+ * .execute();
17
+ * }
18
+ *
19
+ * async down(db: Kysely<any>) {
20
+ * await db.schema.dropTable('users').execute();
21
+ * }
22
+ * }
23
+ * ```
24
+ */
25
+ export declare abstract class AbstractMigration {
26
+ abstract up(db: Kysely<any>): Promise<void>;
27
+ down?(db: Kysely<any>): Promise<void>;
28
+ }
29
+ //# sourceMappingURL=abstract-migration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abstract-migration.d.ts","sourceRoot":"","sources":["../src/abstract-migration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,8BAAsB,iBAAiB;IACrC,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAC3C,IAAI,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CACtC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Abstract base class for Kysely migrations.
3
+ *
4
+ * Extend this class and decorate with `@Migration('name')` to define a
5
+ * migration that is auto-discovered and executed by the MigrationRunner
6
+ * at application startup.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * @Migration('001_create_users')
11
+ * export class CreateUsersTable extends AbstractMigration {
12
+ * async up(db: Kysely<any>) {
13
+ * await db.schema.createTable('users')
14
+ * .addColumn('id', 'uuid', c => c.primaryKey())
15
+ * .execute();
16
+ * }
17
+ *
18
+ * async down(db: Kysely<any>) {
19
+ * await db.schema.dropTable('users').execute();
20
+ * }
21
+ * }
22
+ * ```
23
+ */
24
+ export class AbstractMigration {
25
+ }
26
+ //# sourceMappingURL=abstract-migration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abstract-migration.js","sourceRoot":"","sources":["../src/abstract-migration.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,OAAgB,iBAAiB;CAGtC"}
@@ -0,0 +1,40 @@
1
+ import type { Kysely } from 'kysely';
2
+ import type { TransactionManager } from './transaction-manager.js';
3
+ /**
4
+ * Abstract base class for CRUD repositories backed by Kysely.
5
+ *
6
+ * **PostgreSQL-only:** Uses `RETURNING` clauses in `save()` and `deleteById()`.
7
+ * MySQL and SQLite do not support `RETURNING`. For other dialects, override
8
+ * these methods in your subclass.
9
+ *
10
+ * Provides standard `findAll`, `findById`, `save`, and `deleteById` operations.
11
+ * All queries use `TransactionManager.getConnection()` to be transaction-aware.
12
+ *
13
+ * The `db` getter returns `Kysely<any>` — typed table access is intentionally
14
+ * erased because the transformer cannot generate clean tokens for `Kysely<DB>`
15
+ * generic types. Subclasses can cast as needed for custom queries.
16
+ *
17
+ * @typeParam T - The entity/row type returned by queries.
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * class TodoRepository extends CrudRepository<Todo> {
22
+ * constructor(transactionManager: TransactionManager) {
23
+ * super('todos', transactionManager);
24
+ * }
25
+ * }
26
+ * ```
27
+ */
28
+ export declare abstract class CrudRepository<T extends Record<string, unknown>> {
29
+ protected readonly tableName: string;
30
+ protected readonly transactionManager: TransactionManager;
31
+ protected readonly idColumn: string;
32
+ constructor(tableName: string, transactionManager: TransactionManager, idColumn?: string);
33
+ /** Get the current transaction-aware Kysely connection. */
34
+ protected get db(): Kysely<any>;
35
+ findAll(): Promise<T[]>;
36
+ findById(id: unknown): Promise<T | undefined>;
37
+ save(entity: Record<string, unknown>): Promise<T>;
38
+ deleteById(id: unknown): Promise<T | undefined>;
39
+ }
40
+ //# sourceMappingURL=crud-repository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crud-repository.d.ts","sourceRoot":"","sources":["../src/crud-repository.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAEnE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,8BAAsB,cAAc,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAElE,SAAS,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM;IACpC,SAAS,CAAC,QAAQ,CAAC,kBAAkB,EAAE,kBAAkB;IACzD,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM;gBAFhB,SAAS,EAAE,MAAM,EACjB,kBAAkB,EAAE,kBAAkB,EACtC,QAAQ,GAAE,MAAa;IAG5C,2DAA2D;IAC3D,SAAS,KAAK,EAAE,IAAI,MAAM,CAAC,GAAG,CAAC,CAE9B;IAEK,OAAO,IAAI,OAAO,CAAC,CAAC,EAAE,CAAC;IAMvB,QAAQ,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAQ7C,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAQjD,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;CAOtD"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Abstract base class for CRUD repositories backed by Kysely.
3
+ *
4
+ * **PostgreSQL-only:** Uses `RETURNING` clauses in `save()` and `deleteById()`.
5
+ * MySQL and SQLite do not support `RETURNING`. For other dialects, override
6
+ * these methods in your subclass.
7
+ *
8
+ * Provides standard `findAll`, `findById`, `save`, and `deleteById` operations.
9
+ * All queries use `TransactionManager.getConnection()` to be transaction-aware.
10
+ *
11
+ * The `db` getter returns `Kysely<any>` — typed table access is intentionally
12
+ * erased because the transformer cannot generate clean tokens for `Kysely<DB>`
13
+ * generic types. Subclasses can cast as needed for custom queries.
14
+ *
15
+ * @typeParam T - The entity/row type returned by queries.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * class TodoRepository extends CrudRepository<Todo> {
20
+ * constructor(transactionManager: TransactionManager) {
21
+ * super('todos', transactionManager);
22
+ * }
23
+ * }
24
+ * ```
25
+ */
26
+ export class CrudRepository {
27
+ tableName;
28
+ transactionManager;
29
+ idColumn;
30
+ constructor(tableName, transactionManager, idColumn = 'id') {
31
+ this.tableName = tableName;
32
+ this.transactionManager = transactionManager;
33
+ this.idColumn = idColumn;
34
+ }
35
+ /** Get the current transaction-aware Kysely connection. */
36
+ get db() {
37
+ return this.transactionManager.getConnection();
38
+ }
39
+ async findAll() {
40
+ return this.db.selectFrom(this.tableName).selectAll().execute();
41
+ }
42
+ async findById(id) {
43
+ return this.db
44
+ .selectFrom(this.tableName)
45
+ .selectAll()
46
+ .where(this.idColumn, '=', id)
47
+ .executeTakeFirst();
48
+ }
49
+ async save(entity) {
50
+ return this.db
51
+ .insertInto(this.tableName)
52
+ .values(entity)
53
+ .returningAll()
54
+ .executeTakeFirstOrThrow();
55
+ }
56
+ async deleteById(id) {
57
+ return this.db
58
+ .deleteFrom(this.tableName)
59
+ .where(this.idColumn, '=', id)
60
+ .returningAll()
61
+ .executeTakeFirst();
62
+ }
63
+ }
64
+ //# sourceMappingURL=crud-repository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crud-repository.js","sourceRoot":"","sources":["../src/crud-repository.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,OAAgB,cAAc;IAEb;IACA;IACA;IAHrB,YACqB,SAAiB,EACjB,kBAAsC,EACtC,WAAmB,IAAI;QAFvB,cAAS,GAAT,SAAS,CAAQ;QACjB,uBAAkB,GAAlB,kBAAkB,CAAoB;QACtC,aAAQ,GAAR,QAAQ,CAAe;IACzC,CAAC;IAEJ,2DAA2D;IAC3D,IAAc,EAAE;QACd,OAAO,IAAI,CAAC,kBAAkB,CAAC,aAAa,EAAiB,CAAC;IAChE,CAAC;IAED,KAAK,CAAC,OAAO;QACX,OAAO,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,SAAS,EAAE,CAAC,OAAO,EAE5D,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,EAAW;QACxB,OAAO,IAAI,CAAC,EAAE;aACX,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC;aAC1B,SAAS,EAAE;aACX,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,CAAC;aAC7B,gBAAgB,EAA4B,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAA+B;QACxC,OAAO,IAAI,CAAC,EAAE;aACX,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC;aAC1B,MAAM,CAAC,MAAM,CAAC;aACd,YAAY,EAAE;aACd,uBAAuB,EAAgB,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,EAAW;QAC1B,OAAO,IAAI,CAAC,EAAE;aACX,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC;aAC1B,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,CAAC;aAC7B,YAAY,EAAE;aACd,gBAAgB,EAA4B,CAAC;IAClD,CAAC;CACF"}
@@ -0,0 +1,18 @@
1
+ type ClassDecorator_Stage3 = (target: new (...args: never) => unknown, context: ClassDecoratorContext) => void;
2
+ /**
3
+ * Mark a class as a Kysely migration with a unique name.
4
+ *
5
+ * At compile time, the Kysely transformer plugin discovers @Migration classes
6
+ * and wires them into an auto-managed MigrationRunner.
7
+ *
8
+ * Classes should extend {@link AbstractMigration} which enforces the required
9
+ * `up()` method and optional `down()` method at compile time.
10
+ *
11
+ * @param name - Unique migration key, e.g. '001_create_todos'. Migrations
12
+ * are executed in lexicographic order by name.
13
+ */
14
+ export declare function Migration(name: string): ClassDecorator_Stage3;
15
+ /** Read the migration name from a migration instance's Symbol.metadata. */
16
+ export declare function getMigrationName(instance: object): string | undefined;
17
+ export {};
18
+ //# sourceMappingURL=migration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration.d.ts","sourceRoot":"","sources":["../../src/decorators/migration.ts"],"names":[],"mappings":"AAEA,KAAK,qBAAqB,GAAG,CAC3B,MAAM,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,EACvC,OAAO,EAAE,qBAAqB,KAC3B,IAAI,CAAC;AAEV;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,qBAAqB,CAI7D;AAED,2EAA2E;AAC3E,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGrE"}
@@ -0,0 +1,24 @@
1
+ const MIGRATION_NAME = Symbol('goodie:migration-name');
2
+ /**
3
+ * Mark a class as a Kysely migration with a unique name.
4
+ *
5
+ * At compile time, the Kysely transformer plugin discovers @Migration classes
6
+ * and wires them into an auto-managed MigrationRunner.
7
+ *
8
+ * Classes should extend {@link AbstractMigration} which enforces the required
9
+ * `up()` method and optional `down()` method at compile time.
10
+ *
11
+ * @param name - Unique migration key, e.g. '001_create_todos'. Migrations
12
+ * are executed in lexicographic order by name.
13
+ */
14
+ export function Migration(name) {
15
+ return (_target, context) => {
16
+ context.metadata[MIGRATION_NAME] = name;
17
+ };
18
+ }
19
+ /** Read the migration name from a migration instance's Symbol.metadata. */
20
+ export function getMigrationName(instance) {
21
+ const meta = instance.constructor[Symbol.metadata];
22
+ return meta?.[MIGRATION_NAME];
23
+ }
24
+ //# sourceMappingURL=migration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration.js","sourceRoot":"","sources":["../../src/decorators/migration.ts"],"names":[],"mappings":"AAAA,MAAM,cAAc,GAAG,MAAM,CAAC,uBAAuB,CAAC,CAAC;AAOvD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE;QAC1B,OAAO,CAAC,QAAS,CAAC,cAAc,CAAC,GAAG,IAAI,CAAC;IAC3C,CAAC,CAAC;AACJ,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,MAAM,IAAI,GAAI,QAAQ,CAAC,WAAmB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC5D,OAAO,IAAI,EAAE,CAAC,cAAc,CAAC,CAAC;AAChC,CAAC"}
@@ -0,0 +1,16 @@
1
+ export interface TransactionalOptions {
2
+ /** Transaction propagation strategy (default: 'REQUIRED'). */
3
+ propagation?: 'REQUIRED' | 'REQUIRES_NEW';
4
+ }
5
+ type MethodDecorator_Stage3 = (target: (...args: never) => unknown, context: ClassMethodDecoratorContext) => void;
6
+ /**
7
+ * Mark a method to run inside a database transaction.
8
+ *
9
+ * At compile time, the kysely transformer plugin reads this decorator
10
+ * and wires the `TransactionalInterceptor` via AOP.
11
+ *
12
+ * @param opts - Optional configuration (e.g. propagation strategy).
13
+ */
14
+ export declare function Transactional(_opts?: TransactionalOptions): MethodDecorator_Stage3;
15
+ export {};
16
+ //# sourceMappingURL=transactional.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transactional.d.ts","sourceRoot":"","sources":["../../src/decorators/transactional.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,oBAAoB;IACnC,8DAA8D;IAC9D,WAAW,CAAC,EAAE,UAAU,GAAG,cAAc,CAAC;CAC3C;AAED,KAAK,sBAAsB,GAAG,CAC5B,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,EACnC,OAAO,EAAE,2BAA2B,KACjC,IAAI,CAAC;AAEV;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,KAAK,CAAC,EAAE,oBAAoB,GAC3B,sBAAsB,CAIxB"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Mark a method to run inside a database transaction.
3
+ *
4
+ * At compile time, the kysely transformer plugin reads this decorator
5
+ * and wires the `TransactionalInterceptor` via AOP.
6
+ *
7
+ * @param opts - Optional configuration (e.g. propagation strategy).
8
+ */
9
+ export function Transactional(_opts) {
10
+ return (_target, _context) => {
11
+ // No-op: read at compile time by the kysely transformer plugin
12
+ };
13
+ }
14
+ //# sourceMappingURL=transactional.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transactional.js","sourceRoot":"","sources":["../../src/decorators/transactional.ts"],"names":[],"mappings":"AAUA;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAC3B,KAA4B;IAE5B,OAAO,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE;QAC3B,+DAA+D;IACjE,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ export { AbstractMigration } from './abstract-migration.js';
2
+ export { CrudRepository } from './crud-repository.js';
3
+ export { getMigrationName, Migration } from './decorators/migration.js';
4
+ export { Transactional } from './decorators/transactional.js';
5
+ export type { KyselyPluginOptions } from './kysely-transformer-plugin.js';
6
+ export { createKyselyPlugin } from './kysely-transformer-plugin.js';
7
+ export { MigrationRunner } from './migration-runner.js';
8
+ export type { KyselyProvider } from './transaction-manager.js';
9
+ export { TransactionManager } from './transaction-manager.js';
10
+ export { TransactionalInterceptor } from './transactional-interceptor.js';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,YAAY,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { AbstractMigration } from './abstract-migration.js';
2
+ export { CrudRepository } from './crud-repository.js';
3
+ export { getMigrationName, Migration } from './decorators/migration.js';
4
+ export { Transactional } from './decorators/transactional.js';
5
+ export { createKyselyPlugin } from './kysely-transformer-plugin.js';
6
+ export { MigrationRunner } from './migration-runner.js';
7
+ export { TransactionManager } from './transaction-manager.js';
8
+ export { TransactionalInterceptor } from './transactional-interceptor.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAE9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC"}
@@ -0,0 +1,32 @@
1
+ import type { TransformerPlugin } from '@goodie-ts/transformer';
2
+ /** Options for the Kysely transformer plugin. */
3
+ export interface KyselyPluginOptions {
4
+ /**
5
+ * Explicit class name of the bean that provides a `Kysely<...>` property.
6
+ * Overrides auto-detection. Use this to disambiguate when multiple classes
7
+ * expose a `Kysely` property.
8
+ */
9
+ database?: string;
10
+ }
11
+ /**
12
+ * Create the Kysely transformer plugin.
13
+ *
14
+ * Scans @Transactional decorators on methods and wires
15
+ * TransactionalInterceptor as an AOP interceptor dependency at compile time.
16
+ *
17
+ * Scans @Migration decorated classes and wires a MigrationRunner that
18
+ * executes all migrations at startup via @PostConstruct.
19
+ *
20
+ * **Auto-detection:** The plugin scans decorated classes for properties typed
21
+ * as `Kysely<...>` and auto-wires the owning class as a TransactionManager
22
+ * and/or MigrationRunner constructor dependency. Use `options.database` to
23
+ * override when multiple classes expose a `Kysely` property.
24
+ *
25
+ * **Limitation:** Propagation is detected via AST text matching
26
+ * (`text.includes('REQUIRES_NEW')`). Only string literal values in the
27
+ * decorator argument are supported — const references or computed values
28
+ * will fall back to `'REQUIRED'` silently.
29
+ */
30
+ export declare function createKyselyPlugin(options?: KyselyPluginOptions): TransformerPlugin;
31
+ export default createKyselyPlugin;
32
+ //# sourceMappingURL=kysely-transformer-plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kysely-transformer-plugin.d.ts","sourceRoot":"","sources":["../src/kysely-transformer-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAKV,iBAAiB,EAClB,MAAM,wBAAwB,CAAC;AAEhC,iDAAiD;AACjD,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAeD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,CAAC,EAAE,mBAAmB,GAC5B,iBAAiB,CAqSnB;AAmED,eAAe,kBAAkB,CAAC"}
@@ -0,0 +1,315 @@
1
+ /**
2
+ * Create the Kysely transformer plugin.
3
+ *
4
+ * Scans @Transactional decorators on methods and wires
5
+ * TransactionalInterceptor as an AOP interceptor dependency at compile time.
6
+ *
7
+ * Scans @Migration decorated classes and wires a MigrationRunner that
8
+ * executes all migrations at startup via @PostConstruct.
9
+ *
10
+ * **Auto-detection:** The plugin scans decorated classes for properties typed
11
+ * as `Kysely<...>` and auto-wires the owning class as a TransactionManager
12
+ * and/or MigrationRunner constructor dependency. Use `options.database` to
13
+ * override when multiple classes expose a `Kysely` property.
14
+ *
15
+ * **Limitation:** Propagation is detected via AST text matching
16
+ * (`text.includes('REQUIRES_NEW')`). Only string literal values in the
17
+ * decorator argument are supported — const references or computed values
18
+ * will fall back to `'REQUIRED'` silently.
19
+ */
20
+ export function createKyselyPlugin(options) {
21
+ const classTransactionalInfo = new Map();
22
+ /** Classes discovered with a `Kysely<...>` property. Keyed by filePath:className. */
23
+ const kyselyProviders = [];
24
+ /** @Migration decorated classes discovered during visitClass. */
25
+ const migrationClasses = [];
26
+ return {
27
+ name: 'kysely',
28
+ visitClass(ctx) {
29
+ // Detect Kysely<...> properties for auto-wiring
30
+ for (const prop of ctx.classDeclaration.getProperties()) {
31
+ // Check the type annotation text (AST-level), not the resolved type.
32
+ // This avoids requiring the 'kysely' package to be resolvable at transform time.
33
+ const typeAnnotation = prop.getTypeNode()?.getText();
34
+ if (typeAnnotation?.startsWith('Kysely<')) {
35
+ kyselyProviders.push({
36
+ className: ctx.className,
37
+ filePath: ctx.filePath,
38
+ });
39
+ break; // one match per class is enough
40
+ }
41
+ }
42
+ // Detect @Migration('name') class decorator
43
+ for (const decorator of ctx.classDeclaration.getDecorators()) {
44
+ if (decorator.getName() !== 'Migration')
45
+ continue;
46
+ const args = decorator.getArguments();
47
+ const name = args[0]?.getText().replace(/['"]/g, '') ?? ctx.className;
48
+ migrationClasses.push({
49
+ className: ctx.className,
50
+ filePath: ctx.filePath,
51
+ migrationName: name,
52
+ });
53
+ }
54
+ },
55
+ visitMethod(ctx) {
56
+ const decorators = ctx.methodDeclaration.getDecorators();
57
+ for (const decorator of decorators) {
58
+ if (decorator.getName() !== 'Transactional')
59
+ continue;
60
+ // Parse propagation from decorator arguments
61
+ let propagation = 'REQUIRED';
62
+ const args = decorator.getArguments();
63
+ if (args.length > 0) {
64
+ const text = args[0].getText();
65
+ if (text.includes('REQUIRES_NEW')) {
66
+ propagation = 'REQUIRES_NEW';
67
+ }
68
+ }
69
+ const key = `${ctx.filePath}:${ctx.className}`;
70
+ const existing = classTransactionalInfo.get(key) ?? [];
71
+ existing.push({ methodName: ctx.methodName, propagation });
72
+ classTransactionalInfo.set(key, existing);
73
+ }
74
+ },
75
+ afterResolve(beans) {
76
+ const syntheticBeans = [];
77
+ // --- @Transactional interception ---
78
+ let needsInterceptor = false;
79
+ for (const bean of beans) {
80
+ const className = bean.tokenRef.kind === 'class' ? bean.tokenRef.className : undefined;
81
+ if (!className)
82
+ continue;
83
+ const key = `${bean.tokenRef.importPath}:${className}`;
84
+ const infos = classTransactionalInfo.get(key);
85
+ if (!infos || infos.length === 0)
86
+ continue;
87
+ needsInterceptor = true;
88
+ const existing = (bean.metadata.interceptedMethods ?? []);
89
+ for (const info of infos) {
90
+ const methodEntry = existing.find((m) => m.methodName === info.methodName);
91
+ const interceptorRef = {
92
+ className: 'TransactionalInterceptor',
93
+ importPath: '@goodie-ts/kysely',
94
+ adviceType: 'around',
95
+ order: -40, // Runs after logging (-100), before cache (-50), before resilience
96
+ metadata: { propagation: info.propagation },
97
+ };
98
+ if (methodEntry) {
99
+ methodEntry.interceptors.push(interceptorRef);
100
+ }
101
+ else {
102
+ existing.push({
103
+ methodName: info.methodName,
104
+ interceptors: [interceptorRef],
105
+ });
106
+ }
107
+ }
108
+ bean.metadata.interceptedMethods = existing;
109
+ }
110
+ const hasMigrations = migrationClasses.length > 0;
111
+ if (!needsInterceptor && !hasMigrations)
112
+ return beans;
113
+ // Resolve the database bean for auto-wiring.
114
+ // Priority: explicit `database` option > auto-detected Kysely provider.
115
+ const kyselyProviderDep = resolveKyselyProvider(beans, options?.database, kyselyProviders);
116
+ // --- Transactional synthetic beans ---
117
+ if (needsInterceptor) {
118
+ syntheticBeans.push({
119
+ tokenRef: {
120
+ kind: 'class',
121
+ className: 'TransactionManager',
122
+ importPath: '@goodie-ts/kysely',
123
+ },
124
+ scope: 'singleton',
125
+ eager: false,
126
+ name: undefined,
127
+ constructorDeps: kyselyProviderDep,
128
+ fieldDeps: [],
129
+ factoryKind: 'constructor',
130
+ providesSource: undefined,
131
+ metadata: {},
132
+ sourceLocation: {
133
+ filePath: '@goodie-ts/kysely',
134
+ line: 0,
135
+ column: 0,
136
+ },
137
+ });
138
+ syntheticBeans.push({
139
+ tokenRef: {
140
+ kind: 'class',
141
+ className: 'TransactionalInterceptor',
142
+ importPath: '@goodie-ts/kysely',
143
+ },
144
+ scope: 'singleton',
145
+ eager: false,
146
+ name: undefined,
147
+ constructorDeps: [
148
+ {
149
+ tokenRef: {
150
+ kind: 'class',
151
+ className: 'TransactionManager',
152
+ importPath: '@goodie-ts/kysely',
153
+ },
154
+ optional: false,
155
+ collection: false,
156
+ sourceLocation: {
157
+ filePath: '@goodie-ts/kysely',
158
+ line: 0,
159
+ column: 0,
160
+ },
161
+ },
162
+ ],
163
+ fieldDeps: [],
164
+ factoryKind: 'constructor',
165
+ providesSource: undefined,
166
+ metadata: {},
167
+ sourceLocation: {
168
+ filePath: '@goodie-ts/kysely',
169
+ line: 0,
170
+ column: 0,
171
+ },
172
+ });
173
+ }
174
+ // --- @Migration synthetic beans ---
175
+ if (hasMigrations) {
176
+ if (kyselyProviderDep.length === 0) {
177
+ console.warn('[@goodie-ts/kysely] @Migration classes found but no Kysely provider detected. ' +
178
+ 'MigrationRunner will not be created.');
179
+ }
180
+ else {
181
+ // Sort migrations by name for deterministic execution order
182
+ migrationClasses.sort((a, b) => a.migrationName.localeCompare(b.migrationName));
183
+ // Synthetic bean per @Migration class (no baseTokenRefs — wired as individual deps)
184
+ const migrationDeps = [];
185
+ for (const m of migrationClasses) {
186
+ syntheticBeans.push({
187
+ tokenRef: {
188
+ kind: 'class',
189
+ className: m.className,
190
+ importPath: m.filePath,
191
+ },
192
+ scope: 'singleton',
193
+ eager: false,
194
+ name: undefined,
195
+ constructorDeps: [],
196
+ fieldDeps: [],
197
+ factoryKind: 'constructor',
198
+ providesSource: undefined,
199
+ metadata: {},
200
+ sourceLocation: {
201
+ filePath: m.filePath,
202
+ line: 0,
203
+ column: 0,
204
+ },
205
+ });
206
+ migrationDeps.push({
207
+ tokenRef: {
208
+ kind: 'class',
209
+ className: m.className,
210
+ importPath: m.filePath,
211
+ },
212
+ optional: false,
213
+ collection: false,
214
+ sourceLocation: {
215
+ filePath: '@goodie-ts/kysely',
216
+ line: 0,
217
+ column: 0,
218
+ },
219
+ });
220
+ }
221
+ // MigrationRunner: eager singleton, depends on KyselyProvider + individual migration deps
222
+ syntheticBeans.push({
223
+ tokenRef: {
224
+ kind: 'class',
225
+ className: 'MigrationRunner',
226
+ importPath: '@goodie-ts/kysely',
227
+ },
228
+ scope: 'singleton',
229
+ eager: true,
230
+ name: undefined,
231
+ constructorDeps: [kyselyProviderDep[0], ...migrationDeps],
232
+ fieldDeps: [],
233
+ factoryKind: 'constructor',
234
+ providesSource: undefined,
235
+ metadata: { postConstructMethods: ['migrate'] },
236
+ sourceLocation: {
237
+ filePath: '@goodie-ts/kysely',
238
+ line: 0,
239
+ column: 0,
240
+ },
241
+ });
242
+ }
243
+ }
244
+ return [...beans, ...syntheticBeans];
245
+ },
246
+ codegen(beans) {
247
+ const hasTransactional = beans.some((b) => {
248
+ const methods = b.metadata.interceptedMethods;
249
+ return methods?.some((m) => m.interceptors.some((i) => i.className === 'TransactionalInterceptor'));
250
+ });
251
+ if (!hasTransactional)
252
+ return {};
253
+ // TransactionManager and TransactionalInterceptor imports are already
254
+ // generated by collectClassImports (they are synthetic bean tokens).
255
+ // Only need to contribute the buildInterceptorChain import.
256
+ return {
257
+ imports: ["import { buildInterceptorChain } from '@goodie-ts/aop'"],
258
+ };
259
+ },
260
+ };
261
+ }
262
+ /**
263
+ * Resolve which bean provides the Kysely instance for TransactionManager auto-wiring.
264
+ *
265
+ * Priority:
266
+ * 1. Explicit `database` option (string class name)
267
+ * 2. Auto-detected class with a `Kysely<...>` property (single match)
268
+ * 3. No wiring (zero matches, or multiple matches without explicit option)
269
+ */
270
+ function resolveKyselyProvider(beans, explicitDatabase, autoDetected) {
271
+ // Determine target class name
272
+ let targetClassName;
273
+ if (explicitDatabase) {
274
+ targetClassName = explicitDatabase;
275
+ }
276
+ else if (autoDetected.length === 1) {
277
+ targetClassName = autoDetected[0].className;
278
+ }
279
+ else if (autoDetected.length > 1) {
280
+ const names = autoDetected.map((p) => p.className).join(', ');
281
+ console.warn(`[@goodie-ts/kysely] Multiple Kysely providers detected: ${names}. ` +
282
+ "Use createKyselyPlugin({ database: 'ClassName' }) to disambiguate. " +
283
+ 'TransactionManager will require manual configure().');
284
+ return [];
285
+ }
286
+ else {
287
+ // Zero providers — no auto-wiring
288
+ return [];
289
+ }
290
+ // Find the bean matching the target class name
291
+ const databaseBean = beans.find((b) => b.tokenRef.kind === 'class' && b.tokenRef.className === targetClassName);
292
+ if (databaseBean && databaseBean.tokenRef.kind === 'class') {
293
+ return [
294
+ {
295
+ tokenRef: {
296
+ kind: 'class',
297
+ className: databaseBean.tokenRef.className,
298
+ importPath: databaseBean.tokenRef.importPath,
299
+ },
300
+ optional: false,
301
+ collection: false,
302
+ sourceLocation: {
303
+ filePath: '@goodie-ts/kysely',
304
+ line: 0,
305
+ column: 0,
306
+ },
307
+ },
308
+ ];
309
+ }
310
+ console.warn(`[@goodie-ts/kysely] Database class '${targetClassName}' not found in beans. ` +
311
+ 'TransactionManager will require manual configure().');
312
+ return [];
313
+ }
314
+ export default createKyselyPlugin;
315
+ //# sourceMappingURL=kysely-transformer-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kysely-transformer-plugin.js","sourceRoot":"","sources":["../src/kysely-transformer-plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { AbstractMigration } from './abstract-migration.js';
2
+ import type { KyselyProvider } from './transaction-manager.js';
3
+ /**
4
+ * Runs Kysely migrations at application startup.
5
+ *
6
+ * Auto-wired by the Kysely transformer plugin as an eager singleton
7
+ * with `@PostConstruct` on `migrate()`. Constructor receives the
8
+ * KyselyProvider and all discovered @Migration instances.
9
+ */
10
+ export declare class MigrationRunner {
11
+ private readonly kyselyProvider;
12
+ private readonly migrations;
13
+ constructor(kyselyProvider: KyselyProvider, ...migrations: AbstractMigration[]);
14
+ migrate(): Promise<void>;
15
+ }
16
+ //# sourceMappingURL=migration-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration-runner.d.ts","sourceRoot":"","sources":["../src/migration-runner.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAEjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE/D;;;;;;GAMG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAsB;gBAG/C,cAAc,EAAE,cAAc,EAC9B,GAAG,UAAU,EAAE,iBAAiB,EAAE;IAM9B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAgC/B"}
@@ -0,0 +1,45 @@
1
+ import { Migrator } from 'kysely';
2
+ import { getMigrationName } from './decorators/migration.js';
3
+ /**
4
+ * Runs Kysely migrations at application startup.
5
+ *
6
+ * Auto-wired by the Kysely transformer plugin as an eager singleton
7
+ * with `@PostConstruct` on `migrate()`. Constructor receives the
8
+ * KyselyProvider and all discovered @Migration instances.
9
+ */
10
+ export class MigrationRunner {
11
+ kyselyProvider;
12
+ migrations;
13
+ constructor(kyselyProvider, ...migrations) {
14
+ this.kyselyProvider = kyselyProvider;
15
+ this.migrations = migrations;
16
+ }
17
+ async migrate() {
18
+ const migrationMap = {};
19
+ for (const m of this.migrations) {
20
+ const name = getMigrationName(m);
21
+ if (!name) {
22
+ throw new Error(`MigrationRunner received an object without @Migration metadata: ${m.constructor.name}`);
23
+ }
24
+ // Kysely's Migrator spreads migration objects (...migration), which
25
+ // drops prototype methods from class instances. Bind explicitly.
26
+ migrationMap[name] = {
27
+ up: m.up.bind(m),
28
+ down: m.down?.bind(m),
29
+ };
30
+ }
31
+ const migrator = new Migrator({
32
+ db: this.kyselyProvider.kysely,
33
+ provider: { getMigrations: async () => migrationMap },
34
+ });
35
+ const { error, results } = await migrator.migrateToLatest();
36
+ for (const r of results ?? []) {
37
+ if (r.status === 'Error') {
38
+ console.error(`Migration "${r.migrationName}" failed`);
39
+ }
40
+ }
41
+ if (error)
42
+ throw error;
43
+ }
44
+ }
45
+ //# sourceMappingURL=migration-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration-runner.js","sourceRoot":"","sources":["../src/migration-runner.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAElC,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAG7D;;;;;;GAMG;AACH,MAAM,OAAO,eAAe;IACT,cAAc,CAAiB;IAC/B,UAAU,CAAsB;IAEjD,YACE,cAA8B,EAC9B,GAAG,UAA+B;QAElC,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;QACrC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,YAAY,GAAoC,EAAE,CAAC;QACzD,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YAChC,MAAM,IAAI,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC;YACjC,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,IAAI,KAAK,CACb,mEAAmE,CAAC,CAAC,WAAW,CAAC,IAAI,EAAE,CACxF,CAAC;YACJ,CAAC;YACD,oEAAoE;YACpE,iEAAiE;YACjE,YAAY,CAAC,IAAI,CAAC,GAAG;gBACnB,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;gBAChB,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;aACtB,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC;YAC5B,EAAE,EAAE,IAAI,CAAC,cAAc,CAAC,MAAM;YAC9B,QAAQ,EAAE,EAAE,aAAa,EAAE,KAAK,IAAI,EAAE,CAAC,YAAY,EAAE;SACtD,CAAC,CAAC;QAEH,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,MAAM,QAAQ,CAAC,eAAe,EAAE,CAAC;QAE5D,KAAK,MAAM,CAAC,IAAI,OAAO,IAAI,EAAE,EAAE,CAAC;YAC9B,IAAI,CAAC,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;gBACzB,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,aAAa,UAAU,CAAC,CAAC;YACzD,CAAC;QACH,CAAC;QAED,IAAI,KAAK;YAAE,MAAM,KAAK,CAAC;IACzB,CAAC;CACF"}
@@ -0,0 +1,59 @@
1
+ import type { Kysely, Transaction } from 'kysely';
2
+ /**
3
+ * An object that exposes a `.kysely` property — e.g. a Database wrapper class.
4
+ * Used for duck-type detection in the TransactionManager constructor.
5
+ */
6
+ export interface KyselyProvider {
7
+ kysely: Kysely<any>;
8
+ }
9
+ /**
10
+ * Manages database transactions using AsyncLocalStorage.
11
+ *
12
+ * Provides transaction propagation across async call chains without
13
+ * explicitly threading a transaction object through every method.
14
+ *
15
+ * When auto-wired via `createKyselyPlugin({ database: 'Database' })`,
16
+ * the constructor receives the Database bean and reads its `.kysely` property.
17
+ * Manual `configure()` is still supported for backward compatibility.
18
+ */
19
+ export declare class TransactionManager {
20
+ private readonly storage;
21
+ private kyselyRef?;
22
+ private testTransactionActive;
23
+ constructor(kyselyOrProvider?: Kysely<any> | KyselyProvider);
24
+ /**
25
+ * Configure the Kysely instance used for transactions.
26
+ * Unnecessary when auto-wired via `createKyselyPlugin({ database: '...' })`.
27
+ */
28
+ configure(kysely: Kysely<any>): void;
29
+ private get kysely();
30
+ /**
31
+ * Run a function inside a transaction.
32
+ *
33
+ * - If already in a transaction (REQUIRED propagation), reuses it.
34
+ * - If `requiresNew` is true, always starts a fresh transaction.
35
+ */
36
+ runInTransaction<T>(fn: () => Promise<T>, requiresNew?: boolean): Promise<T>;
37
+ /** Get the current transaction, or undefined if not in one. */
38
+ currentTransaction(): Transaction<any> | undefined;
39
+ /**
40
+ * Get the current database connection — transaction-aware.
41
+ * Returns the active transaction if inside one, otherwise the raw Kysely instance.
42
+ */
43
+ getConnection(): Kysely<any>;
44
+ /**
45
+ * Start a test-scoped transaction that ALL queries will use.
46
+ *
47
+ * Replaces the internal Kysely reference with the transaction object,
48
+ * so any code calling `getConnection()` or `database.kysely` (via the
49
+ * provider proxy) will automatically use this transaction.
50
+ *
51
+ * Designed for test frameworks (e.g. vitest fixtures) where
52
+ * AsyncLocalStorage context may not propagate through `use()`.
53
+ *
54
+ * @returns A rollback function — call it to roll back the transaction
55
+ * and restore the original Kysely instance.
56
+ */
57
+ startTestTransaction(): Promise<() => Promise<void>>;
58
+ }
59
+ //# sourceMappingURL=transaction-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transaction-manager.d.ts","sourceRoot":"","sources":["../src/transaction-manager.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAElD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6C;IACrE,OAAO,CAAC,SAAS,CAAC,CAAc;IAChC,OAAO,CAAC,qBAAqB,CAAS;gBAE1B,gBAAgB,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,cAAc;IAoB3D;;;OAGG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI;IAIpC,OAAO,KAAK,MAAM,GAOjB;IAED;;;;;OAKG;IACG,gBAAgB,CAAC,CAAC,EACtB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,WAAW,UAAQ,GAClB,OAAO,CAAC,CAAC,CAAC;IAiBb,+DAA+D;IAC/D,kBAAkB,IAAI,WAAW,CAAC,GAAG,CAAC,GAAG,SAAS;IAIlD;;;OAGG;IACH,aAAa,IAAI,MAAM,CAAC,GAAG,CAAC;IAI5B;;;;;;;;;;;;OAYG;IACG,oBAAoB,IAAI,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAmC3D"}
@@ -0,0 +1,132 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ /**
3
+ * Manages database transactions using AsyncLocalStorage.
4
+ *
5
+ * Provides transaction propagation across async call chains without
6
+ * explicitly threading a transaction object through every method.
7
+ *
8
+ * When auto-wired via `createKyselyPlugin({ database: 'Database' })`,
9
+ * the constructor receives the Database bean and reads its `.kysely` property.
10
+ * Manual `configure()` is still supported for backward compatibility.
11
+ */
12
+ export class TransactionManager {
13
+ storage = new AsyncLocalStorage();
14
+ kyselyRef;
15
+ testTransactionActive = false;
16
+ constructor(kyselyOrProvider) {
17
+ if (kyselyOrProvider) {
18
+ if ('kysely' in kyselyOrProvider) {
19
+ this.kyselyRef = kyselyOrProvider.kysely;
20
+ // Make the provider's .kysely property transaction-aware.
21
+ // Any code accessing provider.kysely (e.g. database.kysely) will
22
+ // automatically use the active transaction when inside one.
23
+ const tm = this;
24
+ Object.defineProperty(kyselyOrProvider, 'kysely', {
25
+ get() {
26
+ return tm.getConnection();
27
+ },
28
+ configurable: true,
29
+ });
30
+ }
31
+ else {
32
+ this.kyselyRef = kyselyOrProvider;
33
+ }
34
+ }
35
+ }
36
+ /**
37
+ * Configure the Kysely instance used for transactions.
38
+ * Unnecessary when auto-wired via `createKyselyPlugin({ database: '...' })`.
39
+ */
40
+ configure(kysely) {
41
+ this.kyselyRef = kysely;
42
+ }
43
+ get kysely() {
44
+ if (!this.kyselyRef) {
45
+ throw new Error('TransactionManager not configured. Call transactionManager.configure(kysely) or use createKyselyPlugin({ database: "YourDatabase" }).');
46
+ }
47
+ return this.kyselyRef;
48
+ }
49
+ /**
50
+ * Run a function inside a transaction.
51
+ *
52
+ * - If already in a transaction (REQUIRED propagation), reuses it.
53
+ * - If `requiresNew` is true, always starts a fresh transaction.
54
+ */
55
+ async runInTransaction(fn, requiresNew = false) {
56
+ // Inside a test transaction, all queries already use the test transaction.
57
+ // Skip creating new transactions to avoid Kysely's nested transaction error.
58
+ if (this.testTransactionActive) {
59
+ return fn();
60
+ }
61
+ const existing = this.storage.getStore();
62
+ if (existing && !requiresNew) {
63
+ return fn();
64
+ }
65
+ return this.kysely.transaction().execute(async (trx) => {
66
+ return this.storage.run(trx, fn);
67
+ });
68
+ }
69
+ /** Get the current transaction, or undefined if not in one. */
70
+ currentTransaction() {
71
+ return this.storage.getStore();
72
+ }
73
+ /**
74
+ * Get the current database connection — transaction-aware.
75
+ * Returns the active transaction if inside one, otherwise the raw Kysely instance.
76
+ */
77
+ getConnection() {
78
+ return this.currentTransaction() ?? this.kysely;
79
+ }
80
+ /**
81
+ * Start a test-scoped transaction that ALL queries will use.
82
+ *
83
+ * Replaces the internal Kysely reference with the transaction object,
84
+ * so any code calling `getConnection()` or `database.kysely` (via the
85
+ * provider proxy) will automatically use this transaction.
86
+ *
87
+ * Designed for test frameworks (e.g. vitest fixtures) where
88
+ * AsyncLocalStorage context may not propagate through `use()`.
89
+ *
90
+ * @returns A rollback function — call it to roll back the transaction
91
+ * and restore the original Kysely instance.
92
+ */
93
+ async startTestTransaction() {
94
+ const original = this.kyselyRef;
95
+ let triggerRollback;
96
+ let transactionReady;
97
+ const readyPromise = new Promise((resolve) => {
98
+ transactionReady = resolve;
99
+ });
100
+ const rollbackPromise = this.kysely
101
+ .transaction()
102
+ .execute(async (trx) => {
103
+ this.kyselyRef = trx;
104
+ this.testTransactionActive = true;
105
+ transactionReady();
106
+ await new Promise((_resolve, reject) => {
107
+ triggerRollback = () => reject(new TestRollbackSignal());
108
+ });
109
+ })
110
+ .catch((e) => {
111
+ if (!(e instanceof TestRollbackSignal))
112
+ throw e;
113
+ })
114
+ .finally(() => {
115
+ this.testTransactionActive = false;
116
+ this.kyselyRef = original;
117
+ });
118
+ await readyPromise;
119
+ return async () => {
120
+ triggerRollback();
121
+ await rollbackPromise;
122
+ };
123
+ }
124
+ }
125
+ /** @internal Sentinel error for test transaction rollback. */
126
+ class TestRollbackSignal extends Error {
127
+ constructor() {
128
+ super('TestRollbackSignal');
129
+ this.name = 'TestRollbackSignal';
130
+ }
131
+ }
132
+ //# sourceMappingURL=transaction-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transaction-manager.js","sourceRoot":"","sources":["../src/transaction-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { InvocationContext, MethodInterceptor } from '@goodie-ts/aop';
2
+ import type { TransactionManager } from './transaction-manager.js';
3
+ /**
4
+ * AOP interceptor that wraps method execution in a database transaction.
5
+ *
6
+ * Reads propagation strategy from `ctx.metadata` (set by the kysely
7
+ * transformer plugin).
8
+ */
9
+ export declare class TransactionalInterceptor implements MethodInterceptor {
10
+ private readonly transactionManager;
11
+ constructor(transactionManager: TransactionManager);
12
+ intercept(ctx: InvocationContext): unknown;
13
+ }
14
+ //# sourceMappingURL=transactional-interceptor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transactional-interceptor.d.ts","sourceRoot":"","sources":["../src/transactional-interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC3E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAOnE;;;;;GAKG;AACH,qBAAa,wBAAyB,YAAW,iBAAiB;IACpD,OAAO,CAAC,QAAQ,CAAC,kBAAkB;gBAAlB,kBAAkB,EAAE,kBAAkB;IAEnE,SAAS,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO;CAY3C"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * AOP interceptor that wraps method execution in a database transaction.
3
+ *
4
+ * Reads propagation strategy from `ctx.metadata` (set by the kysely
5
+ * transformer plugin).
6
+ */
7
+ export class TransactionalInterceptor {
8
+ transactionManager;
9
+ constructor(transactionManager) {
10
+ this.transactionManager = transactionManager;
11
+ }
12
+ intercept(ctx) {
13
+ const meta = ctx.metadata;
14
+ if (!meta)
15
+ return ctx.proceed();
16
+ const requiresNew = meta.propagation === 'REQUIRES_NEW';
17
+ return this.transactionManager.runInTransaction(async () => {
18
+ // Must await so rejected promises propagate within the transaction scope,
19
+ // ensuring Kysely rolls back on failure.
20
+ return await ctx.proceed();
21
+ }, requiresNew);
22
+ }
23
+ }
24
+ //# sourceMappingURL=transactional-interceptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transactional-interceptor.js","sourceRoot":"","sources":["../src/transactional-interceptor.ts"],"names":[],"mappings":"AAQA;;;;;GAKG;AACH,MAAM,OAAO,wBAAwB;IACN;IAA7B,YAA6B,kBAAsC;QAAtC,uBAAkB,GAAlB,kBAAkB,CAAoB;IAAG,CAAC;IAEvE,SAAS,CAAC,GAAsB;QAC9B,MAAM,IAAI,GAAG,GAAG,CAAC,QAA6C,CAAC;QAC/D,IAAI,CAAC,IAAI;YAAE,OAAO,GAAG,CAAC,OAAO,EAAE,CAAC;QAEhC,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,KAAK,cAAc,CAAC;QAExD,OAAO,IAAI,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,KAAK,IAAI,EAAE;YACzD,0EAA0E;YAC1E,yCAAyC;YACzC,OAAO,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC;QAC7B,CAAC,EAAE,WAAW,CAAC,CAAC;IAClB,CAAC;CACF"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@goodie-ts/kysely",
3
+ "version": "0.4.0",
4
+ "description": "Kysely integration for goodie-ts — @Transactional decorator, TransactionManager, CrudRepository",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/GOOD-Code-ApS/goodie.git",
10
+ "directory": "packages/kysely"
11
+ },
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "goodie": {
16
+ "plugin": "./dist/kysely-transformer-plugin.js"
17
+ },
18
+ "main": "dist/index.js",
19
+ "types": "dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ }
25
+ },
26
+ "scripts": {
27
+ "build": "tsc",
28
+ "clean": "rm -rf dist *.tsbuildinfo"
29
+ },
30
+ "dependencies": {
31
+ "@goodie-ts/aop": "workspace:*"
32
+ },
33
+ "peerDependencies": {
34
+ "@goodie-ts/transformer": "workspace:*",
35
+ "kysely": ">=0.27.0"
36
+ },
37
+ "peerDependenciesMeta": {
38
+ "@goodie-ts/transformer": {
39
+ "optional": true
40
+ }
41
+ },
42
+ "devDependencies": {
43
+ "@goodie-ts/transformer": "workspace:*",
44
+ "@types/node": "^22.0.0",
45
+ "kysely": "^0.27.0",
46
+ "ts-morph": "^24.0.0"
47
+ },
48
+ "files": [
49
+ "dist"
50
+ ]
51
+ }