Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/how-to/evolve-an-entity.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ the same: do the old rows still validate?
> import { z } from "zod";
> import { Entity } from "@btravstack/entity";
> ```
>
> Domain vocabulary — entities, brands, factories — is whatever your own
> domain declares.

## Add an optional field

Expand Down
3 changes: 3 additions & 0 deletions docs/how-to/http-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ from the model.
> import { P } from "unthrown";
> import { Entity } from "@btravstack/entity";
> ```
>
> Domain vocabulary — entities, brands, factories — is whatever your own
> domain declares.

## Use the four `ZodObject` members directly

Expand Down
3 changes: 3 additions & 0 deletions docs/how-to/model-an-aggregate.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ entity rather than a bare schema.
> import { match, P } from "unthrown";
> import { Entity } from "@btravstack/entity";
> ```
>
> Domain vocabulary — entities, brands, factories — is whatever your own
> domain declares.

## Use the class as a field

Expand Down
3 changes: 3 additions & 0 deletions docs/how-to/persist-and-rehydrate.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ without the storage layer knowing about entity internals.
> import { P } from "unthrown";
> import { Entity } from "@btravstack/entity";
> ```
>
> Domain vocabulary — entities, brands, factories — is whatever your own
> domain declares.

## Write with `toJSON()`

Expand Down
3 changes: 3 additions & 0 deletions docs/reference/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ modelled as a value; a bug in domain code goes down the separate defect channel.
> import { P } from "unthrown";
> import { Entity } from "@btravstack/entity";
> ```
>
> Domain vocabulary — entities, brands, factories — is whatever your own
> domain declares.

## `Entity.InvalidEntity`

Expand Down
31 changes: 18 additions & 13 deletions docs/tutorial/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,18 @@ const OrgId = z.uuid().brand("OrgId");
const Slug = z.string().min(1).brand("Slug");
const DisplayName = z.string().min(1).brand("DisplayName");
const Instant = z.iso.datetime().brand("Instant");

const slug = (value: string) => Slug.parse(value);
const name = (value: string) => DisplayName.parse(value);
```

The reason is the one every domain modeller already knows: with plain strings,
`findOrg(slug, name)` type-checks with the arguments swapped. Branded, it does
not. ([The full argument](/explanation/branded-fields).)
`findOrg(orgSlug, orgName)` type-checks with the arguments swapped. Branded, it
does not. ([The full argument](/explanation/branded-fields).)

`slug` and `name` are **mint helpers** — a named `parse` declared beside the
vocabulary it mints, for the two fields a caller supplies by hand later in this
page.

## 2. Declare the entity

Expand Down Expand Up @@ -112,8 +119,8 @@ Now a create use case supplies only the caller's fields:

```ts
const created = createOrganization({
slug: "acme" as z.infer<typeof Slug>,
name: "Acme" as z.infer<typeof DisplayName>,
slug: slug("acme"),
name: name("Acme"),
});

const org = created.getOrThrow();
Expand All @@ -126,11 +133,11 @@ startup still yields a fresh id per entity. And a test can bind fixed generators
instead of stubbing globals. ([Why no I/O](/explanation/no-io).)

Note the asymmetry between the two blocks. A generator hands its value to the
entity, which validates it, so `crypto.randomUUID()` needs nothing; a caller
field is a branded value you are supplying, so it has to be minted. The cast
above is the shortest spelling for a tutorial — real code declares a helper per
piece of vocabulary and writes `slug("acme")`
([Branded fields](/explanation/branded-fields#everywhere-else-parse-through-a-mint-helper)).
entity, which validates it, so `crypto.randomUUID()` needs no brand of its own;
a caller field is a branded value you are supplying, so it has to be minted
which is exactly what `slug` and `name`, declared in step 1, are for. Real code
follows the same pattern for every piece of vocabulary written by hand.
([Branded fields](/explanation/branded-fields#everywhere-else-parse-through-a-mint-helper).)

::: tip `getOrThrow()` is for a tutorial
It is the shortest way to get at a value while you are exploring. Real code
Expand All @@ -143,7 +150,7 @@ The entity is immutable in both halves — the binding is non-writable and the
value is deep-frozen:

```ts
org.name = "Other" as z.infer<typeof DisplayName>; // ✗ compile error — read-only property
org.name = name("Other"); // ✗ compile error — read-only property
```

And you cannot sidestep the entry points:
Expand Down Expand Up @@ -251,9 +258,7 @@ re-derives](/explanation/computed-fields).)
`update` returns a **new** entity — the original is untouched:

```ts
const renamed = org
.update({ name: "Acme Corp" as z.infer<typeof DisplayName> })
.getOrThrow();
const renamed = org.update({ name: name("Acme Corp") }).getOrThrow();

renamed.name; // "Acme Corp"
renamed.shout; // "ACME CORP" — re-derived, never stale
Expand Down
Loading