Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
62d523b
feat!: remove the enum's own constructor
jo16oh Oct 14, 2026
d08e1b2
docs(notes): record removing the enum's own constructor
jo16oh Oct 14, 2026
79f3036
feat: add `[Tag].match` to dispatch an enum on a tag alone
jo16oh Oct 14, 2026
846525b
feat: add `[Tag].key` to read an enum's tag field name
jo16oh Oct 14, 2026
6872f53
feat: restore the enum's `seal`, rejecting another variant's fields
jo16oh Oct 14, 2026
542f623
docs: describe the enum's `seal`, `[Tag].match` and `[Tag].key`
jo16oh Oct 14, 2026
e3a8cb4
docs(notes): record the exclusive `seal` and `[Tag]`
jo16oh Oct 14, 2026
d8eeb92
docs: align the `[Tag].match` example with the book
jo16oh Oct 14, 2026
8f64972
feat: seal an enum by calling its sealer, as a Val's
jo16oh Oct 14, 2026
85b43cd
docs: build an enum from a tagged payload by calling its sealer
jo16oh Oct 14, 2026
cbd8d40
docs(notes): keep the enum's two entries, as a Val's
jo16oh Oct 14, 2026
63aea7a
docs: build a payment from a form's method in the `[Tag].match` example
jo16oh Oct 14, 2026
6547167
docs: use the payment form in the `[Tag].match` JSDoc
jo16oh Oct 14, 2026
969bb6a
docs(notes): record why the enum keeps its entry
jo16oh Oct 14, 2026
96cc2cc
docs: show which call builds an enum value from what arrives
jo16oh Oct 14, 2026
c7d9fc6
docs: build a command from a normalized tag in the `[Tag].match` example
jo16oh Oct 14, 2026
1b81c6e
docs: read the command example's tag and arguments from one input
jo16oh Oct 14, 2026
b82d74a
docs: tell the two enum matches apart by what they match on
jo16oh Oct 14, 2026
c90fd2f
docs: describe the enum rows of the API table in the reader's terms
jo16oh Oct 14, 2026
c0b3a59
docs: drop the companion note from the `.implVariant` row
jo16oh Oct 14, 2026
22b53f0
docs: call the enum companion's seal a custom seal
jo16oh Oct 14, 2026
d21d1bf
docs: write enum variants as `E.Variant` in the API table
jo16oh Oct 14, 2026
e040a5f
docs: describe the enum's seal and `Tag` in the reader's terms
jo16oh Oct 14, 2026
06c099b
docs: shorten two enum rows of the API table
jo16oh Oct 14, 2026
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
22 changes: 12 additions & 10 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,21 +63,23 @@ re-export a companion.
| ------------------------------------ | -------------------------------------------------------- |
| `Enum<K, D, X>` | a closed set of variants, as one union |
| `Enum.sealer<E>(tag?)` | starts an enum whose variants are callable |
| `Enum.companion<E>(tag?)` | the same, for an enum with a seal of its own |
| `E.match(value, handlers)` | dispatches on the tag, exhaustively |
| `E[Variant](payload)` | builds that variant, writing the tag |
| `E[Variant].create(payload)` | the same on a companion, through its seal |
| `E[Variant].patch(value, patch)` | derives a variant, never reaching the tag |
| `E(payload)` / `E.seal(payload)` | selects the variant from the tag, and seals |
| `Enum.companion<E>(tag?)` | the same, for an enum with a custom seal |
| `E.match(value, handlers)` | matches on a variant's tag field, exhaustively |
| `E[Tag].match(tag, handlers)` | matches on a tag string, also exhaustively |
| `E(payload)` / `E.seal(payload)` | builds the variant that the payload's tag selects |
| `E[Tag].key` | the tag field's name |
| `E.Variant(payload)` | builds that variant, writing the tag |
| `E.Variant.create(payload)` | the same on a companion, through its seal |
| `E.Variant.patch(value, patch)` | derives a value of that variant, keeping the tag |
| `.impl(fns?)` | adds members taking the union, and ends the chain |
| `.implVariant(N, sealer => …)` | builds one variant from its own steps |
| `.implSeal(seal)` | replaces the seal every variant passes |
| `.implVariant(N, sealer => …)` | adds one variant's members, and its seal |
| `.implSeal(seal)` | replaces the seal shared by every variant |
| `.implTrait(Tr, fns?)` | implements a trait the enum declares |
| `.implTrait<Tr>(fns)` | the same, for a trait that implements nothing of its own |
| `.implTrait<Tr>(fns)` | the same, for a trait that has no default implementation |
| `Tag<T>` | names the tag field, intersected into `X` |
| `VariantOf<E, N>` | the type of one variant |
| `SeedFor<E, N>` | what that variant's constructor takes |
| `SealedPayload<E>` | what `E(payload)` takes, tag included |
| `SealedPayload<E>` | what an `.implSeal` seal receives, tag included |
| `VariantsOf<E>` / `SharedOf<E>` | the declared variants, and the shared fields |
| `NameOf<E>` / `TagOf<E>` | the enum's name, and the tag field's name |
| `AnyEnum` | a constraint over any enum |
Expand Down
52 changes: 46 additions & 6 deletions docs/src/enums.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,8 @@ Call a variant through the companion. `const { Circle } = Shape` works, and a re
another name, which `companion-mismatch` reports.

The constructor writes the tag. It does not take one, and it deep-copies its payload like any other
constructor. The enum itself takes a payload that already carries the tag, typed
`SealedPayload<Shape>`. It reads the tag, selects that variant's companion, and passes the payload
to its seal.
constructor. For a payload that carries its tag, or a tag that arrives alone, see
[Build a value](#build-a-value).

A variant is a Val. It patches, it compares, and it nests. A patch cannot reach the tag, so it
cannot switch variants.
Expand Down Expand Up @@ -80,6 +79,44 @@ to hard-code it.
Use `match` to split on the tag. For a condition inside a variant, a pattern matching library such
as ts-pattern fits. `_tag` is real data, so `.with({ _tag: "Circle" }, …)` already works.

## Build a value

An enum value can be built from three kinds of input, and each has its own call:

| When you have | Use |
| ------------------------------------------------------------- | ------------------------------------------------------------------------- |
| a payload for a variant you already know | `Shape.Circle(payload)`, or `Shape.Circle.create(payload)` on a companion |
| a payload carrying its tag, such as a schema library's output | `Shape(payload)`, or `Shape.seal(payload)` on a companion |
| the tag alone, with the fields kept elsewhere | `Shape[Tag].match(tag, handlers)` |

`Shape(payload)` reads the tag, selects that variant, and accepts only its fields. Passing a field
of another variant is a type error, even when the tag is a union.

When the tag arrives alone, `[Tag].match` selects the handler from the tag. On a command line, the
first argument is the subcommand, and how to read the rest depends on it:

```ts
import { Enum, Tag } from "valof/experimental";
// ---cut---
type Command = Enum<
"Command",
{ New: { name: string }; Rename: { from: string; to: string }; Check: Record<never, never> }
>;
const Command = Enum.sealer<Command>();
// The subcommand, already normalized to its tag, and the arguments after it.
declare const input: { tag: "New" | "Rename" | "Check"; args: readonly string[] };

const command = Command[Tag].match(input.tag, {
New: () => Command.New({ name: input.args[0] ?? "" }),
Rename: () => Command.Rename({ from: input.args[0] ?? "", to: input.args[1] ?? "" }),
Check: () => Command.Check({}),
});
```

Every tag the argument's type allows needs a handler, so adding a variant to the declaration fails
here too, as it does for `match`. A handler takes no argument and calls its variant's constructor,
so each payload is checked against its own variant.

## Common shape for every variant

The third argument is the shape: the fields every variant holds. It is the same argument a
Expand Down Expand Up @@ -175,21 +212,22 @@ is the enum's, read from the chain as it stands.
Whatever a seal returns propagates, as it does for a Val. The union in it narrows to the variant the
payload named. `patch` derives through the same seal, so no derivation skips it.

On a companion, `Shape.seal` takes that tagged payload. Its return is the variants' seals as a
union.
`Shape.seal` passes a tagged payload through the same seals. Its return is the variants' returns, as
a union.

## Custom tag key

The tag is real data, not a phantom. It crosses the wire, so you can customize the name when an API
already has one:

```ts
import { Enum, type Tag } from "valof/experimental";
import { Enum, Tag } from "valof/experimental";
// ---cut---
type Event = Enum<"Event", { Click: { x: number }; Key: { code: string } }, Tag<"kind">>;
const Event = Enum.sealer<Event>("kind");

Event.Click({ x: 1 }); // { x: 1, kind: "Click" }
Event[Tag].key; // "kind"
```

`Tag` goes in the third argument, beside the shared fields, because the tag is a field every variant
Expand All @@ -198,6 +236,8 @@ companion takes the name again because the proxy writes it at run time, and the
readable from a value. Forget it, misspell it, or pass one where the default applies, and the type
says so.

`Event[Tag].key` returns the name, for a schema library that asks for the discriminator.

## Use a variant's type

`VariantOf` is the type of a single variant, and it is what errors and hovers print. Write it where
Expand Down
Loading
Loading