@geekmidas/testkit 0.0.6 → 0.0.8

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 (97) hide show
  1. package/dist/Factory-WMhTNZ9S.cjs +56 -0
  2. package/dist/Factory-z2m01hMj.mjs +50 -0
  3. package/dist/Factory.cjs +1 -1
  4. package/dist/Factory.mjs +1 -1
  5. package/dist/KyselyFactory-Bdq1s1Go.cjs +215 -0
  6. package/dist/KyselyFactory-ELiHgHVv.mjs +210 -0
  7. package/dist/KyselyFactory.cjs +3 -3
  8. package/dist/KyselyFactory.mjs +3 -3
  9. package/dist/ObjectionFactory-89p-FFEw.mjs +178 -0
  10. package/dist/ObjectionFactory-C47B03Ot.cjs +183 -0
  11. package/dist/ObjectionFactory.cjs +2 -2
  12. package/dist/ObjectionFactory.mjs +2 -2
  13. package/dist/PostgresKyselyMigrator-Bs31emFd.cjs +87 -0
  14. package/dist/PostgresKyselyMigrator-ChIpZFYB.mjs +81 -0
  15. package/dist/PostgresKyselyMigrator.cjs +2 -2
  16. package/dist/PostgresKyselyMigrator.mjs +2 -2
  17. package/dist/PostgresMigrator-BtAWdLss.cjs +151 -0
  18. package/dist/PostgresMigrator-BzqksJcW.mjs +145 -0
  19. package/dist/PostgresMigrator.cjs +1 -1
  20. package/dist/PostgresMigrator.mjs +1 -1
  21. package/dist/VitestKyselyTransactionIsolator-AfxPJEwR.mjs +58 -0
  22. package/dist/VitestKyselyTransactionIsolator-YWnSJiIH.cjs +63 -0
  23. package/dist/VitestKyselyTransactionIsolator.cjs +2 -2
  24. package/dist/VitestKyselyTransactionIsolator.mjs +2 -2
  25. package/dist/VitestObjectionTransactionIsolator-0uX6DW5G.cjs +66 -0
  26. package/dist/VitestObjectionTransactionIsolator-BZRYy8iW.mjs +61 -0
  27. package/dist/VitestObjectionTransactionIsolator.cjs +4 -0
  28. package/dist/VitestObjectionTransactionIsolator.mjs +4 -0
  29. package/dist/VitestTransactionIsolator-DcOz0LZF.cjs +129 -0
  30. package/dist/VitestTransactionIsolator-kFL36T8x.mjs +117 -0
  31. package/dist/VitestTransactionIsolator.cjs +1 -1
  32. package/dist/VitestTransactionIsolator.mjs +1 -1
  33. package/dist/__tests__/Factory.spec.cjs +1 -1
  34. package/dist/__tests__/Factory.spec.mjs +1 -1
  35. package/dist/__tests__/KyselyFactory.spec.cjs +10 -10
  36. package/dist/__tests__/KyselyFactory.spec.mjs +10 -10
  37. package/dist/__tests__/ObjectionFactory.spec.cjs +3 -3
  38. package/dist/__tests__/ObjectionFactory.spec.mjs +3 -3
  39. package/dist/__tests__/PostgresMigrator.spec.cjs +2 -2
  40. package/dist/__tests__/PostgresMigrator.spec.mjs +2 -2
  41. package/dist/__tests__/faker.spec.cjs +1 -1
  42. package/dist/__tests__/faker.spec.mjs +1 -1
  43. package/dist/__tests__/integration.spec.cjs +10 -10
  44. package/dist/__tests__/integration.spec.mjs +10 -10
  45. package/dist/example.cjs +3 -3
  46. package/dist/example.mjs +3 -3
  47. package/dist/faker-CxKkEeYi.mjs +227 -0
  48. package/dist/faker-SMN4ira4.cjs +263 -0
  49. package/dist/faker.cjs +1 -1
  50. package/dist/faker.mjs +1 -1
  51. package/dist/helpers-CKMlwSYT.mjs +47 -0
  52. package/dist/helpers-H4hO5SZR.cjs +53 -0
  53. package/dist/helpers.cjs +1 -1
  54. package/dist/helpers.mjs +1 -1
  55. package/dist/kysely-B-GOhABm.cjs +72 -0
  56. package/dist/kysely-CqfoKVXs.mjs +67 -0
  57. package/dist/kysely.cjs +10 -8
  58. package/dist/kysely.mjs +9 -9
  59. package/dist/objection.cjs +86 -3
  60. package/dist/objection.mjs +83 -3
  61. package/package.json +2 -2
  62. package/src/Factory.ts +97 -0
  63. package/src/KyselyFactory.ts +180 -0
  64. package/src/ObjectionFactory.ts +145 -3
  65. package/src/PostgresKyselyMigrator.ts +54 -0
  66. package/src/PostgresMigrator.ts +90 -0
  67. package/src/VitestKyselyTransactionIsolator.ts +46 -0
  68. package/src/VitestObjectionTransactionIsolator.ts +74 -0
  69. package/src/VitestTransactionIsolator.ts +95 -0
  70. package/src/__tests__/VitestObjectionTransactionIsolator.spec.ts +144 -0
  71. package/src/faker.ts +158 -7
  72. package/src/helpers.ts +34 -0
  73. package/src/kysely.ts +63 -0
  74. package/src/objection.ts +95 -0
  75. package/test/helpers.ts +3 -1
  76. package/dist/Factory-DREHoms3.cjs +0 -15
  77. package/dist/Factory-DlzMkMzb.mjs +0 -9
  78. package/dist/KyselyFactory-BX7Kv2uP.cjs +0 -65
  79. package/dist/KyselyFactory-pOMOFQWE.mjs +0 -60
  80. package/dist/ObjectionFactory-BlkzSEqo.cjs +0 -41
  81. package/dist/ObjectionFactory-ChuX8sZN.mjs +0 -36
  82. package/dist/PostgresKyselyMigrator-D8fm35-s.mjs +0 -27
  83. package/dist/PostgresKyselyMigrator-JTY2LfwD.cjs +0 -33
  84. package/dist/PostgresMigrator-Bz-tnjB6.cjs +0 -67
  85. package/dist/PostgresMigrator-CEoRKTdq.mjs +0 -61
  86. package/dist/VitestKyselyTransactionIsolator-D-qpeVKO.mjs +0 -12
  87. package/dist/VitestKyselyTransactionIsolator-jF6Ohyu_.cjs +0 -17
  88. package/dist/VitestTransactionIsolator-BK9UsrKt.cjs +0 -53
  89. package/dist/VitestTransactionIsolator-e-R3p_X8.mjs +0 -41
  90. package/dist/faker-BwaXA_RF.mjs +0 -85
  91. package/dist/faker-caz-8zt8.cjs +0 -121
  92. package/dist/helpers-DN4sJO4i.mjs +0 -13
  93. package/dist/helpers-DOtYCEvZ.cjs +0 -19
  94. package/dist/kysely-C1-aHdnU.mjs +0 -11
  95. package/dist/kysely-DL3C2eM4.cjs +0 -16
  96. /package/dist/{helpers-B2CfbaTC.cjs → helpers-Bnm3Jy9X.cjs} +0 -0
  97. /package/dist/{helpers-Rf5F71r9.mjs → helpers-CukcFAU9.mjs} +0 -0
@@ -1,14 +1,65 @@
1
1
  import type { Knex } from 'knex';
2
2
  import { Factory, type FactorySeed } from './Factory.ts';
3
3
 
4
+ /**
5
+ * Factory implementation for Objection.js ORM, providing test data creation utilities.
6
+ * Extends the base Factory class with Objection.js-specific database operations.
7
+ *
8
+ * @template Builders - Record of builder functions for creating entities
9
+ * @template Seeds - Record of seed functions for complex test scenarios
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * // Define your models with Objection.js
14
+ * class User extends Model {
15
+ * static tableName = 'users';
16
+ * }
17
+ *
18
+ * // Create builders
19
+ * const builders = {
20
+ * user: (attrs) => User.fromJson({
21
+ * id: faker.string.uuid(),
22
+ * name: faker.person.fullName(),
23
+ * email: faker.internet.email(),
24
+ * ...attrs
25
+ * }),
26
+ * post: (attrs) => Post.fromJson({
27
+ * title: 'Test Post',
28
+ * content: 'Test content',
29
+ * ...attrs
30
+ * })
31
+ * };
32
+ *
33
+ * // Create factory instance
34
+ * const factory = new ObjectionFactory(builders, seeds, knex);
35
+ *
36
+ * // Use in tests
37
+ * const user = await factory.insert('user', { name: 'John Doe' });
38
+ * ```
39
+ */
4
40
  export class ObjectionFactory<
5
41
  Builders extends Record<string, any>,
6
42
  Seeds extends Record<string, any>,
7
43
  > extends Factory<Builders, Seeds> {
44
+ /**
45
+ * Creates a typed seed function with proper type inference.
46
+ * Inherits from the base Factory class implementation.
47
+ *
48
+ * @template Seed - The seed function type
49
+ * @param seedFn - The seed function to wrap
50
+ * @returns The same seed function with proper typing
51
+ */
8
52
  static createSeed<Seed extends FactorySeed>(seedFn: Seed): Seed {
9
53
  return Factory.createSeed(seedFn);
10
54
  }
11
55
 
56
+ /**
57
+ * Creates a new ObjectionFactory instance.
58
+ *
59
+ * @param builders - Record of builder functions for creating individual entities
60
+ * @param seeds - Record of seed functions for creating complex test scenarios
61
+ * @param db - Knex database connection instance
62
+ */
12
63
  constructor(
13
64
  private builders: Builders,
14
65
  private seeds: Seeds,
@@ -17,7 +68,37 @@ export class ObjectionFactory<
17
68
  super();
18
69
  }
19
70
 
20
- insert(factory, attrs = {}) {
71
+ /**
72
+ * Inserts a single record into the database using the specified builder.
73
+ * Uses Objection.js's insertGraph method to handle nested relations.
74
+ *
75
+ * @param factory - The name of the builder to use
76
+ * @param attrs - Optional attributes to override builder defaults
77
+ * @returns A promise resolving to the inserted record with all relations
78
+ * @throws Error if the specified builder doesn't exist
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * // Insert with defaults
83
+ * const user = await factory.insert('user');
84
+ *
85
+ * // Insert with overrides
86
+ * const adminUser = await factory.insert('user', {
87
+ * email: 'admin@example.com',
88
+ * role: 'admin'
89
+ * });
90
+ *
91
+ * // Insert with nested relations
92
+ * const userWithProfile = await factory.insert('user', {
93
+ * name: 'John Doe',
94
+ * profile: {
95
+ * bio: 'Software Developer',
96
+ * avatar: 'avatar.jpg'
97
+ * }
98
+ * });
99
+ * ```
100
+ */
101
+ insert(factory: keyof Builders, attrs: any = {}) {
21
102
  if (!(factory in this.builders)) {
22
103
  throw new Error(
23
104
  `Factory "${
@@ -30,7 +111,36 @@ export class ObjectionFactory<
30
111
  return record.$query(this.db).insertGraph(record).execute();
31
112
  }) as any;
32
113
  }
33
- insertMany(count, builderName, attrs = {}) {
114
+ /**
115
+ * Inserts multiple records into the database using the specified builder.
116
+ * Supports both static attributes and dynamic attribute generation via a function.
117
+ *
118
+ * @param count - The number of records to insert
119
+ * @param builderName - The name of the builder to use
120
+ * @param attrs - Static attributes or a function that generates attributes for each record
121
+ * @returns A promise resolving to an array of inserted records
122
+ * @throws Error if the specified builder doesn't exist
123
+ *
124
+ * @example
125
+ * ```typescript
126
+ * // Insert multiple with same attributes
127
+ * const users = await factory.insertMany(5, 'user', { role: 'member' });
128
+ *
129
+ * // Insert multiple with dynamic attributes
130
+ * const posts = await factory.insertMany(10, 'post', (idx) => ({
131
+ * title: `Post ${idx + 1}`,
132
+ * content: `Content for post ${idx + 1}`,
133
+ * publishedAt: new Date()
134
+ * }));
135
+ *
136
+ * // Create users with sequential emails
137
+ * const admins = await factory.insertMany(3, 'user', (idx) => ({
138
+ * email: `admin${idx + 1}@example.com`,
139
+ * role: 'admin'
140
+ * }));
141
+ * ```
142
+ */
143
+ insertMany(count: number, builderName: keyof Builders, attrs: any = {}) {
34
144
  if (!(builderName in this.builders)) {
35
145
  throw new Error(
36
146
  `Builder "${
@@ -52,7 +162,39 @@ export class ObjectionFactory<
52
162
 
53
163
  return Promise.all(records);
54
164
  }
55
- seed(seedName, attrs = {}) {
165
+ /**
166
+ * Executes a seed function to create complex test scenarios with multiple related records.
167
+ * Seeds are useful for setting up complete test environments with realistic data relationships.
168
+ *
169
+ * @param seedName - The name of the seed to execute
170
+ * @param attrs - Optional configuration attributes for the seed
171
+ * @returns The result of the seed function (typically the primary record created)
172
+ * @throws Error if the specified seed doesn't exist
173
+ *
174
+ * @example
175
+ * ```typescript
176
+ * // Execute a simple seed
177
+ * const user = await factory.seed('userWithProfile');
178
+ *
179
+ * // Execute a seed with configuration
180
+ * const author = await factory.seed('authorWithBooks', {
181
+ * bookCount: 5,
182
+ * includeReviews: true
183
+ * });
184
+ *
185
+ * // Use seed result in tests with Objection.js relations
186
+ * const company = await factory.seed('companyWithDepartments', {
187
+ * departmentCount: 3,
188
+ * employeesPerDepartment: 10
189
+ * });
190
+ *
191
+ * // Access eager loaded relations
192
+ * const companyWithRelations = await Company.query()
193
+ * .findById(company.id)
194
+ * .withGraphFetched('[departments.employees]');
195
+ * ```
196
+ */
197
+ seed(seedName: keyof Seeds, attrs: any = {}) {
56
198
  if (!(seedName in this.seeds)) {
57
199
  throw new Error(
58
200
  `Seed "${
@@ -1,9 +1,56 @@
1
1
  import { type Kysely, type MigrationProvider, Migrator } from 'kysely';
2
2
  import { PostgresMigrator } from './PostgresMigrator';
3
3
 
4
+ /**
5
+ * Default logger instance for migration operations.
6
+ */
4
7
  const logger = console;
5
8
 
9
+ /**
10
+ * PostgreSQL migrator implementation for Kysely ORM.
11
+ * Extends PostgresMigrator to provide Kysely-specific migration functionality.
12
+ * Automatically creates test databases and applies migrations for testing environments.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { FileMigrationProvider } from 'kysely';
17
+ * import { PostgresKyselyMigrator } from '@geekmidas/testkit';
18
+ *
19
+ * // Create migration provider
20
+ * const provider = new FileMigrationProvider({
21
+ * fs: require('fs'),
22
+ * path: require('path'),
23
+ * migrationFolder: path.join(__dirname, 'migrations')
24
+ * });
25
+ *
26
+ * // Create Kysely instance
27
+ * const db = new Kysely<Database>({
28
+ * dialect: new PostgresDialect({
29
+ * pool: new Pool({ connectionString: uri })
30
+ * })
31
+ * });
32
+ *
33
+ * // Create and use migrator
34
+ * const migrator = new PostgresKyselyMigrator({
35
+ * uri: 'postgresql://localhost:5432/test_db',
36
+ * db,
37
+ * provider
38
+ * });
39
+ *
40
+ * const cleanup = await migrator.start();
41
+ * // Run tests...
42
+ * await cleanup();
43
+ * ```
44
+ */
6
45
  export class PostgresKyselyMigrator extends PostgresMigrator {
46
+ /**
47
+ * Creates a new PostgresKyselyMigrator instance.
48
+ *
49
+ * @param options - Configuration options
50
+ * @param options.uri - PostgreSQL connection URI
51
+ * @param options.db - Kysely database instance
52
+ * @param options.provider - Migration provider for locating migration files
53
+ */
7
54
  constructor(
8
55
  private options: {
9
56
  uri: string;
@@ -14,6 +61,13 @@ export class PostgresKyselyMigrator extends PostgresMigrator {
14
61
  super(options.uri);
15
62
  }
16
63
 
64
+ /**
65
+ * Executes Kysely migrations to the latest version.
66
+ * Implements the abstract migrate() method from PostgresMigrator.
67
+ *
68
+ * @throws Error if migrations fail to apply
69
+ * @returns Promise that resolves when all migrations are applied
70
+ */
17
71
  async migrate(): Promise<void> {
18
72
  const migrator = new Migrator({
19
73
  db: this.options.db,
@@ -1,5 +1,19 @@
1
1
  import { Client } from 'pg';
2
2
 
3
+ /**
4
+ * Creates a PostgreSQL client connected to the 'postgres' database.
5
+ * Extracts connection details from the provided URI.
6
+ *
7
+ * @param uri - PostgreSQL connection URI
8
+ * @returns Object containing the target database name and client instance
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * const { database, db } = await setupClient('postgresql://user:pass@localhost:5432/mydb');
13
+ * // database = 'mydb'
14
+ * // db = Client instance connected to 'postgres' database
15
+ * ```
16
+ */
3
17
  async function setupClient(uri: string) {
4
18
  const url = new URL(uri);
5
19
 
@@ -18,13 +32,59 @@ async function setupClient(uri: string) {
18
32
  return { database, db };
19
33
  }
20
34
 
35
+ /**
36
+ * Default logger instance for migration operations.
37
+ */
21
38
  const logger = console;
22
39
 
40
+ /**
41
+ * Abstract base class for PostgreSQL database migration utilities.
42
+ * Provides database creation, migration, and cleanup functionality for testing.
43
+ * Subclasses must implement the migrate() method to define migration logic.
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * class MyMigrator extends PostgresMigrator {
48
+ * async migrate(): Promise<void> {
49
+ * // Run your migrations here
50
+ * await this.runMigrations();
51
+ * }
52
+ * }
53
+ *
54
+ * // Use in tests
55
+ * const migrator = new MyMigrator('postgresql://localhost:5432/test_db');
56
+ * const cleanup = await migrator.start();
57
+ *
58
+ * // Run tests...
59
+ *
60
+ * // Clean up
61
+ * await cleanup();
62
+ * ```
63
+ */
23
64
  export abstract class PostgresMigrator {
65
+ /**
66
+ * Creates a new PostgresMigrator instance.
67
+ *
68
+ * @param uri - PostgreSQL connection URI
69
+ */
24
70
  constructor(private uri: string) {}
25
71
 
72
+ /**
73
+ * Abstract method to be implemented by subclasses.
74
+ * Should contain the migration logic for setting up database schema.
75
+ *
76
+ * @returns Promise that resolves when migrations are complete
77
+ */
26
78
  abstract migrate(): Promise<void>;
27
79
 
80
+ /**
81
+ * Creates a PostgreSQL database if it doesn't already exist.
82
+ * Connects to the 'postgres' database to check and create the target database.
83
+ *
84
+ * @param uri - PostgreSQL connection URI
85
+ * @returns Object indicating whether the database already existed
86
+ * @private
87
+ */
28
88
  private static async create(
29
89
  uri: string,
30
90
  ): Promise<{ alreadyExisted: boolean }> {
@@ -47,6 +107,14 @@ export abstract class PostgresMigrator {
47
107
  }
48
108
  }
49
109
 
110
+ /**
111
+ * Drops a PostgreSQL database.
112
+ * Used for cleanup after tests are complete.
113
+ *
114
+ * @param uri - PostgreSQL connection URI
115
+ * @throws Error if database cannot be dropped
116
+ * @private
117
+ */
50
118
  private static async drop(uri: string): Promise<void> {
51
119
  const { database, db } = await setupClient(uri);
52
120
  try {
@@ -57,6 +125,28 @@ export abstract class PostgresMigrator {
57
125
  }
58
126
  }
59
127
 
128
+ /**
129
+ * Starts the migration process by creating the database and running migrations.
130
+ * Returns a cleanup function that will drop the database when called.
131
+ *
132
+ * @returns Async cleanup function that drops the created database
133
+ *
134
+ * @example
135
+ * ```typescript
136
+ * const migrator = new MyMigrator('postgresql://localhost:5432/test_db');
137
+ *
138
+ * // Start migrations and get cleanup function
139
+ * const cleanup = await migrator.start();
140
+ *
141
+ * try {
142
+ * // Run your tests here
143
+ * await runTests();
144
+ * } finally {
145
+ * // Always clean up
146
+ * await cleanup();
147
+ * }
148
+ * ```
149
+ */
60
150
  async start() {
61
151
  const { database, db } = await setupClient(this.uri);
62
152
  try {
@@ -4,12 +4,58 @@ import {
4
4
  VitestPostgresTransactionIsolator,
5
5
  } from './VitestTransactionIsolator';
6
6
 
7
+ /**
8
+ * Kysely-specific implementation of the Vitest transaction isolator.
9
+ * Provides automatic transaction rollback for test isolation using Kysely's transaction API.
10
+ * Each test runs within a database transaction that is rolled back after completion,
11
+ * ensuring a clean state between tests without the overhead of recreating data.
12
+ *
13
+ * @template Database - The database schema type
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { VitestKyselyTransactionIsolator } from '@geekmidas/testkit';
18
+ * import { db } from './database';
19
+ *
20
+ * // Create isolator instance
21
+ * const isolator = new VitestKyselyTransactionIsolator<Database>();
22
+ *
23
+ * // In your test setup
24
+ * beforeEach(async () => {
25
+ * await isolator.start(db);
26
+ * });
27
+ *
28
+ * afterEach(async () => {
29
+ * await isolator.rollback();
30
+ * });
31
+ *
32
+ * // Tests run in isolated transactions
33
+ * it('should create user', async () => {
34
+ * const user = await db.insertInto('users')
35
+ * .values({ name: 'Test User' })
36
+ * .returningAll()
37
+ * .executeTakeFirst();
38
+ *
39
+ * expect(user).toBeDefined();
40
+ * // This data will be rolled back after the test
41
+ * });
42
+ * ```
43
+ */
7
44
  export class VitestKyselyTransactionIsolator<
8
45
  Database,
9
46
  > extends VitestPostgresTransactionIsolator<
10
47
  Kysely<Database>,
11
48
  Transaction<Database>
12
49
  > {
50
+ /**
51
+ * Creates a Kysely transaction with the specified isolation level.
52
+ * Implements the abstract transact method from VitestPostgresTransactionIsolator.
53
+ *
54
+ * @param conn - The Kysely database connection
55
+ * @param level - The transaction isolation level
56
+ * @param fn - The function to execute within the transaction
57
+ * @returns Promise that resolves when the transaction completes
58
+ */
13
59
  async transact(
14
60
  conn: Kysely<Database>,
15
61
  level: IsolationLevel,
@@ -0,0 +1,74 @@
1
+ import type { Knex } from 'knex';
2
+ import {
3
+ type IsolationLevel,
4
+ VitestPostgresTransactionIsolator,
5
+ } from './VitestTransactionIsolator';
6
+
7
+ /**
8
+ * Objection.js-specific implementation of the Vitest transaction isolator.
9
+ * Provides automatic transaction rollback for test isolation using Objection.js and Knex transaction API.
10
+ * Each test runs within a database transaction that is rolled back after completion,
11
+ * ensuring a clean state between tests without the overhead of recreating data.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import { VitestObjectionTransactionIsolator } from '@geekmidas/testkit';
16
+ * import { knex } from './database';
17
+ * import { User } from './models';
18
+ * import { test } from 'vitest';
19
+ *
20
+ * // Create isolator instance
21
+ * const isolator = new VitestObjectionTransactionIsolator(test);
22
+ *
23
+ * // Use with wrapped test API
24
+ * const isolatedTest = isolator.wrapVitestWithTransaction(knex);
25
+ *
26
+ * isolatedTest('should create user', async ({ trx }) => {
27
+ * const user = await User.query(trx)
28
+ * .insert({ name: 'Test User' });
29
+ *
30
+ * expect(user).toBeDefined();
31
+ * // This data will be rolled back after the test
32
+ * });
33
+ * ```
34
+ */
35
+ export class VitestObjectionTransactionIsolator extends VitestPostgresTransactionIsolator<
36
+ Knex,
37
+ Knex.Transaction
38
+ > {
39
+ /**
40
+ * Creates a Knex transaction with the specified isolation level.
41
+ * Implements the abstract transact method from VitestPostgresTransactionIsolator.
42
+ * This transaction can be used with Objection.js models via Model.query(trx).
43
+ *
44
+ * @param conn - The Knex database connection
45
+ * @param level - The transaction isolation level
46
+ * @param fn - The function to execute within the transaction
47
+ * @returns Promise that resolves when the transaction completes
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * await isolator.transact(knex, IsolationLevel.REPEATABLE_READ, async (trx) => {
52
+ * // Use transaction with Objection models
53
+ * await User.query(trx).insert({ name: 'Test' });
54
+ * await Post.query(trx).where('userId', user.id).delete();
55
+ * });
56
+ * ```
57
+ */
58
+ async transact(
59
+ conn: Knex,
60
+ level: IsolationLevel,
61
+ fn: (trx: Knex.Transaction) => Promise<void>,
62
+ ): Promise<void> {
63
+ const isolationLevel = level.toLowerCase() as Lowercase<IsolationLevel>;
64
+
65
+ await conn.transaction(
66
+ async (trx) => {
67
+ await fn(trx);
68
+ },
69
+ {
70
+ isolationLevel,
71
+ },
72
+ );
73
+ }
74
+ }
@@ -1,28 +1,123 @@
1
1
  import type { TestAPI } from 'vitest';
2
2
 
3
+ /**
4
+ * Type definition for test fixtures that provide transaction access.
5
+ * Used with Vitest's test.extend() API to inject transactions into tests.
6
+ *
7
+ * @template Transaction - The transaction type specific to the database driver
8
+ */
3
9
  export interface DatabaseFixtures<Transaction> {
10
+ /**
11
+ * The database transaction available to the test.
12
+ * All database operations should use this transaction to ensure proper rollback.
13
+ */
4
14
  trx: Transaction;
5
15
  }
6
16
 
17
+ /**
18
+ * PostgreSQL transaction isolation levels.
19
+ * Controls the visibility of concurrent transactions.
20
+ *
21
+ * @see https://www.postgresql.org/docs/current/transaction-iso.html
22
+ */
7
23
  export enum IsolationLevel {
24
+ /**
25
+ * Lowest isolation level. Allows dirty reads.
26
+ * Not recommended for testing.
27
+ */
8
28
  READ_UNCOMMITTED = 'READ UNCOMMITTED',
29
+ /**
30
+ * Default PostgreSQL isolation level.
31
+ * Prevents dirty reads but allows non-repeatable reads.
32
+ */
9
33
  READ_COMMITTED = 'READ COMMITTED',
34
+ /**
35
+ * Prevents dirty reads and non-repeatable reads.
36
+ * Recommended for most test scenarios.
37
+ */
10
38
  REPEATABLE_READ = 'REPEATABLE READ',
39
+ /**
40
+ * Highest isolation level. Prevents all phenomena.
41
+ * May cause performance overhead in tests.
42
+ */
11
43
  SERIALIZABLE = 'SERIALIZABLE',
12
44
  }
13
45
 
46
+ /**
47
+ * Abstract base class for implementing database transaction isolation in Vitest tests.
48
+ * Provides automatic transaction rollback after each test to maintain test isolation.
49
+ * Subclasses must implement the transact() method for their specific database driver.
50
+ *
51
+ * @template Connection - The database connection type
52
+ * @template Transaction - The transaction type
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * // Implement for your database driver
57
+ * class MyDatabaseIsolator extends VitestPostgresTransactionIsolator<MyDB, MyTx> {
58
+ * async transact(conn: MyDB, level: IsolationLevel, fn: (tx: MyTx) => Promise<void>) {
59
+ * await conn.transaction(level, fn);
60
+ * }
61
+ * }
62
+ *
63
+ * // Use in tests
64
+ * const isolator = new MyDatabaseIsolator(test);
65
+ * const isolatedTest = isolator.wrapVitestWithTransaction(db);
66
+ *
67
+ * isolatedTest('should create user', async ({ trx }) => {
68
+ * await trx.insert('users', { name: 'Test' });
69
+ * // Data is automatically rolled back after test
70
+ * });
71
+ * ```
72
+ */
14
73
  export abstract class VitestPostgresTransactionIsolator<
15
74
  Connection,
16
75
  Transaction,
17
76
  > {
77
+ /**
78
+ * Abstract method to create a transaction with the specified isolation level.
79
+ * Must be implemented by subclasses for specific database drivers.
80
+ *
81
+ * @param conn - The database connection
82
+ * @param isolationLevel - The transaction isolation level
83
+ * @param fn - The function to execute within the transaction
84
+ * @returns Promise that resolves when the transaction completes
85
+ */
18
86
  abstract transact(
19
87
  conn: Connection,
20
88
  isolationLevel: IsolationLevel,
21
89
  fn: (trx: Transaction) => Promise<void>,
22
90
  ): Promise<void>;
23
91
 
92
+ /**
93
+ * Creates a new VitestPostgresTransactionIsolator instance.
94
+ *
95
+ * @param api - The Vitest test API (usually the `test` export from vitest)
96
+ */
24
97
  constructor(private readonly api: TestAPI) {}
25
98
 
99
+ /**
100
+ * Creates a wrapped version of Vitest's test API that provides transaction isolation.
101
+ * Each test will run within a database transaction that is automatically rolled back.
102
+ *
103
+ * @param conn - The database connection to use
104
+ * @param setup - Optional setup function to run within the transaction before each test
105
+ * @param level - The transaction isolation level (defaults to REPEATABLE_READ)
106
+ * @returns A wrapped test API with transaction support
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * const isolatedTest = isolator.wrapVitestWithTransaction(db, async (trx) => {
111
+ * // Optional setup: create common test data
112
+ * await trx.insert('settings', { key: 'test', value: 'true' });
113
+ * });
114
+ *
115
+ * isolatedTest('test with transaction', async ({ trx }) => {
116
+ * const user = await trx.insert('users', { name: 'Test' });
117
+ * expect(user).toBeDefined();
118
+ * });
119
+ * ```
120
+ */
26
121
  wrapVitestWithTransaction(
27
122
  conn: Connection,
28
123
  setup?: (trx: Transaction) => Promise<void>,