{"_id":"@anil-labs/factory","name":"@anil-labs/factory","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@anil-labs/factory","version":"0.1.0","description":"Laravel-inspired model factory + faceted faker for TypeScript. Seedable, locale-aware, framework-agnostic, zero runtime deps.","type":"module","license":"MIT","author":{"name":"Anil Kumar Thakur","url":"https://github.com/anilkumarthakur60"},"repository":{"type":"git","url":"git+https://github.com/anilkumarthakur60/factory.git"},"homepage":"https://github.com/anilkumarthakur60/factory#readme","bugs":{"url":"https://github.com/anilkumarthakur60/factory/issues"},"main":"./dist/index.cjs","module":"./dist/index.mjs","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"},"default":"./dist/index.mjs"},"./faker":{"import":{"types":"./dist/faker/index.d.ts","default":"./dist/faker.mjs"},"require":{"types":"./dist/faker/index.d.cts","default":"./dist/faker.cjs"},"default":"./dist/faker.mjs"},"./persist":{"import":{"types":"./dist/persist/index.d.ts","default":"./dist/persist.mjs"},"require":{"types":"./dist/persist/index.d.cts","default":"./dist/persist.cjs"},"default":"./dist/persist.mjs"},"./locales/en":{"import":{"types":"./dist/locales/en.d.ts","default":"./dist/locales/en.mjs"},"require":{"types":"./dist/locales/en.d.cts","default":"./dist/locales/en.cjs"},"default":"./dist/locales/en.mjs"},"./package.json":"./package.json"},"scripts":{"clean":"rm -rf dist coverage","build":"vite build","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","typecheck":"tsc --noEmit","lint":"eslint .","lint:fix":"eslint . --fix","format":"prettier --write .","format:check":"prettier --check .","verify":"npm run typecheck && npm run lint && npm run format:check && npm test","docs:dev":"vitepress dev docs","docs:build":"vitepress build docs","docs:preview":"vitepress preview docs","prepublishOnly":"npm run clean && npm run verify && npm run build","prepack":"node -e \"const f=require('./package.json').files;console.log('\\n[prepack] tarball will include only:',f.join(', '));console.log('[prepack] docs/, src/, tests/, configs are excluded.\\n')\""},"devDependencies":{"@eslint/js":"^10.0.1","@microsoft/api-extractor":"^7.58.7","@types/node":"^25.9.1","@vitest/coverage-v8":"^4.1.7","eslint":"^10.4.0","eslint-config-prettier":"^10.1.8","eslint-plugin-perfectionist":"^5.9.0","prettier":"^3.8.3","typescript":"^6.0.3","typescript-eslint":"^8.59.4","vite":"^8.0.13","vite-plugin-dts":"^5.0.1","vitepress":"^1.6.4","vitest":"^4.1.7"},"engines":{"node":">=20"},"keywords":["factory","faker","test-data","laravel","eloquent","seeder","mock","typescript","fixtures","seedable","locale","esm","cjs","isomorphic","browser","node","bun","deno"],"sideEffects":false,"publishConfig":{"access":"public"},"gitHead":"29ac724e163f05947d960cf6f44c46a93866e8d6","_id":"@anil-labs/factory@0.1.0","_nodeVersion":"24.14.1","_npmVersion":"11.11.0","dist":{"integrity":"sha512-7zOrIo+4RpJDAOBAXJutTBBgpJNkDLhDLtm9oT9c1Or5voATiExn6aYoHtpDqyvtoOTyz8RWqyAlW+l3lEbEjA==","shasum":"8002b45e56718d7deac914e65475f50047556a03","tarball":"https://registry.npmjs.org/@anil-labs/factory/-/factory-0.1.0.tgz","fileCount":98,"unpackedSize":449658,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCfy8QF3vN0BMfuovHIJ1fRvyryPHmiHSoeFk6OJG6llwIgIwZYvmtjtCbzdK3Xf0cdTpoItvYWgJ1YyY1t3ICAmDs="}]},"_npmUser":{"name":"anilkumarthakur","email":"anilkumarthakur60@gmail.com"},"directories":{},"maintainers":[{"name":"anilkumarthakur","email":"anilkumarthakur60@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/factory_0.1.0_1779304229084_0.8316027585999997"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-20T19:10:28.992Z","0.1.0":"2026-05-20T19:10:29.234Z","modified":"2026-05-20T19:10:29.405Z"},"maintainers":[{"name":"anilkumarthakur","email":"anilkumarthakur60@gmail.com"}],"description":"Laravel-inspired model factory + faceted faker for TypeScript. Seedable, locale-aware, framework-agnostic, zero runtime deps.","homepage":"https://github.com/anilkumarthakur60/factory#readme","keywords":["factory","faker","test-data","laravel","eloquent","seeder","mock","typescript","fixtures","seedable","locale","esm","cjs","isomorphic","browser","node","bun","deno"],"repository":{"type":"git","url":"git+https://github.com/anilkumarthakur60/factory.git"},"author":{"name":"Anil Kumar Thakur","url":"https://github.com/anilkumarthakur60"},"bugs":{"url":"https://github.com/anilkumarthakur60/factory/issues"},"license":"MIT","readme":"# @anil-labs/factory\n\nLaravel-inspired model factories + a seedable, locale-aware faceted faker for TypeScript. **Zero runtime dependencies.** ESM + CJS. Browser, Node 20+, Bun, Deno.\n\n```bash\nnpm i @anil-labs/factory\n```\n\n```ts\nimport { defineFactory, oneOf, faker } from '@anil-labs/factory'\n\ninterface User {\n  id: number\n  name: string\n  email: string\n  role: 'admin' | 'editor' | 'viewer'\n  active: boolean\n}\n\nconst UserFactory = defineFactory<User>(({ seq, faker }) => ({\n  id: seq,\n  name: faker.person.fullName(),\n  email: faker.internet.email(),\n  role: oneOf(['admin', 'editor', 'viewer']),\n  active: true,\n}))\n  .state('admin', { role: 'admin' })\n  .state('inactive', { active: false })\n\nUserFactory.make() // → User\nUserFactory.count(5).make() // → User[]\nUserFactory.state('admin').make() // → User with role: 'admin'\nUserFactory.seed(42).make() // → deterministic User\n```\n\n---\n\n## Why one more factory library\n\nMost JS data-generation libraries pick one job and stop:\n\n- **`@faker-js/faker`** gives you faker, no model factories.\n- **`fishery`**, **`rosie`** give you factories, no faker.\n- **ORM-specific seeders** are tied to one DB layer.\n\n`@anil-labs/factory` combines **Laravel-quality model factories** with a **seedable, locale-aware faker**, in one zero-dep package with first-class TypeScript types and pluggable persistence adapters. It's the package I wanted to find when I went looking.\n\n---\n\n## Quick tour\n\n```ts\nimport {\n  defineFactory,\n  faker,\n  oneOf,\n  maybe,\n  array,\n  memoryPersist,\n  httpPersist,\n  sequence,\n  Collection,\n  FactoryRegistry,\n} from '@anil-labs/factory'\n\n// 1. A faker with namespaces\nfaker.seed(123)\nfaker.person.fullName() // \"Olivia Patel\"\nfaker.internet.email()\nfaker.location.streetAddress()\nfaker.string.uuid()\nfaker.lorem.paragraph()\nfaker.number.int({ min: 1, max: 100 })\nfaker.color.hex()\nfaker.finance.creditCardNumber() // Luhn-valid\nfaker.helpers.fromRegExp(/[A-Z]{3}-\\d{4}/)\nfaker.helpers.weightedArrayElement([\n  { value: 'rare', weight: 1 },\n  { value: 'common', weight: 9 },\n])\n\n// 2. A factory\ninterface User {\n  id: number\n  name: string\n  email: string\n  active: boolean\n}\n\nconst UserFactory = defineFactory<User>(({ seq, faker }) => ({\n  id: seq,\n  name: faker.person.fullName(),\n  email: faker.internet.email(),\n  active: true,\n}))\n\n// 3. Build (sync) — Laravel parity\nUserFactory.make() // single\nUserFactory.count(10).make() // array\nUserFactory.with({ active: false }).make() // overrides\nUserFactory.state('inactive').make() // named states\nUserFactory.fieldSequence('active', [true, false]).count(4).make()\nUserFactory.sequence([{ active: true }, { active: false }])\n  .count(4)\n  .make()\n\n// 4. Persist (async) — works with any backend\nconst memory = memoryPersist<User>()\nawait UserFactory.persist(memory).count(3).create()\nmemory.all() // [User, User, User]\n\nawait UserFactory.persist(httpPersist<User>('/api/users')).create()\n\n// 5. Relationships\ninterface Post {\n  id: number\n  title: string\n  userId: number\n}\nconst PostFactory = defineFactory<Post>(({ seq, faker }) => ({\n  id: seq,\n  title: faker.lorem.sentence(4),\n  userId: 0,\n}))\n\nUserFactory.has(PostFactory.count(3), 'posts').make() // attach children\nPostFactory.for(UserFactory, 'userId').make() // set foreign key\n\ninterface Role {\n  id: number\n  name: string\n}\nconst RoleFactory = defineFactory<Role>(({ seq }) => ({ id: seq, name: `Role ${seq}` }))\nUserFactory.hasAttached(RoleFactory.count(2), 'roles', { active: true })\n\n// 6. Collection helpers (Laravel-style)\nconst users = UserFactory.count(20).collect()\nusers.where((u) => u.active).count()\nusers.pluck('email').toArray()\n\n// 7. Registry — look up by name\nFactoryRegistry.register('User', UserFactory)\nFactoryRegistry.resolve<User>('User').count(5).make()\n```\n\n---\n\n## API\n\n### `defineFactory<T>(definition, persist?)`\n\nCreate a new factory. `definition` receives a build context `{ seq, faker }` and returns the base attributes for one item.\n\n```ts\nconst f = defineFactory<User>(({ seq, faker }) => ({\n  id: seq,\n  name: faker.person.fullName(),\n}))\n```\n\nYou can also use the static form: `Factory.define<T>(definition, persist?)`.\n\n### Building methods\n\n| Method                                    | Returns      | Notes                                                                                   |\n| ----------------------------------------- | ------------ | --------------------------------------------------------------------------------------- |\n| `.count(n)`                               | `Factory<T>` | Set how many items to build. Alias: `.times(n)`.                                        |\n| `.with(overrides)`                        | `Factory<T>` | Merge overrides into every built item.                                                  |\n| `.state(name, value)`                     | `Factory<T>` | Register a named state — `value` may be a partial OR `(item, ctx) => partial`.          |\n| `.state(name)`                            | `Factory<T>` | Activate a registered state.                                                            |\n| `.state(sequenceInstance)`                | `Factory<T>` | Attach a sequence as state.                                                             |\n| `.states({ a: …, b: … })`                 | `Factory<T>` | Bulk-register states.                                                                   |\n| `.sequence([…])`                          | `Factory<T>` | Cycle attribute patches across items.                                                   |\n| `.fieldSequence(key, [v1, v2])`           | `Factory<T>` | Cycle one field's values.                                                               |\n| `.has(childFactory, key)`                 | `Factory<T>` | Attach `count` child records under `key`.                                               |\n| `.for(parent, foreignKey, resolver?)`     | `Factory<T>` | Set the foreign-key on each child from a parent (factory, instance, or `() => parent`). |\n| `.hasAttached(childFactory, key, pivot)`  | `Factory<T>` | Many-to-many; `pivot` may be an object or `(parent, child) => object`.                  |\n| `.recycle(model, key)`                    | `Factory<T>` | Add reusable model instances; `.getRecycled(key)` returns one.                          |\n| `.afterMaking(fn)` / `.afterCreating(fn)` | `Factory<T>` | Lifecycle hooks; async hooks awaited only by `create()`.                                |\n| `.persist(fn)`                            | `Factory<T>` | Register the persistence callback for `create()`.                                       |\n| `.seed(n)`                                | `Factory<T>` | Bind to a private deterministic Faker.                                                  |\n| `.locale(name)`                           | `Factory<T>` | Bind to a private Faker on the named locale.                                            |\n\n### Terminal methods\n\n| Method          | Returns             | Notes                                       |\n| --------------- | ------------------- | ------------------------------------------- |\n| `.makeOne()`    | `T`                 | Single item regardless of `count`.          |\n| `.makeMany()`   | `T[]`               | Array of `count` items.                     |\n| `.make()`       | `T \\| T[]`          | Single when `count === 1`, array otherwise. |\n| `.raw()`        | `T \\| T[]`          | Same shape as `make()`.                     |\n| `.collect()`    | `Collection<T>`     | Always a `Collection`.                      |\n| `.create()`     | `Promise<T \\| T[]>` | Persists via `.persist(fn)`.                |\n| `.createMany()` | `Promise<T[]>`      | Always an array; persistence required.      |\n\n### `Sequence` / `sequence([...])`\n\nCycle attribute patches across items. Entries may be literal patches or `({ index, count }) => patch` closures.\n\n```ts\nconst seq = sequence<{ name: string }>([\n  ({ index }) => ({ name: `User ${index}` }),\n  { name: 'Pinned' },\n])\nfactory.state(seq).count(4).make() // → User 0, Pinned, User 2, Pinned\n```\n\n### `Collection<T>`\n\nImmutable iterable wrapper. Methods: `count`, `isEmpty`, `each`, `map`, `pluck`, `where`, `first`, `last`, `sortBy`, `groupBy`, `reduce`, `toArray`, `[Symbol.iterator]`. The underlying `items` array is `Object.freeze`d.\n\n### `FactoryRegistry`\n\nProcess-global lookup table.\n\n```ts\nFactoryRegistry.register('User', UserFactory)\nFactoryRegistry.has('User') // true\nFactoryRegistry.resolve<User>('User') // → Factory<User>\nFactoryRegistry.names() // ['User']\nFactoryRegistry.unregister('User')\nFactoryRegistry.clear()\n```\n\n### `Faker` (the data generator)\n\n```ts\nimport { Faker, faker } from '@anil-labs/factory'\n\nconst f = new Faker({ seed: 7, locale: 'en' })\nf.seed(7)\nf.locale('en')\nf.currentSeed()\nf.currentLocale()\nf.fork() // independent Faker with derived seed\n```\n\nNamespaces (all read from the shared PRNG + locale):\n\n| Namespace  | Examples                                                                                                                                                                          |\n| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `person`   | `firstName({sex})`, `lastName`, `fullName({withPrefix})`, `prefix`, `suffix`, `sex`                                                                                               |\n| `internet` | `email({firstName, lastName})`, `userName`, `url`, `domainName`, `ipv4`, `ipv6`, `mac`, `password(length)`                                                                        |\n| `location` | `streetAddress`, `city`, `state`, `zipCode`, `country`, `countryName`, `fullAddress`, `latitude`, `longitude`                                                                     |\n| `lorem`    | `word`, `words(n)`, `sentence(n?)`, `paragraph(n?)`, `paragraphs(n)`, `text`                                                                                                      |\n| `date`     | `past(days)`, `recent(days)`, `future(days)`, `soon(days)`, `between(a, b)`, `iso(days)`, `birthdate({min, max})`                                                                 |\n| `number`   | `int({min, max})`, `float({min, max, decimals})`, `bigInt({min, max})`, `between(a, b)`                                                                                           |\n| `string`   | `uuid`, `nanoid(length)`, `alpha`, `numeric`, `alphanumeric`, `hexadecimal(length, {prefix})`, `sample`, `slug(words)`                                                            |\n| `color`    | `name`, `hex`, `rgb`, `hsl`                                                                                                                                                       |\n| `company`  | `name`, `jobTitle`, `buzzPhrase`                                                                                                                                                  |\n| `commerce` | `productName`, `department`, `price(min, max, dec)`, `productDescription`                                                                                                         |\n| `finance`  | `amount(min, max, dec, symbol)`, `accountNumber(digits)`, `creditCardNumber` (Luhn-valid), `currencyCode`, `iban(cc, len)`, `bitcoinAddress`                                      |\n| `image`    | `url(w, h)`, `avatar(name)`, `dataUri(w, h)`                                                                                                                                      |\n| `system`   | `fileName({withExt})`, `commonFileExt`, `fileExt`, `mimeType`, `directoryPath`, `filePath`, `semver`                                                                              |\n| `datatype` | `boolean(chance)`                                                                                                                                                                 |\n| `helpers`  | `arrayElement`, `arrayElements(arr, count)`, `shuffle`, `weightedArrayElement`, `multiple(n, fn)`, `repeat`, `fromRegExp`, `unique(fn, n, opts)`, `enumValue`, `maybe(v, chance)` |\n\n### Locales\n\nThe package ships with an English (`en`) corpus. Register your own:\n\n```ts\nimport { registerLocale, faker } from '@anil-labs/factory'\n\nregisterLocale('np', {\n  title: 'नेपाली',\n  firstNames: ['Aakash', 'Bina', 'Chetana', 'Dipesh'],\n  lastNames: ['Adhikari', 'Bhandari', 'Chhetri', 'Dhakal'],\n  // ...etc — see the `LocaleData` interface\n})\nfaker.locale('np')\nfaker.person.fullName() // \"Dipesh Bhandari\"\n```\n\n### Builder helpers\n\n```ts\nimport { oneOf, maybe, array, lazy } from '@anil-labs/factory'\n\ndefineFactory<Profile>(({ faker }) => ({\n  role: oneOf(['admin', 'editor', 'viewer']),\n  bio: maybe(faker.lorem.paragraph(), 0.7),\n  tags: array(2, 5, () => faker.lorem.word()),\n}))\n```\n\n### Persistence adapters\n\n```ts\nimport { memoryPersist, httpPersist, consolePersist } from '@anil-labs/factory'\n\nconst store = memoryPersist<User>()\nUserFactory.persist(store).create()\n\nUserFactory.persist(\n  httpPersist<User>('/api/users', {\n    headers: {\n      /* … */\n    },\n  }),\n)\n\nUserFactory.persist(consolePersist<User>()).create() // just logs each item\n```\n\nWrite your own:\n\n```ts\nconst drizzlePersist =\n  (db): Persist<User> =>\n  async (user) => {\n    const [row] = await db.insert(users).values(user).returning()\n    return row\n  }\n```\n\n### Snapshot helper\n\nNormalises `Date` instances and sorts object keys so snapshots are stable across machines.\n\n```ts\nimport { snapshot } from '@anil-labs/factory'\n\nexpect(snapshot(UserFactory.seed(42).count(3).make())).toMatchSnapshot()\n```\n\n---\n\n## Reproducibility\n\n```ts\nfaker.seed(2026)\nconst a = faker.person.fullName()\nfaker.seed(2026)\nconst b = faker.person.fullName()\n// a === b\n```\n\nFactory-scoped seeds don't pollute the default singleton:\n\n```ts\ndefineFactory(...).seed(7)   // private Faker; the global `faker` is untouched\n```\n\n---\n\n## Laravel parity\n\n| Laravel Eloquent Factory                                       | `@anil-labs/factory`                                            |\n| -------------------------------------------------------------- | --------------------------------------------------------------- |\n| `Factory::new()`                                               | `defineFactory(...)` / `Factory.define(...)`                    |\n| `->count(5)`                                                   | `.count(5)`                                                     |\n| `->state(['admin' => true])`                                   | `.with({ admin: true })`                                        |\n| `->state('admin')` (named)                                     | `.state('admin', { … })` + `.state('admin')`                    |\n| `->sequence(['a' => 1], ['a' => 2])`                           | `.sequence([{ a: 1 }, { a: 2 }])`                               |\n| `->has(Post::factory()->count(3))`                             | `.has(PostFactory.count(3), 'posts')`                           |\n| `->for(User::factory())`                                       | `.for(UserFactory, 'userId')`                                   |\n| `->hasAttached(Role::factory()->count(2), ['active' => true])` | `.hasAttached(RoleFactory.count(2), 'roles', { active: true })` |\n| `->recycle($airline)`                                          | `.recycle(airline, 'Airline')`                                  |\n| `->afterMaking(fn ($u) => ...)`                                | `.afterMaking(u => ...)`                                        |\n| `->afterCreating(fn ($u) => ...)`                              | `.afterCreating(u => ...)`                                      |\n| `Factory::configure()`                                         | chain methods on the factory directly                           |\n| `->make()`                                                     | `.make()`                                                       |\n| `->create()`                                                   | `.persist(fn).create()`                                         |\n| `->raw()`                                                      | `.raw()`                                                        |\n\n---\n\n## License\n\nMIT.\n","readmeFilename":"README.md","_rev":"1-21ec8c33e94640fb552f197c6204f697"}