@geekmidas/testkit 0.0.17 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +302 -199
- package/dist/{Factory-CRquB4vz.d.mts → Factory-Cmr3s3-s.d.mts} +2 -2
- package/dist/Factory.d.mts +2 -2
- package/dist/{KyselyFactory-DRQ83r0o.d.cts → KyselyFactory-BFygzOlO.d.cts} +21 -8
- package/dist/{KyselyFactory-BDS_QqRT.d.mts → KyselyFactory-BTdygZ-i.d.mts} +23 -10
- package/dist/{KyselyFactory-BcYkC0t2.mjs → KyselyFactory-CXY5gJk2.mjs} +25 -12
- package/dist/KyselyFactory-CXY5gJk2.mjs.map +1 -0
- package/dist/{KyselyFactory-Cf0o2YxO.cjs → KyselyFactory-DaaCykWP.cjs} +25 -12
- package/dist/KyselyFactory-DaaCykWP.cjs.map +1 -0
- package/dist/KyselyFactory.cjs +1 -1
- package/dist/KyselyFactory.d.cts +1 -1
- package/dist/KyselyFactory.d.mts +3 -3
- package/dist/KyselyFactory.mjs +1 -1
- package/dist/{ObjectionFactory-C3tHvX1d.d.mts → ObjectionFactory-BagGjikT.d.mts} +26 -13
- package/dist/{ObjectionFactory-C4X78k0B.d.cts → ObjectionFactory-CeSIN3kZ.d.cts} +24 -11
- package/dist/{ObjectionFactory-CDriunkS.cjs → ObjectionFactory-Eb04AOnv.cjs} +28 -15
- package/dist/ObjectionFactory-Eb04AOnv.cjs.map +1 -0
- package/dist/{ObjectionFactory-8hebmnai.mjs → ObjectionFactory-zf2fLKrL.mjs} +28 -15
- package/dist/ObjectionFactory-zf2fLKrL.mjs.map +1 -0
- package/dist/ObjectionFactory.cjs +1 -1
- package/dist/ObjectionFactory.d.cts +1 -1
- package/dist/ObjectionFactory.d.mts +3 -3
- package/dist/ObjectionFactory.mjs +1 -1
- package/dist/{VitestKyselyTransactionIsolator-COCVfvfr.d.mts → VitestKyselyTransactionIsolator-4HOeLQ0d.d.cts} +2 -2
- package/dist/{VitestKyselyTransactionIsolator-Cst3vFjb.cjs → VitestKyselyTransactionIsolator-DX_VPKS-.cjs} +2 -2
- package/dist/{VitestKyselyTransactionIsolator-Cst3vFjb.cjs.map → VitestKyselyTransactionIsolator-DX_VPKS-.cjs.map} +1 -1
- package/dist/{VitestKyselyTransactionIsolator-DYUYVEh9.d.cts → VitestKyselyTransactionIsolator-DnyZMaA-.d.mts} +2 -2
- package/dist/{VitestKyselyTransactionIsolator-BxjlD1YM.mjs → VitestKyselyTransactionIsolator-XDL3ngs_.mjs} +2 -2
- package/dist/{VitestKyselyTransactionIsolator-BxjlD1YM.mjs.map → VitestKyselyTransactionIsolator-XDL3ngs_.mjs.map} +1 -1
- package/dist/VitestKyselyTransactionIsolator.cjs +2 -2
- package/dist/VitestKyselyTransactionIsolator.d.cts +2 -2
- package/dist/VitestKyselyTransactionIsolator.d.mts +2 -2
- package/dist/VitestKyselyTransactionIsolator.mjs +2 -2
- package/dist/{VitestObjectionTransactionIsolator-b973r9O1.d.mts → VitestObjectionTransactionIsolator-COVDlpEo.d.cts} +2 -2
- package/dist/{VitestObjectionTransactionIsolator-DzeF4UAq.cjs → VitestObjectionTransactionIsolator-D_tlOtq8.cjs} +2 -2
- package/dist/{VitestObjectionTransactionIsolator-DzeF4UAq.cjs.map → VitestObjectionTransactionIsolator-D_tlOtq8.cjs.map} +1 -1
- package/dist/{VitestObjectionTransactionIsolator-BU-jXEhz.mjs → VitestObjectionTransactionIsolator-_EhJKu_O.mjs} +2 -2
- package/dist/{VitestObjectionTransactionIsolator-BU-jXEhz.mjs.map → VitestObjectionTransactionIsolator-_EhJKu_O.mjs.map} +1 -1
- package/dist/{VitestObjectionTransactionIsolator-CJ4ds5Qv.d.cts → VitestObjectionTransactionIsolator-lZUSz1w0.d.mts} +2 -2
- package/dist/VitestObjectionTransactionIsolator.cjs +2 -2
- package/dist/VitestObjectionTransactionIsolator.d.cts +2 -2
- package/dist/VitestObjectionTransactionIsolator.d.mts +2 -2
- package/dist/VitestObjectionTransactionIsolator.mjs +2 -2
- package/dist/{VitestTransactionIsolator-CskiiJbW.mjs → VitestTransactionIsolator-BIaMs4c2.mjs} +40 -2
- package/dist/VitestTransactionIsolator-BIaMs4c2.mjs.map +1 -0
- package/dist/{VitestTransactionIsolator-BQ5FpLtC.cjs → VitestTransactionIsolator-BKIrj3Uy.cjs} +45 -1
- package/dist/VitestTransactionIsolator-BKIrj3Uy.cjs.map +1 -0
- package/dist/{VitestTransactionIsolator-DdLNODZg.d.cts → VitestTransactionIsolator-CyG_i_Nj.d.cts} +61 -3
- package/dist/{VitestTransactionIsolator-CsfJBxcb.d.mts → VitestTransactionIsolator-DWDbnITQ.d.mts} +61 -3
- package/dist/VitestTransactionIsolator.cjs +3 -2
- package/dist/VitestTransactionIsolator.d.cts +2 -2
- package/dist/VitestTransactionIsolator.d.mts +2 -2
- package/dist/VitestTransactionIsolator.mjs +2 -2
- package/dist/better-auth.cjs +8 -11
- package/dist/better-auth.cjs.map +1 -1
- package/dist/better-auth.d.cts +2 -2
- package/dist/better-auth.d.mts +2 -2
- package/dist/better-auth.mjs +8 -11
- package/dist/better-auth.mjs.map +1 -1
- package/dist/{directory-B4oYx02C.d.mts → directory-BXavAeJZ.d.mts} +3 -3
- package/dist/{directory-BUcnztHI.d.cts → directory-DlkPEzL4.d.cts} +3 -3
- package/dist/{faker-Br8MzXil.d.mts → faker-DHh7xs4u.d.mts} +3 -3
- package/dist/faker.d.mts +1 -1
- package/dist/kysely.cjs +58 -4
- package/dist/kysely.cjs.map +1 -1
- package/dist/kysely.d.cts +58 -5
- package/dist/kysely.d.mts +59 -6
- package/dist/kysely.mjs +57 -5
- package/dist/kysely.mjs.map +1 -1
- package/dist/objection.cjs +54 -4
- package/dist/objection.cjs.map +1 -1
- package/dist/objection.d.cts +54 -5
- package/dist/objection.d.mts +55 -6
- package/dist/objection.mjs +53 -5
- package/dist/objection.mjs.map +1 -1
- package/dist/os/directory.d.cts +1 -1
- package/dist/os/directory.d.mts +1 -1
- package/dist/os/index.d.cts +1 -1
- package/dist/os/index.d.mts +1 -1
- package/package.json +7 -3
- package/src/KyselyFactory.ts +29 -16
- package/src/ObjectionFactory.ts +34 -19
- package/src/VitestTransactionIsolator.ts +110 -2
- package/src/__tests__/KyselyFactory.spec.ts +10 -10
- package/src/__tests__/ObjectionFactory.spec.ts +9 -12
- package/src/__tests__/integration.spec.ts +171 -14
- package/src/better-auth.ts +13 -15
- package/src/kysely.ts +66 -0
- package/src/objection.ts +61 -0
- package/dist/KyselyFactory-BcYkC0t2.mjs.map +0 -1
- package/dist/KyselyFactory-Cf0o2YxO.cjs.map +0 -1
- package/dist/ObjectionFactory-8hebmnai.mjs.map +0 -1
- package/dist/ObjectionFactory-CDriunkS.cjs.map +0 -1
- package/dist/VitestTransactionIsolator-BQ5FpLtC.cjs.map +0 -1
- package/dist/VitestTransactionIsolator-CskiiJbW.mjs.map +0 -1
|
@@ -323,7 +323,7 @@ describe('ObjectionFactory', () => {
|
|
|
323
323
|
it('should create a builder function with auto-insert', async ({ trx }) => {
|
|
324
324
|
const userBuilder = ObjectionFactory.createBuilder(
|
|
325
325
|
User,
|
|
326
|
-
(attrs,
|
|
326
|
+
({ attrs, faker }) => ({
|
|
327
327
|
name: faker.person.fullName(),
|
|
328
328
|
...attrs,
|
|
329
329
|
}),
|
|
@@ -344,7 +344,7 @@ describe('ObjectionFactory', () => {
|
|
|
344
344
|
}) => {
|
|
345
345
|
const userBuilder = ObjectionFactory.createBuilder(
|
|
346
346
|
User,
|
|
347
|
-
(attrs) => ({
|
|
347
|
+
({ attrs }) => ({
|
|
348
348
|
name: 'No Insert User',
|
|
349
349
|
...attrs,
|
|
350
350
|
}),
|
|
@@ -369,8 +369,8 @@ describe('ObjectionFactory', () => {
|
|
|
369
369
|
|
|
370
370
|
const userBuilder = ObjectionFactory.createBuilder(
|
|
371
371
|
User,
|
|
372
|
-
(attrs, factory, db, fakerInstance) => {
|
|
373
|
-
capturedFactory =
|
|
372
|
+
({ attrs, factory: passedFactory, db, faker: fakerInstance }) => {
|
|
373
|
+
capturedFactory = passedFactory;
|
|
374
374
|
capturedDb = db;
|
|
375
375
|
capturedFaker = fakerInstance;
|
|
376
376
|
|
|
@@ -394,7 +394,7 @@ describe('ObjectionFactory', () => {
|
|
|
394
394
|
it('should handle async item functions', async ({ trx }) => {
|
|
395
395
|
const userBuilder = ObjectionFactory.createBuilder(
|
|
396
396
|
User,
|
|
397
|
-
async (attrs
|
|
397
|
+
async ({ attrs }) => {
|
|
398
398
|
// Simulate async operation
|
|
399
399
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
400
400
|
|
|
@@ -433,13 +433,10 @@ describe('ObjectionFactory', () => {
|
|
|
433
433
|
});
|
|
434
434
|
|
|
435
435
|
it('should allow overriding default values', async ({ trx }) => {
|
|
436
|
-
const userBuilder = ObjectionFactory.createBuilder(
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
...attrs,
|
|
441
|
-
}),
|
|
442
|
-
);
|
|
436
|
+
const userBuilder = ObjectionFactory.createBuilder(User, ({ attrs }) => ({
|
|
437
|
+
name: 'Default Name',
|
|
438
|
+
...attrs,
|
|
439
|
+
}));
|
|
443
440
|
|
|
444
441
|
const builders = { user: userBuilder };
|
|
445
442
|
const factory = new ObjectionFactory(builders, {}, trx);
|
|
@@ -3,7 +3,7 @@ import { TEST_DATABASE_CONFIG } from '../../test/globalSetup';
|
|
|
3
3
|
import { type TestDatabase, createTestTables } from '../../test/helpers';
|
|
4
4
|
import { KyselyFactory } from '../KyselyFactory';
|
|
5
5
|
import { createKyselyDb } from '../helpers';
|
|
6
|
-
import { wrapVitestKyselyTransaction } from '../kysely';
|
|
6
|
+
import { extendWithFixtures, wrapVitestKyselyTransaction } from '../kysely';
|
|
7
7
|
|
|
8
8
|
const db = () => createKyselyDb<TestDatabase>(TEST_DATABASE_CONFIG);
|
|
9
9
|
const it = wrapVitestKyselyTransaction<TestDatabase>(
|
|
@@ -18,7 +18,7 @@ describe('Testkit Integration Tests', () => {
|
|
|
18
18
|
// Create builders for all entities
|
|
19
19
|
const userBuilder = KyselyFactory.createBuilder<TestDatabase, 'users'>(
|
|
20
20
|
'users',
|
|
21
|
-
async (
|
|
21
|
+
async () => ({
|
|
22
22
|
name: 'John Doe',
|
|
23
23
|
email: `user${Date.now()}-${Math.random()}@example.com`,
|
|
24
24
|
role: 'user' as const,
|
|
@@ -29,7 +29,7 @@ describe('Testkit Integration Tests', () => {
|
|
|
29
29
|
|
|
30
30
|
const postBuilder = KyselyFactory.createBuilder<TestDatabase, 'posts'>(
|
|
31
31
|
'posts',
|
|
32
|
-
async (attrs, factory) => {
|
|
32
|
+
async ({ attrs, factory }) => {
|
|
33
33
|
// Create a user if no userId provided
|
|
34
34
|
if (!attrs.userId) {
|
|
35
35
|
const user = await factory.insert('user');
|
|
@@ -55,7 +55,7 @@ describe('Testkit Integration Tests', () => {
|
|
|
55
55
|
const commentBuilder = KyselyFactory.createBuilder<
|
|
56
56
|
TestDatabase,
|
|
57
57
|
'comments'
|
|
58
|
-
>('comments', async (attrs, factory) => {
|
|
58
|
+
>('comments', async ({ attrs, factory }) => {
|
|
59
59
|
let postId = attrs.postId;
|
|
60
60
|
let userId = attrs.userId;
|
|
61
61
|
|
|
@@ -152,7 +152,7 @@ describe('Testkit Integration Tests', () => {
|
|
|
152
152
|
|
|
153
153
|
const userBuilder = KyselyFactory.createBuilder<TestDatabase, 'users'>(
|
|
154
154
|
'users',
|
|
155
|
-
async (
|
|
155
|
+
async () => ({
|
|
156
156
|
name: 'Default User',
|
|
157
157
|
email: `user${Date.now()}-${Math.random()}@example.com`,
|
|
158
158
|
role: 'user' as const,
|
|
@@ -163,7 +163,7 @@ describe('Testkit Integration Tests', () => {
|
|
|
163
163
|
|
|
164
164
|
const postBuilder = KyselyFactory.createBuilder<TestDatabase, 'posts'>(
|
|
165
165
|
'posts',
|
|
166
|
-
async (attrs, factory) => {
|
|
166
|
+
async ({ attrs, factory }) => {
|
|
167
167
|
if (!attrs.userId) {
|
|
168
168
|
const user = await factory.insert('user');
|
|
169
169
|
return {
|
|
@@ -193,7 +193,10 @@ describe('Testkit Integration Tests', () => {
|
|
|
193
193
|
// Create complex seeds
|
|
194
194
|
const seeds = {
|
|
195
195
|
blogWithAdminAndPosts: KyselyFactory.createSeed(
|
|
196
|
-
async (
|
|
196
|
+
async (
|
|
197
|
+
attrs: { postCount?: number },
|
|
198
|
+
factory: KyselyFactory<TestDatabase, typeof builders, {}>,
|
|
199
|
+
) => {
|
|
197
200
|
// Create admin user
|
|
198
201
|
const admin = await factory.insert('user', {
|
|
199
202
|
name: 'Blog Admin',
|
|
@@ -229,13 +232,15 @@ describe('Testkit Integration Tests', () => {
|
|
|
229
232
|
usersWithPosts: KyselyFactory.createSeed(
|
|
230
233
|
async (
|
|
231
234
|
attrs: { userCount?: number; postsPerUser?: number },
|
|
232
|
-
factory:
|
|
233
|
-
db: any,
|
|
235
|
+
factory: KyselyFactory<TestDatabase, typeof builders, {}>,
|
|
234
236
|
) => {
|
|
235
237
|
const userCount = attrs.userCount || 2;
|
|
236
238
|
const postsPerUser = attrs.postsPerUser || 2;
|
|
237
239
|
|
|
238
|
-
const results: Array<{
|
|
240
|
+
const results: Array<{
|
|
241
|
+
user: Awaited<ReturnType<typeof builders.user>>;
|
|
242
|
+
posts: Awaited<ReturnType<typeof builders.post>>[];
|
|
243
|
+
}> = [];
|
|
239
244
|
|
|
240
245
|
for (let i = 0; i < userCount; i++) {
|
|
241
246
|
const user = await factory.insert('user', {
|
|
@@ -306,7 +311,7 @@ describe('Testkit Integration Tests', () => {
|
|
|
306
311
|
it('should handle transaction isolation properly', async ({ trx }) => {
|
|
307
312
|
const userBuilder = KyselyFactory.createBuilder<TestDatabase, 'users'>(
|
|
308
313
|
'users',
|
|
309
|
-
async (
|
|
314
|
+
async ({ faker }) => ({
|
|
310
315
|
name: 'Test User',
|
|
311
316
|
email: faker.internet.email(),
|
|
312
317
|
role: 'user' as const,
|
|
@@ -345,7 +350,7 @@ describe('Testkit Integration Tests', () => {
|
|
|
345
350
|
it('should handle creating many records efficiently', async ({ trx }) => {
|
|
346
351
|
const userBuilder = KyselyFactory.createBuilder<TestDatabase, 'users'>(
|
|
347
352
|
'users',
|
|
348
|
-
async (
|
|
353
|
+
async ({ faker }) => ({
|
|
349
354
|
name: `User ${Math.random()}`,
|
|
350
355
|
email: faker.internet.email().toLowerCase(),
|
|
351
356
|
role: 'user' as const,
|
|
@@ -381,7 +386,7 @@ describe('Testkit Integration Tests', () => {
|
|
|
381
386
|
it('should handle complex attribute generation', async ({ trx }) => {
|
|
382
387
|
const userBuilder = KyselyFactory.createBuilder<TestDatabase, 'users'>(
|
|
383
388
|
'users',
|
|
384
|
-
async (attrs,
|
|
389
|
+
async ({ attrs, faker }) => {
|
|
385
390
|
return {
|
|
386
391
|
name: `Generated User ${attrs.id}`,
|
|
387
392
|
email: faker.internet.email().toLowerCase(),
|
|
@@ -392,7 +397,7 @@ describe('Testkit Integration Tests', () => {
|
|
|
392
397
|
|
|
393
398
|
const postBuilder = KyselyFactory.createBuilder<TestDatabase, 'posts'>(
|
|
394
399
|
'posts',
|
|
395
|
-
async (attrs, factory) => {
|
|
400
|
+
async ({ attrs, factory }) => {
|
|
396
401
|
let userId = attrs.userId;
|
|
397
402
|
if (!userId) {
|
|
398
403
|
const user = await factory.insert('user');
|
|
@@ -445,3 +450,155 @@ describe('Testkit Integration Tests', () => {
|
|
|
445
450
|
});
|
|
446
451
|
});
|
|
447
452
|
});
|
|
453
|
+
|
|
454
|
+
describe('extendWithFixtures', () => {
|
|
455
|
+
// Create builders for use in extended fixtures
|
|
456
|
+
const builders = {
|
|
457
|
+
user: KyselyFactory.createBuilder<TestDatabase, 'users'>(
|
|
458
|
+
'users',
|
|
459
|
+
({ faker }) => ({
|
|
460
|
+
name: faker.person.fullName(),
|
|
461
|
+
email: faker.internet.email().toLowerCase(),
|
|
462
|
+
role: 'user' as const,
|
|
463
|
+
createdAt: new Date(),
|
|
464
|
+
updatedAt: new Date(),
|
|
465
|
+
}),
|
|
466
|
+
),
|
|
467
|
+
post: KyselyFactory.createBuilder<TestDatabase, 'posts'>(
|
|
468
|
+
'posts',
|
|
469
|
+
async ({ attrs, factory, faker }) => {
|
|
470
|
+
const userId = attrs.userId ?? (await factory.insert('user')).id;
|
|
471
|
+
return {
|
|
472
|
+
title: faker.lorem.sentence(),
|
|
473
|
+
content: faker.lorem.paragraphs(),
|
|
474
|
+
userId,
|
|
475
|
+
published: false,
|
|
476
|
+
createdAt: new Date(),
|
|
477
|
+
updatedAt: new Date(),
|
|
478
|
+
};
|
|
479
|
+
},
|
|
480
|
+
),
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
// Create base test with transaction
|
|
484
|
+
const baseTest = wrapVitestKyselyTransaction<TestDatabase>(
|
|
485
|
+
base,
|
|
486
|
+
db,
|
|
487
|
+
createTestTables,
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
// Extend with factory fixture
|
|
491
|
+
const itWithFactory = extendWithFixtures<
|
|
492
|
+
TestDatabase,
|
|
493
|
+
{ factory: KyselyFactory<TestDatabase, typeof builders, {}> }
|
|
494
|
+
>(baseTest, {
|
|
495
|
+
factory: (trx) => new KyselyFactory(builders, {}, trx),
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
itWithFactory(
|
|
499
|
+
'should provide factory fixture alongside trx',
|
|
500
|
+
async ({ trx, factory }) => {
|
|
501
|
+
// Both trx and factory should be available
|
|
502
|
+
expect(trx).toBeDefined();
|
|
503
|
+
expect(factory).toBeDefined();
|
|
504
|
+
expect(factory).toBeInstanceOf(KyselyFactory);
|
|
505
|
+
|
|
506
|
+
// Factory should work with the transaction
|
|
507
|
+
const user = await factory.insert('user', { name: 'Test User' });
|
|
508
|
+
expect(user.id).toBeDefined();
|
|
509
|
+
expect(user.name).toBe('Test User');
|
|
510
|
+
|
|
511
|
+
// Verify user exists in transaction
|
|
512
|
+
const found = await trx
|
|
513
|
+
.selectFrom('users')
|
|
514
|
+
.where('id', '=', user.id)
|
|
515
|
+
.selectAll()
|
|
516
|
+
.executeTakeFirst();
|
|
517
|
+
|
|
518
|
+
expect(found).toBeDefined();
|
|
519
|
+
expect(found?.name).toBe('Test User');
|
|
520
|
+
},
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
itWithFactory(
|
|
524
|
+
'should allow factory to create related records',
|
|
525
|
+
async ({ factory }) => {
|
|
526
|
+
// Create user first
|
|
527
|
+
const user = await factory.insert('user', {
|
|
528
|
+
name: 'Author',
|
|
529
|
+
email: 'author@example.com',
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
// Create posts for the user
|
|
533
|
+
const posts = await factory.insertMany(3, 'post', (idx: number) => ({
|
|
534
|
+
title: `Post ${idx + 1}`,
|
|
535
|
+
userId: user.id,
|
|
536
|
+
published: idx === 0,
|
|
537
|
+
}));
|
|
538
|
+
|
|
539
|
+
expect(posts).toHaveLength(3);
|
|
540
|
+
expect(posts[0].userId).toBe(user.id);
|
|
541
|
+
expect(posts[0].published).toBe(true);
|
|
542
|
+
expect(posts[1].published).toBe(false);
|
|
543
|
+
},
|
|
544
|
+
);
|
|
545
|
+
|
|
546
|
+
// Test with multiple fixtures
|
|
547
|
+
const itWithMultipleFixtures = extendWithFixtures<
|
|
548
|
+
TestDatabase,
|
|
549
|
+
{
|
|
550
|
+
factory: KyselyFactory<TestDatabase, typeof builders, {}>;
|
|
551
|
+
userCount: number;
|
|
552
|
+
}
|
|
553
|
+
>(baseTest, {
|
|
554
|
+
factory: (trx) => new KyselyFactory(builders, {}, trx),
|
|
555
|
+
userCount: () => 42, // Simple fixture that doesn't use trx
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
itWithMultipleFixtures(
|
|
559
|
+
'should support multiple fixtures',
|
|
560
|
+
async ({ trx, factory, userCount }) => {
|
|
561
|
+
expect(trx).toBeDefined();
|
|
562
|
+
expect(factory).toBeInstanceOf(KyselyFactory);
|
|
563
|
+
expect(userCount).toBe(42);
|
|
564
|
+
|
|
565
|
+
// Use the factory
|
|
566
|
+
const user = await factory.insert('user');
|
|
567
|
+
expect(user.id).toBeDefined();
|
|
568
|
+
},
|
|
569
|
+
);
|
|
570
|
+
|
|
571
|
+
// Test async fixture creators
|
|
572
|
+
const itWithAsyncFixture = extendWithFixtures<
|
|
573
|
+
TestDatabase,
|
|
574
|
+
{ initialUser: Awaited<ReturnType<typeof builders.user>> }
|
|
575
|
+
>(baseTest, {
|
|
576
|
+
initialUser: async (trx) => {
|
|
577
|
+
// Create a user directly in the fixture
|
|
578
|
+
const factory = new KyselyFactory(builders, {}, trx);
|
|
579
|
+
return factory.insert('user', {
|
|
580
|
+
name: 'Initial User',
|
|
581
|
+
email: 'initial@example.com',
|
|
582
|
+
});
|
|
583
|
+
},
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
itWithAsyncFixture(
|
|
587
|
+
'should support async fixture creators',
|
|
588
|
+
async ({ trx, initialUser }) => {
|
|
589
|
+
expect(initialUser).toBeDefined();
|
|
590
|
+
expect(initialUser.name).toBe('Initial User');
|
|
591
|
+
expect(initialUser.email).toBe('initial@example.com');
|
|
592
|
+
|
|
593
|
+
// Verify user exists in database
|
|
594
|
+
const found = await trx
|
|
595
|
+
.selectFrom('users')
|
|
596
|
+
.where('id', '=', initialUser.id)
|
|
597
|
+
.selectAll()
|
|
598
|
+
.executeTakeFirst();
|
|
599
|
+
|
|
600
|
+
expect(found).toBeDefined();
|
|
601
|
+
expect(found?.id).toBe(initialUser.id);
|
|
602
|
+
},
|
|
603
|
+
);
|
|
604
|
+
});
|
package/src/better-auth.ts
CHANGED
|
@@ -65,22 +65,24 @@ function matchesWhere(record: any, where?: Where[]): boolean {
|
|
|
65
65
|
matches = recordValue !== value;
|
|
66
66
|
break;
|
|
67
67
|
case 'lt':
|
|
68
|
-
matches = recordValue < value;
|
|
68
|
+
matches = value != null && recordValue < value;
|
|
69
69
|
break;
|
|
70
70
|
case 'lte':
|
|
71
|
-
matches = recordValue <= value;
|
|
71
|
+
matches = value != null && recordValue <= value;
|
|
72
72
|
break;
|
|
73
73
|
case 'gt':
|
|
74
|
-
matches = recordValue > value;
|
|
74
|
+
matches = value != null && recordValue > value;
|
|
75
75
|
break;
|
|
76
76
|
case 'gte':
|
|
77
|
-
matches = recordValue >= value;
|
|
77
|
+
matches = value != null && recordValue >= value;
|
|
78
78
|
break;
|
|
79
79
|
case 'in':
|
|
80
|
-
matches =
|
|
80
|
+
matches =
|
|
81
|
+
Array.isArray(value) && (value as unknown[]).includes(recordValue);
|
|
81
82
|
break;
|
|
82
83
|
case 'not_in':
|
|
83
|
-
matches =
|
|
84
|
+
matches =
|
|
85
|
+
Array.isArray(value) && !(value as unknown[]).includes(recordValue);
|
|
84
86
|
break;
|
|
85
87
|
case 'contains':
|
|
86
88
|
matches =
|
|
@@ -207,7 +209,7 @@ export const memoryAdapter = (
|
|
|
207
209
|
return null;
|
|
208
210
|
},
|
|
209
211
|
|
|
210
|
-
findMany: async ({ where, model, limit, offset, sortBy
|
|
212
|
+
findMany: async ({ where, model, limit, offset, sortBy }) => {
|
|
211
213
|
debugLog('FIND_MANY', { model, where });
|
|
212
214
|
|
|
213
215
|
const modelName = getModelName(model);
|
|
@@ -230,15 +232,11 @@ export const memoryAdapter = (
|
|
|
230
232
|
}
|
|
231
233
|
|
|
232
234
|
return Promise.all(
|
|
233
|
-
results.map(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return transformed;
|
|
237
|
-
}),
|
|
238
|
-
);
|
|
235
|
+
results.map((record) => transformOutput(record, model)),
|
|
236
|
+
) as any;
|
|
239
237
|
},
|
|
240
238
|
|
|
241
|
-
update: async ({ where, update, model
|
|
239
|
+
update: async ({ where, update, model }) => {
|
|
242
240
|
debugLog('UPDATE', { model, where });
|
|
243
241
|
|
|
244
242
|
const modelName = getModelName(model);
|
|
@@ -254,7 +252,7 @@ export const memoryAdapter = (
|
|
|
254
252
|
);
|
|
255
253
|
const updated = { ...record, ...transformedData };
|
|
256
254
|
modelData.set(id, updated);
|
|
257
|
-
return transformOutput(updated, model
|
|
255
|
+
return transformOutput(updated, model) as any;
|
|
258
256
|
}
|
|
259
257
|
}
|
|
260
258
|
return null;
|
package/src/kysely.ts
CHANGED
|
@@ -3,7 +3,9 @@ import type { TestAPI } from 'vitest';
|
|
|
3
3
|
import { VitestKyselyTransactionIsolator } from './VitestKyselyTransactionIsolator';
|
|
4
4
|
import {
|
|
5
5
|
type DatabaseConnection,
|
|
6
|
+
type FixtureCreators,
|
|
6
7
|
IsolationLevel,
|
|
8
|
+
extendWithFixtures as baseExtendWithFixtures,
|
|
7
9
|
} from './VitestTransactionIsolator';
|
|
8
10
|
|
|
9
11
|
/**
|
|
@@ -15,6 +17,10 @@ export { KyselyFactory } from './KyselyFactory';
|
|
|
15
17
|
export { PostgresKyselyMigrator } from './PostgresKyselyMigrator';
|
|
16
18
|
export { VitestKyselyTransactionIsolator } from './VitestKyselyTransactionIsolator';
|
|
17
19
|
export { IsolationLevel } from './VitestTransactionIsolator';
|
|
20
|
+
export type { FixtureCreators } from './VitestTransactionIsolator';
|
|
21
|
+
|
|
22
|
+
// Re-export faker and FakerFactory for type portability in declaration files
|
|
23
|
+
export { faker, type FakerFactory } from './faker';
|
|
18
24
|
|
|
19
25
|
/**
|
|
20
26
|
* Creates a wrapped Vitest test API with automatic transaction rollback for Kysely.
|
|
@@ -82,3 +88,63 @@ export function wrapVitestKyselyTransaction<Database>(
|
|
|
82
88
|
|
|
83
89
|
return wrapper.wrapVitestWithTransaction(connection, setup, level);
|
|
84
90
|
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Extends a Kysely transaction-wrapped test with additional fixtures.
|
|
94
|
+
* Each fixture receives the transaction and can create dependencies like factories or repositories.
|
|
95
|
+
*
|
|
96
|
+
* @template Database - The database schema type
|
|
97
|
+
* @template Extended - The type of additional fixtures to provide
|
|
98
|
+
* @param wrappedTest - The base wrapped test from wrapVitestKyselyTransaction
|
|
99
|
+
* @param fixtures - Object mapping fixture names to creator functions
|
|
100
|
+
* @returns An extended test API with both trx and the additional fixtures
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```typescript
|
|
104
|
+
* import { test } from 'vitest';
|
|
105
|
+
* import { wrapVitestKyselyTransaction, extendWithFixtures, KyselyFactory } from '@geekmidas/testkit/kysely';
|
|
106
|
+
*
|
|
107
|
+
* // Define your builders
|
|
108
|
+
* const builders = {
|
|
109
|
+
* user: KyselyFactory.createBuilder<DB, 'users'>('users', ({ faker }) => ({
|
|
110
|
+
* name: faker.person.fullName(),
|
|
111
|
+
* email: faker.internet.email(),
|
|
112
|
+
* })),
|
|
113
|
+
* };
|
|
114
|
+
*
|
|
115
|
+
* // Create base wrapped test
|
|
116
|
+
* const baseTest = wrapVitestKyselyTransaction<DB>(test, db, createTestTables);
|
|
117
|
+
*
|
|
118
|
+
* // Extend with fixtures - each fixture receives the transaction
|
|
119
|
+
* const it = extendWithFixtures<DB, { factory: KyselyFactory<DB, typeof builders, {}> }>(
|
|
120
|
+
* baseTest,
|
|
121
|
+
* {
|
|
122
|
+
* factory: (trx) => new KyselyFactory(builders, {}, trx),
|
|
123
|
+
* }
|
|
124
|
+
* );
|
|
125
|
+
*
|
|
126
|
+
* // Use in tests - both trx and factory are available
|
|
127
|
+
* it('should create user with factory', async ({ trx, factory }) => {
|
|
128
|
+
* const user = await factory.insert('user', { name: 'Test User' });
|
|
129
|
+
* expect(user.id).toBeDefined();
|
|
130
|
+
*
|
|
131
|
+
* // Verify in database
|
|
132
|
+
* const found = await trx
|
|
133
|
+
* .selectFrom('users')
|
|
134
|
+
* .where('id', '=', user.id)
|
|
135
|
+
* .selectAll()
|
|
136
|
+
* .executeTakeFirst();
|
|
137
|
+
* expect(found?.name).toBe('Test User');
|
|
138
|
+
* });
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export function extendWithFixtures<
|
|
142
|
+
Database,
|
|
143
|
+
Extended extends Record<string, unknown>,
|
|
144
|
+
T extends ReturnType<TestAPI['extend']> = ReturnType<TestAPI['extend']>,
|
|
145
|
+
>(wrappedTest: T, fixtures: FixtureCreators<Transaction<Database>, Extended>) {
|
|
146
|
+
return baseExtendWithFixtures<Transaction<Database>, Extended, T>(
|
|
147
|
+
wrappedTest,
|
|
148
|
+
fixtures,
|
|
149
|
+
);
|
|
150
|
+
}
|
package/src/objection.ts
CHANGED
|
@@ -3,7 +3,9 @@ import type { TestAPI } from 'vitest';
|
|
|
3
3
|
import { VitestObjectionTransactionIsolator } from './VitestObjectionTransactionIsolator';
|
|
4
4
|
import {
|
|
5
5
|
type DatabaseConnection,
|
|
6
|
+
type FixtureCreators,
|
|
6
7
|
IsolationLevel,
|
|
8
|
+
extendWithFixtures as baseExtendWithFixtures,
|
|
7
9
|
} from './VitestTransactionIsolator';
|
|
8
10
|
|
|
9
11
|
/**
|
|
@@ -16,6 +18,10 @@ export { ObjectionFactory } from './ObjectionFactory';
|
|
|
16
18
|
export { VitestObjectionTransactionIsolator } from './VitestObjectionTransactionIsolator';
|
|
17
19
|
export { IsolationLevel } from './VitestTransactionIsolator';
|
|
18
20
|
export { PostgresObjectionMigrator } from './PostgresObjectionMigrator';
|
|
21
|
+
export type { FixtureCreators } from './VitestTransactionIsolator';
|
|
22
|
+
|
|
23
|
+
// Re-export faker and FakerFactory for type portability in declaration files
|
|
24
|
+
export { faker, type FakerFactory } from './faker';
|
|
19
25
|
|
|
20
26
|
/**
|
|
21
27
|
* Creates a wrapped Vitest test API with automatic transaction rollback for Objection.js.
|
|
@@ -98,3 +104,58 @@ export function wrapVitestObjectionTransaction(
|
|
|
98
104
|
|
|
99
105
|
return wrapper.wrapVitestWithTransaction(conn, setup, level);
|
|
100
106
|
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Extends an Objection.js transaction-wrapped test with additional fixtures.
|
|
110
|
+
* Each fixture receives the transaction and can create dependencies like factories or repositories.
|
|
111
|
+
*
|
|
112
|
+
* @template Extended - The type of additional fixtures to provide
|
|
113
|
+
* @param wrappedTest - The base wrapped test from wrapVitestObjectionTransaction
|
|
114
|
+
* @param fixtures - Object mapping fixture names to creator functions
|
|
115
|
+
* @returns An extended test API with both trx and the additional fixtures
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```typescript
|
|
119
|
+
* import { test } from 'vitest';
|
|
120
|
+
* import { wrapVitestObjectionTransaction, extendWithFixtures, ObjectionFactory } from '@geekmidas/testkit/objection';
|
|
121
|
+
* import { User } from './models';
|
|
122
|
+
*
|
|
123
|
+
* // Define your builders
|
|
124
|
+
* const builders = {
|
|
125
|
+
* user: ObjectionFactory.createBuilder(User, ({ faker }) => ({
|
|
126
|
+
* name: faker.person.fullName(),
|
|
127
|
+
* email: faker.internet.email(),
|
|
128
|
+
* })),
|
|
129
|
+
* };
|
|
130
|
+
*
|
|
131
|
+
* // Create base wrapped test
|
|
132
|
+
* const baseTest = wrapVitestObjectionTransaction(test, knex, createTestTables);
|
|
133
|
+
*
|
|
134
|
+
* // Extend with fixtures - each fixture receives the transaction
|
|
135
|
+
* const it = extendWithFixtures<{ factory: ObjectionFactory<typeof builders, {}> }>(
|
|
136
|
+
* baseTest,
|
|
137
|
+
* {
|
|
138
|
+
* factory: (trx) => new ObjectionFactory(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 User.query(trx).findById(user.id);
|
|
149
|
+
* expect(found?.name).toBe('Test User');
|
|
150
|
+
* });
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
export function extendWithFixtures<
|
|
154
|
+
Extended extends Record<string, unknown>,
|
|
155
|
+
T extends ReturnType<TestAPI['extend']> = ReturnType<TestAPI['extend']>,
|
|
156
|
+
>(wrappedTest: T, fixtures: FixtureCreators<Knex.Transaction, Extended>) {
|
|
157
|
+
return baseExtendWithFixtures<Knex.Transaction, Extended, T>(
|
|
158
|
+
wrappedTest,
|
|
159
|
+
fixtures,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"KyselyFactory-BcYkC0t2.mjs","names":["seedFn: Seed","builders: Builders","seeds: Seeds","db: Kysely<DB> | ControlledTransaction<DB, []>","table: TableName","item?: (\n attrs: Attrs,\n factory: Factory,\n db: Kysely<DB>,\n faker: FakerFactory,\n ) =>\n | Partial<Insertable<DB[TableName]>>\n | Promise<Partial<Insertable<DB[TableName]>>>","autoInsert?: boolean","attrs: Attrs","factory: Factory","db: Kysely<DB>","faker: FakerFactory","data: Partial<Insertable<DB[TableName]>>","faker","builderName: K","attrs?: Parameters<Builders[K]>[0]","count: number","attrs?: any","promises: Promise<any>[]","seedName: K","attrs?: Parameters<Seeds[K]>[0]"],"sources":["../src/KyselyFactory.ts"],"sourcesContent":["import type {\n ControlledTransaction,\n Insertable,\n Kysely,\n Selectable,\n} from 'kysely';\nimport { Factory, type FactorySeed } from './Factory.ts';\nimport { type FakerFactory, faker } from './faker.ts';\n\n/**\n * Factory implementation for Kysely ORM, providing test data creation utilities.\n * Extends the base Factory class with Kysely-specific database operations.\n *\n * @template DB - The database schema type\n * @template Builders - Record of builder functions for creating entities\n * @template Seeds - Record of seed functions for complex test scenarios\n *\n * @example\n * ```typescript\n * // Define your database schema\n * interface Database {\n * users: UsersTable;\n * posts: PostsTable;\n * }\n *\n * // Create builders\n * const builders = {\n * user: KyselyFactory.createBuilder<Database, 'users'>('users', (attrs, factory, db, faker) => ({\n * id: faker.string.uuid(),\n * name: faker.person.fullName(),\n * email: faker.internet.email(),\n * ...attrs\n * })),\n * post: KyselyFactory.createBuilder<Database, 'posts'>('posts', (attrs) => ({\n * title: 'Test Post',\n * content: 'Test content',\n * ...attrs\n * }))\n * };\n *\n * // Create factory instance\n * const factory = new KyselyFactory(builders, seeds, db);\n *\n * // Use in tests\n * const user = await factory.insert('user', { name: 'John Doe' });\n * ```\n */\nexport class KyselyFactory<\n DB,\n Builders extends Record<string, any>,\n Seeds extends Record<string, any>,\n> extends Factory<Builders, Seeds> {\n /**\n * Creates a typed seed function with proper type inference.\n * Inherits from the base Factory class implementation.\n *\n * @template Seed - The seed function type\n * @param seedFn - The seed function to wrap\n * @returns The same seed function with proper typing\n */\n static createSeed<Seed extends FactorySeed>(seedFn: Seed): Seed {\n return Factory.createSeed(seedFn);\n }\n\n /**\n * Creates a new KyselyFactory instance.\n *\n * @param builders - Record of builder functions for creating individual entities\n * @param seeds - Record of seed functions for creating complex test scenarios\n * @param db - Kysely database instance or controlled transaction\n */\n constructor(\n private builders: Builders,\n private seeds: Seeds,\n private db: Kysely<DB> | ControlledTransaction<DB, []>,\n ) {\n super();\n }\n\n /**\n * Creates a typed builder function for a specific database table.\n * This is a utility method that helps create builders with proper type inference for Kysely.\n *\n * @template DB - The database schema type\n * @template TableName - The name of the table (must be a key of DB)\n * @template Attrs - The attributes type for the builder (defaults to Partial<Insertable>)\n * @template Factory - The factory instance type\n * @template Result - The result type (defaults to Selectable of the table)\n *\n * @param table - The name of the database table\n * @param item - Optional function to provide default values and transformations\n * @param autoInsert - Whether to automatically insert the record (default: true)\n * @returns A builder function that creates and optionally inserts records\n *\n * @example\n * ```typescript\n * // Create a simple builder with defaults\n * const userBuilder = KyselyFactory.createBuilder<DB, 'users'>('users',\n * (attrs, factory, db, faker) => ({\n * id: faker.string.uuid(),\n * name: faker.person.fullName(),\n * email: faker.internet.email(),\n * createdAt: new Date(),\n * ...attrs\n * })\n * );\n *\n * // Create a builder that doesn't auto-insert (useful for nested inserts)\n * const addressBuilder = KyselyFactory.createBuilder<DB, 'addresses'>('addresses',\n * (attrs) => ({\n * street: '123 Main St',\n * city: 'Anytown',\n * ...attrs\n * }),\n * false // Don't auto-insert\n * );\n * ```\n */\n static createBuilder<\n DB,\n TableName extends keyof DB & string,\n Attrs extends Partial<Insertable<DB[TableName]>> = Partial<\n Insertable<DB[TableName]>\n >,\n Factory = any,\n Result = Selectable<DB[TableName]>,\n >(\n table: TableName,\n item?: (\n attrs: Attrs,\n factory: Factory,\n db: Kysely<DB>,\n faker: FakerFactory,\n ) =>\n | Partial<Insertable<DB[TableName]>>\n | Promise<Partial<Insertable<DB[TableName]>>>,\n autoInsert?: boolean,\n ): (\n attrs: Attrs,\n factory: Factory,\n db: Kysely<DB>,\n faker: FakerFactory,\n ) => Promise<Result> {\n return async (\n attrs: Attrs,\n factory: Factory,\n db: Kysely<DB>,\n faker: FakerFactory,\n ) => {\n // Start with attributes\n let data: Partial<Insertable<DB[TableName]>> = { ...attrs };\n\n // Apply defaults\n if (item) {\n const defaults = await item(attrs, factory, db, faker);\n data = { ...defaults, ...data };\n }\n\n // Handle insertion based on autoInsert flag\n if (autoInsert !== false) {\n // Auto insert is enabled by default\n const result = await db\n .insertInto(table)\n .values(data as Insertable<DB[TableName]>)\n .returningAll()\n .executeTakeFirst();\n\n if (!result) {\n throw new Error(`Failed to insert into ${table}`);\n }\n\n return result as Result;\n } else {\n // Return object for factory to handle insertion\n return { table, data } as any;\n }\n };\n }\n\n /**\n * Inserts a single record into the database using the specified builder.\n * The builder function is responsible for generating the record data with defaults\n * and the factory handles the actual database insertion.\n *\n * @template K - The builder name (must be a key of Builders)\n * @param builderName - The name of the builder to use\n * @param attrs - Optional attributes to override builder defaults\n * @returns A promise resolving to the inserted record\n * @throws Error if the specified builder doesn't exist\n *\n * @example\n * ```typescript\n * // Insert with defaults\n * const user = await factory.insert('user');\n *\n * // Insert with overrides\n * const adminUser = await factory.insert('user', {\n * email: 'admin@example.com',\n * role: 'admin'\n * });\n *\n * // Use the inserted record\n * const post = await factory.insert('post', {\n * userId: user.id,\n * title: 'My First Post'\n * });\n * ```\n */\n async insert<K extends keyof Builders>(\n builderName: K,\n attrs?: Parameters<Builders[K]>[0],\n ): Promise<Awaited<ReturnType<Builders[K]>>> {\n if (!(builderName in this.builders)) {\n throw new Error(\n `Factory \"${\n builderName as string\n }\" does not exist. Make sure it is correct and registered in src/test/setup.ts`,\n );\n }\n\n const result = await this.builders[builderName](\n attrs || {},\n this,\n this.db,\n faker,\n );\n\n // For Kysely, we expect the builder to return an object with table and data properties\n // or to handle the insertion itself and return the inserted record\n if (\n result &&\n typeof result === 'object' &&\n 'table' in result &&\n 'data' in result\n ) {\n // If the builder returns {table: string, data: object}, we insert it\n const inserted = await this.db\n .insertInto(result.table)\n .values(result.data)\n .returningAll()\n .executeTakeFirst();\n\n return inserted as any;\n }\n\n // Otherwise, assume the builder handled the insertion itself\n return result;\n }\n\n /**\n * Inserts multiple records into the database using the specified builder.\n * Supports both static attributes and dynamic attribute generation via a function.\n *\n * @template K - The builder name (must be a key of Builders)\n * @param count - The number of records to insert\n * @param builderName - The name of the builder to use\n * @param attrs - Static attributes or a function that generates attributes for each record\n * @returns A promise resolving to an array of inserted records\n * @throws Error if the specified builder doesn't exist\n *\n * @example\n * ```typescript\n * // Insert multiple with same attributes\n * const users = await factory.insertMany(5, 'user', { role: 'member' });\n *\n * // Insert multiple with dynamic attributes\n * const posts = await factory.insertMany(10, 'post', (idx, faker) => ({\n * title: `Post ${idx + 1}`,\n * content: faker.lorem.paragraph(),\n * publishedAt: faker.date.past()\n * }));\n *\n * // Create users with sequential emails\n * const admins = await factory.insertMany(3, 'user', (idx) => ({\n * email: `admin${idx + 1}@example.com`,\n * role: 'admin'\n * }));\n * ```\n */\n // Method overloads for better type inference\n async insertMany<K extends keyof Builders>(\n count: number,\n builderName: K,\n attrs?: Parameters<Builders[K]>[0],\n ): Promise<Awaited<ReturnType<Builders[K]>>[]>;\n async insertMany<K extends keyof Builders>(\n count: number,\n builderName: K,\n attrs: (\n idx: number,\n faker: FakerFactory,\n ) => Parameters<Builders[K]>[0] | Promise<Parameters<Builders[K]>[0]>,\n ): Promise<Awaited<ReturnType<Builders[K]>>[]>;\n async insertMany<K extends keyof Builders>(\n count: number,\n builderName: K,\n attrs?: any,\n ): Promise<Awaited<ReturnType<Builders[K]>>[]> {\n if (!(builderName in this.builders)) {\n throw new Error(\n `Builder \"${\n builderName as string\n }\" is not registered in this factory. Make sure it is correct and registered in src/test/setup.ts`,\n );\n }\n\n const promises: Promise<any>[] = [];\n\n for (let i = 0; i < count; i++) {\n const newAttrs =\n typeof attrs === 'function' ? await attrs(i, faker) : attrs;\n promises.push(this.insert(builderName, newAttrs));\n }\n\n return Promise.all(promises);\n }\n\n /**\n * Executes a seed function to create complex test scenarios with multiple related records.\n * Seeds are useful for setting up complete test environments with realistic data relationships.\n *\n * @template K - The seed name (must be a key of Seeds)\n * @param seedName - The name of the seed to execute\n * @param attrs - Optional configuration attributes for the seed\n * @returns The result of the seed function (typically the primary record created)\n * @throws Error if the specified seed doesn't exist\n *\n * @example\n * ```typescript\n * // Execute a simple seed\n * const user = await factory.seed('userWithProfile');\n *\n * // Execute a seed with configuration\n * const author = await factory.seed('authorWithBooks', {\n * bookCount: 5,\n * includeReviews: true\n * });\n *\n * // Use seed result in tests\n * const company = await factory.seed('companyWithDepartments', {\n * departmentCount: 3,\n * employeesPerDepartment: 10\n * });\n * expect(company.departments).toHaveLength(3);\n * ```\n */\n seed<K extends keyof Seeds>(\n seedName: K,\n attrs?: Parameters<Seeds[K]>[0],\n ): ReturnType<Seeds[K]> {\n if (!(seedName in this.seeds)) {\n throw new Error(\n `Seed \"${\n seedName as string\n }\" is not registered in this factory. Make sure it is correct and registered in src/test/setup.ts`,\n );\n }\n\n return this.seeds[seedName](attrs || {}, this, this.db);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+CA,IAAa,gBAAb,cAIU,QAAyB;;;;;;;;;CASjC,OAAO,WAAqCA,QAAoB;AAC9D,SAAO,QAAQ,WAAW,OAAO;CAClC;;;;;;;;CASD,YACUC,UACAC,OACAC,IACR;AACA,SAAO;EAJC;EACA;EACA;CAGT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyCD,OAAO,cASLC,OACAC,MAQAC,YAMmB;AACnB,SAAO,OACLC,OACAC,SACAC,IACAC,YACG;GAEH,IAAIC,OAA2C,EAAE,GAAG,MAAO;AAG3D,OAAI,MAAM;IACR,MAAM,WAAW,MAAM,KAAK,OAAO,SAAS,IAAIC,QAAM;AACtD,WAAO;KAAE,GAAG;KAAU,GAAG;IAAM;GAChC;AAGD,OAAI,eAAe,OAAO;IAExB,MAAM,SAAS,MAAM,GAClB,WAAW,MAAM,CACjB,OAAO,KAAkC,CACzC,cAAc,CACd,kBAAkB;AAErB,SAAK,OACH,OAAM,IAAI,OAAO,wBAAwB,MAAM;AAGjD,WAAO;GACR,MAEC,QAAO;IAAE;IAAO;GAAM;EAEzB;CACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BD,MAAM,OACJC,aACAC,OAC2C;AAC3C,QAAM,eAAe,KAAK,UACxB,OAAM,IAAI,OACP,WACC,YACD;EAIL,MAAM,SAAS,MAAM,KAAK,SAAS,aACjC,SAAS,CAAE,GACX,MACA,KAAK,IACL,MACD;AAID,MACE,iBACO,WAAW,YAClB,WAAW,UACX,UAAU,QACV;GAEA,MAAM,WAAW,MAAM,KAAK,GACzB,WAAW,OAAO,MAAM,CACxB,OAAO,OAAO,KAAK,CACnB,cAAc,CACd,kBAAkB;AAErB,UAAO;EACR;AAGD,SAAO;CACR;CA8CD,MAAM,WACJC,OACAF,aACAG,OAC6C;AAC7C,QAAM,eAAe,KAAK,UACxB,OAAM,IAAI,OACP,WACC,YACD;EAIL,MAAMC,WAA2B,CAAE;AAEnC,OAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK;GAC9B,MAAM,kBACG,UAAU,aAAa,MAAM,MAAM,GAAG,MAAM,GAAG;AACxD,YAAS,KAAK,KAAK,OAAO,aAAa,SAAS,CAAC;EAClD;AAED,SAAO,QAAQ,IAAI,SAAS;CAC7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BD,KACEC,UACAC,OACsB;AACtB,QAAM,YAAY,KAAK,OACrB,OAAM,IAAI,OACP,QACC,SACD;AAIL,SAAO,KAAK,MAAM,UAAU,SAAS,CAAE,GAAE,MAAM,KAAK,GAAG;CACxD;AACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"KyselyFactory-Cf0o2YxO.cjs","names":["Factory","seedFn: Seed","builders: Builders","seeds: Seeds","db: Kysely<DB> | ControlledTransaction<DB, []>","table: TableName","item?: (\n attrs: Attrs,\n factory: Factory,\n db: Kysely<DB>,\n faker: FakerFactory,\n ) =>\n | Partial<Insertable<DB[TableName]>>\n | Promise<Partial<Insertable<DB[TableName]>>>","autoInsert?: boolean","attrs: Attrs","factory: Factory","db: Kysely<DB>","faker: FakerFactory","data: Partial<Insertable<DB[TableName]>>","faker","builderName: K","attrs?: Parameters<Builders[K]>[0]","count: number","attrs?: any","promises: Promise<any>[]","seedName: K","attrs?: Parameters<Seeds[K]>[0]"],"sources":["../src/KyselyFactory.ts"],"sourcesContent":["import type {\n ControlledTransaction,\n Insertable,\n Kysely,\n Selectable,\n} from 'kysely';\nimport { Factory, type FactorySeed } from './Factory.ts';\nimport { type FakerFactory, faker } from './faker.ts';\n\n/**\n * Factory implementation for Kysely ORM, providing test data creation utilities.\n * Extends the base Factory class with Kysely-specific database operations.\n *\n * @template DB - The database schema type\n * @template Builders - Record of builder functions for creating entities\n * @template Seeds - Record of seed functions for complex test scenarios\n *\n * @example\n * ```typescript\n * // Define your database schema\n * interface Database {\n * users: UsersTable;\n * posts: PostsTable;\n * }\n *\n * // Create builders\n * const builders = {\n * user: KyselyFactory.createBuilder<Database, 'users'>('users', (attrs, factory, db, faker) => ({\n * id: faker.string.uuid(),\n * name: faker.person.fullName(),\n * email: faker.internet.email(),\n * ...attrs\n * })),\n * post: KyselyFactory.createBuilder<Database, 'posts'>('posts', (attrs) => ({\n * title: 'Test Post',\n * content: 'Test content',\n * ...attrs\n * }))\n * };\n *\n * // Create factory instance\n * const factory = new KyselyFactory(builders, seeds, db);\n *\n * // Use in tests\n * const user = await factory.insert('user', { name: 'John Doe' });\n * ```\n */\nexport class KyselyFactory<\n DB,\n Builders extends Record<string, any>,\n Seeds extends Record<string, any>,\n> extends Factory<Builders, Seeds> {\n /**\n * Creates a typed seed function with proper type inference.\n * Inherits from the base Factory class implementation.\n *\n * @template Seed - The seed function type\n * @param seedFn - The seed function to wrap\n * @returns The same seed function with proper typing\n */\n static createSeed<Seed extends FactorySeed>(seedFn: Seed): Seed {\n return Factory.createSeed(seedFn);\n }\n\n /**\n * Creates a new KyselyFactory instance.\n *\n * @param builders - Record of builder functions for creating individual entities\n * @param seeds - Record of seed functions for creating complex test scenarios\n * @param db - Kysely database instance or controlled transaction\n */\n constructor(\n private builders: Builders,\n private seeds: Seeds,\n private db: Kysely<DB> | ControlledTransaction<DB, []>,\n ) {\n super();\n }\n\n /**\n * Creates a typed builder function for a specific database table.\n * This is a utility method that helps create builders with proper type inference for Kysely.\n *\n * @template DB - The database schema type\n * @template TableName - The name of the table (must be a key of DB)\n * @template Attrs - The attributes type for the builder (defaults to Partial<Insertable>)\n * @template Factory - The factory instance type\n * @template Result - The result type (defaults to Selectable of the table)\n *\n * @param table - The name of the database table\n * @param item - Optional function to provide default values and transformations\n * @param autoInsert - Whether to automatically insert the record (default: true)\n * @returns A builder function that creates and optionally inserts records\n *\n * @example\n * ```typescript\n * // Create a simple builder with defaults\n * const userBuilder = KyselyFactory.createBuilder<DB, 'users'>('users',\n * (attrs, factory, db, faker) => ({\n * id: faker.string.uuid(),\n * name: faker.person.fullName(),\n * email: faker.internet.email(),\n * createdAt: new Date(),\n * ...attrs\n * })\n * );\n *\n * // Create a builder that doesn't auto-insert (useful for nested inserts)\n * const addressBuilder = KyselyFactory.createBuilder<DB, 'addresses'>('addresses',\n * (attrs) => ({\n * street: '123 Main St',\n * city: 'Anytown',\n * ...attrs\n * }),\n * false // Don't auto-insert\n * );\n * ```\n */\n static createBuilder<\n DB,\n TableName extends keyof DB & string,\n Attrs extends Partial<Insertable<DB[TableName]>> = Partial<\n Insertable<DB[TableName]>\n >,\n Factory = any,\n Result = Selectable<DB[TableName]>,\n >(\n table: TableName,\n item?: (\n attrs: Attrs,\n factory: Factory,\n db: Kysely<DB>,\n faker: FakerFactory,\n ) =>\n | Partial<Insertable<DB[TableName]>>\n | Promise<Partial<Insertable<DB[TableName]>>>,\n autoInsert?: boolean,\n ): (\n attrs: Attrs,\n factory: Factory,\n db: Kysely<DB>,\n faker: FakerFactory,\n ) => Promise<Result> {\n return async (\n attrs: Attrs,\n factory: Factory,\n db: Kysely<DB>,\n faker: FakerFactory,\n ) => {\n // Start with attributes\n let data: Partial<Insertable<DB[TableName]>> = { ...attrs };\n\n // Apply defaults\n if (item) {\n const defaults = await item(attrs, factory, db, faker);\n data = { ...defaults, ...data };\n }\n\n // Handle insertion based on autoInsert flag\n if (autoInsert !== false) {\n // Auto insert is enabled by default\n const result = await db\n .insertInto(table)\n .values(data as Insertable<DB[TableName]>)\n .returningAll()\n .executeTakeFirst();\n\n if (!result) {\n throw new Error(`Failed to insert into ${table}`);\n }\n\n return result as Result;\n } else {\n // Return object for factory to handle insertion\n return { table, data } as any;\n }\n };\n }\n\n /**\n * Inserts a single record into the database using the specified builder.\n * The builder function is responsible for generating the record data with defaults\n * and the factory handles the actual database insertion.\n *\n * @template K - The builder name (must be a key of Builders)\n * @param builderName - The name of the builder to use\n * @param attrs - Optional attributes to override builder defaults\n * @returns A promise resolving to the inserted record\n * @throws Error if the specified builder doesn't exist\n *\n * @example\n * ```typescript\n * // Insert with defaults\n * const user = await factory.insert('user');\n *\n * // Insert with overrides\n * const adminUser = await factory.insert('user', {\n * email: 'admin@example.com',\n * role: 'admin'\n * });\n *\n * // Use the inserted record\n * const post = await factory.insert('post', {\n * userId: user.id,\n * title: 'My First Post'\n * });\n * ```\n */\n async insert<K extends keyof Builders>(\n builderName: K,\n attrs?: Parameters<Builders[K]>[0],\n ): Promise<Awaited<ReturnType<Builders[K]>>> {\n if (!(builderName in this.builders)) {\n throw new Error(\n `Factory \"${\n builderName as string\n }\" does not exist. Make sure it is correct and registered in src/test/setup.ts`,\n );\n }\n\n const result = await this.builders[builderName](\n attrs || {},\n this,\n this.db,\n faker,\n );\n\n // For Kysely, we expect the builder to return an object with table and data properties\n // or to handle the insertion itself and return the inserted record\n if (\n result &&\n typeof result === 'object' &&\n 'table' in result &&\n 'data' in result\n ) {\n // If the builder returns {table: string, data: object}, we insert it\n const inserted = await this.db\n .insertInto(result.table)\n .values(result.data)\n .returningAll()\n .executeTakeFirst();\n\n return inserted as any;\n }\n\n // Otherwise, assume the builder handled the insertion itself\n return result;\n }\n\n /**\n * Inserts multiple records into the database using the specified builder.\n * Supports both static attributes and dynamic attribute generation via a function.\n *\n * @template K - The builder name (must be a key of Builders)\n * @param count - The number of records to insert\n * @param builderName - The name of the builder to use\n * @param attrs - Static attributes or a function that generates attributes for each record\n * @returns A promise resolving to an array of inserted records\n * @throws Error if the specified builder doesn't exist\n *\n * @example\n * ```typescript\n * // Insert multiple with same attributes\n * const users = await factory.insertMany(5, 'user', { role: 'member' });\n *\n * // Insert multiple with dynamic attributes\n * const posts = await factory.insertMany(10, 'post', (idx, faker) => ({\n * title: `Post ${idx + 1}`,\n * content: faker.lorem.paragraph(),\n * publishedAt: faker.date.past()\n * }));\n *\n * // Create users with sequential emails\n * const admins = await factory.insertMany(3, 'user', (idx) => ({\n * email: `admin${idx + 1}@example.com`,\n * role: 'admin'\n * }));\n * ```\n */\n // Method overloads for better type inference\n async insertMany<K extends keyof Builders>(\n count: number,\n builderName: K,\n attrs?: Parameters<Builders[K]>[0],\n ): Promise<Awaited<ReturnType<Builders[K]>>[]>;\n async insertMany<K extends keyof Builders>(\n count: number,\n builderName: K,\n attrs: (\n idx: number,\n faker: FakerFactory,\n ) => Parameters<Builders[K]>[0] | Promise<Parameters<Builders[K]>[0]>,\n ): Promise<Awaited<ReturnType<Builders[K]>>[]>;\n async insertMany<K extends keyof Builders>(\n count: number,\n builderName: K,\n attrs?: any,\n ): Promise<Awaited<ReturnType<Builders[K]>>[]> {\n if (!(builderName in this.builders)) {\n throw new Error(\n `Builder \"${\n builderName as string\n }\" is not registered in this factory. Make sure it is correct and registered in src/test/setup.ts`,\n );\n }\n\n const promises: Promise<any>[] = [];\n\n for (let i = 0; i < count; i++) {\n const newAttrs =\n typeof attrs === 'function' ? await attrs(i, faker) : attrs;\n promises.push(this.insert(builderName, newAttrs));\n }\n\n return Promise.all(promises);\n }\n\n /**\n * Executes a seed function to create complex test scenarios with multiple related records.\n * Seeds are useful for setting up complete test environments with realistic data relationships.\n *\n * @template K - The seed name (must be a key of Seeds)\n * @param seedName - The name of the seed to execute\n * @param attrs - Optional configuration attributes for the seed\n * @returns The result of the seed function (typically the primary record created)\n * @throws Error if the specified seed doesn't exist\n *\n * @example\n * ```typescript\n * // Execute a simple seed\n * const user = await factory.seed('userWithProfile');\n *\n * // Execute a seed with configuration\n * const author = await factory.seed('authorWithBooks', {\n * bookCount: 5,\n * includeReviews: true\n * });\n *\n * // Use seed result in tests\n * const company = await factory.seed('companyWithDepartments', {\n * departmentCount: 3,\n * employeesPerDepartment: 10\n * });\n * expect(company.departments).toHaveLength(3);\n * ```\n */\n seed<K extends keyof Seeds>(\n seedName: K,\n attrs?: Parameters<Seeds[K]>[0],\n ): ReturnType<Seeds[K]> {\n if (!(seedName in this.seeds)) {\n throw new Error(\n `Seed \"${\n seedName as string\n }\" is not registered in this factory. Make sure it is correct and registered in src/test/setup.ts`,\n );\n }\n\n return this.seeds[seedName](attrs || {}, this, this.db);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+CA,IAAa,gBAAb,cAIUA,wBAAyB;;;;;;;;;CASjC,OAAO,WAAqCC,QAAoB;AAC9D,SAAO,wBAAQ,WAAW,OAAO;CAClC;;;;;;;;CASD,YACUC,UACAC,OACAC,IACR;AACA,SAAO;EAJC;EACA;EACA;CAGT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyCD,OAAO,cASLC,OACAC,MAQAC,YAMmB;AACnB,SAAO,OACLC,OACAC,SACAC,IACAC,YACG;GAEH,IAAIC,OAA2C,EAAE,GAAG,MAAO;AAG3D,OAAI,MAAM;IACR,MAAM,WAAW,MAAM,KAAK,OAAO,SAAS,IAAIC,QAAM;AACtD,WAAO;KAAE,GAAG;KAAU,GAAG;IAAM;GAChC;AAGD,OAAI,eAAe,OAAO;IAExB,MAAM,SAAS,MAAM,GAClB,WAAW,MAAM,CACjB,OAAO,KAAkC,CACzC,cAAc,CACd,kBAAkB;AAErB,SAAK,OACH,OAAM,IAAI,OAAO,wBAAwB,MAAM;AAGjD,WAAO;GACR,MAEC,QAAO;IAAE;IAAO;GAAM;EAEzB;CACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BD,MAAM,OACJC,aACAC,OAC2C;AAC3C,QAAM,eAAe,KAAK,UACxB,OAAM,IAAI,OACP,WACC,YACD;EAIL,MAAM,SAAS,MAAM,KAAK,SAAS,aACjC,SAAS,CAAE,GACX,MACA,KAAK,IACLF,oBACD;AAID,MACE,iBACO,WAAW,YAClB,WAAW,UACX,UAAU,QACV;GAEA,MAAM,WAAW,MAAM,KAAK,GACzB,WAAW,OAAO,MAAM,CACxB,OAAO,OAAO,KAAK,CACnB,cAAc,CACd,kBAAkB;AAErB,UAAO;EACR;AAGD,SAAO;CACR;CA8CD,MAAM,WACJG,OACAF,aACAG,OAC6C;AAC7C,QAAM,eAAe,KAAK,UACxB,OAAM,IAAI,OACP,WACC,YACD;EAIL,MAAMC,WAA2B,CAAE;AAEnC,OAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK;GAC9B,MAAM,kBACG,UAAU,aAAa,MAAM,MAAM,GAAGL,oBAAM,GAAG;AACxD,YAAS,KAAK,KAAK,OAAO,aAAa,SAAS,CAAC;EAClD;AAED,SAAO,QAAQ,IAAI,SAAS;CAC7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BD,KACEM,UACAC,OACsB;AACtB,QAAM,YAAY,KAAK,OACrB,OAAM,IAAI,OACP,QACC,SACD;AAIL,SAAO,KAAK,MAAM,UAAU,SAAS,CAAE,GAAE,MAAM,KAAK,GAAG;CACxD;AACF"}
|