---
title: Reference
description: The public functions, classes and types of @yarunoka/builder, generated from their doc comments.
sidebar:
  order: 2
---

<!-- Generated by `npm run docs:generate`. Edit the doc comments in src/, not this file. -->

## Functions

### createYrnkBuilder

`createYrnkBuilder(options?: YrnkBuilderOptions): YrnkBuilder`

The store — the one stateful object of this package. One draft, one "something changed" signal: finer subscription granularity is a binding's concern, and this minimal contract is exactly what React's useSyncExternalStore consumes in one line.

The derived values are pure over (snapshot, resolvers), so the expensive one — the exit through core's parse — is memoized per that pair. Swapping resolvers replaces the snapshot identity even though no field changed: the derived values change, and identity is the signal subscribers watch.

## Types

### BuilderOp

```ts
type BuilderOp =
  // document
  | { readonly type: 'document/set-label'; readonly value: string }
  | { readonly type: 'document/set-description'; readonly value: string }
  | { readonly type: 'document/set-timezone'; readonly value: string }
  // bulk
  | { readonly type: 'document/replace'; readonly draft: DraftDocument }
  | { readonly type: 'document/load'; readonly document: YrnkDocument }
  // resolvers
  | { readonly type: 'resolvers/add' }
  | { readonly type: 'resolvers/set'; readonly id: DraftId; readonly name: string }
  | { readonly type: 'resolvers/remove'; readonly id: DraftId }
  // calendar — the three built-in date-list positions
  | {
      readonly type: 'calendar/set-date-set-mode';
      readonly target: DateSetTarget;
      readonly mode: 'unset' | 'list' | 'name';
    }
  | {
      readonly type: 'calendar/set-date-set-name';
      readonly target: DateSetTarget;
      readonly name: string;
    }
  | { readonly type: 'calendar/add-date'; readonly target: DateSetTarget }
  | {
      readonly type: 'calendar/set-date';
      readonly target: DateSetTarget;
      readonly id: DraftId;
      readonly date: string;
    }
  | { readonly type: 'calendar/remove-date'; readonly target: DateSetTarget; readonly id: DraftId }
  // calendar — workweek and business hours
  | { readonly type: 'calendar/set-workweek'; readonly days: readonly YrnkDayName[] }
  | { readonly type: 'calendar/add-business-hours-window' }
  | {
      readonly type: 'calendar/set-business-hours-window';
      readonly id: DraftId;
      readonly start: string;
      readonly end: string;
    }
  | { readonly type: 'calendar/remove-business-hours-window'; readonly id: DraftId }
  // calendar — the date_sets namespace
  | { readonly type: 'calendar/add-date-set' }
  | { readonly type: 'calendar/rename-date-set'; readonly id: DraftId; readonly name: string }
  | { readonly type: 'calendar/remove-date-set'; readonly id: DraftId }
  | { readonly type: 'calendar/add-date-set-date'; readonly setId: DraftId }
  | {
      readonly type: 'calendar/set-date-set-date';
      readonly setId: DraftId;
      readonly id: DraftId;
      readonly date: string;
    }
  | {
      readonly type: 'calendar/remove-date-set-date';
      readonly setId: DraftId;
      readonly id: DraftId;
    }
  // schedules list
  | { readonly type: 'schedules/add' }
  | { readonly type: 'schedules/remove'; readonly id: DraftId }
  | { readonly type: 'schedules/move'; readonly id: DraftId; readonly to: number }
  // one schedule
  | { readonly type: 'schedule/set-label'; readonly scheduleId: DraftId; readonly value: string }
  | {
      readonly type: 'schedule/set-description';
      readonly scheduleId: DraftId;
      readonly value: string;
    }
  | { readonly type: 'schedule/set-from'; readonly scheduleId: DraftId; readonly value: string }
  | { readonly type: 'schedule/set-until'; readonly scheduleId: DraftId; readonly value: string }
  | { readonly type: 'schedule/add-year'; readonly scheduleId: DraftId }
  | {
      readonly type: 'schedule/set-year';
      readonly scheduleId: DraftId;
      readonly id: DraftId;
      readonly value: string;
    }
  | { readonly type: 'schedule/remove-year'; readonly scheduleId: DraftId; readonly id: DraftId }
  | { readonly type: 'schedule/add-month'; readonly scheduleId: DraftId }
  | {
      readonly type: 'schedule/set-month';
      readonly scheduleId: DraftId;
      readonly id: DraftId;
      readonly value: string;
    }
  | { readonly type: 'schedule/remove-month'; readonly scheduleId: DraftId; readonly id: DraftId }
  | { readonly type: 'schedule/add-day-atom'; readonly scheduleId: DraftId }
  | {
      readonly type: 'schedule/set-day-atom';
      readonly scheduleId: DraftId;
      readonly id: DraftId;
      readonly atom: DraftDayAtom;
    }
  | {
      readonly type: 'schedule/remove-day-atom';
      readonly scheduleId: DraftId;
      readonly id: DraftId;
    }
  | {
      readonly type: 'schedule/set-shift';
      readonly scheduleId: DraftId;
      readonly shift: DraftShift | null;
    }
  | { readonly type: 'schedule/set-if'; readonly scheduleId: DraftId; readonly if: DraftIf | null }
  | {
      readonly type: 'schedule/set-time-kind';
      readonly scheduleId: DraftId;
      readonly kind: DraftTimeSpec['kind'];
    }
  | { readonly type: 'schedule/add-time'; readonly scheduleId: DraftId }
  | {
      readonly type: 'schedule/set-time';
      readonly scheduleId: DraftId;
      readonly id: DraftId;
      readonly value: string;
    }
  | { readonly type: 'schedule/remove-time'; readonly scheduleId: DraftId; readonly id: DraftId }
  | {
      readonly type: 'schedule/set-grid-every';
      readonly scheduleId: DraftId;
      readonly count: string;
      readonly unit: YrnkTimeUnit | null;
    }
  | {
      readonly type: 'schedule/set-grid-between';
      readonly scheduleId: DraftId;
      readonly between: DraftBetween;
    }
  | {
      readonly type: 'schedule/set-sequence-every';
      readonly scheduleId: DraftId;
      readonly count: string;
      readonly unit: YrnkTimeUnit | null;
    };
```

The closed set of editing operations. An op writes the draft and nothing else — no validation, no refusal: the draft accepts what was typed, validity is derived afterwards (errors / optionsAt), and "don't offer that" is the UI's move, informed by optionsAt. Two classes of caller bug throw instead of writing: addressing an id that does not exist, and aiming a form-specific op at another form (a date-list op at a position not in list mode, a times / grid / sequence op at another time form).

Granularity: lists whose elements carry ids get element-level ops (add / set / remove); small sum-typed values (an atom, shift, if, between) are set whole — the UI builds the value, the op places it.

### DateSetTarget

```ts
type DateSetTarget = 'holidays' | 'business-holidays' | 'business-days';
```

Which built-in date-list position of the calendar an op addresses.

### DraftBetween

```ts
type DraftBetween =
  | { readonly kind: 'whole-day' }
  | { readonly kind: 'window'; readonly start: string; readonly end: string }
  | { readonly kind: 'business-hour' };
```

The grid's between position — a closed choice among the whole day, an explicit window, and the business_hour word, so no null form is needed.

### DraftCalendar

```ts
type DraftCalendar = {
  readonly holidays: DraftDateSetPosition;
  readonly businessHolidays: DraftDateSetPosition;
  readonly businessDays: DraftDateSetPosition;
  /** [] reads as "key omitted" (the default Mon–Fri workweek) */
  readonly workweek: readonly YrnkDayName[];
  /** [] reads as "key omitted" */
  readonly businessHours: readonly DraftEntry<{ readonly start: string; readonly end: string }>[];
  readonly dateSets: readonly DraftDateSetEntry[];
};
```

The calendar under edit: the three built-in date-list positions, the workweek, business hours, and the open date_sets namespace.

### DraftDateSetEntry

```ts
type DraftDateSetEntry = {
  readonly id: DraftId;
  readonly name: string;
  readonly dates: readonly DraftEntry<string>[];
};
```

An entry of date_sets. The model holds a Record, but a draft holds a list of named entries: renaming is then an in-place text edit, and a duplicated name is representable mid-edit (parse rejects it at the exit).

### DraftDateSetPosition

```ts
type DraftDateSetPosition =
  | { readonly mode: 'unset' }
  | { readonly mode: 'list'; readonly dates: readonly DraftEntry<string>[] }
  | { readonly mode: 'name'; readonly name: string };
```

A built-in date-list position (holidays / business_holidays / business_days). An explicit empty list is a meaningful statement ("there are no such days"), so absence needs its own mode rather than the empty-equals-omitted reading.

### DraftDayAtom

```ts
type DraftDayAtom =
  | { readonly kind: null }
  | { readonly kind: 'month-day'; readonly day: string }
  | { readonly kind: 'weekday'; readonly day: YrnkDayName | null }
  | {
      readonly kind: 'ordinal-weekday';
      readonly ordinal: YrnkOrdinal | null;
      readonly day: YrnkDayName | null;
    }
  | { readonly kind: 'last-day-of-month' }
  | { readonly kind: 'calendar-word'; readonly word: YrnkCalendarWord | null }
  | { readonly kind: 'name'; readonly name: string }
  | { readonly kind: 'day-cycle'; readonly interval: string };
```

One atom of the days enumeration: the model's forms with their leaves loosened, plus kind: null while the form is not chosen yet.

### DraftDayAtomEntry

```ts
type DraftDayAtomEntry = { readonly id: DraftId; readonly atom: DraftDayAtom };
```

A days-list element wrapped for identity — the day atom's counterpart of DraftEntry.

### DraftDayCondition

```ts
type DraftDayCondition = Exclude<DraftDayAtom, { kind: 'day-cycle' }>;
```

A day atom legal as a shift / if condition — every form except the day cycle.

### DraftDocument

```ts
type DraftDocument = {
  /** '' reads as "key omitted" */
  readonly label: string;
  readonly description: string;
  /** Always written out: a document cannot omit its timezone */
  readonly timezone: string;
  /** [] reads as "key omitted" */
  readonly resolvers: readonly DraftEntry<string>[];
  readonly calendar: DraftCalendar;
  /** [] is spellable but invalid; parse rejects it at the exit */
  readonly schedules: readonly DraftSchedule[];
};
```

The draft model's root — the document model of @yarunoka/core mirrored with every node loosened to tolerate work in progress. Three loosenings and nothing else:

- A freely-typed leaf (a time, a date, a boundary, an integer, a name, the timezone, an annotation) holds whatever string was typed. Validation is derived at read time; the draft never stores its own error state.

- A closed-set leaf holds its value or null (not chosen yet), and a structural choice (the time form, the day atom form) holds kind: null the same way.

- "Absent" splits by whether the document could spell it: where an empty spelling is invalid (annotations, boundaries, the axes, the resolvers list, workweek, business hours), the empty string / empty list reads as "key omitted"; where an explicit empty list is a meaningful statement (a date list), an explicit mode tells the forms apart.

### DraftEntry

```ts
type DraftEntry<T> = { readonly id: DraftId; readonly value: T };
```

A list element wrapped for identity: the id names the slot, not the value.

### DraftEveryTuple

```ts
type DraftEveryTuple = {
  readonly count: string;
  readonly unit: YrnkTimeUnit | null;
};
```

The every tuple under edit: the count as typed, and the unit or null.

### DraftId

```ts
type DraftId = string;
```

Opaque, draft-only; the UI's list key and the ops' address. toYrnk drops it.

### DraftIf

```ts
type DraftIf = {
  /**
   * null means "the day itself", exactly as in the model — the default
   * is itself a valid choice, so "not chosen yet" needs no extra state
   */
  readonly direction: YrnkDirection | null;
  readonly negated: boolean;
  readonly condition: DraftDayCondition;
};
```

The if modifier under edit — filtering without moving, as in the model.

### DraftPath

```ts
type DraftPath = readonly string[];
```

Where a problem sits in the draft: field names and draft ids, outermost first (e.g. ['schedules', 's3', 'from']). Ids rather than indexes, so the path survives list edits around it.

### DraftProblem

```ts
type DraftProblem = {
  readonly path: DraftPath;
  /** Wording comes from @yarunoka/core wherever core has the rule */
  readonly message: string;
  readonly origin: 'field' | 'document';
};
```

One validation finding. A field problem is a core validation helper's answer placed at its draft path; a document problem is core's parse() rejecting the whole (it carries no path — it appears only once every field is individually clean).

### DraftSchedule

```ts
type DraftSchedule = {
  readonly id: DraftId;
  readonly label: string;
  readonly description: string;
  /** '' reads as "key omitted" */
  readonly from: string;
  readonly until: string;
  /** [] reads as "axis absent" (no restriction) */
  readonly years: readonly DraftEntry<string>[];
  readonly months: readonly DraftEntry<string>[];
  readonly days: readonly DraftDayAtomEntry[];
  readonly shift: DraftShift | null;
  readonly if: DraftIf | null;
  readonly time: DraftTimeSpec;
};
```

One schedule under edit. The shape mirrors the model's schedule — the axes, the modifiers, the time part — with the id as the schedule's address for ops, options, and problem paths.

### DraftShift

```ts
type DraftShift = {
  readonly direction: YrnkDirection | null;
  readonly orSame: boolean;
  readonly condition: DraftDayCondition;
};
```

The shift modifier under edit. Unlike the model's shift, direction may be null — a shift can exist before its direction is chosen.

### DraftTimeSpec

```ts
type DraftTimeSpec =
  | { readonly kind: null }
  | { readonly kind: 'times'; readonly times: readonly DraftEntry<string>[] }
  | { readonly kind: 'grid'; readonly every: DraftEveryTuple; readonly between: DraftBetween }
  | { readonly kind: 'allday' }
  | { readonly kind: 'sequence'; readonly every: DraftEveryTuple };
```

The time part under edit: one of the model's four forms, or kind: null while the form is not chosen yet.

### OptionContext

```ts
type OptionContext =
  | { readonly at: 'day-atom-kind'; readonly scheduleId: DraftId }
  | { readonly at: 'condition-kind'; readonly scheduleId: DraftId; readonly of: 'shift' | 'if' }
  | { readonly at: 'time-kind'; readonly scheduleId: DraftId }
  | { readonly at: 'between-kind'; readonly scheduleId: DraftId }
  | { readonly at: 'calendar-word'; readonly scheduleId: DraftId }
  | { readonly at: 'name'; readonly scheduleId: DraftId };
```

A decision point the UI can ask about. Every context is addressed through a schedule: the questions are asked while editing one, and the answers that depend on the document (the calendar, the names) read it through the draft as a whole.

### Options

```ts
type Options = readonly {
  readonly value: string;
  readonly available: boolean;
  /** Why not, when available is false */
  readonly reason?: string;
}[];
```

What a decision point offers: every value of its closed set, each marked available or not. Unavailable options are answered rather than dropped — whether to hide or to disable is the UI's decision.

### PreviewOccurrence

```ts
type PreviewOccurrence = {
  readonly occurrence: YrnkOccurrence;
  /** The draft schedules this occurrence came from */
  readonly scheduleIds: readonly DraftId[];
};
```

One line of a preview: the occurrence as core answers it, and which draft schedules produced it.

### PreviewRequest

```ts
type PreviewRequest =
  | {
      /** How many upcoming occurrences to answer */
      readonly next: number;
      /**
       * The instant the answered occurrences lie strictly after; the
       * current instant when omitted
       */
      readonly after?: YrnkInstant;
      /** How far the search may reach; ten years when omitted */
      readonly horizon?: Temporal.Duration;
    }
  | { readonly range: { readonly from: YrnkInstant; readonly through: YrnkInstant } };
```

What to preview: the next N occurrences after an instant, or every occurrence in an explicit range.

### PreviewResult

```ts
type PreviewResult =
  | {
      readonly ok: true;
      readonly occurrences: readonly PreviewOccurrence[];
      /** True when the horizon ended a "next N" search short of N */
      readonly exhausted: boolean;
    }
  | { readonly ok: false; readonly problems: readonly DraftProblem[] };
```

What preview() answers: the occurrences and whether the horizon cut a "next N" search short, or — when the draft cannot export — the same problems errors() reports.

### ToYrnkResult

```ts
type ToYrnkResult =
  | { readonly ok: true; readonly document: YrnkDocument; readonly raw: Record<string, unknown> }
  | { readonly ok: false; readonly problems: readonly DraftProblem[] };
```

The exit's answer: the parsed document with its raw spelling when the draft exports cleanly, or the problems that keep it from the wire.

### YrnkBuilder

```ts
type YrnkBuilder = {
  /** The current draft — an immutable snapshot */
  getState(): DraftDocument;
  /** Change notification; the returned function unsubscribes */
  subscribe(listener: () => void): () => void;
  dispatch(op: BuilderOp): void;
  setResolvers(resolvers: Readonly<Record<string, YrnkResolver>>): void;

  errors(): readonly DraftProblem[];
  optionsAt(context: OptionContext): Options;
  toYrnk(): ToYrnkResult;
  preview(request: PreviewRequest): PreviewResult;
};
```

The store's handle: the external-store trio (getState / subscribe / dispatch) plus the derived values an editor reads — validation, options, the strict document, and upcoming occurrences.

### YrnkBuilderOptions

```ts
type YrnkBuilderOptions = {
  /** The document to start editing; an empty new draft when omitted */
  readonly initial?: YrnkDocument;
  /** What the host binds the draft's declared resolver names to */
  readonly resolvers?: Readonly<Record<string, YrnkResolver>>;
};
```

What createYrnkBuilder accepts: the starting document and the host's resolver bindings.
