@geekmidas/testkit 0.4.0 → 1.0.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 (209) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/Factory-BFVnMMCC.mjs.map +1 -1
  3. package/dist/{Factory-c16c27Y6.d.cts → Factory-BOX312yd.d.cts} +3 -2
  4. package/dist/Factory-BOX312yd.d.cts.map +1 -0
  5. package/dist/Factory-BhjUOBWN.cjs.map +1 -1
  6. package/dist/{Factory-BcGJjLc8.d.mts → Factory-SFupxRC2.d.mts} +2 -1
  7. package/dist/Factory-SFupxRC2.d.mts.map +1 -0
  8. package/dist/Factory.d.cts +2 -2
  9. package/dist/Factory.d.mts +1 -1
  10. package/dist/KyselyFactory-BFqVIn_0.cjs.map +1 -1
  11. package/dist/KyselyFactory-DMswpwji.mjs.map +1 -1
  12. package/dist/{KyselyFactory-uZ45h7YU.d.cts → KyselyFactory-Dy5zzV4B.d.cts} +4 -3
  13. package/dist/KyselyFactory-Dy5zzV4B.d.cts.map +1 -0
  14. package/dist/{KyselyFactory-Cj-EultY.d.mts → KyselyFactory-vAxYodck.d.mts} +3 -2
  15. package/dist/KyselyFactory-vAxYodck.d.mts.map +1 -0
  16. package/dist/KyselyFactory.d.cts +3 -3
  17. package/dist/KyselyFactory.d.mts +2 -2
  18. package/dist/{ObjectionFactory-DL4qkuF1.d.mts → ObjectionFactory-BWjB49-i.d.mts} +3 -2
  19. package/dist/ObjectionFactory-BWjB49-i.d.mts.map +1 -0
  20. package/dist/ObjectionFactory-BeFBYcan.cjs.map +1 -1
  21. package/dist/{ObjectionFactory-CdhzKs4f.d.cts → ObjectionFactory-CD-WFuMJ.d.cts} +4 -3
  22. package/dist/ObjectionFactory-CD-WFuMJ.d.cts.map +1 -0
  23. package/dist/ObjectionFactory-QCJ7u0Ql.mjs.map +1 -1
  24. package/dist/ObjectionFactory.d.cts +3 -3
  25. package/dist/ObjectionFactory.d.mts +2 -2
  26. package/dist/{PostgresKyselyMigrator-upT-hmrz.mjs → PostgresKyselyMigrator-6sE1KOni.mjs} +2 -2
  27. package/dist/PostgresKyselyMigrator-6sE1KOni.mjs.map +1 -0
  28. package/dist/{PostgresKyselyMigrator-CIx3AFSR.d.mts → PostgresKyselyMigrator-CBltSOq5.d.cts} +3 -2
  29. package/dist/PostgresKyselyMigrator-CBltSOq5.d.cts.map +1 -0
  30. package/dist/{PostgresKyselyMigrator-CfytARcA.cjs → PostgresKyselyMigrator-D6IbPq8t.cjs} +2 -2
  31. package/dist/PostgresKyselyMigrator-D6IbPq8t.cjs.map +1 -0
  32. package/dist/{PostgresKyselyMigrator-CQ3aUoy_.d.cts → PostgresKyselyMigrator-DrVWncqd.d.mts} +3 -2
  33. package/dist/PostgresKyselyMigrator-DrVWncqd.d.mts.map +1 -0
  34. package/dist/PostgresKyselyMigrator.cjs +2 -2
  35. package/dist/PostgresKyselyMigrator.d.cts +2 -2
  36. package/dist/PostgresKyselyMigrator.d.mts +2 -2
  37. package/dist/PostgresKyselyMigrator.mjs +2 -2
  38. package/dist/{PostgresMigrator-DbuJGAVy.mjs → PostgresMigrator-BjjenqSd.mjs} +2 -2
  39. package/dist/PostgresMigrator-BjjenqSd.mjs.map +1 -0
  40. package/dist/{PostgresMigrator-D5UkK1_K.d.cts → PostgresMigrator-Bres0U6E.d.cts} +2 -1
  41. package/dist/PostgresMigrator-Bres0U6E.d.cts.map +1 -0
  42. package/dist/{PostgresMigrator-DFcNdCvD.cjs → PostgresMigrator-D6dQn0x2.cjs} +2 -2
  43. package/dist/PostgresMigrator-D6dQn0x2.cjs.map +1 -0
  44. package/dist/{PostgresMigrator-DQaRxoaY.d.mts → PostgresMigrator-S-YYosAC.d.mts} +2 -1
  45. package/dist/PostgresMigrator-S-YYosAC.d.mts.map +1 -0
  46. package/dist/PostgresMigrator.cjs +1 -1
  47. package/dist/PostgresMigrator.d.cts +1 -1
  48. package/dist/PostgresMigrator.d.mts +1 -1
  49. package/dist/PostgresMigrator.mjs +1 -1
  50. package/dist/{PostgresObjectionMigrator-CZHHcCOv.d.cts → PostgresObjectionMigrator-CPfBAP7r.d.cts} +3 -2
  51. package/dist/PostgresObjectionMigrator-CPfBAP7r.d.cts.map +1 -0
  52. package/dist/{PostgresObjectionMigrator-BG6ymgnt.cjs → PostgresObjectionMigrator-DK8ODIHQ.cjs} +2 -2
  53. package/dist/PostgresObjectionMigrator-DK8ODIHQ.cjs.map +1 -0
  54. package/dist/{PostgresObjectionMigrator-D_hCcrQu.d.mts → PostgresObjectionMigrator-DVEqB5tp.d.mts} +3 -2
  55. package/dist/PostgresObjectionMigrator-DVEqB5tp.d.mts.map +1 -0
  56. package/dist/{PostgresObjectionMigrator-DPj2pOpX.mjs → PostgresObjectionMigrator-D_QxXbIN.mjs} +2 -2
  57. package/dist/PostgresObjectionMigrator-D_QxXbIN.mjs.map +1 -0
  58. package/dist/PostgresObjectionMigrator.cjs +2 -2
  59. package/dist/PostgresObjectionMigrator.d.cts +2 -2
  60. package/dist/PostgresObjectionMigrator.d.mts +2 -2
  61. package/dist/PostgresObjectionMigrator.mjs +2 -2
  62. package/dist/{VitestKyselyTransactionIsolator-D3EZZhjZ.d.cts → VitestKyselyTransactionIsolator-CduJlHoT.d.cts} +4 -3
  63. package/dist/VitestKyselyTransactionIsolator-CduJlHoT.d.cts.map +1 -0
  64. package/dist/{VitestKyselyTransactionIsolator-Dxlp1u0f.d.mts → VitestKyselyTransactionIsolator-Cswnnj0k.d.mts} +4 -3
  65. package/dist/VitestKyselyTransactionIsolator-Cswnnj0k.d.mts.map +1 -0
  66. package/dist/{VitestKyselyTransactionIsolator-EvDLk5zg.cjs → VitestKyselyTransactionIsolator-D7RRXOBa.cjs} +2 -2
  67. package/dist/VitestKyselyTransactionIsolator-D7RRXOBa.cjs.map +1 -0
  68. package/dist/{VitestKyselyTransactionIsolator-CNURW8y6.mjs → VitestKyselyTransactionIsolator-DceyIqr4.mjs} +2 -2
  69. package/dist/VitestKyselyTransactionIsolator-DceyIqr4.mjs.map +1 -0
  70. package/dist/VitestKyselyTransactionIsolator.cjs +1 -1
  71. package/dist/VitestKyselyTransactionIsolator.d.cts +2 -2
  72. package/dist/VitestKyselyTransactionIsolator.d.mts +2 -2
  73. package/dist/VitestKyselyTransactionIsolator.mjs +1 -1
  74. package/dist/{VitestObjectionTransactionIsolator-1TpsPqfG.d.cts → VitestObjectionTransactionIsolator-BXoR6xdG.d.cts} +4 -3
  75. package/dist/VitestObjectionTransactionIsolator-BXoR6xdG.d.cts.map +1 -0
  76. package/dist/{VitestObjectionTransactionIsolator-CM5KTAFA.cjs → VitestObjectionTransactionIsolator-CdLRrzNf.cjs} +2 -2
  77. package/dist/VitestObjectionTransactionIsolator-CdLRrzNf.cjs.map +1 -0
  78. package/dist/{VitestObjectionTransactionIsolator-jQFaCz0u.mjs → VitestObjectionTransactionIsolator-OF2osYY5.mjs} +2 -2
  79. package/dist/VitestObjectionTransactionIsolator-OF2osYY5.mjs.map +1 -0
  80. package/dist/{VitestObjectionTransactionIsolator-i9jIgU8Q.d.mts → VitestObjectionTransactionIsolator-x6hY5j4u.d.mts} +4 -3
  81. package/dist/VitestObjectionTransactionIsolator-x6hY5j4u.d.mts.map +1 -0
  82. package/dist/VitestObjectionTransactionIsolator.cjs +1 -1
  83. package/dist/VitestObjectionTransactionIsolator.d.cts +2 -2
  84. package/dist/VitestObjectionTransactionIsolator.d.mts +2 -2
  85. package/dist/VitestObjectionTransactionIsolator.mjs +1 -1
  86. package/dist/{VitestTransactionIsolator-BvR19bYn.d.mts → VitestTransactionIsolator-BNWJqh9f.d.mts} +3 -2
  87. package/dist/VitestTransactionIsolator-BNWJqh9f.d.mts.map +1 -0
  88. package/dist/VitestTransactionIsolator-CMfJXZP8.cjs.map +1 -1
  89. package/dist/{VitestTransactionIsolator-CwQaxZLP.d.cts → VitestTransactionIsolator-CSroc7Df.d.cts} +3 -2
  90. package/dist/VitestTransactionIsolator-CSroc7Df.d.cts.map +1 -0
  91. package/dist/VitestTransactionIsolator-DQ7tLqgV.mjs.map +1 -1
  92. package/dist/VitestTransactionIsolator.d.cts +1 -1
  93. package/dist/VitestTransactionIsolator.d.mts +1 -1
  94. package/dist/aws.cjs.map +1 -1
  95. package/dist/aws.d.cts +2 -0
  96. package/dist/aws.d.cts.map +1 -0
  97. package/dist/aws.d.mts +2 -0
  98. package/dist/aws.d.mts.map +1 -0
  99. package/dist/aws.mjs.map +1 -1
  100. package/dist/benchmark.cjs.map +1 -1
  101. package/dist/benchmark.d.cts +1 -0
  102. package/dist/benchmark.d.cts.map +1 -0
  103. package/dist/benchmark.d.mts +1 -0
  104. package/dist/benchmark.d.mts.map +1 -0
  105. package/dist/benchmark.mjs.map +1 -1
  106. package/dist/better-auth.cjs +29 -30
  107. package/dist/better-auth.cjs.map +1 -1
  108. package/dist/better-auth.d.cts +2 -2
  109. package/dist/better-auth.d.cts.map +1 -0
  110. package/dist/better-auth.d.mts.map +1 -0
  111. package/dist/better-auth.mjs +29 -30
  112. package/dist/better-auth.mjs.map +1 -1
  113. package/dist/directory-B-Ozljzk.mjs.map +1 -1
  114. package/dist/directory-BVC8g7cX.cjs.map +1 -1
  115. package/dist/{directory-BXavAeJZ.d.mts → directory-CVrfTq1I.d.mts} +2 -1
  116. package/dist/directory-CVrfTq1I.d.mts.map +1 -0
  117. package/dist/{directory-Mi7tdOuD.d.cts → directory-DAnMWi50.d.cts} +2 -1
  118. package/dist/directory-DAnMWi50.d.cts.map +1 -0
  119. package/dist/faker-B14IEMIN.cjs.map +1 -1
  120. package/dist/faker-BGKYFoCT.mjs.map +1 -1
  121. package/dist/{faker-DvxiCtxc.d.cts → faker-Cg76aFNO.d.cts} +3 -3
  122. package/dist/faker-Cg76aFNO.d.cts.map +1 -0
  123. package/dist/faker-DHh7xs4u.d.mts.map +1 -0
  124. package/dist/faker.d.cts +1 -1
  125. package/dist/helpers.cjs.map +1 -1
  126. package/dist/helpers.d.cts +1 -0
  127. package/dist/helpers.d.cts.map +1 -0
  128. package/dist/helpers.d.mts +1 -0
  129. package/dist/helpers.d.mts.map +1 -0
  130. package/dist/helpers.mjs.map +1 -1
  131. package/dist/kysely.cjs +3 -3
  132. package/dist/kysely.cjs.map +1 -1
  133. package/dist/kysely.d.cts +8 -7
  134. package/dist/kysely.d.cts.map +1 -0
  135. package/dist/kysely.d.mts +7 -6
  136. package/dist/kysely.d.mts.map +1 -0
  137. package/dist/kysely.mjs +3 -3
  138. package/dist/kysely.mjs.map +1 -1
  139. package/dist/logger.cjs.map +1 -1
  140. package/dist/logger.d.cts +1 -0
  141. package/dist/logger.d.cts.map +1 -0
  142. package/dist/logger.d.mts +1 -0
  143. package/dist/logger.d.mts.map +1 -0
  144. package/dist/logger.mjs.map +1 -1
  145. package/dist/objection.cjs +3 -3
  146. package/dist/objection.cjs.map +1 -1
  147. package/dist/objection.d.cts +8 -7
  148. package/dist/objection.d.cts.map +1 -0
  149. package/dist/objection.d.mts +7 -6
  150. package/dist/objection.d.mts.map +1 -0
  151. package/dist/objection.mjs +3 -3
  152. package/dist/objection.mjs.map +1 -1
  153. package/dist/os/directory.d.cts +1 -1
  154. package/dist/os/directory.d.mts +1 -1
  155. package/dist/os/index.d.cts +1 -1
  156. package/dist/os/index.d.mts +1 -1
  157. package/dist/timer.cjs.map +1 -1
  158. package/dist/timer.d.cts +2 -0
  159. package/dist/timer.d.cts.map +1 -0
  160. package/dist/timer.d.mts +2 -0
  161. package/dist/timer.d.mts.map +1 -0
  162. package/dist/timer.mjs.map +1 -1
  163. package/package.json +5 -5
  164. package/src/Factory.ts +72 -72
  165. package/src/KyselyFactory.ts +330 -330
  166. package/src/ObjectionFactory.ts +354 -355
  167. package/src/PostgresKyselyMigrator.ts +37 -37
  168. package/src/PostgresMigrator.ts +107 -107
  169. package/src/PostgresObjectionMigrator.ts +91 -91
  170. package/src/VitestKyselyTransactionIsolator.ts +27 -27
  171. package/src/VitestObjectionTransactionIsolator.ts +39 -39
  172. package/src/VitestTransactionIsolator.ts +196 -195
  173. package/src/__tests__/Factory.spec.ts +163 -155
  174. package/src/__tests__/KyselyFactory.spec.ts +443 -439
  175. package/src/__tests__/ObjectionFactory.spec.ts +563 -557
  176. package/src/__tests__/PostgresKyselyMigrator.spec.ts +641 -641
  177. package/src/__tests__/PostgresMigrator.spec.ts +341 -341
  178. package/src/__tests__/PostgresObjectionMigrator.spec.ts +578 -578
  179. package/src/__tests__/VitestObjectionTransactionIsolator.spec.ts +114 -114
  180. package/src/__tests__/benchmark.spec.ts +140 -0
  181. package/src/__tests__/better-auth.spec.ts +15 -15
  182. package/src/__tests__/faker.spec.ts +226 -137
  183. package/src/__tests__/integration.spec.ts +597 -597
  184. package/src/__tests__/utilities.spec.ts +211 -0
  185. package/src/aws.ts +104 -104
  186. package/src/benchmark.ts +12 -12
  187. package/src/better-auth.ts +286 -301
  188. package/src/faker.ts +153 -153
  189. package/src/helpers.ts +6 -6
  190. package/src/kysely.ts +33 -33
  191. package/src/logger.ts +10 -10
  192. package/src/objection.ts +31 -31
  193. package/src/os/directory.ts +11 -10
  194. package/src/timer.ts +1 -1
  195. package/test/globalSetup.ts +45 -45
  196. package/test/helpers.ts +189 -189
  197. package/test/migrations/1749664623372_user.ts +13 -13
  198. package/tsconfig.json +9 -0
  199. package/vitest.config.ts +4 -4
  200. package/dist/PostgresKyselyMigrator-CfytARcA.cjs.map +0 -1
  201. package/dist/PostgresKyselyMigrator-upT-hmrz.mjs.map +0 -1
  202. package/dist/PostgresMigrator-DFcNdCvD.cjs.map +0 -1
  203. package/dist/PostgresMigrator-DbuJGAVy.mjs.map +0 -1
  204. package/dist/PostgresObjectionMigrator-BG6ymgnt.cjs.map +0 -1
  205. package/dist/PostgresObjectionMigrator-DPj2pOpX.mjs.map +0 -1
  206. package/dist/VitestKyselyTransactionIsolator-CNURW8y6.mjs.map +0 -1
  207. package/dist/VitestKyselyTransactionIsolator-EvDLk5zg.cjs.map +0 -1
  208. package/dist/VitestObjectionTransactionIsolator-CM5KTAFA.cjs.map +0 -1
  209. package/dist/VitestObjectionTransactionIsolator-jQFaCz0u.mjs.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import type { Knex } from 'knex';
2
2
  import type { Model } from 'objection';
3
- import { type ExtractSeedAttrs, Factory, type FactorySeed } from './Factory.ts';
4
- import { type FakerFactory, faker } from './faker.ts';
3
+ import { type ExtractSeedAttrs, Factory, type FactorySeed } from './Factory';
4
+ import { type FakerFactory, faker } from './faker';
5
5
 
6
6
  /**
7
7
  * Factory implementation for Objection.js ORM, providing test data creation utilities.
@@ -40,374 +40,373 @@ import { type FakerFactory, faker } from './faker.ts';
40
40
  * ```
41
41
  */
42
42
  export class ObjectionFactory<
43
- Builders extends Record<string, any>,
44
- Seeds extends Record<string, any>,
43
+ Builders extends Record<string, any>,
44
+ Seeds extends Record<string, any>,
45
45
  > extends Factory<Builders, Seeds> {
46
- /**
47
- * Creates a typed seed function with proper type inference.
48
- * Inherits from the base Factory class implementation.
49
- *
50
- * @template Seed - The seed function type
51
- * @param seedFn - The seed function to wrap (receives { attrs, factory, db } object)
52
- * @returns The same seed function with proper typing
53
- *
54
- * @example
55
- * ```typescript
56
- * const seeds = {
57
- * userWithPosts: ObjectionFactory.createSeed(
58
- * async ({ attrs, factory }) => {
59
- * const user = await factory.insert('user', attrs);
60
- * await factory.insertMany(3, 'post', { userId: user.id });
61
- * return user;
62
- * },
63
- * ),
64
- * };
65
- * ```
66
- */
67
- static createSeed<Seed extends FactorySeed>(seedFn: Seed): Seed {
68
- return Factory.createSeed(seedFn);
69
- }
46
+ /**
47
+ * Creates a typed seed function with proper type inference.
48
+ * Inherits from the base Factory class implementation.
49
+ *
50
+ * @template Seed - The seed function type
51
+ * @param seedFn - The seed function to wrap (receives { attrs, factory, db } object)
52
+ * @returns The same seed function with proper typing
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * const seeds = {
57
+ * userWithPosts: ObjectionFactory.createSeed(
58
+ * async ({ attrs, factory }) => {
59
+ * const user = await factory.insert('user', attrs);
60
+ * await factory.insertMany(3, 'post', { userId: user.id });
61
+ * return user;
62
+ * },
63
+ * ),
64
+ * };
65
+ * ```
66
+ */
67
+ static override createSeed<Seed extends FactorySeed>(seedFn: Seed): Seed {
68
+ return Factory.createSeed(seedFn);
69
+ }
70
70
 
71
- /**
72
- * Creates a typed builder function for Objection.js models.
73
- * This is a utility method that helps create builders with proper type inference.
74
- *
75
- * @template TModel - The Objection.js Model class type
76
- * @template Attrs - The attributes type for the builder (defaults to Partial of model)
77
- * @template Factory - The factory instance type
78
- * @template Result - The result type (defaults to the model instance)
79
- *
80
- * @param ModelClass - The Objection.js Model class
81
- * @param defaults - Optional function to provide default values (receives destructured context)
82
- * @param autoInsert - Whether to automatically insert the record (default: true)
83
- * @returns A builder function that creates and optionally inserts records
84
- *
85
- * @example
86
- * ```typescript
87
- * // Create a simple builder with defaults - destructure only what you need
88
- * const userBuilder = ObjectionFactory.createBuilder(User,
89
- * ({ attrs, faker }) => ({
90
- * id: faker.string.uuid(),
91
- * name: faker.person.fullName(),
92
- * email: faker.internet.email(),
93
- * createdAt: new Date(),
94
- * ...attrs
95
- * })
96
- * );
97
- *
98
- * // Only need faker? Just destructure that
99
- * const leaveTypeBuilder = ObjectionFactory.createBuilder(LeaveType,
100
- * ({ faker }) => ({
101
- * name: faker.helpers.arrayElement(['Annual', 'Sick', 'Maternity']),
102
- * code: faker.string.alpha({ length: 3, casing: 'upper' }),
103
- * })
104
- * );
105
- *
106
- * // Create a builder that doesn't auto-insert (useful for nested inserts)
107
- * const addressBuilder = ObjectionFactory.createBuilder(Address,
108
- * ({ attrs }) => ({
109
- * street: '123 Main St',
110
- * city: 'Anytown',
111
- * ...attrs
112
- * }),
113
- * false // Don't auto-insert
114
- * );
115
- *
116
- * // Use with relations
117
- * const postBuilder = ObjectionFactory.createBuilder(Post,
118
- * async ({ attrs, factory, faker }) => ({
119
- * title: faker.lorem.sentence(),
120
- * content: faker.lorem.paragraphs(),
121
- * authorId: attrs.authorId || (await factory.insert('user')).id,
122
- * ...attrs
123
- * })
124
- * );
125
- * ```
126
- */
127
- static createBuilder<
128
- TModel extends typeof Model,
129
- Attrs extends Partial<InstanceType<TModel>> = Partial<InstanceType<TModel>>,
130
- Factory = any,
131
- Result = InstanceType<TModel>,
132
- >(
133
- ModelClass: TModel,
134
- defaults?: (context: {
135
- attrs: Attrs;
136
- factory: Factory;
137
- db: Knex;
138
- faker: FakerFactory;
139
- }) =>
140
- | Partial<InstanceType<TModel>>
141
- | Promise<Partial<InstanceType<TModel>>>,
142
- autoInsert?: boolean,
143
- ): (
144
- attrs: Attrs,
145
- factory: Factory,
146
- db: Knex,
147
- faker: FakerFactory,
148
- ) => Promise<Result> {
149
- return async (
150
- attrs: Attrs,
151
- factory: Factory,
152
- db: Knex,
153
- fakerInstance: FakerFactory,
154
- ) => {
155
- // Start with attributes
156
- let data: Partial<InstanceType<TModel>> = { ...attrs };
71
+ /**
72
+ * Creates a typed builder function for Objection.js models.
73
+ * This is a utility method that helps create builders with proper type inference.
74
+ *
75
+ * @template TModel - The Objection.js Model class type
76
+ * @template Attrs - The attributes type for the builder (defaults to Partial of model)
77
+ * @template Factory - The factory instance type
78
+ * @template Result - The result type (defaults to the model instance)
79
+ *
80
+ * @param ModelClass - The Objection.js Model class
81
+ * @param defaults - Optional function to provide default values (receives destructured context)
82
+ * @param autoInsert - Whether to automatically insert the record (default: true)
83
+ * @returns A builder function that creates and optionally inserts records
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * // Create a simple builder with defaults - destructure only what you need
88
+ * const userBuilder = ObjectionFactory.createBuilder(User,
89
+ * ({ attrs, faker }) => ({
90
+ * id: faker.string.uuid(),
91
+ * name: faker.person.fullName(),
92
+ * email: faker.internet.email(),
93
+ * createdAt: new Date(),
94
+ * ...attrs
95
+ * })
96
+ * );
97
+ *
98
+ * // Only need faker? Just destructure that
99
+ * const leaveTypeBuilder = ObjectionFactory.createBuilder(LeaveType,
100
+ * ({ faker }) => ({
101
+ * name: faker.helpers.arrayElement(['Annual', 'Sick', 'Maternity']),
102
+ * code: faker.string.alpha({ length: 3, casing: 'upper' }),
103
+ * })
104
+ * );
105
+ *
106
+ * // Create a builder that doesn't auto-insert (useful for nested inserts)
107
+ * const addressBuilder = ObjectionFactory.createBuilder(Address,
108
+ * ({ attrs }) => ({
109
+ * street: '123 Main St',
110
+ * city: 'Anytown',
111
+ * ...attrs
112
+ * }),
113
+ * false // Don't auto-insert
114
+ * );
115
+ *
116
+ * // Use with relations
117
+ * const postBuilder = ObjectionFactory.createBuilder(Post,
118
+ * async ({ attrs, factory, faker }) => ({
119
+ * title: faker.lorem.sentence(),
120
+ * content: faker.lorem.paragraphs(),
121
+ * authorId: attrs.authorId || (await factory.insert('user')).id,
122
+ * ...attrs
123
+ * })
124
+ * );
125
+ * ```
126
+ */
127
+ static createBuilder<
128
+ TModel extends typeof Model,
129
+ Attrs extends Partial<InstanceType<TModel>> = Partial<InstanceType<TModel>>,
130
+ Factory = any,
131
+ Result = InstanceType<TModel>,
132
+ >(
133
+ ModelClass: TModel,
134
+ defaults?: (context: {
135
+ attrs: Attrs;
136
+ factory: Factory;
137
+ db: Knex;
138
+ faker: FakerFactory;
139
+ }) =>
140
+ | Partial<InstanceType<TModel>>
141
+ | Promise<Partial<InstanceType<TModel>>>,
142
+ autoInsert?: boolean,
143
+ ): (
144
+ attrs: Attrs,
145
+ factory: Factory,
146
+ db: Knex,
147
+ faker: FakerFactory,
148
+ ) => Promise<Result> {
149
+ return async (
150
+ attrs: Attrs,
151
+ factory: Factory,
152
+ db: Knex,
153
+ fakerInstance: FakerFactory,
154
+ ) => {
155
+ // Start with attributes
156
+ let data: Partial<InstanceType<TModel>> = { ...attrs };
157
157
 
158
- // Apply defaults
159
- if (defaults) {
160
- const defaultValues = await defaults({
161
- attrs,
162
- factory,
163
- db,
164
- faker: fakerInstance,
165
- });
166
- data = { ...defaultValues, ...data };
167
- }
158
+ // Apply defaults
159
+ if (defaults) {
160
+ const defaultValues = await defaults({
161
+ attrs,
162
+ factory,
163
+ db,
164
+ faker: fakerInstance,
165
+ });
166
+ data = { ...defaultValues, ...data };
167
+ }
168
168
 
169
- // Create model instance
170
- const model = ModelClass.fromJson(data) as InstanceType<TModel>;
169
+ // Create model instance
170
+ const model = ModelClass.fromJson(data) as InstanceType<TModel>;
171
171
 
172
- // Handle insertion based on autoInsert flag
173
- if (autoInsert !== false) {
174
- // Auto insert is enabled by default
175
- // Extract only defined values for insertion
176
- const insertData = Object.entries(model).reduce((acc, [key, value]) => {
177
- if (value !== undefined && key !== 'id') {
178
- acc[key] = value;
179
- }
180
- return acc;
181
- }, {} as any);
172
+ // Handle insertion based on autoInsert flag
173
+ if (autoInsert !== false) {
174
+ // Auto insert is enabled by default
175
+ // Extract only defined values for insertion
176
+ const insertData = Object.entries(model).reduce((acc, [key, value]) => {
177
+ if (value !== undefined && key !== 'id') {
178
+ acc[key] = value;
179
+ }
180
+ return acc;
181
+ }, {} as any);
182
182
 
183
- // Use static query method to insert data directly
184
- // @ts-ignore
185
- const result = await ModelClass.query(db).insert(insertData);
186
- return result as Result;
187
- } else {
188
- // Return model for factory to handle insertion
189
- return model as Result;
190
- }
191
- };
192
- }
183
+ // Use static query method to insert data directly
184
+ const result = await ModelClass.query(db).insert(insertData);
185
+ return result as Result;
186
+ } else {
187
+ // Return model for factory to handle insertion
188
+ return model as Result;
189
+ }
190
+ };
191
+ }
193
192
 
194
- /**
195
- * Creates a new ObjectionFactory instance.
196
- *
197
- * @param builders - Record of builder functions for creating individual entities
198
- * @param seeds - Record of seed functions for creating complex test scenarios
199
- * @param db - Knex database connection instance
200
- */
201
- constructor(
202
- private builders: Builders,
203
- private seeds: Seeds,
204
- private db: Knex,
205
- ) {
206
- super();
207
- }
193
+ /**
194
+ * Creates a new ObjectionFactory instance.
195
+ *
196
+ * @param builders - Record of builder functions for creating individual entities
197
+ * @param seeds - Record of seed functions for creating complex test scenarios
198
+ * @param db - Knex database connection instance
199
+ */
200
+ constructor(
201
+ private builders: Builders,
202
+ private seeds: Seeds,
203
+ private db: Knex,
204
+ ) {
205
+ super();
206
+ }
208
207
 
209
- /**
210
- * Inserts a single record into the database using the specified builder.
211
- * Uses Objection.js's insertGraph method to handle nested relations.
212
- *
213
- * @template K - The builder name (must be a key of Builders)
214
- * @param builderName - The name of the builder to use
215
- * @param attrs - Optional attributes to override builder defaults
216
- * @returns A promise resolving to the inserted record with all relations
217
- * @throws Error if the specified builder doesn't exist
218
- *
219
- * @example
220
- * ```typescript
221
- * // Insert with defaults
222
- * const user = await factory.insert('user');
223
- *
224
- * // Insert with overrides
225
- * const adminUser = await factory.insert('user', {
226
- * email: 'admin@example.com',
227
- * role: 'admin'
228
- * });
229
- *
230
- * // Insert with nested relations
231
- * const userWithProfile = await factory.insert('user', {
232
- * name: 'John Doe',
233
- * profile: {
234
- * bio: 'Software Developer',
235
- * avatar: 'avatar.jpg'
236
- * }
237
- * });
238
- * ```
239
- */
240
- async insert<K extends keyof Builders>(
241
- builderName: K,
242
- attrs?: Parameters<Builders[K]>[0],
243
- ): Promise<Awaited<ReturnType<Builders[K]>>> {
244
- if (!(builderName in this.builders)) {
245
- throw new Error(
246
- `Factory "${
247
- builderName as string
248
- }" does not exist. Make sure it is correct and registered in src/test/setup.ts`,
249
- );
250
- }
208
+ /**
209
+ * Inserts a single record into the database using the specified builder.
210
+ * Uses Objection.js's insertGraph method to handle nested relations.
211
+ *
212
+ * @template K - The builder name (must be a key of Builders)
213
+ * @param builderName - The name of the builder to use
214
+ * @param attrs - Optional attributes to override builder defaults
215
+ * @returns A promise resolving to the inserted record with all relations
216
+ * @throws Error if the specified builder doesn't exist
217
+ *
218
+ * @example
219
+ * ```typescript
220
+ * // Insert with defaults
221
+ * const user = await factory.insert('user');
222
+ *
223
+ * // Insert with overrides
224
+ * const adminUser = await factory.insert('user', {
225
+ * email: 'admin@example.com',
226
+ * role: 'admin'
227
+ * });
228
+ *
229
+ * // Insert with nested relations
230
+ * const userWithProfile = await factory.insert('user', {
231
+ * name: 'John Doe',
232
+ * profile: {
233
+ * bio: 'Software Developer',
234
+ * avatar: 'avatar.jpg'
235
+ * }
236
+ * });
237
+ * ```
238
+ */
239
+ async insert<K extends keyof Builders>(
240
+ builderName: K,
241
+ attrs?: Parameters<Builders[K]>[0],
242
+ ): Promise<Awaited<ReturnType<Builders[K]>>> {
243
+ if (!(builderName in this.builders)) {
244
+ throw new Error(
245
+ `Factory "${
246
+ builderName as string
247
+ }" does not exist. Make sure it is correct and registered in src/test/setup.ts`,
248
+ );
249
+ }
251
250
 
252
- const result = await this.builders[builderName](
253
- attrs || {},
254
- this,
255
- this.db,
256
- faker,
257
- );
251
+ const result = await this.builders[builderName](
252
+ attrs || {},
253
+ this,
254
+ this.db,
255
+ faker,
256
+ );
258
257
 
259
- // If the builder returns a model instance, insert it
260
- if (result && typeof result.$query === 'function') {
261
- // Extract data from model, excluding undefined values and id
262
- const insertData = Object.entries(result).reduce((acc, [key, value]) => {
263
- if (value !== undefined && key !== 'id') {
264
- acc[key] = value;
265
- }
266
- return acc;
267
- }, {} as any);
258
+ // If the builder returns a model instance, insert it
259
+ if (result && typeof result.$query === 'function') {
260
+ // Extract data from model, excluding undefined values and id
261
+ const insertData = Object.entries(result).reduce((acc, [key, value]) => {
262
+ if (value !== undefined && key !== 'id') {
263
+ acc[key] = value;
264
+ }
265
+ return acc;
266
+ }, {} as any);
268
267
 
269
- // Use the model's constructor to get the query builder
270
- return await result.constructor.query(this.db).insert(insertData);
271
- }
268
+ // Use the model's constructor to get the query builder
269
+ return await result.constructor.query(this.db).insert(insertData);
270
+ }
272
271
 
273
- // Otherwise, assume the builder handled insertion itself
274
- return result;
275
- }
276
- /**
277
- * Inserts multiple records into the database using the specified builder.
278
- * Supports both static attributes and dynamic attribute generation via a function.
279
- *
280
- * @param count - The number of records to insert
281
- * @param builderName - The name of the builder to use
282
- * @param attrs - Static attributes or a function that generates attributes for each record
283
- * @returns A promise resolving to an array of inserted records
284
- * @throws Error if the specified builder doesn't exist
285
- *
286
- * @example
287
- * ```typescript
288
- * // Insert multiple with same attributes
289
- * const users = await factory.insertMany(5, 'user', { role: 'member' });
290
- *
291
- * // Insert multiple with dynamic attributes
292
- * const posts = await factory.insertMany(10, 'post', (idx) => ({
293
- * title: `Post ${idx + 1}`,
294
- * content: `Content for post ${idx + 1}`,
295
- * publishedAt: new Date()
296
- * }));
297
- *
298
- * // Create users with sequential emails
299
- * const admins = await factory.insertMany(3, 'user', (idx) => ({
300
- * email: `admin${idx + 1}@example.com`,
301
- * role: 'admin'
302
- * }));
303
- * ```
304
- */
305
- // Method overloads for better type inference
306
- async insertMany<K extends keyof Builders>(
307
- count: number,
308
- builderName: K,
309
- attrs?: Parameters<Builders[K]>[0],
310
- ): Promise<Awaited<ReturnType<Builders[K]>>[]>;
311
- async insertMany<K extends keyof Builders>(
312
- count: number,
313
- builderName: K,
314
- attrs: (idx: number, faker: FakerFactory) => Parameters<Builders[K]>[0],
315
- ): Promise<Awaited<ReturnType<Builders[K]>>[]>;
316
- async insertMany<K extends keyof Builders>(
317
- count: number,
318
- builderName: K,
319
- attrs?: any,
320
- ): Promise<Awaited<ReturnType<Builders[K]>>[]> {
321
- if (!(builderName in this.builders)) {
322
- throw new Error(
323
- `Builder "${
324
- builderName as string
325
- }" is not registered in this factory. Make sure it is correct and registered in src/test/setup.ts`,
326
- );
327
- }
272
+ // Otherwise, assume the builder handled insertion itself
273
+ return result;
274
+ }
275
+ /**
276
+ * Inserts multiple records into the database using the specified builder.
277
+ * Supports both static attributes and dynamic attribute generation via a function.
278
+ *
279
+ * @param count - The number of records to insert
280
+ * @param builderName - The name of the builder to use
281
+ * @param attrs - Static attributes or a function that generates attributes for each record
282
+ * @returns A promise resolving to an array of inserted records
283
+ * @throws Error if the specified builder doesn't exist
284
+ *
285
+ * @example
286
+ * ```typescript
287
+ * // Insert multiple with same attributes
288
+ * const users = await factory.insertMany(5, 'user', { role: 'member' });
289
+ *
290
+ * // Insert multiple with dynamic attributes
291
+ * const posts = await factory.insertMany(10, 'post', (idx) => ({
292
+ * title: `Post ${idx + 1}`,
293
+ * content: `Content for post ${idx + 1}`,
294
+ * publishedAt: new Date()
295
+ * }));
296
+ *
297
+ * // Create users with sequential emails
298
+ * const admins = await factory.insertMany(3, 'user', (idx) => ({
299
+ * email: `admin${idx + 1}@example.com`,
300
+ * role: 'admin'
301
+ * }));
302
+ * ```
303
+ */
304
+ // Method overloads for better type inference
305
+ async insertMany<K extends keyof Builders>(
306
+ count: number,
307
+ builderName: K,
308
+ attrs?: Parameters<Builders[K]>[0],
309
+ ): Promise<Awaited<ReturnType<Builders[K]>>[]>;
310
+ async insertMany<K extends keyof Builders>(
311
+ count: number,
312
+ builderName: K,
313
+ attrs: (idx: number, faker: FakerFactory) => Parameters<Builders[K]>[0],
314
+ ): Promise<Awaited<ReturnType<Builders[K]>>[]>;
315
+ async insertMany<K extends keyof Builders>(
316
+ count: number,
317
+ builderName: K,
318
+ attrs?: any,
319
+ ): Promise<Awaited<ReturnType<Builders[K]>>[]> {
320
+ if (!(builderName in this.builders)) {
321
+ throw new Error(
322
+ `Builder "${
323
+ builderName as string
324
+ }" is not registered in this factory. Make sure it is correct and registered in src/test/setup.ts`,
325
+ );
326
+ }
328
327
 
329
- const records: any[] = [];
330
- for (let i = 0; i < count; i++) {
331
- const newAttrs =
332
- typeof attrs === 'function' ? await (attrs as any)(i, faker) : attrs;
328
+ const records: any[] = [];
329
+ for (let i = 0; i < count; i++) {
330
+ const newAttrs =
331
+ typeof attrs === 'function' ? await (attrs as any)(i, faker) : attrs;
333
332
 
334
- records.push(
335
- this.builders[builderName](newAttrs, this, this.db, faker).then(
336
- (record: any) => {
337
- // If the builder returns a model instance, insert it
338
- if (record && typeof record.$query === 'function') {
339
- // Extract data from model, excluding undefined values and id
340
- const insertData = Object.entries(record).reduce(
341
- (acc, [key, value]) => {
342
- if (value !== undefined && key !== 'id') {
343
- acc[key] = value;
344
- }
345
- return acc;
346
- },
347
- {} as any,
348
- );
333
+ records.push(
334
+ this.builders[builderName](newAttrs, this, this.db, faker).then(
335
+ (record: any) => {
336
+ // If the builder returns a model instance, insert it
337
+ if (record && typeof record.$query === 'function') {
338
+ // Extract data from model, excluding undefined values and id
339
+ const insertData = Object.entries(record).reduce(
340
+ (acc, [key, value]) => {
341
+ if (value !== undefined && key !== 'id') {
342
+ acc[key] = value;
343
+ }
344
+ return acc;
345
+ },
346
+ {} as any,
347
+ );
349
348
 
350
- // Use the model's constructor to get the query builder
351
- return record.constructor.query(this.db).insert(insertData);
352
- }
353
- // Otherwise, assume the builder handled insertion itself
354
- return record;
355
- },
356
- ),
357
- );
358
- }
349
+ // Use the model's constructor to get the query builder
350
+ return record.constructor.query(this.db).insert(insertData);
351
+ }
352
+ // Otherwise, assume the builder handled insertion itself
353
+ return record;
354
+ },
355
+ ),
356
+ );
357
+ }
359
358
 
360
- return Promise.all(records);
361
- }
362
- /**
363
- * Executes a seed function to create complex test scenarios with multiple related records.
364
- * Seeds are useful for setting up complete test environments with realistic data relationships.
365
- *
366
- * @template K - The seed name (must be a key of Seeds)
367
- * @param seedName - The name of the seed to execute
368
- * @param attrs - Optional configuration attributes for the seed
369
- * @returns The result of the seed function (typically the primary record created)
370
- * @throws Error if the specified seed doesn't exist
371
- *
372
- * @example
373
- * ```typescript
374
- * // Execute a simple seed
375
- * const user = await factory.seed('userWithProfile');
376
- *
377
- * // Execute a seed with configuration
378
- * const author = await factory.seed('authorWithBooks', {
379
- * bookCount: 5,
380
- * includeReviews: true
381
- * });
382
- *
383
- * // Use seed result in tests with Objection.js relations
384
- * const company = await factory.seed('companyWithDepartments', {
385
- * departmentCount: 3,
386
- * employeesPerDepartment: 10
387
- * });
388
- *
389
- * // Access eager loaded relations
390
- * const companyWithRelations = await Company.query()
391
- * .findById(company.id)
392
- * .withGraphFetched('[departments.employees]');
393
- * ```
394
- */
395
- seed<K extends keyof Seeds>(
396
- seedName: K,
397
- attrs?: ExtractSeedAttrs<Seeds[K]>,
398
- ): ReturnType<Seeds[K]> {
399
- if (!(seedName in this.seeds)) {
400
- throw new Error(
401
- `Seed "${
402
- seedName as string
403
- }" is not registered in this factory. Make sure it is correct and registered in src/test/setup.ts`,
404
- );
405
- }
359
+ return Promise.all(records);
360
+ }
361
+ /**
362
+ * Executes a seed function to create complex test scenarios with multiple related records.
363
+ * Seeds are useful for setting up complete test environments with realistic data relationships.
364
+ *
365
+ * @template K - The seed name (must be a key of Seeds)
366
+ * @param seedName - The name of the seed to execute
367
+ * @param attrs - Optional configuration attributes for the seed
368
+ * @returns The result of the seed function (typically the primary record created)
369
+ * @throws Error if the specified seed doesn't exist
370
+ *
371
+ * @example
372
+ * ```typescript
373
+ * // Execute a simple seed
374
+ * const user = await factory.seed('userWithProfile');
375
+ *
376
+ * // Execute a seed with configuration
377
+ * const author = await factory.seed('authorWithBooks', {
378
+ * bookCount: 5,
379
+ * includeReviews: true
380
+ * });
381
+ *
382
+ * // Use seed result in tests with Objection.js relations
383
+ * const company = await factory.seed('companyWithDepartments', {
384
+ * departmentCount: 3,
385
+ * employeesPerDepartment: 10
386
+ * });
387
+ *
388
+ * // Access eager loaded relations
389
+ * const companyWithRelations = await Company.query()
390
+ * .findById(company.id)
391
+ * .withGraphFetched('[departments.employees]');
392
+ * ```
393
+ */
394
+ seed<K extends keyof Seeds>(
395
+ seedName: K,
396
+ attrs?: ExtractSeedAttrs<Seeds[K]>,
397
+ ): ReturnType<Seeds[K]> {
398
+ if (!(seedName in this.seeds)) {
399
+ throw new Error(
400
+ `Seed "${
401
+ seedName as string
402
+ }" is not registered in this factory. Make sure it is correct and registered in src/test/setup.ts`,
403
+ );
404
+ }
406
405
 
407
- return this.seeds[seedName]({
408
- attrs: attrs || {},
409
- factory: this,
410
- db: this.db,
411
- });
412
- }
406
+ return this.seeds[seedName]({
407
+ attrs: attrs || {},
408
+ factory: this,
409
+ db: this.db,
410
+ });
411
+ }
413
412
  }