@geekmidas/testkit 0.0.9 → 0.0.11

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 (101) hide show
  1. package/dist/Factory-B9LP1L18.d.cts +131 -0
  2. package/dist/Factory-DiZSNxC0.d.mts +131 -0
  3. package/dist/Factory.d.cts +3 -0
  4. package/dist/Factory.d.mts +3 -0
  5. package/dist/KyselyFactory-B-zlLbov.d.mts +200 -0
  6. package/dist/KyselyFactory-CrLWxJW4.d.cts +200 -0
  7. package/dist/KyselyFactory.d.cts +4 -0
  8. package/dist/KyselyFactory.d.mts +4 -0
  9. package/dist/ObjectionFactory-CJCpvwts.d.mts +213 -0
  10. package/dist/{ObjectionFactory-C47B03Ot.cjs → ObjectionFactory-Wq80ypMM.cjs} +82 -40
  11. package/dist/{ObjectionFactory-89p-FFEw.mjs → ObjectionFactory-aqM0dDW7.mjs} +82 -40
  12. package/dist/ObjectionFactory-vME-wNlq.d.cts +213 -0
  13. package/dist/ObjectionFactory.cjs +2 -1
  14. package/dist/ObjectionFactory.d.cts +4 -0
  15. package/dist/ObjectionFactory.d.mts +4 -0
  16. package/dist/ObjectionFactory.mjs +2 -1
  17. package/dist/{PostgresKyselyMigrator-ChIpZFYB.mjs → PostgresKyselyMigrator-Bdhl251C.mjs} +1 -1
  18. package/dist/PostgresKyselyMigrator-CQ3aUoy_.d.cts +67 -0
  19. package/dist/{PostgresKyselyMigrator-Bs31emFd.cjs → PostgresKyselyMigrator-CfytARcA.cjs} +1 -1
  20. package/dist/PostgresKyselyMigrator-_6yHZigp.d.mts +67 -0
  21. package/dist/PostgresKyselyMigrator.cjs +2 -2
  22. package/dist/PostgresKyselyMigrator.d.cts +3 -0
  23. package/dist/PostgresKyselyMigrator.d.mts +3 -0
  24. package/dist/PostgresKyselyMigrator.mjs +2 -2
  25. package/dist/PostgresMigrator-BlvuQl7d.d.mts +84 -0
  26. package/dist/PostgresMigrator-D5UkK1_K.d.cts +84 -0
  27. package/dist/{PostgresMigrator-BtAWdLss.cjs → PostgresMigrator-DFcNdCvD.cjs} +2 -1
  28. package/dist/{PostgresMigrator-BzqksJcW.mjs → PostgresMigrator-DxPC_gGu.mjs} +3 -2
  29. package/dist/PostgresMigrator.cjs +1 -1
  30. package/dist/PostgresMigrator.d.cts +2 -0
  31. package/dist/PostgresMigrator.d.mts +2 -0
  32. package/dist/PostgresMigrator.mjs +1 -1
  33. package/dist/{PostgresObjectionMigrator-B88aTT0m.cjs → PostgresObjectionMigrator-BG6ymgnt.cjs} +1 -1
  34. package/dist/PostgresObjectionMigrator-C69n7vzr.d.mts +77 -0
  35. package/dist/PostgresObjectionMigrator-CZHHcCOv.d.cts +77 -0
  36. package/dist/{PostgresObjectionMigrator-DydSgYFv.mjs → PostgresObjectionMigrator-G4h5FLvU.mjs} +1 -1
  37. package/dist/PostgresObjectionMigrator.cjs +2 -2
  38. package/dist/PostgresObjectionMigrator.d.cts +3 -0
  39. package/dist/PostgresObjectionMigrator.d.mts +3 -0
  40. package/dist/PostgresObjectionMigrator.mjs +2 -2
  41. package/dist/VitestKyselyTransactionIsolator-ClCazkBO.d.mts +56 -0
  42. package/dist/VitestKyselyTransactionIsolator-UE1J-UoP.d.cts +56 -0
  43. package/dist/VitestKyselyTransactionIsolator.d.cts +3 -0
  44. package/dist/VitestKyselyTransactionIsolator.d.mts +3 -0
  45. package/dist/VitestObjectionTransactionIsolator-CO2nTi9r.d.cts +57 -0
  46. package/dist/VitestObjectionTransactionIsolator-D264iuPy.d.mts +57 -0
  47. package/dist/VitestObjectionTransactionIsolator.d.cts +3 -0
  48. package/dist/VitestObjectionTransactionIsolator.d.mts +3 -0
  49. package/dist/VitestTransactionIsolator-DHf2MxmC.d.cts +118 -0
  50. package/dist/VitestTransactionIsolator-Xqyjlmw6.d.mts +118 -0
  51. package/dist/VitestTransactionIsolator.d.cts +2 -0
  52. package/dist/VitestTransactionIsolator.d.mts +2 -0
  53. package/dist/__tests__/Factory.spec.d.cts +1 -0
  54. package/dist/__tests__/Factory.spec.d.mts +1 -0
  55. package/dist/__tests__/KyselyFactory.spec.cjs +2 -2
  56. package/dist/__tests__/KyselyFactory.spec.d.cts +1 -0
  57. package/dist/__tests__/KyselyFactory.spec.d.mts +1 -0
  58. package/dist/__tests__/KyselyFactory.spec.mjs +2 -2
  59. package/dist/__tests__/ObjectionFactory.spec.cjs +117 -1
  60. package/dist/__tests__/ObjectionFactory.spec.d.cts +1 -0
  61. package/dist/__tests__/ObjectionFactory.spec.d.mts +1 -0
  62. package/dist/__tests__/ObjectionFactory.spec.mjs +117 -1
  63. package/dist/__tests__/PostgresMigrator.spec.cjs +1 -1
  64. package/dist/__tests__/PostgresMigrator.spec.d.cts +1 -0
  65. package/dist/__tests__/PostgresMigrator.spec.d.mts +1 -0
  66. package/dist/__tests__/PostgresMigrator.spec.mjs +1 -1
  67. package/dist/__tests__/PostgresObjectionMigrator.spec.cjs +2 -2
  68. package/dist/__tests__/PostgresObjectionMigrator.spec.d.cts +1 -0
  69. package/dist/__tests__/PostgresObjectionMigrator.spec.d.mts +1 -0
  70. package/dist/__tests__/PostgresObjectionMigrator.spec.mjs +2 -2
  71. package/dist/__tests__/VitestObjectionTransactionIsolator.spec.cjs +4 -3
  72. package/dist/__tests__/VitestObjectionTransactionIsolator.spec.d.cts +1 -0
  73. package/dist/__tests__/VitestObjectionTransactionIsolator.spec.d.mts +1 -0
  74. package/dist/__tests__/VitestObjectionTransactionIsolator.spec.mjs +4 -3
  75. package/dist/__tests__/faker.spec.d.cts +1 -0
  76. package/dist/__tests__/faker.spec.d.mts +1 -0
  77. package/dist/__tests__/integration.spec.cjs +2 -2
  78. package/dist/__tests__/integration.spec.d.cts +1 -0
  79. package/dist/__tests__/integration.spec.d.mts +1 -0
  80. package/dist/__tests__/integration.spec.mjs +2 -2
  81. package/dist/example.d.cts +26 -0
  82. package/dist/example.d.mts +26 -0
  83. package/dist/faker-DAiFK3T3.d.cts +155 -0
  84. package/dist/faker-nN9Ki6fn.d.mts +155 -0
  85. package/dist/faker.d.cts +2 -0
  86. package/dist/faker.d.mts +2 -0
  87. package/dist/helpers.d.cts +41 -0
  88. package/dist/helpers.d.mts +41 -0
  89. package/dist/kysely.cjs +2 -2
  90. package/dist/kysely.d.cts +73 -0
  91. package/dist/kysely.d.mts +73 -0
  92. package/dist/kysely.mjs +2 -2
  93. package/dist/objection.cjs +4 -3
  94. package/dist/objection.d.cts +88 -0
  95. package/dist/objection.d.mts +88 -0
  96. package/dist/objection.mjs +4 -3
  97. package/package.json +5 -5
  98. package/src/ObjectionFactory.ts +156 -13
  99. package/src/PostgresMigrator.ts +3 -1
  100. package/src/__tests__/ObjectionFactory.spec.ts +162 -0
  101. package/PostgresKyselyMigrator.spec +0 -471
@@ -1,5 +1,7 @@
1
1
  import type { Knex } from 'knex';
2
+ import type { Model } from 'objection';
2
3
  import { Factory, type FactorySeed } from './Factory.ts';
4
+ import { type FakerFactory, faker } from './faker.ts';
3
5
 
4
6
  /**
5
7
  * Factory implementation for Objection.js ORM, providing test data creation utilities.
@@ -53,6 +55,105 @@ export class ObjectionFactory<
53
55
  return Factory.createSeed(seedFn);
54
56
  }
55
57
 
58
+ /**
59
+ * Creates a typed builder function for Objection.js models.
60
+ * This is a utility method that helps create builders with proper type inference.
61
+ *
62
+ * @template TModel - The Objection.js Model class type
63
+ * @template Attrs - The attributes type for the builder (defaults to Partial of model)
64
+ * @template Factory - The factory instance type
65
+ * @template Result - The result type (defaults to the model instance)
66
+ *
67
+ * @param ModelClass - The Objection.js Model class
68
+ * @param item - Optional function to provide default values and transformations
69
+ * @param autoInsert - Whether to automatically insert the record (default: true)
70
+ * @returns A builder function that creates and optionally inserts records
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * // Create a simple builder with defaults
75
+ * const userBuilder = ObjectionFactory.createBuilder(User,
76
+ * (attrs, factory, db, faker) => ({
77
+ * id: faker.string.uuid(),
78
+ * name: faker.person.fullName(),
79
+ * email: faker.internet.email(),
80
+ * createdAt: new Date(),
81
+ * ...attrs
82
+ * })
83
+ * );
84
+ *
85
+ * // Create a builder that doesn't auto-insert (useful for nested inserts)
86
+ * const addressBuilder = ObjectionFactory.createBuilder(Address,
87
+ * (attrs) => ({
88
+ * street: '123 Main St',
89
+ * city: 'Anytown',
90
+ * ...attrs
91
+ * }),
92
+ * false // Don't auto-insert
93
+ * );
94
+ *
95
+ * // Use with relations
96
+ * const postBuilder = ObjectionFactory.createBuilder(Post,
97
+ * async (attrs, factory) => ({
98
+ * title: faker.lorem.sentence(),
99
+ * content: faker.lorem.paragraphs(),
100
+ * authorId: attrs.authorId || (await factory.insert('user')).id,
101
+ * ...attrs
102
+ * })
103
+ * );
104
+ * ```
105
+ */
106
+ static createBuilder<
107
+ TModel extends typeof Model,
108
+ Attrs extends Partial<InstanceType<TModel>> = Partial<InstanceType<TModel>>,
109
+ Factory = any,
110
+ Result = InstanceType<TModel>,
111
+ >(
112
+ ModelClass: TModel,
113
+ item?: (
114
+ attrs: Attrs,
115
+ factory: Factory,
116
+ db: Knex,
117
+ faker: FakerFactory,
118
+ ) => Partial<InstanceType<TModel>> | Promise<Partial<InstanceType<TModel>>>,
119
+ autoInsert?: boolean,
120
+ ): (
121
+ attrs: Attrs,
122
+ factory: Factory,
123
+ db: Knex,
124
+ faker: FakerFactory,
125
+ ) => Promise<Result> {
126
+ return async (
127
+ attrs: Attrs,
128
+ factory: Factory,
129
+ db: Knex,
130
+ faker: FakerFactory,
131
+ ) => {
132
+ // Start with attributes
133
+ let data: Partial<InstanceType<TModel>> = { ...attrs };
134
+
135
+ // Apply defaults
136
+ if (item) {
137
+ const defaults = await item(attrs, factory, db, faker);
138
+ data = { ...defaults, ...data };
139
+ }
140
+
141
+ // Create model instance
142
+ const model = ModelClass.fromJson(data) as InstanceType<TModel>;
143
+
144
+ // Handle insertion based on autoInsert flag
145
+ if (autoInsert !== false) {
146
+ // Auto insert is enabled by default
147
+ // @ts-ignore
148
+ const result = await model.$query(db).insertGraph(model).execute();
149
+ return result as Result;
150
+ } else {
151
+ // Return model for factory to handle insertion
152
+ return model as Result;
153
+ }
154
+ };
155
+ }
156
+
56
157
  /**
57
158
  * Creates a new ObjectionFactory instance.
58
159
  *
@@ -72,7 +173,8 @@ export class ObjectionFactory<
72
173
  * Inserts a single record into the database using the specified builder.
73
174
  * Uses Objection.js's insertGraph method to handle nested relations.
74
175
  *
75
- * @param factory - The name of the builder to use
176
+ * @template K - The builder name (must be a key of Builders)
177
+ * @param builderName - The name of the builder to use
76
178
  * @param attrs - Optional attributes to override builder defaults
77
179
  * @returns A promise resolving to the inserted record with all relations
78
180
  * @throws Error if the specified builder doesn't exist
@@ -98,18 +200,32 @@ export class ObjectionFactory<
98
200
  * });
99
201
  * ```
100
202
  */
101
- insert(factory: keyof Builders, attrs: any = {}) {
102
- if (!(factory in this.builders)) {
203
+ async insert<K extends keyof Builders>(
204
+ builderName: K,
205
+ attrs?: Parameters<Builders[K]>[0],
206
+ ): Promise<Awaited<ReturnType<Builders[K]>>> {
207
+ if (!(builderName in this.builders)) {
103
208
  throw new Error(
104
209
  `Factory "${
105
- factory as string
210
+ builderName as string
106
211
  }" does not exist. Make sure it is correct and registered in src/test/setup.ts`,
107
212
  );
108
213
  }
109
214
 
110
- return this.builders[factory](attrs, {}, this.db).then((record: any) => {
111
- return record.$query(this.db).insertGraph(record).execute();
112
- }) as any;
215
+ const result = await this.builders[builderName](
216
+ attrs || {},
217
+ this,
218
+ this.db,
219
+ faker,
220
+ );
221
+
222
+ // If the builder returns a model instance, insert it
223
+ if (result && typeof result.$query === 'function') {
224
+ return await result.$query(this.db).insertGraph(result).execute();
225
+ }
226
+
227
+ // Otherwise, assume the builder handled insertion itself
228
+ return result;
113
229
  }
114
230
  /**
115
231
  * Inserts multiple records into the database using the specified builder.
@@ -140,7 +256,22 @@ export class ObjectionFactory<
140
256
  * }));
141
257
  * ```
142
258
  */
143
- insertMany(count: number, builderName: keyof Builders, attrs: any = {}) {
259
+ // Method overloads for better type inference
260
+ async insertMany<K extends keyof Builders>(
261
+ count: number,
262
+ builderName: K,
263
+ attrs?: Parameters<Builders[K]>[0],
264
+ ): Promise<Awaited<ReturnType<Builders[K]>>[]>;
265
+ async insertMany<K extends keyof Builders>(
266
+ count: number,
267
+ builderName: K,
268
+ attrs: (idx: number, faker: FakerFactory) => Parameters<Builders[K]>[0],
269
+ ): Promise<Awaited<ReturnType<Builders[K]>>[]>;
270
+ async insertMany<K extends keyof Builders>(
271
+ count: number,
272
+ builderName: K,
273
+ attrs?: any,
274
+ ): Promise<Awaited<ReturnType<Builders[K]>>[]> {
144
275
  if (!(builderName in this.builders)) {
145
276
  throw new Error(
146
277
  `Builder "${
@@ -151,11 +282,19 @@ export class ObjectionFactory<
151
282
 
152
283
  const records: any[] = [];
153
284
  for (let i = 0; i < count; i++) {
154
- const newAttrs = typeof attrs === 'function' ? (attrs as any)(i) : attrs;
285
+ const newAttrs =
286
+ typeof attrs === 'function' ? (attrs as any)(i, faker) : attrs;
155
287
 
156
288
  records.push(
157
- this.builders[builderName](newAttrs, {}, this.db).then((record: any) =>
158
- record.$query(this.db).insertGraph(record).execute(),
289
+ this.builders[builderName](newAttrs, this, this.db, faker).then(
290
+ (record: any) => {
291
+ // If the builder returns a model instance, insert it
292
+ if (record && typeof record.$query === 'function') {
293
+ return record.$query(this.db).insertGraph(record).execute();
294
+ }
295
+ // Otherwise, assume the builder handled insertion itself
296
+ return record;
297
+ },
159
298
  ),
160
299
  );
161
300
  }
@@ -166,6 +305,7 @@ export class ObjectionFactory<
166
305
  * Executes a seed function to create complex test scenarios with multiple related records.
167
306
  * Seeds are useful for setting up complete test environments with realistic data relationships.
168
307
  *
308
+ * @template K - The seed name (must be a key of Seeds)
169
309
  * @param seedName - The name of the seed to execute
170
310
  * @param attrs - Optional configuration attributes for the seed
171
311
  * @returns The result of the seed function (typically the primary record created)
@@ -194,7 +334,10 @@ export class ObjectionFactory<
194
334
  * .withGraphFetched('[departments.employees]');
195
335
  * ```
196
336
  */
197
- seed(seedName: keyof Seeds, attrs: any = {}) {
337
+ seed<K extends keyof Seeds>(
338
+ seedName: K,
339
+ attrs?: Parameters<Seeds[K]>[0],
340
+ ): ReturnType<Seeds[K]> {
198
341
  if (!(seedName in this.seeds)) {
199
342
  throw new Error(
200
343
  `Seed "${
@@ -203,6 +346,6 @@ export class ObjectionFactory<
203
346
  );
204
347
  }
205
348
 
206
- return this.seeds[seedName](attrs, this, this.db);
349
+ return this.seeds[seedName](attrs || {}, this, this.db);
207
350
  }
208
351
  }
@@ -1,4 +1,6 @@
1
- import { Client } from 'pg';
1
+ import pg from 'pg';
2
+
3
+ const { Client } = pg;
2
4
 
3
5
  /**
4
6
  * Creates a PostgreSQL client connected to the 'postgres' database.
@@ -3,6 +3,7 @@ import { Model } from 'objection';
3
3
  import { afterAll, beforeAll, describe, expect, it } from 'vitest';
4
4
  import { setupKnexTest } from '../../test/helpers';
5
5
  import { ObjectionFactory } from '../ObjectionFactory';
6
+ import { faker } from '../faker';
6
7
 
7
8
  // Define real Objection models for testing
8
9
  class User extends Model {
@@ -386,6 +387,167 @@ describe.skip('ObjectionFactory', () => {
386
387
  });
387
388
  });
388
389
 
390
+ describe('createBuilder static method', () => {
391
+ it('should create a builder function with auto-insert', async () => {
392
+ const userBuilder = ObjectionFactory.createBuilder(
393
+ User,
394
+ (attrs, factory, db, faker) => ({
395
+ name: faker.person.fullName(),
396
+ email: faker.internet.email(),
397
+ role: 'user',
398
+ createdAt: new Date(),
399
+ updatedAt: new Date(),
400
+ ...attrs,
401
+ }),
402
+ );
403
+
404
+ const builders = { user: userBuilder };
405
+ factory = new ObjectionFactory(builders, {}, trx);
406
+
407
+ const result = await factory.insert('user', { name: 'Test User' });
408
+
409
+ expect(result).toBeInstanceOf(User);
410
+ expect(result.name).toBe('Test User');
411
+ expect(result.email).toMatch(/@/);
412
+ expect(result.id).toBeDefined();
413
+ });
414
+
415
+ it('should create a builder function without auto-insert', async () => {
416
+ const userBuilder = ObjectionFactory.createBuilder(
417
+ User,
418
+ (attrs) => ({
419
+ name: 'No Insert User',
420
+ email: 'noinsert@example.com',
421
+ role: 'user',
422
+ createdAt: new Date(),
423
+ updatedAt: new Date(),
424
+ ...attrs,
425
+ }),
426
+ false, // Don't auto-insert
427
+ );
428
+
429
+ const builders = { user: userBuilder };
430
+ factory = new ObjectionFactory(builders, {}, trx);
431
+
432
+ const result = await factory.insert('user');
433
+
434
+ // The factory's insert method should handle the insertion
435
+ expect(result).toBeInstanceOf(User);
436
+ expect(result.name).toBe('No Insert User');
437
+ expect(result.id).toBeDefined();
438
+ });
439
+
440
+ it('should pass all parameters to the item function', async () => {
441
+ let capturedFactory: any;
442
+ let capturedDb: any;
443
+ let capturedFaker: any;
444
+
445
+ const userBuilder = ObjectionFactory.createBuilder(
446
+ User,
447
+ (attrs, factory, db, fakerInstance) => {
448
+ capturedFactory = factory;
449
+ capturedDb = db;
450
+ capturedFaker = fakerInstance;
451
+
452
+ return {
453
+ name: 'Test User',
454
+ email: 'test@example.com',
455
+ role: 'user',
456
+ createdAt: new Date(),
457
+ updatedAt: new Date(),
458
+ ...attrs,
459
+ };
460
+ },
461
+ );
462
+
463
+ const builders = { user: userBuilder };
464
+ factory = new ObjectionFactory(builders, {}, trx);
465
+
466
+ await factory.insert('user');
467
+
468
+ expect(capturedFactory).toBe(factory);
469
+ expect(capturedDb).toBe(trx);
470
+ expect(capturedFaker).toBe(faker);
471
+ });
472
+
473
+ it('should handle async item functions', async () => {
474
+ const userBuilder = ObjectionFactory.createBuilder(
475
+ User,
476
+ async (attrs, factory, db, faker) => {
477
+ // Simulate async operation
478
+ await new Promise((resolve) => setTimeout(resolve, 10));
479
+
480
+ return {
481
+ name: 'Async User',
482
+ email: faker.internet.email(),
483
+ role: 'user',
484
+ createdAt: new Date(),
485
+ updatedAt: new Date(),
486
+ ...attrs,
487
+ };
488
+ },
489
+ );
490
+
491
+ const builders = { user: userBuilder };
492
+ factory = new ObjectionFactory(builders, {}, trx);
493
+
494
+ const result = await factory.insert('user');
495
+
496
+ expect(result).toBeInstanceOf(User);
497
+ expect(result.name).toBe('Async User');
498
+ expect(result.id).toBeDefined();
499
+ });
500
+
501
+ it('should work without item function', async () => {
502
+ const userBuilder = ObjectionFactory.createBuilder(User);
503
+
504
+ const builders = { user: userBuilder };
505
+ factory = new ObjectionFactory(builders, {}, trx);
506
+
507
+ const attrs = {
508
+ name: 'Manual User',
509
+ email: 'manual@example.com',
510
+ role: 'user',
511
+ createdAt: new Date(),
512
+ updatedAt: new Date(),
513
+ };
514
+
515
+ const result = await factory.insert('user', attrs);
516
+
517
+ expect(result).toBeInstanceOf(User);
518
+ expect(result.name).toBe('Manual User');
519
+ expect(result.email).toBe('manual@example.com');
520
+ expect(result.id).toBeDefined();
521
+ });
522
+
523
+ it('should allow overriding default values', async () => {
524
+ const userBuilder = ObjectionFactory.createBuilder(
525
+ User,
526
+ (attrs, factory, db, faker) => ({
527
+ name: 'Default Name',
528
+ email: 'default@example.com',
529
+ role: 'user',
530
+ createdAt: new Date(),
531
+ updatedAt: new Date(),
532
+ ...attrs,
533
+ }),
534
+ );
535
+
536
+ const builders = { user: userBuilder };
537
+ factory = new ObjectionFactory(builders, {}, trx);
538
+
539
+ const result = await factory.insert('user', {
540
+ name: 'Override Name',
541
+ email: 'override@example.com',
542
+ });
543
+
544
+ expect(result).toBeInstanceOf(User);
545
+ expect(result.name).toBe('Override Name');
546
+ expect(result.email).toBe('override@example.com');
547
+ expect(result.role).toBe('user'); // Default not overridden
548
+ });
549
+ });
550
+
389
551
  describe('error handling', () => {
390
552
  it('should handle builder errors gracefully', async () => {
391
553
  const userBuilder = async (attrs: any, factory: any, db: Knex) => {