@geekmidas/testkit 9.0.2 → 10.0.0-alpha.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 (125) hide show
  1. package/dist/{Factory-CUM2767q.d.cts → Factory-C6W78ulZ.d.mts} +2 -2
  2. package/dist/{Factory-ClyZLRsO.d.mts.map → Factory-C6W78ulZ.d.mts.map} +1 -1
  3. package/dist/{Factory-ClyZLRsO.d.mts → Factory-DSEADOaF.d.cts} +2 -2
  4. package/dist/{Factory-CUM2767q.d.cts.map → Factory-DSEADOaF.d.cts.map} +1 -1
  5. package/dist/Factory.d.cts +2 -2
  6. package/dist/Factory.d.mts +2 -2
  7. package/dist/{KyselyFactory-OMwcuAX_.d.cts → KyselyFactory-Cc2UmOJk.d.mts} +3 -3
  8. package/dist/{KyselyFactory-CTZmiGI9.d.mts.map → KyselyFactory-Cc2UmOJk.d.mts.map} +1 -1
  9. package/dist/{KyselyFactory-CTZmiGI9.d.mts → KyselyFactory-DzH0N1zr.d.cts} +3 -3
  10. package/dist/{KyselyFactory-OMwcuAX_.d.cts.map → KyselyFactory-DzH0N1zr.d.cts.map} +1 -1
  11. package/dist/KyselyFactory.d.cts +3 -3
  12. package/dist/KyselyFactory.d.mts +3 -3
  13. package/dist/{ObjectionFactory-DDrKwAoO.d.mts → ObjectionFactory-BYnPr9ZP.d.mts} +3 -3
  14. package/dist/{ObjectionFactory-DDrKwAoO.d.mts.map → ObjectionFactory-BYnPr9ZP.d.mts.map} +1 -1
  15. package/dist/{ObjectionFactory-DvmZVHhe.d.cts → ObjectionFactory-BmKdG0nT.d.cts} +3 -3
  16. package/dist/{ObjectionFactory-DvmZVHhe.d.cts.map → ObjectionFactory-BmKdG0nT.d.cts.map} +1 -1
  17. package/dist/ObjectionFactory.d.cts +3 -3
  18. package/dist/ObjectionFactory.d.mts +3 -3
  19. package/dist/{VitestKyselyTransactionIsolator-CfSMOFlN.mjs → VitestKyselyTransactionIsolator-1Saieke7.mjs} +10 -3
  20. package/dist/VitestKyselyTransactionIsolator-1Saieke7.mjs.map +1 -0
  21. package/dist/{VitestKyselyTransactionIsolator-0CqOsuc9.cjs → VitestKyselyTransactionIsolator-BjJSXryR.cjs} +10 -3
  22. package/dist/VitestKyselyTransactionIsolator-BjJSXryR.cjs.map +1 -0
  23. package/dist/{VitestKyselyTransactionIsolator-CduJlHoT.d.cts → VitestKyselyTransactionIsolator-CwlTo6yf.d.mts} +8 -3
  24. package/dist/VitestKyselyTransactionIsolator-CwlTo6yf.d.mts.map +1 -0
  25. package/dist/{VitestKyselyTransactionIsolator-Cswnnj0k.d.mts → VitestKyselyTransactionIsolator-DJasVuIZ.d.cts} +8 -3
  26. package/dist/VitestKyselyTransactionIsolator-DJasVuIZ.d.cts.map +1 -0
  27. package/dist/VitestKyselyTransactionIsolator.cjs +2 -2
  28. package/dist/VitestKyselyTransactionIsolator.d.cts +2 -2
  29. package/dist/VitestKyselyTransactionIsolator.d.mts +2 -2
  30. package/dist/VitestKyselyTransactionIsolator.mjs +2 -2
  31. package/dist/{VitestObjectionTransactionIsolator-CNo40EdC.mjs → VitestObjectionTransactionIsolator-B_VSWFum.mjs} +2 -2
  32. package/dist/{VitestObjectionTransactionIsolator-CNo40EdC.mjs.map → VitestObjectionTransactionIsolator-B_VSWFum.mjs.map} +1 -1
  33. package/dist/{VitestObjectionTransactionIsolator-BXoR6xdG.d.cts → VitestObjectionTransactionIsolator-CTTjvTPO.d.cts} +2 -2
  34. package/dist/{VitestObjectionTransactionIsolator-BXoR6xdG.d.cts.map → VitestObjectionTransactionIsolator-CTTjvTPO.d.cts.map} +1 -1
  35. package/dist/{VitestObjectionTransactionIsolator-x6hY5j4u.d.mts → VitestObjectionTransactionIsolator-CyZG6nq4.d.mts} +2 -2
  36. package/dist/{VitestObjectionTransactionIsolator-x6hY5j4u.d.mts.map → VitestObjectionTransactionIsolator-CyZG6nq4.d.mts.map} +1 -1
  37. package/dist/{VitestObjectionTransactionIsolator-B5J9jjKq.cjs → VitestObjectionTransactionIsolator-DDoJTu7e.cjs} +2 -2
  38. package/dist/{VitestObjectionTransactionIsolator-B5J9jjKq.cjs.map → VitestObjectionTransactionIsolator-DDoJTu7e.cjs.map} +1 -1
  39. package/dist/VitestObjectionTransactionIsolator.cjs +2 -2
  40. package/dist/VitestObjectionTransactionIsolator.d.cts +2 -2
  41. package/dist/VitestObjectionTransactionIsolator.d.mts +2 -2
  42. package/dist/VitestObjectionTransactionIsolator.mjs +2 -2
  43. package/dist/{VitestTransactionIsolator-BNWJqh9f.d.mts → VitestTransactionIsolator-BLaw80cx.d.mts} +20 -4
  44. package/dist/VitestTransactionIsolator-BLaw80cx.d.mts.map +1 -0
  45. package/dist/{VitestTransactionIsolator-D6ZxLWys.mjs → VitestTransactionIsolator-CvwPecpl.mjs} +17 -4
  46. package/dist/VitestTransactionIsolator-CvwPecpl.mjs.map +1 -0
  47. package/dist/{VitestTransactionIsolator-CtZaOUjH.cjs → VitestTransactionIsolator-DyiX-QtK.cjs} +17 -4
  48. package/dist/VitestTransactionIsolator-DyiX-QtK.cjs.map +1 -0
  49. package/dist/{VitestTransactionIsolator-CSroc7Df.d.cts → VitestTransactionIsolator-glcxyS_R.d.cts} +20 -4
  50. package/dist/VitestTransactionIsolator-glcxyS_R.d.cts.map +1 -0
  51. package/dist/VitestTransactionIsolator.cjs +1 -1
  52. package/dist/VitestTransactionIsolator.d.cts +2 -2
  53. package/dist/VitestTransactionIsolator.d.mts +2 -2
  54. package/dist/VitestTransactionIsolator.mjs +1 -1
  55. package/dist/better-auth.d.cts +2 -2
  56. package/dist/better-auth.d.mts +2 -2
  57. package/dist/{faker-BcjUfHxx.d.mts → faker-D9gz7KjY.d.mts} +3 -3
  58. package/dist/{faker-BcjUfHxx.d.mts.map → faker-D9gz7KjY.d.mts.map} +1 -1
  59. package/dist/{faker-zVCm31nU.d.cts → faker-km9UhOS6.d.cts} +3 -3
  60. package/dist/{faker-zVCm31nU.d.cts.map → faker-km9UhOS6.d.cts.map} +1 -1
  61. package/dist/faker.d.cts +1 -1
  62. package/dist/faker.d.mts +1 -1
  63. package/dist/kysely.cjs +2 -2
  64. package/dist/kysely.d.cts +5 -5
  65. package/dist/kysely.d.mts +5 -5
  66. package/dist/kysely.mjs +2 -2
  67. package/dist/objection.cjs +2 -2
  68. package/dist/objection.d.cts +5 -5
  69. package/dist/objection.d.mts +5 -5
  70. package/dist/objection.mjs +2 -2
  71. package/dist/requestContext.cjs +1 -1
  72. package/dist/requestContext.d.cts +1 -1
  73. package/dist/requestContext.d.mts +1 -1
  74. package/dist/requestContext.mjs +1 -1
  75. package/package.json +10 -7
  76. package/CHANGELOG.md +0 -151
  77. package/dist/VitestKyselyTransactionIsolator-0CqOsuc9.cjs.map +0 -1
  78. package/dist/VitestKyselyTransactionIsolator-CduJlHoT.d.cts.map +0 -1
  79. package/dist/VitestKyselyTransactionIsolator-CfSMOFlN.mjs.map +0 -1
  80. package/dist/VitestKyselyTransactionIsolator-Cswnnj0k.d.mts.map +0 -1
  81. package/dist/VitestTransactionIsolator-BNWJqh9f.d.mts.map +0 -1
  82. package/dist/VitestTransactionIsolator-CSroc7Df.d.cts.map +0 -1
  83. package/dist/VitestTransactionIsolator-CtZaOUjH.cjs.map +0 -1
  84. package/dist/VitestTransactionIsolator-D6ZxLWys.mjs.map +0 -1
  85. package/src/Factory.ts +0 -174
  86. package/src/KyselyFactory.ts +0 -391
  87. package/src/ObjectionFactory.ts +0 -412
  88. package/src/PostgresKyselyMigrator.ts +0 -104
  89. package/src/PostgresMigrator.ts +0 -193
  90. package/src/PostgresObjectionMigrator.ts +0 -138
  91. package/src/VitestKyselyTransactionIsolator.ts +0 -72
  92. package/src/VitestObjectionTransactionIsolator.ts +0 -75
  93. package/src/VitestTransactionIsolator.ts +0 -348
  94. package/src/__tests__/Factory.spec.ts +0 -178
  95. package/src/__tests__/KyselyFactory.spec.ts +0 -458
  96. package/src/__tests__/ObjectionFactory.spec.ts +0 -586
  97. package/src/__tests__/PostgresKyselyMigrator.spec.ts +0 -794
  98. package/src/__tests__/PostgresMigrator.spec.ts +0 -471
  99. package/src/__tests__/PostgresObjectionMigrator.spec.ts +0 -634
  100. package/src/__tests__/VitestObjectionTransactionIsolator.spec.ts +0 -138
  101. package/src/__tests__/benchmark.spec.ts +0 -140
  102. package/src/__tests__/better-auth.spec.ts +0 -21
  103. package/src/__tests__/faker.spec.ts +0 -231
  104. package/src/__tests__/initScript.spec.ts +0 -332
  105. package/src/__tests__/integration.spec.ts +0 -610
  106. package/src/__tests__/requestContext.spec.ts +0 -113
  107. package/src/__tests__/utilities.spec.ts +0 -211
  108. package/src/aws.ts +0 -131
  109. package/src/benchmark.ts +0 -48
  110. package/src/better-auth.ts +0 -310
  111. package/src/faker.ts +0 -349
  112. package/src/helpers.ts +0 -45
  113. package/src/initScript.ts +0 -122
  114. package/src/kysely.ts +0 -166
  115. package/src/logger.ts +0 -18
  116. package/src/objection.ts +0 -157
  117. package/src/os/directory.ts +0 -26
  118. package/src/os/index.ts +0 -1
  119. package/src/requestContext.ts +0 -119
  120. package/src/timer.ts +0 -3
  121. package/test/globalSetup.ts +0 -54
  122. package/test/helpers.ts +0 -268
  123. package/test/migrations/1749664623372_user.ts +0 -22
  124. package/tsconfig.json +0 -9
  125. package/vitest.config.ts +0 -8
package/src/faker.ts DELETED
@@ -1,349 +0,0 @@
1
- import { faker as baseFaker } from '@faker-js/faker';
2
-
3
- // NOTE: This is a simple way to extend `faker` with additional methods
4
-
5
- /**
6
- * Atomic counter implementation for thread-safe sequence generation.
7
- * Provides a clean abstraction for generating sequential numbers in tests.
8
- * While JavaScript is single-threaded, this class makes the intent explicit.
9
- *
10
- * @example
11
- * ```typescript
12
- * const counter = new AtomicCounter(100);
13
- * console.log(counter.increment()); // 101
14
- * console.log(counter.increment()); // 102
15
- * console.log(counter.get()); // 102
16
- * counter.reset(200);
17
- * console.log(counter.increment()); // 201
18
- * ```
19
- */
20
- class AtomicCounter {
21
- /**
22
- * The current counter value.
23
- * @private
24
- */
25
- private value: number;
26
-
27
- /**
28
- * Creates a new atomic counter.
29
- * @param initialValue - The starting value (default: 0)
30
- */
31
- constructor(initialValue = 0) {
32
- this.value = initialValue;
33
- }
34
-
35
- /**
36
- * Increments the counter and returns the new value.
37
- * @returns The incremented value
38
- */
39
- increment(): number {
40
- // In Node.js, JavaScript is single-threaded within the event loop,
41
- // so this operation is already atomic. However, this class provides
42
- // a cleaner abstraction and makes the intent explicit.
43
- return ++this.value;
44
- }
45
-
46
- /**
47
- * Gets the current counter value without incrementing.
48
- * @returns The current value
49
- */
50
- get(): number {
51
- return this.value;
52
- }
53
-
54
- /**
55
- * Resets the counter to a specific value.
56
- * @param value - The new value (default: 0)
57
- */
58
- reset(value = 0): void {
59
- this.value = value;
60
- }
61
- }
62
-
63
- /**
64
- * Generates random timestamp fields for database records.
65
- * Creates a createdAt date in the past and an updatedAt date between creation and now.
66
- * Milliseconds are set to 0 for cleaner database storage.
67
- *
68
- * @returns Object with createdAt and updatedAt Date fields
69
- *
70
- * @example
71
- * ```typescript
72
- * const { createdAt, updatedAt } = timestamps();
73
- * console.log(createdAt); // 2023-05-15T10:30:00.000Z
74
- * console.log(updatedAt); // 2023-11-20T14:45:00.000Z
75
- *
76
- * // Use in factory
77
- * const user = {
78
- * name: 'John Doe',
79
- * ...timestamps()
80
- * };
81
- * ```
82
- */
83
- export function timestamps(): Timestamps {
84
- const createdAt = faker.date.past();
85
- const updatedAt = faker.date.between({
86
- from: createdAt,
87
- to: new Date(),
88
- });
89
-
90
- createdAt.setMilliseconds(0);
91
- updatedAt.setMilliseconds(0);
92
-
93
- return { createdAt, updatedAt };
94
- }
95
-
96
- /**
97
- * Generates a reverse domain name identifier.
98
- * Useful for creating unique identifiers that follow domain naming conventions.
99
- *
100
- * @param suffix - Optional suffix to append to the identifier
101
- * @returns A reverse domain name string (e.g., "com.example.feature123")
102
- *
103
- * @example
104
- * ```typescript
105
- * console.log(identifier()); // "com.example.widget1"
106
- * console.log(identifier('user')); // "org.acme.user"
107
- * console.log(identifier('api')); // "net.demo.api"
108
- * ```
109
- */
110
- export function identifier(suffix?: string): string {
111
- return [
112
- faker.internet.domainSuffix(),
113
- faker.internet.domainWord(),
114
- suffix ? suffix : faker.internet.domainWord() + sequence('identifier'),
115
- ].join('.');
116
- }
117
-
118
- /**
119
- * Storage for named sequence counters.
120
- * Each sequence maintains its own independent counter.
121
- * @private
122
- */
123
- const sequences = new Map<string, AtomicCounter>();
124
-
125
- /**
126
- * Generates sequential numbers for a named sequence.
127
- * Useful for creating unique IDs or numbered test data.
128
- * Each named sequence maintains its own counter.
129
- *
130
- * @param name - The sequence name (default: 'default')
131
- * @returns The next number in the sequence
132
- *
133
- * @example
134
- * ```typescript
135
- * console.log(sequence()); // 1
136
- * console.log(sequence()); // 2
137
- * console.log(sequence('user')); // 1
138
- * console.log(sequence('user')); // 2
139
- * console.log(sequence()); // 3
140
- *
141
- * // Use in factories
142
- * const email = `user${sequence('email')}@example.com`;
143
- * ```
144
- */
145
- export function sequence(name = 'default'): number {
146
- if (!sequences.has(name)) {
147
- sequences.set(name, new AtomicCounter());
148
- }
149
-
150
- const counter = sequences.get(name) as AtomicCounter;
151
- return counter.increment();
152
- }
153
-
154
- /**
155
- * Resets a named sequence counter to a specific value.
156
- * Useful for resetting sequences between test suites.
157
- *
158
- * @param name - The sequence name to reset (default: 'default')
159
- * @param value - The new starting value (default: 0)
160
- *
161
- * @example
162
- * ```typescript
163
- * sequence('user'); // 1
164
- * sequence('user'); // 2
165
- * resetSequence('user');
166
- * sequence('user'); // 1
167
- *
168
- * resetSequence('order', 1000);
169
- * sequence('order'); // 1001
170
- * ```
171
- */
172
- export function resetSequence(name = 'default', value = 0): void {
173
- if (sequences.has(name)) {
174
- const counter = sequences.get(name) as AtomicCounter;
175
- counter.reset(value);
176
- } else {
177
- sequences.set(name, new AtomicCounter(value));
178
- }
179
- }
180
-
181
- /**
182
- * Resets all sequence counters.
183
- * Useful for cleaning up between test suites to ensure predictable sequences.
184
- *
185
- * @example
186
- * ```typescript
187
- * // In test setup
188
- * beforeEach(() => {
189
- * resetAllSequences();
190
- * });
191
- *
192
- * it('starts sequences from 1', () => {
193
- * expect(sequence()).toBe(1);
194
- * expect(sequence('user')).toBe(1);
195
- * });
196
- * ```
197
- */
198
- export function resetAllSequences(): void {
199
- sequences.clear();
200
- }
201
-
202
- /**
203
- * Generates a random price as a number.
204
- * Converts faker's string price to a numeric value.
205
- *
206
- * @returns A random price number
207
- *
208
- * @example
209
- * ```typescript
210
- * const productPrice = price(); // 29.99
211
- * const total = price() * quantity; // Numeric calculation
212
- * ```
213
- */
214
- function price(): number {
215
- return +faker.commerce.price();
216
- }
217
-
218
- type Coordinate = {
219
- lat: number;
220
- lng: number;
221
- };
222
-
223
- export function coordinateInRadius(
224
- center: Coordinate,
225
- radius: number,
226
- ): Coordinate {
227
- // Earth's radius in meters
228
- const earth = 6378137;
229
- // Convert radius from meters to degrees
230
- const d = radius / earth;
231
-
232
- // Random bearing and distance
233
- const theta = 2 * Math.PI * Math.random();
234
- const r = d * Math.sqrt(Math.random());
235
-
236
- const lat1 = (center.lat * Math.PI) / 180;
237
- const lng1 = (center.lng * Math.PI) / 180;
238
-
239
- const lat2 = Math.asin(
240
- Math.sin(lat1) * Math.cos(r) +
241
- Math.cos(lat1) * Math.sin(r) * Math.cos(theta),
242
- );
243
- const lng2 =
244
- lng1 +
245
- Math.atan2(
246
- Math.sin(theta) * Math.sin(r) * Math.cos(lat1),
247
- Math.cos(r) - Math.sin(lat1) * Math.sin(lat2),
248
- );
249
-
250
- return {
251
- lat: (lat2 * 180) / Math.PI,
252
- lng: (lng2 * 180) / Math.PI,
253
- };
254
- }
255
-
256
- function coordinateOutsideRadius(
257
- center: Coordinate,
258
- minRadiusMeters: number,
259
- maxRadiusMeters: number,
260
- ): Coordinate {
261
- // Earth's radius in meters
262
- const earth = 6378137;
263
-
264
- // Convert radii from meters to radians
265
- const minD = minRadiusMeters / earth;
266
- const maxD = maxRadiusMeters / earth;
267
-
268
- // Random bearing
269
- const theta = 2 * Math.PI * Math.random();
270
-
271
- // Random distance in annular ring (uniform distribution by area)
272
- // For uniform distribution in annulus: r = sqrt(r_min² + (r_max² - r_min²) * random)
273
- const r = Math.sqrt(
274
- minD * minD + (maxD * maxD - minD * minD) * Math.random(),
275
- );
276
-
277
- const lat1 = (center.lat * Math.PI) / 180;
278
- const lng1 = (center.lng * Math.PI) / 180;
279
-
280
- const lat2 = Math.asin(
281
- Math.sin(lat1) * Math.cos(r) +
282
- Math.cos(lat1) * Math.sin(r) * Math.cos(theta),
283
- );
284
- const lng2 =
285
- lng1 +
286
- Math.atan2(
287
- Math.sin(theta) * Math.sin(r) * Math.cos(lat1),
288
- Math.cos(r) - Math.sin(lat1) * Math.sin(lat2),
289
- );
290
-
291
- // Normalize longitude to [-180, 180]
292
- const normalizedLng = (((lng2 * 180) / Math.PI + 540) % 360) - 180;
293
-
294
- return {
295
- lat: (lat2 * 180) / Math.PI,
296
- lng: normalizedLng,
297
- };
298
- }
299
-
300
- /**
301
- * Enhanced faker instance with additional utility methods for testing.
302
- * Extends @faker-js/faker with custom methods for common test data generation patterns.
303
- *
304
- * @example
305
- * ```typescript
306
- * import { faker } from '@geekmidas/testkit';
307
- *
308
- * // Use standard faker methods
309
- * const name = faker.person.fullName();
310
- * const email = faker.internet.email();
311
- *
312
- * // Use custom extensions
313
- * const { createdAt, updatedAt } = faker.timestamps();
314
- * const id = faker.identifier('user');
315
- * const orderNumber = faker.sequence('order');
316
- * const productPrice = faker.price();
317
- * ```
318
- */
319
- export const faker = Object.freeze(
320
- Object.assign({}, baseFaker, {
321
- timestamps,
322
- identifier,
323
- sequence,
324
- resetSequence,
325
- resetAllSequences,
326
- price,
327
- coordinates: {
328
- within: coordinateInRadius,
329
- outside: coordinateOutsideRadius,
330
- },
331
- }),
332
- );
333
-
334
- /**
335
- * Type definition for timestamp fields.
336
- * Used by the timestamps() function to generate date fields.
337
- */
338
- export type Timestamps = {
339
- /** The creation date */
340
- createdAt: Date;
341
- /** The last update date */
342
- updatedAt: Date;
343
- };
344
-
345
- /**
346
- * Type definition for the enhanced faker factory.
347
- * Includes all standard faker methods plus custom extensions.
348
- */
349
- export type FakerFactory = typeof faker;
package/src/helpers.ts DELETED
@@ -1,45 +0,0 @@
1
- import { CamelCasePlugin, Kysely, PostgresDialect } from 'kysely';
2
- import pg from 'pg';
3
-
4
- /**
5
- * Creates a Kysely database instance with PostgreSQL dialect and camelCase plugin.
6
- * This is a convenience function for quickly setting up a Kysely connection for testing.
7
- *
8
- * @template Database - The database schema type
9
- * @param config - PostgreSQL connection configuration (pg.Pool config)
10
- * @returns A configured Kysely instance
11
- *
12
- * @example
13
- * ```typescript
14
- * interface Database {
15
- * users: UsersTable;
16
- * posts: PostsTable;
17
- * }
18
- *
19
- * // Create from connection string
20
- * const db = createKyselyDb<Database>({
21
- * connectionString: 'postgresql://user:pass@localhost:5432/testdb'
22
- * });
23
- *
24
- * // Create with detailed config
25
- * const db = createKyselyDb<Database>({
26
- * host: 'localhost',
27
- * port: 5432,
28
- * database: 'testdb',
29
- * user: 'testuser',
30
- * password: 'testpass',
31
- * max: 10 // connection pool size
32
- * });
33
- *
34
- * // Use in tests
35
- * const users = await db.selectFrom('users').selectAll().execute();
36
- * ```
37
- */
38
- export function createKyselyDb<Database>(config: any): Kysely<Database> {
39
- return new Kysely({
40
- dialect: new PostgresDialect({
41
- pool: new pg.Pool(config),
42
- }),
43
- plugins: [new CamelCasePlugin()],
44
- });
45
- }
package/src/initScript.ts DELETED
@@ -1,122 +0,0 @@
1
- import { readFileSync } from 'node:fs';
2
- import pg from 'pg';
3
-
4
- const { Client } = pg;
5
-
6
- /**
7
- * Parse a shell init script (like docker/postgres/init.sh) and extract
8
- * SQL blocks from heredoc sections (<<-EOSQL ... EOSQL).
9
- *
10
- * @param content - The shell script content
11
- * @param env - Environment variables to substitute ($VAR_NAME references)
12
- * @returns Array of SQL strings ready to execute
13
- * @internal Exported for testing
14
- */
15
- export function parseInitScript(
16
- content: string,
17
- env: Record<string, string>,
18
- ): string[] {
19
- const blocks: string[] = [];
20
- const lines = content.split('\n');
21
- let inHeredoc = false;
22
- let currentBlock: string[] = [];
23
-
24
- for (const line of lines) {
25
- if (inHeredoc) {
26
- // Check for heredoc terminator (EOSQL at start of line, with optional leading whitespace)
27
- if (/^\s*EOSQL\s*$/.test(line)) {
28
- const sql = substituteEnvVars(currentBlock.join('\n'), env);
29
- blocks.push(sql);
30
- currentBlock = [];
31
- inHeredoc = false;
32
- } else {
33
- currentBlock.push(line);
34
- }
35
- } else if (
36
- line.includes('<<-EOSQL') ||
37
- line.includes('<< EOSQL') ||
38
- line.includes('<<EOSQL')
39
- ) {
40
- inHeredoc = true;
41
- currentBlock = [];
42
- }
43
- }
44
-
45
- return blocks;
46
- }
47
-
48
- /**
49
- * Replace shell variable references ($VAR_NAME and ${VAR_NAME})
50
- * with values from the provided env object.
51
- */
52
- function substituteEnvVars(sql: string, env: Record<string, string>): string {
53
- // Replace ${VAR_NAME} syntax
54
- let result = sql.replace(/\$\{(\w+)\}/g, (_, name) => env[name] ?? '');
55
- // Replace $VAR_NAME syntax (word boundary after)
56
- result = result.replace(/\$(\w+)/g, (_, name) => env[name] ?? '');
57
- // Unescape bash-escaped dollar signs (\$ → $) AFTER variable substitution
58
- // This handles PL/pgSQL dollar-quoting like DO \$\$ ... END \$\$;
59
- result = result.replace(/\\\$/g, '$');
60
- return result;
61
- }
62
-
63
- /**
64
- * Read a postgres init script, parse out the SQL blocks,
65
- * substitute environment variables, and execute against a database.
66
- *
67
- * This is intended to run `docker/postgres/init.sh` against a test database
68
- * so that per-app users and schemas are created (matching what Docker does
69
- * on first volume initialization).
70
- *
71
- * Uses `CREATE ... IF NOT EXISTS` and `DO $$ ... END $$` wrappers where
72
- * needed so the script is idempotent.
73
- *
74
- * @param scriptPath - Path to the init.sh file
75
- * @param databaseUrl - PostgreSQL connection URL (should point to the test database)
76
- *
77
- * @example
78
- * ```typescript
79
- * // In your globalSetup.ts
80
- * import { runInitScript } from '@geekmidas/testkit/postgres';
81
- * import { Credentials } from '@geekmidas/envkit/credentials';
82
- *
83
- * const cleanup = await migrator.start();
84
- *
85
- * // Create per-app users in the test database
86
- * await runInitScript('docker/postgres/init.sh', Credentials.DATABASE_URL, {
87
- * ...process.env,
88
- * ...Credentials,
89
- * });
90
- * ```
91
- */
92
- export async function runInitScript(
93
- scriptPath: string,
94
- databaseUrl: string,
95
- env?: Record<string, string>,
96
- ): Promise<void> {
97
- const content = readFileSync(scriptPath, 'utf-8');
98
- const resolvedEnv = env ?? ({ ...process.env } as Record<string, string>);
99
- const blocks = parseInitScript(content, resolvedEnv);
100
-
101
- if (blocks.length === 0) {
102
- return;
103
- }
104
-
105
- const url = new URL(databaseUrl);
106
- const client = new Client({
107
- user: url.username,
108
- password: decodeURIComponent(url.password),
109
- host: url.hostname,
110
- port: parseInt(url.port, 10),
111
- database: url.pathname.slice(1),
112
- });
113
-
114
- try {
115
- await client.connect();
116
- for (const sql of blocks) {
117
- await client.query(sql);
118
- }
119
- } finally {
120
- await client.end();
121
- }
122
- }
package/src/kysely.ts DELETED
@@ -1,166 +0,0 @@
1
- import type { Kysely, Transaction } from 'kysely';
2
- import type { TestAPI } from 'vitest';
3
- import { VitestKyselyTransactionIsolator } from './VitestKyselyTransactionIsolator';
4
- import {
5
- extendWithFixtures as baseExtendWithFixtures,
6
- type DatabaseConnection,
7
- type FixtureCreators,
8
- type IsolationLevel,
9
- } from './VitestTransactionIsolator';
10
-
11
- /**
12
- * Kysely-specific exports for test utilities.
13
- * Provides factories, migrators, and transaction isolators for Kysely ORM.
14
- */
15
-
16
- export type { ExtractSeedAttrs, FactorySeed } from './Factory';
17
- export { KyselyFactory } from './KyselyFactory';
18
- export { PostgresKyselyMigrator } from './PostgresKyselyMigrator';
19
- export { VitestKyselyTransactionIsolator } from './VitestKyselyTransactionIsolator';
20
- export type {
21
- DatabaseFixtures,
22
- ExtendedDatabaseFixtures,
23
- FixtureCreators,
24
- TestWithExtendedFixtures,
25
- TransactionWrapperOptions,
26
- } from './VitestTransactionIsolator';
27
- export { IsolationLevel } from './VitestTransactionIsolator';
28
-
29
- /**
30
- * Kysely-specific options for transaction wrapping.
31
- */
32
- export interface KyselyTransactionOptions<
33
- Database,
34
- Extended extends Record<string, unknown> = {},
35
- > {
36
- /** Function that creates or returns a Kysely database instance */
37
- connection: DatabaseConnection<Kysely<Database>>;
38
- /** Optional setup function to run within the transaction before each test */
39
- setup?: (trx: Transaction<Database>) => Promise<void>;
40
- /** Transaction isolation level (defaults to REPEATABLE_READ) */
41
- isolationLevel?: IsolationLevel;
42
- /** Additional fixtures that depend on the transaction */
43
- fixtures?: FixtureCreators<Transaction<Database>, Extended>;
44
- }
45
-
46
- // Re-export faker and FakerFactory for type portability in declaration files
47
- export { type FakerFactory, faker } from './faker';
48
-
49
- /**
50
- * Creates a wrapped Vitest test API with automatic transaction rollback for Kysely.
51
- * Each test runs in an isolated database transaction that is rolled back after completion.
52
- * This ensures tests don't affect each other's data and run faster than truncating tables.
53
- *
54
- * @template Database - The database schema type
55
- * @template Extended - Additional fixtures to provide
56
- * @param api - The Vitest test API (usually `test` from vitest)
57
- * @param options - Configuration options for transaction wrapping
58
- * @returns A wrapped test API that provides transaction isolation
59
- *
60
- * @example
61
- * ```typescript
62
- * import { test } from 'vitest';
63
- * import { wrapVitestKyselyTransaction } from '@geekmidas/testkit/kysely';
64
- * import { db } from './database';
65
- *
66
- * // Create isolated test with automatic rollback
67
- * const isolatedTest = wrapVitestKyselyTransaction(test, {
68
- * connection: db,
69
- * });
70
- *
71
- * // Use in tests - each test gets its own transaction
72
- * isolatedTest('should create user', async ({ trx }) => {
73
- * const user = await trx
74
- * .insertInto('users')
75
- * .values({ name: 'Test User', email: 'test@example.com' })
76
- * .returningAll()
77
- * .executeTakeFirst();
78
- *
79
- * expect(user).toBeDefined();
80
- * });
81
- *
82
- * // With fixtures for factories
83
- * const it = wrapVitestKyselyTransaction<DB, { factory: Factory }>(test, {
84
- * connection: db,
85
- * fixtures: {
86
- * factory: (trx) => new Factory(trx),
87
- * },
88
- * });
89
- *
90
- * it('should create user with factory', async ({ trx, factory }) => {
91
- * const user = await factory.insert('user', { name: 'Test' });
92
- * expect(user.id).toBeDefined();
93
- * });
94
- * ```
95
- */
96
- export function wrapVitestKyselyTransaction<
97
- Database,
98
- Extended extends Record<string, unknown> = {},
99
- >(api: TestAPI, options: KyselyTransactionOptions<Database, Extended>) {
100
- const wrapper = new VitestKyselyTransactionIsolator<Database>(api);
101
-
102
- return wrapper.wrapVitestWithTransaction(options);
103
- }
104
-
105
- /**
106
- * Extends a Kysely transaction-wrapped test with additional fixtures.
107
- * Each fixture receives the transaction and can create dependencies like factories or repositories.
108
- *
109
- * @template Database - The database schema type
110
- * @template Extended - The type of additional fixtures to provide
111
- * @param wrappedTest - The base wrapped test from wrapVitestKyselyTransaction
112
- * @param fixtures - Object mapping fixture names to creator functions
113
- * @returns An extended test API with both trx and the additional fixtures
114
- *
115
- * @example
116
- * ```typescript
117
- * import { test } from 'vitest';
118
- * import { wrapVitestKyselyTransaction, extendWithFixtures, KyselyFactory } from '@geekmidas/testkit/kysely';
119
- *
120
- * // Define your builders
121
- * const builders = {
122
- * user: KyselyFactory.createBuilder<DB, 'users'>('users', ({ faker }) => ({
123
- * name: faker.person.fullName(),
124
- * email: faker.internet.email(),
125
- * })),
126
- * };
127
- *
128
- * // Create base wrapped test
129
- * const baseTest = wrapVitestKyselyTransaction<DB>(test, {
130
- * connection: db,
131
- * setup: createTestTables,
132
- * });
133
- *
134
- * // Extend with fixtures - each fixture receives the transaction
135
- * const it = extendWithFixtures<DB, { factory: KyselyFactory<DB, typeof builders, {}> }>(
136
- * baseTest,
137
- * {
138
- * factory: (trx) => new KyselyFactory(builders, {}, trx),
139
- * }
140
- * );
141
- *
142
- * // Use in tests - both trx and factory are available
143
- * it('should create user with factory', async ({ trx, factory }) => {
144
- * const user = await factory.insert('user', { name: 'Test User' });
145
- * expect(user.id).toBeDefined();
146
- *
147
- * // Verify in database
148
- * const found = await trx
149
- * .selectFrom('users')
150
- * .where('id', '=', user.id)
151
- * .selectAll()
152
- * .executeTakeFirst();
153
- * expect(found?.name).toBe('Test User');
154
- * });
155
- * ```
156
- */
157
- export function extendWithFixtures<
158
- Database,
159
- Extended extends Record<string, unknown>,
160
- T extends ReturnType<TestAPI['extend']> = ReturnType<TestAPI['extend']>,
161
- >(wrappedTest: T, fixtures: FixtureCreators<Transaction<Database>, Extended>) {
162
- return baseExtendWithFixtures<Transaction<Database>, Extended, T>(
163
- wrappedTest,
164
- fixtures,
165
- );
166
- }
package/src/logger.ts DELETED
@@ -1,18 +0,0 @@
1
- import type { Logger } from '@geekmidas/logger';
2
- import { vi } from 'vitest';
3
-
4
- /**
5
- * Creates a mock Logger for testing
6
- */
7
- export function createMockLogger(): Logger {
8
- const logger: Logger = {
9
- debug: vi.fn(),
10
- info: vi.fn(),
11
- warn: vi.fn(),
12
- error: vi.fn(),
13
- fatal: vi.fn(),
14
- trace: vi.fn(),
15
- child: vi.fn(() => logger),
16
- };
17
- return logger;
18
- }