@anil-labs/factory 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +54 -0
- package/LICENSE +21 -0
- package/README.md +371 -0
- package/dist/builders/index.d.cts +40 -0
- package/dist/builders/index.d.ts +40 -0
- package/dist/chunks/faker-BOtDMmjd.cjs +1430 -0
- package/dist/chunks/faker-BOtDMmjd.cjs.map +1 -0
- package/dist/chunks/faker-BlEhpR26.mjs +1287 -0
- package/dist/chunks/faker-BlEhpR26.mjs.map +1 -0
- package/dist/chunks/persist-DcARfeC-.cjs +134 -0
- package/dist/chunks/persist-DcARfeC-.cjs.map +1 -0
- package/dist/chunks/persist-ZGX3NWMF.mjs +117 -0
- package/dist/chunks/persist-ZGX3NWMF.mjs.map +1 -0
- package/dist/core/collection.d.cts +41 -0
- package/dist/core/collection.d.ts +41 -0
- package/dist/core/factory.d.cts +115 -0
- package/dist/core/factory.d.ts +115 -0
- package/dist/core/index.d.cts +6 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/registry.d.cts +20 -0
- package/dist/core/registry.d.ts +20 -0
- package/dist/core/sequence.d.cts +36 -0
- package/dist/core/sequence.d.ts +36 -0
- package/dist/core/types.d.cts +47 -0
- package/dist/core/types.d.ts +47 -0
- package/dist/faker/color.d.cts +22 -0
- package/dist/faker/color.d.ts +22 -0
- package/dist/faker/commerce.d.cts +21 -0
- package/dist/faker/commerce.d.ts +21 -0
- package/dist/faker/company.d.cts +20 -0
- package/dist/faker/company.d.ts +20 -0
- package/dist/faker/datatype.d.cts +16 -0
- package/dist/faker/datatype.d.ts +16 -0
- package/dist/faker/date.d.cts +29 -0
- package/dist/faker/date.d.ts +29 -0
- package/dist/faker/faker.d.cts +82 -0
- package/dist/faker/faker.d.ts +82 -0
- package/dist/faker/finance.d.cts +25 -0
- package/dist/faker/finance.d.ts +25 -0
- package/dist/faker/helpers.d.cts +52 -0
- package/dist/faker/helpers.d.ts +52 -0
- package/dist/faker/image.d.cts +22 -0
- package/dist/faker/image.d.ts +22 -0
- package/dist/faker/index.d.cts +21 -0
- package/dist/faker/index.d.ts +21 -0
- package/dist/faker/internet.d.cts +33 -0
- package/dist/faker/internet.d.ts +33 -0
- package/dist/faker/locale.d.cts +26 -0
- package/dist/faker/locale.d.ts +26 -0
- package/dist/faker/location.d.cts +30 -0
- package/dist/faker/location.d.ts +30 -0
- package/dist/faker/lorem.d.cts +26 -0
- package/dist/faker/lorem.d.ts +26 -0
- package/dist/faker/number.d.cts +31 -0
- package/dist/faker/number.d.ts +31 -0
- package/dist/faker/person.d.cts +29 -0
- package/dist/faker/person.d.ts +29 -0
- package/dist/faker/regex.d.cts +19 -0
- package/dist/faker/regex.d.ts +19 -0
- package/dist/faker/string.d.cts +33 -0
- package/dist/faker/string.d.ts +33 -0
- package/dist/faker/system.d.cts +29 -0
- package/dist/faker/system.d.ts +29 -0
- package/dist/faker.cjs +26 -0
- package/dist/faker.mjs +3 -0
- package/dist/index.cjs +635 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +37 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.mjs +596 -0
- package/dist/index.mjs.map +1 -0
- package/dist/locales/en.cjs +351 -0
- package/dist/locales/en.cjs.map +1 -0
- package/dist/locales/en.d.cts +11 -0
- package/dist/locales/en.d.ts +11 -0
- package/dist/locales/en.mjs +350 -0
- package/dist/locales/en.mjs.map +1 -0
- package/dist/locales/types.d.cts +30 -0
- package/dist/locales/types.d.ts +30 -0
- package/dist/persist/console.d.cts +15 -0
- package/dist/persist/console.d.ts +15 -0
- package/dist/persist/http.d.cts +42 -0
- package/dist/persist/http.d.ts +42 -0
- package/dist/persist/index.d.cts +5 -0
- package/dist/persist/index.d.ts +5 -0
- package/dist/persist/memory.d.cts +26 -0
- package/dist/persist/memory.d.ts +26 -0
- package/dist/persist.cjs +5 -0
- package/dist/persist.mjs +2 -0
- package/dist/prng/index.d.cts +5 -0
- package/dist/prng/index.d.ts +5 -0
- package/dist/prng/mulberry32.d.cts +19 -0
- package/dist/prng/mulberry32.d.ts +19 -0
- package/dist/prng/types.d.cts +23 -0
- package/dist/prng/types.d.ts +23 -0
- package/dist/snapshot/index.d.cts +16 -0
- package/dist/snapshot/index.d.ts +16 -0
- package/package.json +136 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_faker = require("./chunks/faker-BOtDMmjd.cjs");
|
|
3
|
+
const require_locales_en = require("./locales/en.cjs");
|
|
4
|
+
const require_persist = require("./chunks/persist-DcARfeC-.cjs");
|
|
5
|
+
//#region src/core/collection.ts
|
|
6
|
+
/**
|
|
7
|
+
* Immutable iterable wrapper around an array, with Laravel-style helpers.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* const users = new Collection(userArray)
|
|
12
|
+
* users.where(u => u.active).count()
|
|
13
|
+
* users.pluck('email').toArray()
|
|
14
|
+
* for (const u of users) console.log(u.name)
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
var Collection = class Collection {
|
|
18
|
+
/** Underlying frozen items. */
|
|
19
|
+
items;
|
|
20
|
+
constructor(items = []) {
|
|
21
|
+
this.items = Object.freeze([...items]);
|
|
22
|
+
}
|
|
23
|
+
/** Number of items. */
|
|
24
|
+
count() {
|
|
25
|
+
return this.items.length;
|
|
26
|
+
}
|
|
27
|
+
/** True when there are no items. */
|
|
28
|
+
isEmpty() {
|
|
29
|
+
return this.items.length === 0;
|
|
30
|
+
}
|
|
31
|
+
/** Run `fn` for each item; returns the collection for chaining. */
|
|
32
|
+
each(fn) {
|
|
33
|
+
this.items.forEach(fn);
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
/** Project to a new collection via `fn`. */
|
|
37
|
+
map(fn) {
|
|
38
|
+
return new Collection(this.items.map(fn));
|
|
39
|
+
}
|
|
40
|
+
/** Pluck a single field across all items. */
|
|
41
|
+
pluck(key) {
|
|
42
|
+
return new Collection(this.items.map((item) => item[key]));
|
|
43
|
+
}
|
|
44
|
+
/** Filter to items matching `predicate`. */
|
|
45
|
+
where(predicate) {
|
|
46
|
+
return new Collection(this.items.filter(predicate));
|
|
47
|
+
}
|
|
48
|
+
/** First item matching predicate (or first overall if no predicate). */
|
|
49
|
+
first(predicate) {
|
|
50
|
+
if (!predicate) return this.items[0];
|
|
51
|
+
return this.items.find(predicate);
|
|
52
|
+
}
|
|
53
|
+
/** Last item in the collection. */
|
|
54
|
+
last() {
|
|
55
|
+
return this.items[this.items.length - 1];
|
|
56
|
+
}
|
|
57
|
+
/** Sort by a key or comparator, returning a new collection. */
|
|
58
|
+
sortBy(key, direction = "asc") {
|
|
59
|
+
return new Collection([...this.items].sort((a, b) => {
|
|
60
|
+
const av = a[key];
|
|
61
|
+
const bv = b[key];
|
|
62
|
+
if (av === bv) return 0;
|
|
63
|
+
const cmp = av < bv ? -1 : 1;
|
|
64
|
+
return direction === "asc" ? cmp : -cmp;
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
/** Group items into a `Map` keyed by `fn`. */
|
|
68
|
+
groupBy(fn) {
|
|
69
|
+
const out = /* @__PURE__ */ new Map();
|
|
70
|
+
for (const item of this.items) {
|
|
71
|
+
const key = fn(item);
|
|
72
|
+
const bucket = out.get(key);
|
|
73
|
+
if (bucket) bucket.push(item);
|
|
74
|
+
else out.set(key, [item]);
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
/** Reduce to a single value. */
|
|
79
|
+
reduce(fn, initial) {
|
|
80
|
+
return this.items.reduce(fn, initial);
|
|
81
|
+
}
|
|
82
|
+
/** Plain-array view (a copy — the collection's storage is frozen). */
|
|
83
|
+
toArray() {
|
|
84
|
+
return [...this.items];
|
|
85
|
+
}
|
|
86
|
+
[Symbol.iterator]() {
|
|
87
|
+
return this.items[Symbol.iterator]();
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/core/sequence.ts
|
|
92
|
+
/**
|
|
93
|
+
* Cycle through a list of attribute patches across generated items.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* factory.sequence([{ role: 'admin' }, { role: 'editor' }]).count(4).make()
|
|
98
|
+
* // → admin, editor, admin, editor
|
|
99
|
+
*
|
|
100
|
+
* factory.sequence([({ index }) => ({ name: `User ${index}` })]).count(3).make()
|
|
101
|
+
* // → User 0, User 1, User 2
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
var Sequence = class Sequence {
|
|
105
|
+
cursor = 0;
|
|
106
|
+
entries;
|
|
107
|
+
constructor(entries) {
|
|
108
|
+
if (entries.length === 0) throw new Error("[Sequence] At least one entry is required.");
|
|
109
|
+
this.entries = [...entries];
|
|
110
|
+
}
|
|
111
|
+
/** Resolve the next patch, cycling when exhausted. */
|
|
112
|
+
next() {
|
|
113
|
+
const entry = this.entries[this.cursor % this.entries.length] ?? (() => {
|
|
114
|
+
throw new Error("[Sequence] unreachable: empty entries");
|
|
115
|
+
})();
|
|
116
|
+
const info = {
|
|
117
|
+
index: this.cursor,
|
|
118
|
+
count: this.cursor + 1
|
|
119
|
+
};
|
|
120
|
+
this.cursor++;
|
|
121
|
+
return typeof entry === "function" ? entry(info) : entry;
|
|
122
|
+
}
|
|
123
|
+
/** Reset the cursor to 0. */
|
|
124
|
+
reset() {
|
|
125
|
+
this.cursor = 0;
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
/** Current cursor position (zero-based). */
|
|
129
|
+
get currentIndex() {
|
|
130
|
+
return this.cursor;
|
|
131
|
+
}
|
|
132
|
+
/** Return a fresh sequence with the same entries but cursor reset. */
|
|
133
|
+
clone() {
|
|
134
|
+
return new Sequence([...this.entries]);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
/** Functional shorthand for `new Sequence(entries)`. */
|
|
138
|
+
function sequence(entries) {
|
|
139
|
+
return new Sequence(entries);
|
|
140
|
+
}
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/core/factory.ts
|
|
143
|
+
/**
|
|
144
|
+
* Laravel-inspired model factory for TypeScript. Immutable fluent chain — every
|
|
145
|
+
* method returns a new factory, never mutating the original.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```ts
|
|
149
|
+
* interface User { id: number; name: string; email: string; active: boolean }
|
|
150
|
+
*
|
|
151
|
+
* const UserFactory = defineFactory<User>(({ seq, faker }) => ({
|
|
152
|
+
* id: seq,
|
|
153
|
+
* name: faker.person.fullName(),
|
|
154
|
+
* email: faker.internet.email(),
|
|
155
|
+
* active: true,
|
|
156
|
+
* }))
|
|
157
|
+
* .state('admin', { role: 'admin' })
|
|
158
|
+
* .state('inactive', { active: false })
|
|
159
|
+
*
|
|
160
|
+
* UserFactory.make() // single User
|
|
161
|
+
* UserFactory.count(5).make() // User[]
|
|
162
|
+
* UserFactory.state('admin').make() // with admin overrides
|
|
163
|
+
* await UserFactory.persist(...).create()
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
var Factory = class Factory {
|
|
167
|
+
/** @internal */ definition;
|
|
168
|
+
/** @internal */ internals;
|
|
169
|
+
/** @internal — use {@link defineFactory} or {@link Factory.define} instead. */
|
|
170
|
+
constructor(definition, internals) {
|
|
171
|
+
this.definition = definition;
|
|
172
|
+
this.internals = internals;
|
|
173
|
+
}
|
|
174
|
+
/** Create a new factory. Mirrors Laravel's `Factory::new()`. */
|
|
175
|
+
static define(definition, persist) {
|
|
176
|
+
return new Factory(definition, {
|
|
177
|
+
faker: require_faker.faker,
|
|
178
|
+
count: 1,
|
|
179
|
+
overrides: {},
|
|
180
|
+
states: /* @__PURE__ */ new Map(),
|
|
181
|
+
activeStates: [],
|
|
182
|
+
sequences: [],
|
|
183
|
+
fieldSequences: /* @__PURE__ */ new Map(),
|
|
184
|
+
afterMaking: [],
|
|
185
|
+
afterCreating: [],
|
|
186
|
+
hasRelations: [],
|
|
187
|
+
hasAttachedRelations: [],
|
|
188
|
+
recycle: /* @__PURE__ */ new Map(),
|
|
189
|
+
persist: persist ?? null,
|
|
190
|
+
ownsFaker: false
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
/** Internal: build a copy with a single field replaced. */
|
|
194
|
+
clone(patch) {
|
|
195
|
+
const next = {
|
|
196
|
+
faker: this.internals.faker,
|
|
197
|
+
count: this.internals.count,
|
|
198
|
+
overrides: this.internals.overrides,
|
|
199
|
+
states: new Map(this.internals.states),
|
|
200
|
+
activeStates: [...this.internals.activeStates],
|
|
201
|
+
sequences: this.internals.sequences.map((s) => s.clone()),
|
|
202
|
+
fieldSequences: new Map(this.internals.fieldSequences),
|
|
203
|
+
afterMaking: [...this.internals.afterMaking],
|
|
204
|
+
afterCreating: [...this.internals.afterCreating],
|
|
205
|
+
hasRelations: [...this.internals.hasRelations],
|
|
206
|
+
hasAttachedRelations: [...this.internals.hasAttachedRelations],
|
|
207
|
+
recycle: new Map(this.internals.recycle),
|
|
208
|
+
persist: this.internals.persist,
|
|
209
|
+
ownsFaker: this.internals.ownsFaker,
|
|
210
|
+
...patch
|
|
211
|
+
};
|
|
212
|
+
return new Factory(this.definition, next);
|
|
213
|
+
}
|
|
214
|
+
/** Set how many items will be built on the next terminal call. */
|
|
215
|
+
count(n) {
|
|
216
|
+
if (!Number.isInteger(n) || n < 0) throw new Error(`[Factory] count(n): expected a non-negative integer, got ${String(n)}.`);
|
|
217
|
+
return this.clone({ count: n });
|
|
218
|
+
}
|
|
219
|
+
/** Alias of {@link count} — matches Laravel's `->times()`. */
|
|
220
|
+
times(n) {
|
|
221
|
+
return this.count(n);
|
|
222
|
+
}
|
|
223
|
+
/** Merge inline overrides into every built item. */
|
|
224
|
+
with(overrides) {
|
|
225
|
+
return this.clone({ overrides: {
|
|
226
|
+
...this.internals.overrides,
|
|
227
|
+
...overrides
|
|
228
|
+
} });
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Register a named state OR activate a previously-registered state.
|
|
232
|
+
*
|
|
233
|
+
* - Two-arg form (`state(name, value)`) **registers** the state.
|
|
234
|
+
* - One-arg form (`state(name)`) **activates** it.
|
|
235
|
+
* - One-arg with a Sequence (`state(seq)`) attaches a sequence as a state.
|
|
236
|
+
*/
|
|
237
|
+
state(arg1, value) {
|
|
238
|
+
if (arg1 instanceof Sequence) return this.clone({ sequences: [...this.internals.sequences, arg1.clone()] });
|
|
239
|
+
if (value !== void 0) {
|
|
240
|
+
const states = new Map(this.internals.states);
|
|
241
|
+
states.set(arg1, value);
|
|
242
|
+
return this.clone({ states });
|
|
243
|
+
}
|
|
244
|
+
if (!this.internals.states.has(arg1)) throw new Error(`[Factory] Unknown state "${arg1}". Register it first via .state("${arg1}", ...).`);
|
|
245
|
+
return this.clone({ activeStates: [...this.internals.activeStates, arg1] });
|
|
246
|
+
}
|
|
247
|
+
/** Bulk-register multiple states. */
|
|
248
|
+
states(map) {
|
|
249
|
+
const states = new Map(this.internals.states);
|
|
250
|
+
for (const [name, value] of Object.entries(map)) states.set(name, value);
|
|
251
|
+
return this.clone({ states });
|
|
252
|
+
}
|
|
253
|
+
/** Cycle values through one field across generated items. */
|
|
254
|
+
fieldSequence(field, values) {
|
|
255
|
+
const fieldSequences = new Map(this.internals.fieldSequences);
|
|
256
|
+
fieldSequences.set(field, values);
|
|
257
|
+
return this.clone({ fieldSequences });
|
|
258
|
+
}
|
|
259
|
+
/** Attach a sequence of attribute patches. */
|
|
260
|
+
sequence(entries) {
|
|
261
|
+
const seq = new Sequence(entries);
|
|
262
|
+
return this.clone({ sequences: [...this.internals.sequences, seq] });
|
|
263
|
+
}
|
|
264
|
+
/** Attach child records under `key` (one-to-many). */
|
|
265
|
+
has(childFactory, key) {
|
|
266
|
+
return this.clone({ hasRelations: [...this.internals.hasRelations, {
|
|
267
|
+
kind: "has",
|
|
268
|
+
factory: childFactory,
|
|
269
|
+
count: childFactory.internals.count,
|
|
270
|
+
key
|
|
271
|
+
}] });
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Set a foreign-key field by either resolving from another factory
|
|
275
|
+
* (eager — built once), a plain object (used directly), or a lazy callback
|
|
276
|
+
* (built per-item).
|
|
277
|
+
*/
|
|
278
|
+
for(parent, foreignKey, resolver) {
|
|
279
|
+
let resolved;
|
|
280
|
+
if (typeof parent === "function") resolved = parent();
|
|
281
|
+
else if (parent instanceof Factory) resolved = parent.makeOne();
|
|
282
|
+
else resolved = parent;
|
|
283
|
+
const patch = resolver ? resolver(resolved) : { [foreignKey]: resolved["id"] };
|
|
284
|
+
return this.with(patch);
|
|
285
|
+
}
|
|
286
|
+
/** Attach related records with pivot/intermediate data (many-to-many). */
|
|
287
|
+
hasAttached(childFactory, key, pivot = {}) {
|
|
288
|
+
return this.clone({ hasAttachedRelations: [...this.internals.hasAttachedRelations, {
|
|
289
|
+
kind: "hasAttached",
|
|
290
|
+
factory: childFactory,
|
|
291
|
+
count: childFactory.internals.count,
|
|
292
|
+
key,
|
|
293
|
+
pivot
|
|
294
|
+
}] });
|
|
295
|
+
}
|
|
296
|
+
/** Add models to the recycle pool keyed by `key`. */
|
|
297
|
+
recycle(models, key) {
|
|
298
|
+
const list = Array.isArray(models) ? models : [models];
|
|
299
|
+
const next = new Map(this.internals.recycle);
|
|
300
|
+
next.set(key, list);
|
|
301
|
+
return this.clone({ recycle: next });
|
|
302
|
+
}
|
|
303
|
+
/** Pick a random recycled model from `key`, or undefined. */
|
|
304
|
+
getRecycled(key) {
|
|
305
|
+
const pool = this.internals.recycle.get(key);
|
|
306
|
+
if (!pool || pool.length === 0) return void 0;
|
|
307
|
+
return pool[this.internals.faker.rawPrng().int(0, pool.length - 1)];
|
|
308
|
+
}
|
|
309
|
+
/** Register a hook fired after each item is built. */
|
|
310
|
+
afterMaking(fn) {
|
|
311
|
+
return this.clone({ afterMaking: [...this.internals.afterMaking, fn] });
|
|
312
|
+
}
|
|
313
|
+
/** Register a hook fired after each item is persisted via `create()`. */
|
|
314
|
+
afterCreating(fn) {
|
|
315
|
+
return this.clone({ afterCreating: [...this.internals.afterCreating, fn] });
|
|
316
|
+
}
|
|
317
|
+
/** Set or replace the persistence callback used by `create()`. */
|
|
318
|
+
persist(fn) {
|
|
319
|
+
return this.clone({ persist: fn });
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Bind this factory to its own private `Faker` instance, seeded as given.
|
|
323
|
+
* Useful when one factory in a suite must be deterministic without
|
|
324
|
+
* affecting the shared default faker.
|
|
325
|
+
*/
|
|
326
|
+
seed(seed) {
|
|
327
|
+
const own = new require_faker.Faker({ seed });
|
|
328
|
+
return this.clone({
|
|
329
|
+
faker: own,
|
|
330
|
+
ownsFaker: true
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
/** Set the locale on this factory's faker (creates one if it was shared). */
|
|
334
|
+
locale(name) {
|
|
335
|
+
const own = this.internals.ownsFaker ? this.internals.faker : new require_faker.Faker({ seed: this.internals.faker.currentSeed() });
|
|
336
|
+
own.locale(name);
|
|
337
|
+
return this.clone({
|
|
338
|
+
faker: own,
|
|
339
|
+
ownsFaker: true
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
/** Build a single item (ignoring `count`). */
|
|
343
|
+
makeOne() {
|
|
344
|
+
const item = this.buildOne(0);
|
|
345
|
+
this.fireAfterMakingSync([item]);
|
|
346
|
+
return item;
|
|
347
|
+
}
|
|
348
|
+
/** Build `count` items. */
|
|
349
|
+
makeMany() {
|
|
350
|
+
const items = this.buildMany();
|
|
351
|
+
this.fireAfterMakingSync(items);
|
|
352
|
+
return items;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Build — single item when `count === 1`, array otherwise.
|
|
356
|
+
* Use {@link makeOne} / {@link makeMany} when you need a specific shape.
|
|
357
|
+
*/
|
|
358
|
+
make() {
|
|
359
|
+
return this.internals.count === 1 ? this.makeOne() : this.makeMany();
|
|
360
|
+
}
|
|
361
|
+
/** Raw attribute objects with the same shape as `make()`. */
|
|
362
|
+
raw() {
|
|
363
|
+
return this.make();
|
|
364
|
+
}
|
|
365
|
+
/** Build and return as a {@link Collection}. */
|
|
366
|
+
collect() {
|
|
367
|
+
return new Collection(this.makeMany());
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Build and persist via the registered persistence callback.
|
|
371
|
+
* Returns single item when `count === 1`, array otherwise.
|
|
372
|
+
*/
|
|
373
|
+
async create() {
|
|
374
|
+
const persist = this.internals.persist;
|
|
375
|
+
if (!persist) throw new Error("[Factory] create(): no persistence callback registered. Use .persist(fn) or pass one to defineFactory().");
|
|
376
|
+
const items = this.buildMany();
|
|
377
|
+
await this.runHooks(items, this.internals.afterMaking);
|
|
378
|
+
const persisted = await Promise.all(items.map((item) => Promise.resolve(persist(item))));
|
|
379
|
+
await this.runHooks(persisted, this.internals.afterCreating);
|
|
380
|
+
return this.internals.count === 1 ? persisted[0] : persisted;
|
|
381
|
+
}
|
|
382
|
+
/** Always-array variant of {@link create}. */
|
|
383
|
+
async createMany() {
|
|
384
|
+
const result = await this.create();
|
|
385
|
+
return Array.isArray(result) ? result : [result];
|
|
386
|
+
}
|
|
387
|
+
buildOne(index) {
|
|
388
|
+
const ctx = {
|
|
389
|
+
seq: index + 1,
|
|
390
|
+
faker: this.internals.faker
|
|
391
|
+
};
|
|
392
|
+
let item = this.definition(ctx);
|
|
393
|
+
for (const stateName of this.internals.activeStates) {
|
|
394
|
+
const state = this.internals.states.get(stateName);
|
|
395
|
+
if (!state) continue;
|
|
396
|
+
item = {
|
|
397
|
+
...item,
|
|
398
|
+
...typeof state === "function" ? state(item, ctx) : state
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
for (const sequence of this.internals.sequences) item = {
|
|
402
|
+
...item,
|
|
403
|
+
...sequence.next()
|
|
404
|
+
};
|
|
405
|
+
for (const [field, values] of this.internals.fieldSequences) {
|
|
406
|
+
const value = values[index % values.length];
|
|
407
|
+
item = {
|
|
408
|
+
...item,
|
|
409
|
+
[field]: value
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
item = {
|
|
413
|
+
...item,
|
|
414
|
+
...this.internals.overrides
|
|
415
|
+
};
|
|
416
|
+
for (const rel of this.internals.hasRelations) {
|
|
417
|
+
const children = rel.factory.count(rel.count).make();
|
|
418
|
+
const arr = Array.isArray(children) ? children : [children];
|
|
419
|
+
item = {
|
|
420
|
+
...item,
|
|
421
|
+
[rel.key]: arr
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
for (const rel of this.internals.hasAttachedRelations) {
|
|
425
|
+
const children = rel.factory.count(rel.count).make();
|
|
426
|
+
const withPivot = (Array.isArray(children) ? children : [children]).map((child) => {
|
|
427
|
+
const pivotData = typeof rel.pivot === "function" ? rel.pivot(item, child) : rel.pivot;
|
|
428
|
+
return {
|
|
429
|
+
...child,
|
|
430
|
+
pivot: pivotData
|
|
431
|
+
};
|
|
432
|
+
});
|
|
433
|
+
item = {
|
|
434
|
+
...item,
|
|
435
|
+
[rel.key]: withPivot
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
return item;
|
|
439
|
+
}
|
|
440
|
+
buildMany() {
|
|
441
|
+
const out = [];
|
|
442
|
+
for (let i = 0; i < this.internals.count; i++) out.push(this.buildOne(i));
|
|
443
|
+
return out;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Fire afterMaking hooks for the sync build path (`make`/`makeOne`/`makeMany`/
|
|
447
|
+
* `collect`). Async hooks are kicked off without awaiting — use {@link create}
|
|
448
|
+
* if you need guaranteed sequencing.
|
|
449
|
+
*/
|
|
450
|
+
fireAfterMakingSync(items) {
|
|
451
|
+
for (let i = 0; i < items.length; i++) {
|
|
452
|
+
const item = items[i];
|
|
453
|
+
if (item === void 0) continue;
|
|
454
|
+
for (const hook of this.internals.afterMaking) {
|
|
455
|
+
const r = hook(item, i);
|
|
456
|
+
if (r instanceof Promise) r.catch(swallow);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
async runHooks(items, hooks) {
|
|
461
|
+
for (let i = 0; i < items.length; i++) {
|
|
462
|
+
const item = items[i];
|
|
463
|
+
if (item === void 0) continue;
|
|
464
|
+
for (const hook of hooks) await hook(item, i);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
function swallow() {}
|
|
469
|
+
/** Functional alias for `Factory.define()`. */
|
|
470
|
+
function defineFactory(definition, persist) {
|
|
471
|
+
return Factory.define(definition, persist);
|
|
472
|
+
}
|
|
473
|
+
//#endregion
|
|
474
|
+
//#region src/core/registry.ts
|
|
475
|
+
/**
|
|
476
|
+
* Process-global registry that lets you look up factories by name.
|
|
477
|
+
* Mirrors how Laravel resolves `UserFactory` from `User::factory()`.
|
|
478
|
+
*
|
|
479
|
+
* @example
|
|
480
|
+
* ```ts
|
|
481
|
+
* FactoryRegistry.register('User', UserFactory)
|
|
482
|
+
* const f = FactoryRegistry.resolve<User>('User')
|
|
483
|
+
* f.count(5).make()
|
|
484
|
+
* ```
|
|
485
|
+
*/
|
|
486
|
+
var FactoryRegistry = {
|
|
487
|
+
register(name, factory) {
|
|
488
|
+
registry.set(name, factory);
|
|
489
|
+
},
|
|
490
|
+
resolve(name) {
|
|
491
|
+
const f = registry.get(name);
|
|
492
|
+
if (!f) throw new Error(`[FactoryRegistry] No factory registered as "${name}". Registered: ${[...registry.keys()].join(", ")}`);
|
|
493
|
+
return f;
|
|
494
|
+
},
|
|
495
|
+
has(name) {
|
|
496
|
+
return registry.has(name);
|
|
497
|
+
},
|
|
498
|
+
unregister(name) {
|
|
499
|
+
return registry.delete(name);
|
|
500
|
+
},
|
|
501
|
+
clear() {
|
|
502
|
+
registry.clear();
|
|
503
|
+
},
|
|
504
|
+
names() {
|
|
505
|
+
return [...registry.keys()];
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
var registry = /* @__PURE__ */ new Map();
|
|
509
|
+
//#endregion
|
|
510
|
+
//#region src/builders/index.ts
|
|
511
|
+
/**
|
|
512
|
+
* Pick one value from `choices` at call time. Reads from the default faker.
|
|
513
|
+
* Use inside factory definitions to express "any of these".
|
|
514
|
+
*
|
|
515
|
+
* @example
|
|
516
|
+
* ```ts
|
|
517
|
+
* defineFactory<User>(({ faker }) => ({
|
|
518
|
+
* role: oneOf(['admin', 'editor', 'viewer']),
|
|
519
|
+
* }))
|
|
520
|
+
* ```
|
|
521
|
+
*/
|
|
522
|
+
function oneOf(choices) {
|
|
523
|
+
return require_faker.faker.helpers.arrayElement(choices);
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Return `value` with probability `chance`, else `undefined`. Useful for
|
|
527
|
+
* optional fields.
|
|
528
|
+
*
|
|
529
|
+
* @example
|
|
530
|
+
* ```ts
|
|
531
|
+
* { bio: maybe(faker.lorem.sentence(), 0.6) }
|
|
532
|
+
* ```
|
|
533
|
+
*/
|
|
534
|
+
function maybe(value, chance = .5) {
|
|
535
|
+
return require_faker.faker.helpers.maybe(value, chance);
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Build an array of length `[min, max]` (inclusive) by calling `fn(index)`.
|
|
539
|
+
* If only `min` is given, the length is exactly `min`.
|
|
540
|
+
*
|
|
541
|
+
* @example
|
|
542
|
+
* ```ts
|
|
543
|
+
* { tags: array(2, 5, () => faker.lorem.word()) }
|
|
544
|
+
* ```
|
|
545
|
+
*/
|
|
546
|
+
function array(min, maxOrFn, fn) {
|
|
547
|
+
if (typeof maxOrFn === "function") return Array.from({ length: min }, (_, i) => maxOrFn(i));
|
|
548
|
+
if (!fn) throw new Error("[builders] array(min, max, fn): missing builder function.");
|
|
549
|
+
const length = require_faker.faker.helpers.arrayElement(Array.from({ length: maxOrFn - min + 1 }, (_, i) => min + i));
|
|
550
|
+
return Array.from({ length }, (_, i) => fn(i));
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Defer evaluation until the value is actually read. Inside factory
|
|
554
|
+
* definitions you rarely need this (the definition is a function already),
|
|
555
|
+
* but it's useful for sequence entries and state values.
|
|
556
|
+
*/
|
|
557
|
+
function lazy(fn) {
|
|
558
|
+
return { resolve: fn };
|
|
559
|
+
}
|
|
560
|
+
//#endregion
|
|
561
|
+
//#region src/snapshot/index.ts
|
|
562
|
+
/**
|
|
563
|
+
* Snapshot helpers.
|
|
564
|
+
*
|
|
565
|
+
* The package doesn't write files itself — it just normalises payloads so
|
|
566
|
+
* test runners (vitest, jest) can snapshot them deterministically.
|
|
567
|
+
*
|
|
568
|
+
* @example
|
|
569
|
+
* ```ts
|
|
570
|
+
* import { snapshot } from '@anil-labs/factory'
|
|
571
|
+
*
|
|
572
|
+
* const items = UserFactory.seed(42).count(3).make()
|
|
573
|
+
* expect(snapshot(items)).toMatchSnapshot()
|
|
574
|
+
* ```
|
|
575
|
+
*/
|
|
576
|
+
/** Recursively normalise dates, undefineds, and key order so snapshots are stable. */
|
|
577
|
+
function snapshot(value) {
|
|
578
|
+
return normalise(value);
|
|
579
|
+
}
|
|
580
|
+
function normalise(value) {
|
|
581
|
+
if (value === null || value === void 0) return value;
|
|
582
|
+
if (value instanceof Date) return {
|
|
583
|
+
__type: "Date",
|
|
584
|
+
value: value.toISOString()
|
|
585
|
+
};
|
|
586
|
+
if (Array.isArray(value)) return value.map(normalise);
|
|
587
|
+
if (typeof value === "object") {
|
|
588
|
+
const out = {};
|
|
589
|
+
for (const key of Object.keys(value).sort()) out[key] = normalise(value[key]);
|
|
590
|
+
return out;
|
|
591
|
+
}
|
|
592
|
+
return value;
|
|
593
|
+
}
|
|
594
|
+
//#endregion
|
|
595
|
+
exports.Collection = Collection;
|
|
596
|
+
exports.Color = require_faker.Color;
|
|
597
|
+
exports.Commerce = require_faker.Commerce;
|
|
598
|
+
exports.Company = require_faker.Company;
|
|
599
|
+
exports.Datatype = require_faker.Datatype;
|
|
600
|
+
exports.DateGen = require_faker.DateGen;
|
|
601
|
+
exports.Factory = Factory;
|
|
602
|
+
exports.FactoryRegistry = FactoryRegistry;
|
|
603
|
+
exports.Faker = require_faker.Faker;
|
|
604
|
+
exports.Finance = require_faker.Finance;
|
|
605
|
+
exports.Helpers = require_faker.Helpers;
|
|
606
|
+
exports.Image = require_faker.Image;
|
|
607
|
+
exports.Internet = require_faker.Internet;
|
|
608
|
+
exports.LocaleRef = require_faker.LocaleRef;
|
|
609
|
+
exports.Location = require_faker.Location;
|
|
610
|
+
exports.Lorem = require_faker.Lorem;
|
|
611
|
+
exports.Mulberry32 = require_faker.Mulberry32;
|
|
612
|
+
exports.NumberGen = require_faker.NumberGen;
|
|
613
|
+
exports.Person = require_faker.Person;
|
|
614
|
+
exports.Sequence = Sequence;
|
|
615
|
+
exports.StringGen = require_faker.StringGen;
|
|
616
|
+
exports.System = require_faker.System;
|
|
617
|
+
exports.array = array;
|
|
618
|
+
exports.consolePersist = require_persist.consolePersist;
|
|
619
|
+
exports.createPrng = require_faker.createPrng;
|
|
620
|
+
exports.defineFactory = defineFactory;
|
|
621
|
+
exports.en = require_locales_en.en;
|
|
622
|
+
exports.faker = require_faker.faker;
|
|
623
|
+
exports.generateFromRegex = require_faker.generateFromRegex;
|
|
624
|
+
exports.getLocale = require_faker.getLocale;
|
|
625
|
+
exports.httpPersist = require_persist.httpPersist;
|
|
626
|
+
exports.lazy = lazy;
|
|
627
|
+
exports.listLocales = require_faker.listLocales;
|
|
628
|
+
exports.maybe = maybe;
|
|
629
|
+
exports.memoryPersist = require_persist.memoryPersist;
|
|
630
|
+
exports.oneOf = oneOf;
|
|
631
|
+
exports.registerLocale = require_faker.registerLocale;
|
|
632
|
+
exports.sequence = sequence;
|
|
633
|
+
exports.snapshot = snapshot;
|
|
634
|
+
|
|
635
|
+
//# sourceMappingURL=index.cjs.map
|