Skip to content

refactor!: make a factory a function instead of an object with create - #20

Merged
btravers merged 2 commits into
mainfrom
refactor/factory-returns-a-function
Aug 7, 2026
Merged

refactor!: make a factory a function instead of an object with create#20
btravers merged 2 commits into
mainfrom
refactor/factory-returns-a-function

Conversation

@btravers

@btravers btravers commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Stacked on #19 — based on feat/entity-is-a-schema, not main, because
both touch entity.ts and types.ts. Merge #19 first and this retargets to
main cleanly.

// before
const orgs = Organization.factory({ id: () => ids.next(), createdAt: () => clock.now() });
orgs.create({ slug, name });

// after
const createOrg = Organization.factory({ id: () => ids.next(), createdAt: () => clock.now() });
createOrg({ slug, name });

Nothing but create ever consumed the generators, so the object around it was
ceremony around the only thing a factory does. factoryAsync changes the same
way.

On the vocabulary

When I argued against renaming createbuild earlier, the reason was that
createInput (22 uses), CreateInput<T> and CreateInputOf anchor the word.
Removing the method does not undermine that: createInput still names what a
caller may send when creating, and the factory is still the create path. What
goes is a method name that only ever restated its receiver.

The call sites read better for it — createOrg({ slug, name }) says what it
does at the point of use, where orgs.create(...) needed the variable and the
method together to make sense.

Naming still fits

A "factory" that is a function is the ordinary factory-function shape, and
arguably a better fit for the DDD pattern than an object with a single method.

Gate

format --check, lint, typecheck (three passes), test (120, 11 files),
knip, build — all green.

Copilot AI lite review requested due to automatic review settings August 7, 2026 01:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors the entity “factory” API so Entity.factory(...) / factoryAsync(...) return a callable factory function instead of an object with a single .create(...) method, reducing ceremony while preserving the same create-path semantics in this DDD entity builder.

Changes:

  • Change EntityFactory / AsyncEntityFactory types from { create(...) } to (input) => Result | AsyncResult.
  • Update Entity.factory / factoryAsync implementations to return a function directly.
  • Migrate docs, type tests, runtime tests, and add a changeset describing the breaking API shift.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
README.md Updates public documentation examples and the “entry points” table for callable factories.
packages/entity/src/types.ts Redefines EntityFactory and AsyncEntityFactory as function types.
packages/entity/src/entity.ts Returns factory functions directly from factory / factoryAsync.
packages/entity/src/entity.test-d.ts Updates type-level tests to call factories as functions (no .create).
packages/entity/src/crud.spec.ts Updates runtime tests to call factories as functions (sync + async).
packages/entity/README.md Updates package-level README examples and API table for callable factories.
CLAUDE.md Updates repo guidance to describe the callable factory usage.
.changeset/factory-returns-a-function.md Adds a changeset documenting the breaking change and migration step.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread README.md Outdated
Comment on lines +724 to +728
const createOrgAsync = Organization.factoryAsync({
id: () => ids.nextFromSequence(),
createdAt: () => clock.now(),
});
(await orgs.create({ slug, name })).getOrThrow();
(await createOrg({ slug, name })).getOrThrow();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e8e5554 — the example calls createOrgAsync now.

A rename artefact: I renamed the binding from asyncOrgs to createOrgAsync but the call site was orgs.create(...), and my substitution rewrote that to createOrg(...) from the sync rule. So the snippet ended up awaiting a function that returns a Result, not an AsyncResult — it would not have compiled if README snippets were typechecked.

Also rebased onto main now that #19 is merged. One conflict, in the package README statics table: main had gained the _the class itself_ row from #19, while this branch changed the two factory rows to their function signatures. Kept both.

Base automatically changed from feat/entity-is-a-schema to main August 7, 2026 01:14
@btravers
btravers force-pushed the refactor/factory-returns-a-function branch from ede5a9a to e8e5554 Compare August 7, 2026 01:16
@btravers
btravers merged commit afb883b into main Aug 7, 2026
13 checks passed
@btravers
btravers deleted the refactor/factory-returns-a-function branch August 7, 2026 01:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants