diff --git a/docs/how-to/evolve-an-entity.md b/docs/how-to/evolve-an-entity.md index 3700da5..9b2371b 100644 --- a/docs/how-to/evolve-an-entity.md +++ b/docs/how-to/evolve-an-entity.md @@ -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 diff --git a/docs/how-to/http-contract.md b/docs/how-to/http-contract.md index 74db232..e5fa1f8 100644 --- a/docs/how-to/http-contract.md +++ b/docs/how-to/http-contract.md @@ -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 diff --git a/docs/how-to/model-an-aggregate.md b/docs/how-to/model-an-aggregate.md index 176051f..404a363 100644 --- a/docs/how-to/model-an-aggregate.md +++ b/docs/how-to/model-an-aggregate.md @@ -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 diff --git a/docs/how-to/persist-and-rehydrate.md b/docs/how-to/persist-and-rehydrate.md index f2cf034..11b9760 100644 --- a/docs/how-to/persist-and-rehydrate.md +++ b/docs/how-to/persist-and-rehydrate.md @@ -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()` diff --git a/docs/reference/errors.md b/docs/reference/errors.md index c3373a8..3afe351 100644 --- a/docs/reference/errors.md +++ b/docs/reference/errors.md @@ -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` diff --git a/docs/tutorial/getting-started.md b/docs/tutorial/getting-started.md index bb8d371..cf4d9ea 100644 --- a/docs/tutorial/getting-started.md +++ b/docs/tutorial/getting-started.md @@ -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 @@ -112,8 +119,8 @@ Now a create use case supplies only the caller's fields: ```ts const created = createOrganization({ - slug: "acme" as z.infer, - name: "Acme" as z.infer, + slug: slug("acme"), + name: name("Acme"), }); const org = created.getOrThrow(); @@ -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 @@ -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; // ✗ compile error — read-only property +org.name = name("Other"); // ✗ compile error — read-only property ``` And you cannot sidestep the entry points: @@ -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 }) - .getOrThrow(); +const renamed = org.update({ name: name("Acme Corp") }).getOrThrow(); renamed.name; // "Acme Corp" renamed.shout; // "ACME CORP" — re-derived, never stale