@c9up/atlas 0.2.0 → 0.2.2
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 +8 -0
- package/db.darwin-arm64.node +0 -0
- package/db.darwin-x64.node +0 -0
- package/db.linux-arm64-gnu.node +0 -0
- package/db.linux-x64-gnu.node +0 -0
- package/db.win32-x64-msvc.node +0 -0
- package/dist/AtlasProvider.d.ts +60 -7
- package/dist/AtlasProvider.d.ts.map +1 -1
- package/dist/AtlasProvider.js +204 -38
- package/dist/AtlasProvider.js.map +1 -1
- package/dist/BaseEntity.d.ts +17 -0
- package/dist/BaseEntity.d.ts.map +1 -1
- package/dist/BaseEntity.js.map +1 -1
- package/dist/BaseModel.d.ts +63 -27
- package/dist/BaseModel.d.ts.map +1 -1
- package/dist/BaseModel.js +108 -51
- package/dist/BaseModel.js.map +1 -1
- package/dist/BaseRepository.d.ts +11 -1
- package/dist/BaseRepository.d.ts.map +1 -1
- package/dist/BaseRepository.js +95 -20
- package/dist/BaseRepository.js.map +1 -1
- package/dist/ConnectionManager.d.ts +106 -0
- package/dist/ConnectionManager.d.ts.map +1 -0
- package/dist/ConnectionManager.js +228 -0
- package/dist/ConnectionManager.js.map +1 -0
- package/dist/ModelQuery.d.ts +256 -43
- package/dist/ModelQuery.d.ts.map +1 -1
- package/dist/ModelQuery.js +899 -231
- package/dist/ModelQuery.js.map +1 -1
- package/dist/Transaction.d.ts +54 -1
- package/dist/Transaction.d.ts.map +1 -1
- package/dist/Transaction.js +121 -59
- package/dist/Transaction.js.map +1 -1
- package/dist/adapters/NapiDbAdapter.d.ts +11 -0
- package/dist/adapters/NapiDbAdapter.d.ts.map +1 -1
- package/dist/adapters/NapiDbAdapter.js +35 -7
- package/dist/adapters/NapiDbAdapter.js.map +1 -1
- package/dist/console/contract.d.ts +62 -0
- package/dist/console/contract.d.ts.map +1 -0
- package/dist/console/contract.js +38 -0
- package/dist/console/contract.js.map +1 -0
- package/dist/console/factoryCommands.d.ts +23 -0
- package/dist/console/factoryCommands.d.ts.map +1 -0
- package/dist/console/factoryCommands.js +62 -0
- package/dist/console/factoryCommands.js.map +1 -0
- package/dist/console/migrationCommands.d.ts +38 -8
- package/dist/console/migrationCommands.d.ts.map +1 -1
- package/dist/console/migrationCommands.js +157 -50
- package/dist/console/migrationCommands.js.map +1 -1
- package/dist/console/schemaCheckCommand.d.ts +11 -17
- package/dist/console/schemaCheckCommand.d.ts.map +1 -1
- package/dist/console/schemaCheckCommand.js +21 -15
- package/dist/console/schemaCheckCommand.js.map +1 -1
- package/dist/console/schemaDumpCommand.d.ts +30 -0
- package/dist/console/schemaDumpCommand.d.ts.map +1 -0
- package/dist/console/schemaDumpCommand.js +69 -0
- package/dist/console/schemaDumpCommand.js.map +1 -0
- package/dist/console/schemaGenerateCommand.d.ts +100 -0
- package/dist/console/schemaGenerateCommand.d.ts.map +1 -0
- package/dist/console/schemaGenerateCommand.js +246 -0
- package/dist/console/schemaGenerateCommand.js.map +1 -0
- package/dist/console/seederCommands.d.ts +46 -0
- package/dist/console/seederCommands.d.ts.map +1 -0
- package/dist/console/seederCommands.js +136 -0
- package/dist/console/seederCommands.js.map +1 -0
- package/dist/decorators/entity.d.ts +13 -2
- package/dist/decorators/entity.d.ts.map +1 -1
- package/dist/decorators/entity.js +10 -0
- package/dist/decorators/entity.js.map +1 -1
- package/dist/decorators/hooks.d.ts +3 -3
- package/dist/decorators/hooks.d.ts.map +1 -1
- package/dist/decorators/hooks.js.map +1 -1
- package/dist/events.d.ts +5 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js.map +1 -1
- package/dist/factories.d.ts +8 -0
- package/dist/factories.d.ts.map +1 -0
- package/dist/factories.js +8 -0
- package/dist/factories.js.map +1 -0
- package/dist/index.d.ts +11 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -4
- package/dist/index.js.map +1 -1
- package/dist/lucid-schema.d.ts +15 -0
- package/dist/lucid-schema.d.ts.map +1 -0
- package/dist/lucid-schema.js +16 -0
- package/dist/lucid-schema.js.map +1 -0
- package/dist/orm.d.ts +14 -0
- package/dist/orm.d.ts.map +1 -0
- package/dist/orm.js +14 -0
- package/dist/orm.js.map +1 -0
- package/dist/query/DatabaseQueryBuilder.d.ts +557 -0
- package/dist/query/DatabaseQueryBuilder.d.ts.map +1 -0
- package/dist/query/DatabaseQueryBuilder.js +1798 -0
- package/dist/query/DatabaseQueryBuilder.js.map +1 -0
- package/dist/query/DmlBuilder.d.ts +62 -0
- package/dist/query/DmlBuilder.d.ts.map +1 -0
- package/dist/query/DmlBuilder.js +84 -0
- package/dist/query/DmlBuilder.js.map +1 -0
- package/dist/query/QueryBuilder.d.ts +5 -0
- package/dist/query/QueryBuilder.d.ts.map +1 -1
- package/dist/query/QueryBuilder.js +7 -0
- package/dist/query/QueryBuilder.js.map +1 -1
- package/dist/query/RawQueryBuilder.d.ts +47 -0
- package/dist/query/RawQueryBuilder.d.ts.map +1 -0
- package/dist/query/RawQueryBuilder.js +138 -0
- package/dist/query/RawQueryBuilder.js.map +1 -0
- package/dist/query/interpolate.d.ts +45 -0
- package/dist/query/interpolate.d.ts.map +1 -0
- package/dist/query/interpolate.js +51 -0
- package/dist/query/interpolate.js.map +1 -0
- package/dist/query/native.d.ts +8 -0
- package/dist/query/native.d.ts.map +1 -1
- package/dist/query/native.js +14 -0
- package/dist/query/native.js.map +1 -1
- package/dist/query/operators.d.ts +4 -0
- package/dist/query/operators.d.ts.map +1 -0
- package/dist/query/operators.js +27 -0
- package/dist/query/operators.js.map +1 -0
- package/dist/schema/Migration.d.ts +31 -7
- package/dist/schema/Migration.d.ts.map +1 -1
- package/dist/schema/Migration.js +34 -7
- package/dist/schema/Migration.js.map +1 -1
- package/dist/schema/MigrationRunner.d.ts +56 -6
- package/dist/schema/MigrationRunner.d.ts.map +1 -1
- package/dist/schema/MigrationRunner.js +382 -112
- package/dist/schema/MigrationRunner.js.map +1 -1
- package/dist/schema/Schema.d.ts +59 -2
- package/dist/schema/Schema.d.ts.map +1 -1
- package/dist/schema/Schema.js +143 -9
- package/dist/schema/Schema.js.map +1 -1
- package/dist/schema/SchemaDumper.d.ts +121 -0
- package/dist/schema/SchemaDumper.d.ts.map +1 -0
- package/dist/schema/SchemaDumper.js +365 -0
- package/dist/schema/SchemaDumper.js.map +1 -0
- package/dist/schema/Seeder.d.ts +13 -0
- package/dist/schema/Seeder.d.ts.map +1 -1
- package/dist/schema/Seeder.js +28 -5
- package/dist/schema/Seeder.js.map +1 -1
- package/dist/schema/TableBuilder.d.ts +27 -8
- package/dist/schema/TableBuilder.d.ts.map +1 -1
- package/dist/schema/TableBuilder.js +50 -12
- package/dist/schema/TableBuilder.js.map +1 -1
- package/dist/schema/catalog.d.ts +25 -1
- package/dist/schema/catalog.d.ts.map +1 -1
- package/dist/schema/catalog.js +69 -22
- package/dist/schema/catalog.js.map +1 -1
- package/dist/schema/introspect.d.ts +1 -1
- package/dist/schema/introspect.d.ts.map +1 -1
- package/dist/schema/introspect.js +26 -11
- package/dist/schema/introspect.js.map +1 -1
- package/dist/seeders.d.ts +7 -0
- package/dist/seeders.d.ts.map +1 -0
- package/dist/seeders.js +7 -0
- package/dist/seeders.js.map +1 -0
- package/dist/services/db.d.ts +120 -9
- package/dist/services/db.d.ts.map +1 -1
- package/dist/services/db.js +182 -26
- package/dist/services/db.js.map +1 -1
- package/dist/testing/DatabaseCleanup.d.ts +29 -6
- package/dist/testing/DatabaseCleanup.d.ts.map +1 -1
- package/dist/testing/DatabaseCleanup.js +44 -17
- package/dist/testing/DatabaseCleanup.js.map +1 -1
- package/dist/testing/DbAssertions.d.ts +54 -0
- package/dist/testing/DbAssertions.d.ts.map +1 -0
- package/dist/testing/DbAssertions.js +156 -0
- package/dist/testing/DbAssertions.js.map +1 -0
- package/dist/testing/Factory.d.ts +111 -25
- package/dist/testing/Factory.d.ts.map +1 -1
- package/dist/testing/Factory.js +331 -56
- package/dist/testing/Factory.js.map +1 -1
- package/dist/testing/TestUtils.d.ts +49 -0
- package/dist/testing/TestUtils.d.ts.map +1 -0
- package/dist/testing/TestUtils.js +79 -0
- package/dist/testing/TestUtils.js.map +1 -0
- package/dist/testing/index.d.ts +3 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +3 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/types/relations.d.ts +31 -0
- package/dist/types/relations.d.ts.map +1 -0
- package/dist/types/relations.js +20 -0
- package/dist/types/relations.js.map +1 -0
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +30 -2
- package/src/AtlasProvider.ts +297 -51
- package/src/BaseEntity.ts +29 -0
- package/src/BaseModel.ts +174 -37
- package/src/BaseRepository.ts +136 -17
- package/src/ConnectionManager.ts +298 -0
- package/src/ModelQuery.ts +1309 -260
- package/src/Transaction.ts +194 -60
- package/src/adapters/NapiDbAdapter.ts +72 -14
- package/src/console/contract.ts +96 -0
- package/src/console/factoryCommands.ts +73 -0
- package/src/console/migrationCommands.ts +253 -63
- package/src/console/schemaCheckCommand.ts +26 -22
- package/src/console/schemaDumpCommand.ts +96 -0
- package/src/console/schemaGenerateCommand.ts +380 -0
- package/src/console/seederCommands.ts +174 -0
- package/src/decorators/entity.ts +18 -2
- package/src/decorators/hooks.ts +5 -3
- package/src/events.ts +5 -0
- package/src/factories.ts +10 -0
- package/src/index.ts +50 -3
- package/src/lucid-schema.ts +22 -0
- package/src/orm.ts +13 -0
- package/src/query/DatabaseQueryBuilder.ts +2724 -0
- package/src/query/DmlBuilder.ts +131 -0
- package/src/query/QueryBuilder.ts +8 -0
- package/src/query/RawQueryBuilder.ts +173 -0
- package/src/query/interpolate.ts +73 -0
- package/src/query/native.ts +29 -0
- package/src/query/operators.ts +27 -0
- package/src/schema/Migration.ts +36 -7
- package/src/schema/MigrationRunner.ts +483 -119
- package/src/schema/Schema.ts +168 -19
- package/src/schema/SchemaDumper.ts +518 -0
- package/src/schema/Seeder.ts +47 -10
- package/src/schema/TableBuilder.ts +48 -11
- package/src/schema/catalog.ts +94 -24
- package/src/schema/introspect.ts +26 -9
- package/src/seeders.ts +16 -0
- package/src/services/db.ts +343 -31
- package/src/testing/DatabaseCleanup.ts +73 -21
- package/src/testing/DbAssertions.ts +220 -0
- package/src/testing/Factory.ts +559 -79
- package/src/testing/TestUtils.ts +123 -0
- package/src/testing/index.ts +12 -1
- package/src/types/relations.ts +43 -0
package/src/testing/Factory.ts
CHANGED
|
@@ -19,6 +19,7 @@ import type { BaseEntity } from "../BaseEntity.js";
|
|
|
19
19
|
import { BaseRepository, type DatabaseConnection } from "../BaseRepository.js";
|
|
20
20
|
import { getPrimaryKey, getRelationMetadata } from "../decorators/entity.js";
|
|
21
21
|
import { getConnection } from "../services/db.js";
|
|
22
|
+
import { transaction } from "../Transaction.js";
|
|
22
23
|
|
|
23
24
|
type EntityConstructor<T extends BaseEntity> = new () => T;
|
|
24
25
|
|
|
@@ -33,19 +34,51 @@ interface WithRequest {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
37
|
+
* Per-factory internals reached across factory closures to support NESTED
|
|
38
|
+
* `.with()` (Adonis Lucid: `post.with('comments', 5)` inside a parent's `.with`
|
|
39
|
+
* callback). Kept off the public {@link FactoryBuilder} type and keyed by the
|
|
40
|
+
* builder object in a {@link WeakMap} — no `any`/cast, no leaked API surface.
|
|
41
|
+
*/
|
|
42
|
+
interface FactoryInternals {
|
|
43
|
+
/** Take and clear the queued `.with()` requests (before a `make` clears them). */
|
|
44
|
+
consumeWith(): WithRequest[];
|
|
45
|
+
/** Take and clear the queued m2m `.pivotAttributes()` (set in a `.with` callback). */
|
|
46
|
+
consumePivot():
|
|
47
|
+
| Record<string, unknown>
|
|
48
|
+
| Array<Record<string, unknown>>
|
|
49
|
+
| undefined;
|
|
50
|
+
/** Persist this factory's `.with()` relations onto an already-created parent. */
|
|
51
|
+
applyRelations(
|
|
52
|
+
parent: BaseEntity,
|
|
53
|
+
reqs: WithRequest[],
|
|
54
|
+
db: DatabaseConnection,
|
|
55
|
+
recursive?: Record<string, unknown>,
|
|
56
|
+
): Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const factoryInternals = new WeakMap<object, FactoryInternals>();
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Runtime context handed to the factory callbacks (Adonis Lucid parity):
|
|
63
|
+
* `faker` for fake data, `isStubbed` (true during a `makeStubbed*` build), and
|
|
64
|
+
* `$trx` — the bound transaction/connection, if any, so a hook's own DB queries
|
|
65
|
+
* commit/roll back alongside the factory. A callback that ignores it keeps
|
|
66
|
+
* working.
|
|
39
67
|
*/
|
|
40
68
|
export interface FactoryContext {
|
|
41
69
|
faker: Faker;
|
|
70
|
+
isStubbed: boolean;
|
|
71
|
+
$trx?: DatabaseConnection;
|
|
42
72
|
}
|
|
43
73
|
|
|
44
74
|
/** Attributes callback — receives {@link FactoryContext}, returns the row shape. */
|
|
45
75
|
type DefaultsFn = (ctx: FactoryContext) => Record<string, unknown>;
|
|
46
76
|
|
|
47
|
-
/**
|
|
48
|
-
|
|
77
|
+
/**
|
|
78
|
+
* A named state — mutates the built model INSTANCE (Adonis Lucid: states receive
|
|
79
|
+
* the instance + runtime context, not a raw data object).
|
|
80
|
+
*/
|
|
81
|
+
type StateFn<T extends BaseEntity> = (model: T, ctx: FactoryContext) => void;
|
|
49
82
|
|
|
50
83
|
/**
|
|
51
84
|
* Process-wide counter for stubbed primary keys (Lucid's stub id). Every
|
|
@@ -55,6 +88,13 @@ type StateFn<D> = (data: D) => void;
|
|
|
55
88
|
*/
|
|
56
89
|
let stubIdCounter = 0;
|
|
57
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Global stub-id generator override (Adonis Lucid `Factory.stubId`). Set via
|
|
93
|
+
* {@link Factory.stubId} when models use non-integer primary keys (uuid, etc.);
|
|
94
|
+
* receives the running counter and the instance, returns the id to assign.
|
|
95
|
+
*/
|
|
96
|
+
let globalStubId: ((counter: number, model: BaseEntity) => unknown) | undefined;
|
|
97
|
+
|
|
58
98
|
/** Keys that must never be written through a recursive merge — prototype-pollution guard. */
|
|
59
99
|
const FORBIDDEN_MERGE_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
60
100
|
|
|
@@ -85,9 +125,31 @@ function deepMerge(
|
|
|
85
125
|
return out;
|
|
86
126
|
}
|
|
87
127
|
|
|
128
|
+
/**
|
|
129
|
+
* A `merge` callback (Adonis Lucid) — receives the built model INSTANCE, the
|
|
130
|
+
* resolved attributes, and the runtime context, e.g.
|
|
131
|
+
* `.merge((user, attributes) => { user.merge(attributes) })`.
|
|
132
|
+
*/
|
|
133
|
+
type MergeFn<T extends BaseEntity> = (
|
|
134
|
+
model: T,
|
|
135
|
+
attributes: Record<string, unknown>,
|
|
136
|
+
ctx: FactoryContext,
|
|
137
|
+
) => void;
|
|
138
|
+
|
|
88
139
|
export interface FactoryBuilder<T extends BaseEntity> {
|
|
89
|
-
/**
|
|
90
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Override specific fields for the next call (reset after consumption). Pass an
|
|
142
|
+
* object to shallow-merge, a CALLBACK to mutate the resolved attributes
|
|
143
|
+
* imperatively (Adonis Lucid `merge`), or an ARRAY to override per row on
|
|
144
|
+
* `makeMany`/`createMany` — `merge([{a:1},{a:2}]).createMany(2)` applies index
|
|
145
|
+
* `i` to row `i` (rows past the array length keep the plain defaults).
|
|
146
|
+
*/
|
|
147
|
+
merge(
|
|
148
|
+
overrides:
|
|
149
|
+
| Partial<Record<string, unknown>>
|
|
150
|
+
| MergeFn<T>
|
|
151
|
+
| Array<Partial<Record<string, unknown>>>,
|
|
152
|
+
): FactoryBuilder<T>;
|
|
91
153
|
|
|
92
154
|
/**
|
|
93
155
|
* Like {@link merge} but deep — nested plain objects are merged key by key
|
|
@@ -98,8 +160,12 @@ export interface FactoryBuilder<T extends BaseEntity> {
|
|
|
98
160
|
overrides: Partial<Record<string, unknown>>,
|
|
99
161
|
): FactoryBuilder<T>;
|
|
100
162
|
|
|
101
|
-
/**
|
|
102
|
-
|
|
163
|
+
/**
|
|
164
|
+
* Declare a named variation. The callback receives the built model INSTANCE
|
|
165
|
+
* and the runtime context (Adonis Lucid `state`), e.g.
|
|
166
|
+
* `.state('admin', (user) => { user.role = 'admin' })`.
|
|
167
|
+
*/
|
|
168
|
+
state(name: string, fn: StateFn<T>): FactoryBuilder<T>;
|
|
103
169
|
|
|
104
170
|
/**
|
|
105
171
|
* Activate one or more declared states for the NEXT build. Multiple applies
|
|
@@ -135,11 +201,17 @@ export interface FactoryBuilder<T extends BaseEntity> {
|
|
|
135
201
|
/** Create and persist multiple entities (fires hooks per row). */
|
|
136
202
|
createMany(count: number, db?: DatabaseConnection): Promise<T[]>;
|
|
137
203
|
|
|
138
|
-
/**
|
|
139
|
-
|
|
204
|
+
/**
|
|
205
|
+
* Build an entity INSTANCE without persisting it (Adonis Lucid `make`). The
|
|
206
|
+
* instance has NO primary key and `$isPersisted === false` — use it to
|
|
207
|
+
* exercise model logic (computed props, validation) with no DB round trip.
|
|
208
|
+
* For an instance that looks persisted (stub id, `$isPersisted === true`),
|
|
209
|
+
* use {@link makeStubbed}.
|
|
210
|
+
*/
|
|
211
|
+
make(): T;
|
|
140
212
|
|
|
141
|
-
/** Build
|
|
142
|
-
makeMany(count: number):
|
|
213
|
+
/** Build many un-persisted instances (Adonis Lucid `makeMany`). */
|
|
214
|
+
makeMany(count: number): T[];
|
|
143
215
|
|
|
144
216
|
/**
|
|
145
217
|
* Build an entity INSTANCE without persisting it (Lucid's `makeStubbed`).
|
|
@@ -162,12 +234,10 @@ export interface FactoryBuilder<T extends BaseEntity> {
|
|
|
162
234
|
/**
|
|
163
235
|
* Queue related rows to create together with the next `create`/`createMany`
|
|
164
236
|
* (Adonis Lucid `.with`). `count` defaults to 1 (ignored past 1 for hasOne).
|
|
165
|
-
* The callback receives the related factory to customize it
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* callback's factory is not itself applied. `.with()` runs on persistence
|
|
170
|
-
* only — `make`/`makeStubbed` ignore it.
|
|
237
|
+
* The callback receives the related factory to customize it — `merge`/`apply`,
|
|
238
|
+
* `.pivotAttributes()` for m2m pivot columns, and its own nested `.with()`
|
|
239
|
+
* (arbitrarily deep, Adonis Lucid nested factories). `.with()` runs on
|
|
240
|
+
* persistence only — `make`/`makeStubbed` ignore it.
|
|
171
241
|
*/
|
|
172
242
|
with(
|
|
173
243
|
name: string,
|
|
@@ -176,13 +246,76 @@ export interface FactoryBuilder<T extends BaseEntity> {
|
|
|
176
246
|
): FactoryBuilder<T>;
|
|
177
247
|
|
|
178
248
|
/**
|
|
179
|
-
* Register a callback that runs on the built entity INSTANCE
|
|
180
|
-
*
|
|
181
|
-
* instance-producing
|
|
182
|
-
* `
|
|
183
|
-
*
|
|
249
|
+
* Register a callback that runs on the built entity INSTANCE (Adonis Lucid
|
|
250
|
+
* `.tap`). Receives the model, the runtime {@link FactoryContext}, and this
|
|
251
|
+
* factory builder. Multiple taps run in order, on every instance-producing
|
|
252
|
+
* path (`make`/`makeMany`/`create`/`createMany`/`makeStubbed*`); reset after
|
|
253
|
+
* consumption.
|
|
254
|
+
*/
|
|
255
|
+
tap(
|
|
256
|
+
fn: (model: T, ctx: FactoryContext, builder: FactoryBuilder<T>) => void,
|
|
257
|
+
): FactoryBuilder<T>;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Replace the default `new Model()` instantiation (Adonis Lucid `.newUp`).
|
|
261
|
+
* The callback receives the resolved attributes and the runtime context and
|
|
262
|
+
* returns the model instance to use for every subsequent build.
|
|
263
|
+
*/
|
|
264
|
+
newUp(
|
|
265
|
+
fn: (attributes: Record<string, unknown>, ctx: FactoryContext) => T,
|
|
266
|
+
): FactoryBuilder<T>;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Set pivot columns for the NEXT many-to-many `.with()` link (Adonis Lucid
|
|
270
|
+
* `.pivotAttributes`). Called on the RELATED factory inside a `.with()`
|
|
271
|
+
* callback; the values are written on the pivot row alongside the link. A
|
|
272
|
+
* single object applies to every linked row; pass an ARRAY for different
|
|
273
|
+
* values per row (its length should match the related-row count).
|
|
274
|
+
*/
|
|
275
|
+
pivotAttributes(
|
|
276
|
+
attrs: Record<string, unknown> | Array<Record<string, unknown>>,
|
|
277
|
+
): FactoryBuilder<T>;
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Bind the connection/transaction for subsequent `create`/`createMany` via an
|
|
281
|
+
* options object (Adonis Lucid `.query({ client }) / .query({ connection })`).
|
|
282
|
+
* Sugar over {@link client} / {@link connection}; `client` wins over `connection`.
|
|
283
|
+
*/
|
|
284
|
+
query(options: {
|
|
285
|
+
client?: DatabaseConnection;
|
|
286
|
+
connection?: string;
|
|
287
|
+
}): FactoryBuilder<T>;
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Register a lifecycle hook that runs BEFORE the given event (Adonis Lucid
|
|
291
|
+
* factory `before`). `create` fires before the INSERT; `makeStubbed` fires
|
|
292
|
+
* before the stub is finalised (so it can assign the primary key). The
|
|
293
|
+
* callback receives the factory and the model instance. Persistent (declared
|
|
294
|
+
* once, applies to every build).
|
|
295
|
+
*/
|
|
296
|
+
before(
|
|
297
|
+
event: "create" | "makeStubbed",
|
|
298
|
+
callback: (
|
|
299
|
+
factory: FactoryBuilder<T>,
|
|
300
|
+
model: T,
|
|
301
|
+
ctx: FactoryContext,
|
|
302
|
+
) => void,
|
|
303
|
+
): FactoryBuilder<T>;
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Register a lifecycle hook that runs AFTER the given event (Adonis Lucid
|
|
307
|
+
* factory `after`). `create` fires after the INSERT; `makeStubbed` after the
|
|
308
|
+
* stub is built. `make` fires after an un-persisted `make`/`makeMany` instance
|
|
309
|
+
* is built and tapped (Adonis Lucid `after('make')`). Persistent.
|
|
184
310
|
*/
|
|
185
|
-
|
|
311
|
+
after(
|
|
312
|
+
event: "make" | "create" | "makeStubbed",
|
|
313
|
+
callback: (
|
|
314
|
+
factory: FactoryBuilder<T>,
|
|
315
|
+
model: T,
|
|
316
|
+
ctx: FactoryContext,
|
|
317
|
+
) => void,
|
|
318
|
+
): FactoryBuilder<T>;
|
|
186
319
|
}
|
|
187
320
|
|
|
188
321
|
/**
|
|
@@ -193,14 +326,47 @@ export interface FactoryBuilder<T extends BaseEntity> {
|
|
|
193
326
|
* name: 'Test User',
|
|
194
327
|
* }))
|
|
195
328
|
*/
|
|
329
|
+
/**
|
|
330
|
+
* Adonis Lucid `Factory.define(Model, callback).build()` entry point. `define`
|
|
331
|
+
* captures the model and its defaults; `build()` returns the usable factory
|
|
332
|
+
* builder. Equivalent to the one-call {@link factory} shorthand, which stays.
|
|
333
|
+
*
|
|
334
|
+
* const UserFactory = Factory.define(User, ({ faker }) => ({
|
|
335
|
+
* email: faker.internet.email(),
|
|
336
|
+
* })).build()
|
|
337
|
+
*/
|
|
338
|
+
export const Factory = {
|
|
339
|
+
define<T extends BaseEntity>(
|
|
340
|
+
entityClass: EntityConstructor<T>,
|
|
341
|
+
defaults: DefaultsFn,
|
|
342
|
+
): { build(): FactoryBuilder<T> } {
|
|
343
|
+
return { build: () => factory(entityClass, defaults) };
|
|
344
|
+
},
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Override the global stub-id generator (Adonis Lucid `Factory.stubId`). Use
|
|
348
|
+
* when your models have non-integer primary keys — the callback receives the
|
|
349
|
+
* running counter and the instance and returns the id to assign. Pass
|
|
350
|
+
* `null`/nothing to restore the default incrementing integer.
|
|
351
|
+
*/
|
|
352
|
+
stubId(generator?: ((counter: number, model: BaseEntity) => unknown) | null) {
|
|
353
|
+
globalStubId = generator ?? undefined;
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
|
|
196
357
|
export function factory<T extends BaseEntity>(
|
|
197
358
|
entityClass: EntityConstructor<T>,
|
|
198
359
|
defaults: DefaultsFn,
|
|
199
360
|
): FactoryBuilder<T> {
|
|
200
361
|
// Persistent state — lives across calls.
|
|
201
|
-
const states = new Map<string, StateFn<
|
|
362
|
+
const states = new Map<string, StateFn<T>>();
|
|
202
363
|
// Transient state — resets after every `make`/`create`.
|
|
203
364
|
let pendingOverrides: Partial<Record<string, unknown>> = {};
|
|
365
|
+
// Per-row overrides from `merge([...])`, applied by row index on makeMany/createMany.
|
|
366
|
+
let pendingOverridesList: Array<Partial<Record<string, unknown>>> | undefined;
|
|
367
|
+
// `merge(callback)` mutators, applied on the built INSTANCE (with the resolved
|
|
368
|
+
// attributes) after instantiation, in call order.
|
|
369
|
+
let pendingMergeFns: MergeFn<T>[] = [];
|
|
204
370
|
// Recursive overrides are kept apart from the shallow `pendingOverrides`
|
|
205
371
|
// because they must deep-merge into `defaults()`, which only exists at build
|
|
206
372
|
// time — a shallow spread here would clobber a whole nested object.
|
|
@@ -212,7 +378,22 @@ export function factory<T extends BaseEntity>(
|
|
|
212
378
|
const relations = new Map<string, RelationResolver>();
|
|
213
379
|
let pendingWith: WithRequest[] = [];
|
|
214
380
|
// Transient `.tap()` callbacks run on the built instance before persistence.
|
|
215
|
-
|
|
381
|
+
// Adonis Lucid passes the model, the runtime context, and the factory builder.
|
|
382
|
+
let pendingTap: Array<
|
|
383
|
+
(model: T, ctx: FactoryContext, builder: FactoryBuilder<T>) => void
|
|
384
|
+
> = [];
|
|
385
|
+
// Transient pivot columns for the NEXT m2m `.with()` link (Adonis Lucid
|
|
386
|
+
// `.pivotAttributes()`), read by the parent factory's applyRelations. A single
|
|
387
|
+
// object applies to every linked row; an array sets per-row values.
|
|
388
|
+
let pendingPivot:
|
|
389
|
+
| Record<string, unknown>
|
|
390
|
+
| Array<Record<string, unknown>>
|
|
391
|
+
| undefined;
|
|
392
|
+
// Persistent custom instantiation (Adonis Lucid `.newUp`) — replaces
|
|
393
|
+
// `new Model() + setProp` when set.
|
|
394
|
+
let customNewUp:
|
|
395
|
+
| ((attributes: Record<string, unknown>, ctx: FactoryContext) => T)
|
|
396
|
+
| undefined;
|
|
216
397
|
// Persistent connection bound via `.client()`/`.connection()`, used when
|
|
217
398
|
// create()/createMany() are called without an explicit `db`.
|
|
218
399
|
let boundClient: DatabaseConnection | undefined;
|
|
@@ -228,12 +409,34 @@ export function factory<T extends BaseEntity>(
|
|
|
228
409
|
return conn;
|
|
229
410
|
};
|
|
230
411
|
|
|
412
|
+
// Persistent lifecycle hooks (Adonis Lucid factory before/after), by event.
|
|
413
|
+
type Hook = (
|
|
414
|
+
factory: FactoryBuilder<T>,
|
|
415
|
+
model: T,
|
|
416
|
+
ctx: FactoryContext,
|
|
417
|
+
) => void;
|
|
418
|
+
const beforeHooks: Record<"create" | "makeStubbed", Hook[]> = {
|
|
419
|
+
create: [],
|
|
420
|
+
makeStubbed: [],
|
|
421
|
+
};
|
|
422
|
+
// `make` has no `before` counterpart in Lucid (nothing to gate before an
|
|
423
|
+
// un-persisted build), so it lives on the after-side only.
|
|
424
|
+
const afterHooks: Record<"make" | "create" | "makeStubbed", Hook[]> = {
|
|
425
|
+
make: [],
|
|
426
|
+
create: [],
|
|
427
|
+
makeStubbed: [],
|
|
428
|
+
};
|
|
429
|
+
|
|
231
430
|
// Primary-key property, resolved once, for stub-id assignment. Same fallback
|
|
232
431
|
// as BaseRepository so a model without an explicit `@PrimaryKey` uses `id`.
|
|
233
432
|
const primaryKey = getPrimaryKey(entityClass) ?? "id";
|
|
234
433
|
|
|
235
|
-
/** Hydrate a fresh (not-yet-persisted) instance
|
|
236
|
-
const newInstance = (
|
|
434
|
+
/** Hydrate a fresh (not-yet-persisted) instance — or defer to `.newUp` when set. */
|
|
435
|
+
const newInstance = (
|
|
436
|
+
data: Record<string, unknown>,
|
|
437
|
+
ctx: FactoryContext,
|
|
438
|
+
): T => {
|
|
439
|
+
if (customNewUp) return customNewUp(data, ctx);
|
|
237
440
|
const entity = new entityClass();
|
|
238
441
|
for (const [key, value] of Object.entries(data)) {
|
|
239
442
|
entity.setProp(key, value);
|
|
@@ -241,47 +444,137 @@ export function factory<T extends BaseEntity>(
|
|
|
241
444
|
return entity;
|
|
242
445
|
};
|
|
243
446
|
|
|
447
|
+
/** Run every tap with the Lucid `(model, ctx, builder)` signature. */
|
|
448
|
+
const runTaps = (
|
|
449
|
+
taps: Array<
|
|
450
|
+
(model: T, ctx: FactoryContext, builder: FactoryBuilder<T>) => void
|
|
451
|
+
>,
|
|
452
|
+
entity: T,
|
|
453
|
+
ctx: FactoryContext,
|
|
454
|
+
): void => {
|
|
455
|
+
for (const tap of taps) tap(entity, ctx, builder);
|
|
456
|
+
};
|
|
457
|
+
|
|
244
458
|
/** Build a stubbed instance: hydrate, run taps, give it a stub id if none was
|
|
245
459
|
* supplied, then mark persisted so it mirrors a fetched row. Taps run before
|
|
246
460
|
* `markAsPersisted` so tapped fields land in the clean snapshot. */
|
|
461
|
+
/** Build the runtime context for a build (Adonis Lucid factory `ctx`). */
|
|
462
|
+
const makeCtx = (isStubbed: boolean): FactoryContext => ({
|
|
463
|
+
faker,
|
|
464
|
+
isStubbed,
|
|
465
|
+
$trx: boundClient,
|
|
466
|
+
});
|
|
467
|
+
|
|
247
468
|
const stub = (
|
|
248
469
|
data: Record<string, unknown>,
|
|
249
|
-
taps: Array<
|
|
470
|
+
taps: Array<
|
|
471
|
+
(model: T, ctx: FactoryContext, builder: FactoryBuilder<T>) => void
|
|
472
|
+
>,
|
|
473
|
+
mergeFns: MergeFn<T>[],
|
|
474
|
+
stateNames: string[],
|
|
475
|
+
ctx: FactoryContext,
|
|
250
476
|
): T => {
|
|
251
|
-
const entity = newInstance(data);
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
477
|
+
const entity = newInstance(data, ctx);
|
|
478
|
+
applyMergeFns(entity, data, mergeFns, ctx);
|
|
479
|
+
applyStates(entity, stateNames, ctx);
|
|
480
|
+
runTaps(taps, entity, ctx);
|
|
481
|
+
// before('makeStubbed') runs BEFORE the stub id, so a hook can set the PK.
|
|
482
|
+
for (const hook of beforeHooks.makeStubbed) hook(builder, entity, ctx);
|
|
483
|
+
// Assign a stub id only when neither the data NOR a before hook set the PK.
|
|
484
|
+
// A global `Factory.stubId` override generates it (uuid, etc.) when set.
|
|
485
|
+
if (entity[primaryKey] === undefined) {
|
|
486
|
+
const next = ++stubIdCounter;
|
|
487
|
+
entity.setProp(
|
|
488
|
+
primaryKey,
|
|
489
|
+
globalStubId ? globalStubId(next, entity) : next,
|
|
490
|
+
);
|
|
255
491
|
}
|
|
256
492
|
entity.markAsPersisted();
|
|
493
|
+
for (const hook of afterHooks.makeStubbed) hook(builder, entity, ctx);
|
|
257
494
|
return entity;
|
|
258
495
|
};
|
|
259
496
|
|
|
260
|
-
const buildData = (
|
|
497
|
+
const buildData = (
|
|
498
|
+
ctx: FactoryContext,
|
|
499
|
+
index = 0,
|
|
500
|
+
): Record<string, unknown> => {
|
|
261
501
|
let data: Record<string, unknown> = {
|
|
262
|
-
...defaults(
|
|
502
|
+
...defaults(ctx),
|
|
263
503
|
...pendingOverrides,
|
|
504
|
+
// Per-row array overrides (merge([...])) win over the shared object.
|
|
505
|
+
...(pendingOverridesList?.[index] ?? {}),
|
|
264
506
|
};
|
|
265
507
|
// Deep overrides layer on top of the shallow one so nested defaults survive.
|
|
266
508
|
if (Object.keys(pendingRecursive).length > 0) {
|
|
267
509
|
data = deepMerge(data, pendingRecursive);
|
|
268
510
|
}
|
|
269
|
-
|
|
511
|
+
return data;
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Run `merge(callback)` mutators on the built INSTANCE (Adonis Lucid passes the
|
|
516
|
+
* model, the resolved attributes, and the context). Runs before states/taps.
|
|
517
|
+
*/
|
|
518
|
+
const applyMergeFns = (
|
|
519
|
+
entity: T,
|
|
520
|
+
attributes: Record<string, unknown>,
|
|
521
|
+
mergeFns: MergeFn<T>[],
|
|
522
|
+
ctx: FactoryContext,
|
|
523
|
+
): void => {
|
|
524
|
+
for (const fn of mergeFns) fn(entity, attributes, ctx);
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Run the named states on the built INSTANCE (Adonis Lucid). Separated from
|
|
529
|
+
* {@link buildData} because states mutate the model, not the raw attributes.
|
|
530
|
+
*/
|
|
531
|
+
const applyStates = (
|
|
532
|
+
entity: T,
|
|
533
|
+
stateNames: string[],
|
|
534
|
+
ctx: FactoryContext,
|
|
535
|
+
): void => {
|
|
536
|
+
for (const name of stateNames) {
|
|
270
537
|
const fn = states.get(name);
|
|
271
|
-
if (!fn)
|
|
538
|
+
if (!fn) {
|
|
272
539
|
throw new Error(
|
|
273
540
|
`Factory state '${name}' is not defined on ${entityClass.name}Factory`,
|
|
274
541
|
);
|
|
275
|
-
|
|
542
|
+
}
|
|
543
|
+
fn(entity, ctx);
|
|
276
544
|
}
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
/** Build one raw row and clear transient state — the persistence paths
|
|
548
|
+
* (`create`/`createMany`) need the plain object, not an instance. */
|
|
549
|
+
const consumeData = (ctx: FactoryContext): Record<string, unknown> => {
|
|
550
|
+
const data = buildData(ctx);
|
|
551
|
+
resetPending();
|
|
277
552
|
return data;
|
|
278
553
|
};
|
|
279
554
|
|
|
555
|
+
/** Build `count` raw rows (distinct faker/Date values per row), then reset. */
|
|
556
|
+
const consumeDataMany = (
|
|
557
|
+
count: number,
|
|
558
|
+
ctx: FactoryContext,
|
|
559
|
+
): Record<string, unknown>[] => {
|
|
560
|
+
const rows: Record<string, unknown>[] = [];
|
|
561
|
+
for (let i = 0; i < count; i++) rows.push(buildData(ctx, i));
|
|
562
|
+
resetPending();
|
|
563
|
+
return rows;
|
|
564
|
+
};
|
|
565
|
+
|
|
280
566
|
const resetPending = (): void => {
|
|
281
567
|
pendingOverrides = {};
|
|
568
|
+
pendingOverridesList = undefined;
|
|
569
|
+
pendingMergeFns = [];
|
|
282
570
|
pendingRecursive = {};
|
|
283
571
|
pendingStates = [];
|
|
284
572
|
pendingTap = [];
|
|
573
|
+
pendingPivot = undefined;
|
|
574
|
+
// Also clear queued relations, so `.with(...).make()` — which ignores
|
|
575
|
+
// relations — cannot leak them into the NEXT create() (create/createMany
|
|
576
|
+
// capture the queue before make() runs, so they are unaffected).
|
|
577
|
+
pendingWith = [];
|
|
285
578
|
};
|
|
286
579
|
|
|
287
580
|
/**
|
|
@@ -293,26 +586,77 @@ export function factory<T extends BaseEntity>(
|
|
|
293
586
|
const persist = async (
|
|
294
587
|
repo: BaseRepository<T>,
|
|
295
588
|
data: Record<string, unknown>,
|
|
296
|
-
taps: Array<
|
|
589
|
+
taps: Array<
|
|
590
|
+
(model: T, ctx: FactoryContext, builder: FactoryBuilder<T>) => void
|
|
591
|
+
>,
|
|
592
|
+
mergeFns: MergeFn<T>[],
|
|
593
|
+
stateNames: string[],
|
|
594
|
+
ctx: FactoryContext,
|
|
297
595
|
): Promise<T> => {
|
|
298
|
-
if (
|
|
299
|
-
|
|
300
|
-
|
|
596
|
+
if (
|
|
597
|
+
taps.length === 0 &&
|
|
598
|
+
mergeFns.length === 0 &&
|
|
599
|
+
stateNames.length === 0 &&
|
|
600
|
+
beforeHooks.create.length === 0 &&
|
|
601
|
+
afterHooks.create.length === 0 &&
|
|
602
|
+
!customNewUp
|
|
603
|
+
) {
|
|
604
|
+
return repo.create(data);
|
|
605
|
+
}
|
|
606
|
+
const entity = newInstance(data, ctx);
|
|
607
|
+
applyMergeFns(entity, data, mergeFns, ctx);
|
|
608
|
+
applyStates(entity, stateNames, ctx);
|
|
609
|
+
runTaps(taps, entity, ctx);
|
|
610
|
+
for (const hook of beforeHooks.create) hook(builder, entity, ctx);
|
|
301
611
|
await repo.save(entity);
|
|
612
|
+
for (const hook of afterHooks.create) hook(builder, entity, ctx);
|
|
302
613
|
return entity;
|
|
303
614
|
};
|
|
304
615
|
|
|
616
|
+
/**
|
|
617
|
+
* Persist a parent and its queued `.with()` relations. With relations, the
|
|
618
|
+
* whole graph runs in a managed transaction (Adonis Lucid: if a related write
|
|
619
|
+
* fails the parent insert rolls back too); the parent is created through a
|
|
620
|
+
* `useTransaction`-bound repo so its `related()` proxies also use the trx.
|
|
621
|
+
* With no relations there's nothing to make atomic — a single insert already
|
|
622
|
+
* is — so it stays on the plain connection.
|
|
623
|
+
*/
|
|
624
|
+
const persistWithRelations = async (
|
|
625
|
+
conn: DatabaseConnection,
|
|
626
|
+
withReqs: WithRequest[],
|
|
627
|
+
recursive: Record<string, unknown>,
|
|
628
|
+
build: (repo: BaseRepository<T>, ctx: FactoryContext) => Promise<T>,
|
|
629
|
+
): Promise<T> => {
|
|
630
|
+
if (withReqs.length === 0) {
|
|
631
|
+
// ctx.$trx is the connection actually used to persist (an explicit
|
|
632
|
+
// create(db) client, or the bound one) — not just the bound client.
|
|
633
|
+
return build(new BaseRepository(entityClass, conn), {
|
|
634
|
+
faker,
|
|
635
|
+
isStubbed: false,
|
|
636
|
+
$trx: conn,
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
return transaction(conn, async (trx) => {
|
|
640
|
+
const repo = new BaseRepository(entityClass, conn).useTransaction(trx);
|
|
641
|
+
const entity = await build(repo, { faker, isStubbed: false, $trx: trx });
|
|
642
|
+
await applyRelations(entity, withReqs, trx, recursive);
|
|
643
|
+
return entity;
|
|
644
|
+
});
|
|
645
|
+
};
|
|
646
|
+
|
|
305
647
|
/**
|
|
306
648
|
* Persist the queued `.with()` relations for one just-created parent, routing
|
|
307
649
|
* through the parent's relation proxy so the FK / pivot is wired by the
|
|
308
650
|
* already-tested relation-write code (not re-derived here).
|
|
309
651
|
*/
|
|
310
652
|
const applyRelations = async (
|
|
311
|
-
parent:
|
|
653
|
+
parent: BaseEntity,
|
|
312
654
|
reqs: WithRequest[],
|
|
313
655
|
db: DatabaseConnection,
|
|
656
|
+
recursive?: Record<string, unknown>,
|
|
314
657
|
): Promise<void> => {
|
|
315
658
|
const meta = getRelationMetadata(entityClass);
|
|
659
|
+
const hasRecursive = recursive && Object.keys(recursive).length > 0;
|
|
316
660
|
for (const req of reqs) {
|
|
317
661
|
const relMeta = meta.find((r) => r.propertyKey === req.name);
|
|
318
662
|
if (!relMeta) {
|
|
@@ -327,23 +671,60 @@ export function factory<T extends BaseEntity>(
|
|
|
327
671
|
);
|
|
328
672
|
}
|
|
329
673
|
const childFactory = resolver();
|
|
674
|
+
// mergeRecursive cascades the SAME overrides onto every related factory
|
|
675
|
+
// (Adonis Lucid) — applied before the callback so an explicit merge in
|
|
676
|
+
// the callback still wins.
|
|
677
|
+
if (hasRecursive && recursive) childFactory.mergeRecursive(recursive);
|
|
330
678
|
if (req.callback) req.callback(childFactory);
|
|
679
|
+
const childInternals = factoryInternals.get(childFactory);
|
|
331
680
|
|
|
332
681
|
const proxy = parent.related(req.name);
|
|
682
|
+
if (proxy.type === "belongsTo") {
|
|
683
|
+
// FK lives on the parent: create() runs the owner's OWN with-graph,
|
|
684
|
+
// then associate re-saves the parent with the FK set.
|
|
685
|
+
const owner = await childFactory.create(db);
|
|
686
|
+
await proxy.associate(owner);
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// The proxy-persisted branches bypass the child factory's create() path,
|
|
691
|
+
// so its queued nested `.with()` must be captured before make() clears it,
|
|
692
|
+
// then recursed onto each persisted child (Adonis Lucid nested factories:
|
|
693
|
+
// `post.with('comments', 5)` inside the callback).
|
|
694
|
+
const nestedWith = childInternals?.consumeWith() ?? [];
|
|
695
|
+
// Pivot columns from a `.pivotAttributes()` in the callback — captured
|
|
696
|
+
// now, before make() clears the child's transient state.
|
|
697
|
+
const nestedPivot = childInternals?.consumePivot();
|
|
698
|
+
const recurse = async (children: BaseEntity[]): Promise<void> => {
|
|
699
|
+
if (nestedWith.length === 0 || !childInternals) return;
|
|
700
|
+
for (const child of children) {
|
|
701
|
+
// Cascade the recursive overrides further down the graph.
|
|
702
|
+
await childInternals.applyRelations(child, nestedWith, db, recursive);
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
|
|
333
706
|
if (proxy.type === "hasMany") {
|
|
334
|
-
await proxy.createMany(childFactory.makeMany(req.count));
|
|
707
|
+
await recurse(await proxy.createMany(childFactory.makeMany(req.count)));
|
|
335
708
|
} else if (proxy.type === "hasOne") {
|
|
336
|
-
await proxy.create(childFactory.make());
|
|
709
|
+
await recurse([await proxy.create(childFactory.make())]);
|
|
337
710
|
} else if (proxy.type === "manyToMany") {
|
|
338
|
-
// The m2m proxy's create() inserts the related row AND the pivot link
|
|
339
|
-
|
|
340
|
-
|
|
711
|
+
// The m2m proxy's create() inserts the related row AND the pivot link,
|
|
712
|
+
// with the `.pivotAttributes()` columns on the pivot row. An array of
|
|
713
|
+
// pivot attrs applies per-row (`pivot[i]`); a single object, to all.
|
|
714
|
+
const rows = childFactory.makeMany(req.count);
|
|
715
|
+
// Adonis Lucid: an array's length must match the related-row count —
|
|
716
|
+
// reject a mismatch rather than silently leave rows without pivot data.
|
|
717
|
+
if (Array.isArray(nestedPivot) && nestedPivot.length !== rows.length) {
|
|
718
|
+
throw new Error(
|
|
719
|
+
`Factory .with('${req.name}'): pivotAttributes array length (${nestedPivot.length}) must match the related-row count (${rows.length}).`,
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
for (let i = 0; i < rows.length; i++) {
|
|
723
|
+
const pivot = Array.isArray(nestedPivot)
|
|
724
|
+
? nestedPivot[i]
|
|
725
|
+
: nestedPivot;
|
|
726
|
+
await recurse([await proxy.create(rows[i], pivot)]);
|
|
341
727
|
}
|
|
342
|
-
} else if (proxy.type === "belongsTo") {
|
|
343
|
-
// FK lives on the parent: create the owner, then associate re-saves
|
|
344
|
-
// the parent with the FK set.
|
|
345
|
-
const owner = await childFactory.create(db);
|
|
346
|
-
await proxy.associate(owner);
|
|
347
728
|
} else {
|
|
348
729
|
throw new Error(
|
|
349
730
|
`Factory .with('${req.name}'): '${relMeta.type}' relations are not supported`,
|
|
@@ -354,7 +735,13 @@ export function factory<T extends BaseEntity>(
|
|
|
354
735
|
|
|
355
736
|
const builder: FactoryBuilder<T> = {
|
|
356
737
|
merge(overrides) {
|
|
357
|
-
|
|
738
|
+
if (typeof overrides === "function") {
|
|
739
|
+
pendingMergeFns.push(overrides);
|
|
740
|
+
} else if (Array.isArray(overrides)) {
|
|
741
|
+
pendingOverridesList = overrides;
|
|
742
|
+
} else {
|
|
743
|
+
pendingOverrides = { ...pendingOverrides, ...overrides };
|
|
744
|
+
}
|
|
358
745
|
return builder;
|
|
359
746
|
},
|
|
360
747
|
|
|
@@ -388,6 +775,31 @@ export function factory<T extends BaseEntity>(
|
|
|
388
775
|
return builder;
|
|
389
776
|
},
|
|
390
777
|
|
|
778
|
+
newUp(fn) {
|
|
779
|
+
customNewUp = fn;
|
|
780
|
+
return builder;
|
|
781
|
+
},
|
|
782
|
+
|
|
783
|
+
pivotAttributes(attrs) {
|
|
784
|
+
// An array (per-row values) replaces wholesale; objects merge (repeated
|
|
785
|
+
// `.pivotAttributes({...})` accumulate, matching the single-object case).
|
|
786
|
+
pendingPivot =
|
|
787
|
+
Array.isArray(attrs) || Array.isArray(pendingPivot)
|
|
788
|
+
? attrs
|
|
789
|
+
: { ...pendingPivot, ...attrs };
|
|
790
|
+
return builder;
|
|
791
|
+
},
|
|
792
|
+
|
|
793
|
+
before(event, callback) {
|
|
794
|
+
beforeHooks[event].push(callback);
|
|
795
|
+
return builder;
|
|
796
|
+
},
|
|
797
|
+
|
|
798
|
+
after(event, callback) {
|
|
799
|
+
afterHooks[event].push(callback);
|
|
800
|
+
return builder;
|
|
801
|
+
},
|
|
802
|
+
|
|
391
803
|
client(connection) {
|
|
392
804
|
boundClient = connection;
|
|
393
805
|
return builder;
|
|
@@ -404,19 +816,54 @@ export function factory<T extends BaseEntity>(
|
|
|
404
816
|
return builder;
|
|
405
817
|
},
|
|
406
818
|
|
|
819
|
+
query(options) {
|
|
820
|
+
if (options.client) {
|
|
821
|
+
boundClient = options.client;
|
|
822
|
+
} else if (options.connection) {
|
|
823
|
+
const conn = getConnection(options.connection);
|
|
824
|
+
if (!conn) {
|
|
825
|
+
throw new Error(
|
|
826
|
+
`Factory ${entityClass.name}: no connection registered under '${options.connection}'.`,
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
boundClient = conn;
|
|
830
|
+
}
|
|
831
|
+
return builder;
|
|
832
|
+
},
|
|
833
|
+
|
|
407
834
|
make() {
|
|
408
|
-
|
|
835
|
+
// Lucid `make`: an UN-persisted instance (no PK, `$isPersisted` false).
|
|
836
|
+
// Taps run on it, like the other instance-producing paths.
|
|
837
|
+
const taps = pendingTap;
|
|
838
|
+
const mergeFns = pendingMergeFns;
|
|
839
|
+
const stateNames = pendingStates;
|
|
840
|
+
const ctx = makeCtx(false);
|
|
841
|
+
const data = buildData(ctx);
|
|
842
|
+
const entity = newInstance(data, ctx);
|
|
843
|
+
applyMergeFns(entity, data, mergeFns, ctx);
|
|
844
|
+
applyStates(entity, stateNames, ctx);
|
|
845
|
+
runTaps(taps, entity, ctx);
|
|
846
|
+
for (const hook of afterHooks.make) hook(builder, entity, ctx);
|
|
409
847
|
resetPending();
|
|
410
|
-
return
|
|
848
|
+
return entity;
|
|
411
849
|
},
|
|
412
850
|
|
|
413
851
|
makeMany(count) {
|
|
414
852
|
// Re-evaluate defaults for each row so `Date.now()` / faker generate
|
|
415
|
-
// distinct values
|
|
416
|
-
|
|
417
|
-
const
|
|
853
|
+
// distinct values, each hydrated into its own un-persisted instance.
|
|
854
|
+
const taps = pendingTap;
|
|
855
|
+
const mergeFns = pendingMergeFns;
|
|
856
|
+
const stateNames = pendingStates;
|
|
857
|
+
const ctx = makeCtx(false);
|
|
858
|
+
const rows: T[] = [];
|
|
418
859
|
for (let i = 0; i < count; i++) {
|
|
419
|
-
|
|
860
|
+
const data = buildData(ctx, i);
|
|
861
|
+
const entity = newInstance(data, ctx);
|
|
862
|
+
applyMergeFns(entity, data, mergeFns, ctx);
|
|
863
|
+
applyStates(entity, stateNames, ctx);
|
|
864
|
+
runTaps(taps, entity, ctx);
|
|
865
|
+
for (const hook of afterHooks.make) hook(builder, entity, ctx);
|
|
866
|
+
rows.push(entity);
|
|
420
867
|
}
|
|
421
868
|
resetPending();
|
|
422
869
|
return rows;
|
|
@@ -424,8 +871,10 @@ export function factory<T extends BaseEntity>(
|
|
|
424
871
|
|
|
425
872
|
makeStubbed() {
|
|
426
873
|
const taps = pendingTap;
|
|
427
|
-
const
|
|
428
|
-
const
|
|
874
|
+
const mergeFns = pendingMergeFns;
|
|
875
|
+
const stateNames = pendingStates;
|
|
876
|
+
const ctx = makeCtx(true);
|
|
877
|
+
const entity = stub(buildData(ctx), taps, mergeFns, stateNames, ctx);
|
|
429
878
|
resetPending();
|
|
430
879
|
return entity;
|
|
431
880
|
},
|
|
@@ -434,45 +883,76 @@ export function factory<T extends BaseEntity>(
|
|
|
434
883
|
// Re-evaluate defaults per row (distinct Date.now()/faker values), same
|
|
435
884
|
// as makeMany; each row then gets its own stub id + taps via `stub`.
|
|
436
885
|
const taps = pendingTap;
|
|
886
|
+
const mergeFns = pendingMergeFns;
|
|
887
|
+
const stateNames = pendingStates;
|
|
888
|
+
const ctx = makeCtx(true);
|
|
437
889
|
const rows: T[] = [];
|
|
438
890
|
for (let i = 0; i < count; i++) {
|
|
439
|
-
rows.push(stub(buildData(), taps));
|
|
891
|
+
rows.push(stub(buildData(ctx, i), taps, mergeFns, stateNames, ctx));
|
|
440
892
|
}
|
|
441
893
|
resetPending();
|
|
442
894
|
return rows;
|
|
443
895
|
},
|
|
444
896
|
|
|
445
897
|
async create(db) {
|
|
446
|
-
|
|
447
|
-
//
|
|
448
|
-
//
|
|
898
|
+
// Capture + clear the relation/tap queues, then consume the raw data
|
|
899
|
+
// (which resets the rest of the pending state) — BEFORE resolveConnection,
|
|
900
|
+
// so a missing connection can't throw with pending overrides/states/with/
|
|
901
|
+
// tap still dirty and leak them into the next create().
|
|
449
902
|
const withReqs = pendingWith;
|
|
450
903
|
pendingWith = [];
|
|
451
904
|
const taps = pendingTap;
|
|
452
|
-
const
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
905
|
+
const mergeFns = pendingMergeFns;
|
|
906
|
+
const stateNames = pendingStates;
|
|
907
|
+
// Snapshot the recursive overrides BEFORE consumeData resets them — they
|
|
908
|
+
// cascade onto every related factory (Adonis Lucid `mergeRecursive`).
|
|
909
|
+
const recursive = { ...pendingRecursive };
|
|
910
|
+
const ctx = makeCtx(false);
|
|
911
|
+
const data = consumeData(ctx);
|
|
912
|
+
const conn = resolveConnection(db);
|
|
913
|
+
return persistWithRelations(conn, withReqs, recursive, (repo, txCtx) =>
|
|
914
|
+
persist(repo, data, taps, mergeFns, stateNames, txCtx),
|
|
915
|
+
);
|
|
457
916
|
},
|
|
458
917
|
|
|
459
918
|
async createMany(count, db) {
|
|
460
|
-
const conn = resolveConnection(db);
|
|
461
919
|
const withReqs = pendingWith;
|
|
462
920
|
pendingWith = [];
|
|
463
921
|
const taps = pendingTap;
|
|
464
|
-
const
|
|
465
|
-
const
|
|
922
|
+
const mergeFns = pendingMergeFns;
|
|
923
|
+
const stateNames = pendingStates;
|
|
924
|
+
const recursive = { ...pendingRecursive };
|
|
925
|
+
const ctx = makeCtx(false);
|
|
926
|
+
const rows = consumeDataMany(count, ctx);
|
|
927
|
+
const conn = resolveConnection(db);
|
|
466
928
|
const created: T[] = [];
|
|
467
929
|
for (const data of rows) {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
930
|
+
// Each created parent + its relations are atomic (Lucid semantics).
|
|
931
|
+
created.push(
|
|
932
|
+
await persistWithRelations(conn, withReqs, recursive, (repo, txCtx) =>
|
|
933
|
+
persist(repo, data, taps, mergeFns, stateNames, txCtx),
|
|
934
|
+
),
|
|
935
|
+
);
|
|
472
936
|
}
|
|
473
937
|
return created;
|
|
474
938
|
},
|
|
475
939
|
};
|
|
476
940
|
|
|
941
|
+
// Register this builder's internals so a PARENT factory can drive nested
|
|
942
|
+
// `.with()` on it (kept off the public type, keyed by the builder object).
|
|
943
|
+
factoryInternals.set(builder, {
|
|
944
|
+
consumeWith() {
|
|
945
|
+
const reqs = pendingWith;
|
|
946
|
+
pendingWith = [];
|
|
947
|
+
return reqs;
|
|
948
|
+
},
|
|
949
|
+
consumePivot() {
|
|
950
|
+
const pivot = pendingPivot;
|
|
951
|
+
pendingPivot = undefined;
|
|
952
|
+
return pivot;
|
|
953
|
+
},
|
|
954
|
+
applyRelations,
|
|
955
|
+
});
|
|
956
|
+
|
|
477
957
|
return builder;
|
|
478
958
|
}
|