@vielzeug/codex 2.2.7 → 2.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/catalog.json +1693 -0
- package/data/llms-full.txt +27748 -0
- package/data/llms.txt +40 -0
- package/data/manifest.json +8 -0
- package/data/packages/arsenal.json +210 -0
- package/data/packages/assay.json +39 -0
- package/data/packages/clockwork.json +67 -0
- package/data/packages/codex.json +43 -0
- package/data/packages/coins.json +102 -0
- package/data/packages/conduit.json +60 -0
- package/data/packages/courier.json +58 -0
- package/data/packages/dnd.json +77 -0
- package/data/packages/familiar.json +40 -0
- package/data/packages/flux.json +93 -0
- package/data/packages/forge.json +84 -0
- package/data/packages/herald.json +108 -0
- package/data/packages/keymap.json +60 -0
- package/data/packages/ledger.json +57 -0
- package/data/packages/lingua.json +67 -0
- package/data/packages/necromancer.json +50 -0
- package/data/packages/orbit.json +99 -0
- package/data/packages/ore.json +73 -0
- package/data/packages/prism.json +66 -0
- package/data/packages/pulse.json +69 -0
- package/data/packages/refine.json +12 -0
- package/data/packages/ripple.json +83 -0
- package/data/packages/rune.json +79 -0
- package/data/packages/sandbox.json +40 -0
- package/data/packages/scout.json +60 -0
- package/data/packages/scroll.json +109 -0
- package/data/packages/sourcerer.json +72 -0
- package/data/packages/spell.json +133 -0
- package/data/packages/tempo.json +81 -0
- package/data/packages/vault.json +85 -0
- package/data/packages/ward.json +114 -0
- package/data/packages/wayfinder.json +110 -0
- package/data/refine.json +11926 -0
- package/data/search.json +1432 -0
- package/package.json +1 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"apiSource": "// Tempo keeps this re-export so all consumers share one Temporal implementation and version.\nexport { Temporal } from '@js-temporal/polyfill';\nexport { inTimeZone, toInstant } from './_convert';\nexport { endOf, startOf } from './boundary';\nexport { classifyExpiry, timeDiff } from './classify';\nexport { clamp, contains, isAfter, isBefore, isSame } from './compare';\nexport { difference, isValid, now, nowInstant, parse, shift } from './core';\nexport {\n TempoError,\n TempoInvalidInputError,\n TempoInvalidTzError,\n TempoMissingTzError,\n TempoUnsupportedInputError,\n} from './errors';\nexport {\n format,\n formatDuration,\n formatInstant,\n formatParts,\n formatRange,\n formatRangeParts,\n formatRelative,\n formatZoned,\n humanize,\n parseDuration,\n} from './format';\nexport { dateRange, recurrence } from './range';\nexport type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';\n",
|
|
3
|
+
"docs": {
|
|
4
|
+
"index": "---\ntitle: Tempo — Temporal date and time utilities\ndescription: Explicit Temporal parsing, timezone-safe arithmetic, and localized date/time formatting for TypeScript.\npackage: tempo\ncategory: time\nkeywords: [temporal, date-time, timezone, formatting, arithmetic, dst, intl]\nrelated: [rune, vault]\nexports: [Temporal, parse, now, nowInstant, isValid, toInstant, inTimeZone, shift, difference, contains, clamp, isBefore, isAfter, isSame, startOf, endOf, format, formatParts, formatRange, formatRangeParts, formatInstant, formatZoned, formatRelative, parseDuration, formatDuration, classifyExpiry, timeDiff, humanize, dateRange, recurrence, TempoError, TempoInvalidInputError, TempoInvalidTzError, TempoMissingTzError, TempoUnsupportedInputError]\nenvironments: [browser, node, ssr, deno]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"tempo\" />\n\n## Why Tempo?\n\nDate/time bugs come from treating an instant and a wall-clock value as interchangeable. Tempo requires an explicit parse target and requires `timeZone` whenever a wall-clock value becomes an instant.\n\n```ts\n// Before\nconst reminder = new Date(meeting.getTime() - 15 * 60_000);\n\n// After\nimport { parse, shift, toInstant } from '@vielzeug/tempo';\n\nconst localMeeting = parse('2026-03-21T10:30:00', { as: 'plainDateTime' });\nconst meeting = toInstant(localMeeting, { timeZone: 'America/New_York' });\nconst reminder = shift(meeting, { minutes: -15 }, { timeZone: 'America/New_York' });\n```\n\n| Feature | Tempo | date-fns | Native Date |\n| --- | --- | --- | --- |\n| Bundle size | <PackageInfo package=\"tempo\" type=\"size\" /> | ~10 kB | 0 kB |\n| Zero dependencies | <ore-icon name=\"x\" size=\"16\"></ore-icon> `@js-temporal/polyfill` | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n| Explicit wall-time conversion | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Manual | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| DST-safe arithmetic | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Manual | Manual |\n| Localized formatting | <ore-icon name=\"check\" size=\"16\"></ore-icon> `Intl` | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n\n<div class=\"decision-callout\">\n\n**Use Tempo when** you need Temporal values, explicit timezone rules, and DST-safe operations.\n\n**Consider native `Date` when** your data is only elapsed milliseconds and you do not need calendar or timezone behavior.\n\n</div>\n\n## Installation\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/tempo\n```\n\n```sh [npm]\nnpm install @vielzeug/tempo\n```\n\n```sh [yarn]\nyarn add @vielzeug/tempo\n```\n\n:::\n\n## Quick Start\n\nParse a wall-clock input explicitly, attach its timezone, then format it for a user.\n\n```ts\nimport { format, inTimeZone, parse, shift, toInstant } from '@vielzeug/tempo';\n\nconst localMeeting = parse('2026-03-21T10:30:00', { as: 'plainDateTime' });\nconst meeting = toInstant(localMeeting, { timeZone: 'America/New_York' });\nconst reminder = shift(meeting, { minutes: -15 }, { timeZone: 'America/New_York' });\nconst text = format(inTimeZone(reminder, 'America/New_York'), {\n locale: 'en-US',\n pattern: 'short',\n});\n```\n\n## Features\n\n<div class=\"features-grid\">\n\n- `parse()` — Requires an explicit ISO target: instant, zoned date-time, plain date-time, or plain date.\n- `toInstant()` / `inTimeZone()` — Convert wall-clock and absolute values with explicit timezone semantics.\n- `shift()` / `difference()` — Perform DST-safe arithmetic and duration calculation.\n- `contains()` / `clamp()` — Use named range fields instead of ambiguous positional inputs.\n- `classifyExpiry()` — Classify fixed elapsed-time thresholds in milliseconds or larger units without month or year approximation.\n- `format()` / `formatRelative()` / `formatDuration()` — Render UI, relative, and duration values through `Intl`.\n- `dateRange()` / `recurrence()` — Lazily generate zoned calendar sequences.\n\n</div>\n\n## Documentation\n\n<div class=\"doc-links\">\n\n- [Usage Guide](./usage.md)\n- [API Reference](./api.md)\n- [Examples](./examples.md)\n- [Migration Guide](./migration.md)\n\n</div>\n\n## See Also\n\n<div class=\"see-also\">\n\n- [Rune](/rune/) — format stable Temporal timestamps before writing structured log records.\n- [Vault](/vault/) — derive explicit expiry moments before storing records with TTL policies.\n\n</div>\n\n<!-- markdownlint-enable MD025 MD033 MD060 -->\n",
|
|
5
|
+
"api": "---\ntitle: Tempo — API Reference\ndescription: Reference for Tempo Temporal parsing, conversion, arithmetic, formatting, and classification APIs.\n---\n\n[[toc]]\n\n## API Overview\n\n| Symbol | Purpose | Execution mode | Common gotcha |\n| --- | --- | --- | --- |\n| `parse()` | Parse ISO text to an explicit Temporal kind | Sync | `as` is required |\n| `isValid()` | Narrow an unknown runtime value to `TimeInput` | Sync | Does not parse strings |\n| `toInstant()` | Resolve a value as an absolute instant | Sync | Plain values require `timeZone` |\n| `inTimeZone()` | Project a value to a zone | Sync | Preserves instant, changes wall-clock fields |\n| `shift()` / `difference()` | DST-safe arithmetic | Sync | Calendar work needs a timezone |\n| `contains()` / `clamp()` | Named range operations | Sync | Bounds normalize automatically |\n| `classifyExpiry()` | Classify fixed elapsed-time thresholds | Sync | Use milliseconds or larger units; months and years are rejected |\n| `format()` family | Localized and machine formatting | Sync | Use `timeZone`, not `tz` |\n| `dateRange()` / `recurrence()` | Lazy zoned sequences | Sync | Plain inputs need `timeZone` |\n\n## Package Entry Point\n\n| Import | Purpose |\n| --- | --- |\n| `@vielzeug/tempo` | Tempo utilities, errors, types, and shared `Temporal` namespace |\n\n## Core Functions\n\n### `parse(input, { as })`\n\n```ts\nparse(input: string, options: { as: 'instant' }): Temporal.Instant;\nparse(input: string, options: { as: 'zonedDateTime' }): Temporal.ZonedDateTime;\nparse(input: string, options: { as: 'plainDateTime' }): Temporal.PlainDateTime;\nparse(input: string, options: { as: 'plainDate' }): Temporal.PlainDate;\n```\n\nParses an ISO 8601 string as the requested temporal kind.\n\n**Parameters**\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `input` | `string` | ISO 8601 input |\n| `options.as` | `ParseAs` | Required result kind |\n\n**Returns:** Requested Temporal value.\n\n**Example:**\n\n```ts\nimport { parse } from '@vielzeug/tempo';\n\nconst instant = parse('2026-03-21T10:15:30Z', { as: 'instant' });\n```\n\n---\n\n### `isValid(value)`\n\n```ts\nisValid(value: unknown): value is TimeInput;\n```\n\nReturns whether `value` is a Tempo-supported Temporal value. It does not parse ISO strings.\n\n**Example:**\n\n```ts\nimport { isValid, parse } from '@vielzeug/tempo';\n\nconst value: unknown = parse('2026-03-21T10:15:30Z', { as: 'instant' });\nconst valid = isValid(value); // true\n```\n\n---\n\n### `now({ timeZone })` / `nowInstant()`\n\n```ts\nnow(options: { timeZone: string }): Temporal.ZonedDateTime;\nnowInstant(): Temporal.Instant;\n```\n\nReturns current zoned or absolute time.\n\n**Example:**\n\n```ts\nimport { now, nowInstant } from '@vielzeug/tempo';\n\nnow({ timeZone: 'Europe/Berlin' });\nnowInstant();\n```\n\n---\n\n### `toInstant(input, options?)` / `inTimeZone(input, timeZone)`\n\n```ts\ntoInstant(input: AbsoluteTime): Temporal.Instant;\ntoInstant(input: WallTime, options: { timeZone: string } & DisambiguationOptions): Temporal.Instant;\ninTimeZone(input: TimeInput, timeZone: string): Temporal.ZonedDateTime;\n```\n\n`toInstant()` resolves wall-clock values. `inTimeZone()` projects a value into a requested timezone.\n\n**Example:**\n\n```ts\nimport { inTimeZone, parse, toInstant } from '@vielzeug/tempo';\n\nconst local = parse('2026-11-01T01:30:00', { as: 'plainDateTime' });\nconst instant = toInstant(local, { disambiguation: 'later', timeZone: 'America/New_York' });\ninTimeZone(instant, 'Europe/Berlin');\n```\n\n---\n\n### `shift(input, duration, options?)`\n\n```ts\nshift(input: Temporal.ZonedDateTime, duration: Temporal.DurationLike, options?: ShiftOptions): Temporal.ZonedDateTime;\nshift(\n input: Exclude<TimeInput, Temporal.ZonedDateTime>,\n duration: Temporal.DurationLike,\n options: ShiftOptions & { timeZone: string },\n): Temporal.ZonedDateTime;\n```\n\nAdds a duration through Temporal calendar rules and returns a zoned value. Non-`ZonedDateTime` inputs require `options.timeZone`.\n\n**Returns:** `Temporal.ZonedDateTime`.\n\n**Example:**\n\n```ts\nimport { parse, shift } from '@vielzeug/tempo';\n\nconst before = parse('2026-03-08T01:30:00-05:00[America/New_York]', { as: 'zonedDateTime' });\nshift(before, { hours: 1 });\n```\n\n---\n\n### `difference({ start, end, ...options })`\n\n```ts\ndifference(input: DifferenceInput): Temporal.Duration;\n```\n\nReturns duration from `start` to `end`.\n\n**Example:**\n\n```ts\nimport { difference, parse } from '@vielzeug/tempo';\n\nconst start = parse('2026-03-21T10:00:00Z', { as: 'instant' });\nconst end = parse('2026-03-21T12:00:00Z', { as: 'instant' });\ndifference({ end, largestUnit: 'hour', start });\n```\n\n## Range and Comparison\n\n### `contains({ value, start, end, ...options })`\n\n```ts\ncontains(input: ContainsInput): boolean;\n```\n\nReturns whether `value` lies in inclusive normalized bounds.\n\n### `clamp({ value, start, end, ...options })`\n\n```ts\nclamp(input: ClampInput & { value: Temporal.ZonedDateTime }): Temporal.ZonedDateTime;\nclamp(input: ClampInput): Temporal.Instant;\n```\n\nReturns the nearest bound when `value` falls outside the range. Returns a `ZonedDateTime` when `value` is one, otherwise an `Instant`.\n\n### `isBefore(a, b, options?)` / `isAfter(a, b, options?)` / `isSame(a, b, options?)`\n\n```ts\nisBefore(a: TimeInput, b: TimeInput, options?: CompareOptions): boolean;\nisAfter(a: TimeInput, b: TimeInput, options?: CompareOptions): boolean;\nisSame(a: TimeInput, b: TimeInput, options?: CompareOptions): boolean;\n```\n\nCompare absolute values or calendar boundaries when `unit` is supplied.\n\n### `startOf(input, unit, options?)` / `endOf(input, unit, options?)`\n\n```ts\nstartOf(input: TimeInput, unit: BoundaryUnit, options?: BoundaryOptions): Temporal.ZonedDateTime;\nendOf(input: TimeInput, unit: BoundaryUnit, options?: BoundaryOptions): Temporal.ZonedDateTime;\n```\n\nReturns the first or last nanosecond of the requested boundary unit.\n\n## Formatting\n\n### `format(input, options?)`\n\n```ts\nformat(input: TimeInput, options?: FormatOptions): string;\n```\n\nFormats a value through `Intl.DateTimeFormat`.\n\n**Example:**\n\n```ts\nimport { format, parse } from '@vielzeug/tempo';\n\nformat(parse('2026-03-21T10:15:30Z', { as: 'instant' }), {\n locale: 'en-GB',\n pattern: 'short',\n timeZone: 'UTC',\n});\n```\n\n### `formatInstant()` / `formatZoned()` / `formatRelative()` / `formatDuration()`\n\n```ts\nformatInstant(input: TimeInput, options?: TimeZoneOptions): string;\nformatZoned(input: TimeInput, options?: TimeZoneOptions): string;\nformatRelative(input: RelativeTimeInput, options?: RelativeFormatOptions): string;\nformatDuration(input: string | Temporal.DurationLike, options?: DurationFormatOptions): string;\n```\n\n`formatInstant()` produces UTC transport text (`timeZone` needed for wall-time input, ignored for `Instant`). `formatZoned()` produces zoned ISO text (`timeZone` required for non-`ZonedDateTime` input). `formatDuration()` falls back to English when `Intl.DurationFormat` is unavailable.\n\n### `formatParts()` / `formatRange()` / `formatRangeParts()`\n\n```ts\nformatParts(input: TimeInput, options?: FormatOptions): Intl.DateTimeFormatPart[];\nformatRange(start: TimeInput, end: TimeInput, options?: FormatOptions): string;\nformatRangeParts(\n start: TimeInput,\n end: TimeInput,\n options?: FormatOptions,\n): ReturnType<Intl.DateTimeFormat['formatRangeToParts']>;\n```\n\nReturn `Intl` parts or localized range strings using `FormatOptions`.\n\n### `parseDuration()` / `humanize()`\n\n```ts\nparseDuration(input: string | Temporal.DurationLike): Temporal.Duration;\nhumanize(diff: TimeDiffResult, options?: { locale?: Intl.LocalesArgument }): string;\n```\n\n`humanize()` localizes numbers only. Unit names remain English.\n\n## Classification and Sequences\n\n### `classifyExpiry({ value, thresholds, relativeTo?, timeZone? })`\n\n```ts\nclassifyExpiry<K extends string>(input: ClassifyExpiryInput<K>): K | null;\n```\n\nClassifies an expiry against fixed elapsed-time thresholds in milliseconds or larger units. Months and years throw `TempoInvalidInputError`.\n\n### `timeDiff(a, b?, options?)`\n\n```ts\ntimeDiff(a: TimeInput, b?: TimeInput, options?: TimeZoneOptions): TimeDiffResult;\n```\n\nReturns absolute calendar difference in its largest meaningful unit.\n\n### `dateRange()` / `recurrence()`\n\n```ts\ndateRange(start: TimeInput, end: TimeInput, step: Temporal.DurationLike, options?: TimeZoneOptions): Generator<Temporal.ZonedDateTime>;\nrecurrence(start: TimeInput, rule: RecurrenceRule, options?: TimeZoneOptions): Generator<Temporal.ZonedDateTime>;\n```\n\nReturns lazy `ZonedDateTime` sequences.\n\n## Types\n\n```ts\ntype AbsoluteTime = Temporal.Instant | Temporal.ZonedDateTime;\ntype WallTime = Temporal.PlainDate | Temporal.PlainDateTime;\ntype TimeInput = AbsoluteTime | WallTime;\ntype RelativeTimeInput = AbsoluteTime;\ntype ParseAs = 'instant' | 'plainDate' | 'plainDateTime' | 'zonedDateTime';\ntype Disambiguation = 'compatible' | 'earlier' | 'later' | 'reject';\ntype FormatPattern = 'date-only' | 'long' | 'medium' | 'short' | 'time-only';\ntype TempoUnit = 'day' | 'hour' | 'microsecond' | 'millisecond' | 'minute' | 'month' | 'nanosecond' | 'second' | 'week' | 'year';\ntype CalendarUnit = Extract<TempoUnit, 'day' | 'month' | 'week' | 'year'>;\ntype BoundaryUnit = Exclude<TempoUnit, 'microsecond' | 'millisecond' | 'nanosecond' | 'second'>;\ntype WeekStartDay = 1 | 2 | 3 | 4 | 5 | 6 | 7;\ntype FixedDuration = Pick<Temporal.DurationLike, 'days' | 'hours' | 'microseconds' | 'milliseconds' | 'minutes' | 'nanoseconds' | 'seconds' | 'weeks'>;\ntype ExpiryThresholds<K extends string> = Record<K, FixedDuration>;\ntype TimeDiffUnit = Exclude<TempoUnit, 'microsecond' | 'nanosecond'>;\ntype TimeDiffResult = { unit: TimeDiffUnit; value: number };\ntype RecurrenceRule =\n | { frequency: 'daily' | 'monthly' | 'weekly' | 'yearly'; interval?: number; count: number; until?: TimeInput }\n | { frequency: 'daily' | 'monthly' | 'weekly' | 'yearly'; interval?: number; count?: number; until: TimeInput };\n\ninterface TimeZoneOptions { timeZone?: string }\ninterface DisambiguationOptions { disambiguation?: Disambiguation }\ninterface ShiftOptions extends DisambiguationOptions, TimeZoneOptions {}\ninterface DifferenceInput extends DisambiguationOptions, TimeZoneOptions {\n start: TimeInput;\n end: TimeInput;\n largestUnit?: Temporal.DateTimeUnit;\n smallestUnit?: Temporal.DateTimeUnit;\n roundingIncrement?: number;\n roundingMode?: Temporal.RoundingMode;\n}\ntype FormatOptions =\n | { intl: Intl.DateTimeFormatOptions; locale?: Intl.LocalesArgument; pattern?: never; timeZone?: string }\n | { intl?: never; locale?: Intl.LocalesArgument; pattern?: FormatPattern; timeZone?: string };\ninterface RelativeFormatOptions {\n base?: RelativeTimeInput;\n locale?: Intl.LocalesArgument;\n numeric?: Intl.RelativeTimeFormatNumeric;\n style?: Intl.RelativeTimeFormatStyle;\n}\ninterface DurationFormatOptions {\n locale?: Intl.LocalesArgument;\n style?: 'digital' | 'long' | 'narrow' | 'short';\n}\ninterface BoundaryOptions extends TimeZoneOptions { weekStartsOn?: WeekStartDay }\ninterface CompareOptions extends TimeZoneOptions { unit?: BoundaryUnit; weekStartsOn?: WeekStartDay }\ninterface ContainsInput extends CompareOptions { value: TimeInput; start: TimeInput; end: TimeInput }\ninterface ClampInput extends CompareOptions { value: TimeInput; start: TimeInput; end: TimeInput }\ninterface ClassifyExpiryInput<K extends string> extends TimeZoneOptions {\n value: TimeInput;\n thresholds: ExpiryThresholds<K>;\n relativeTo?: Temporal.Instant;\n}\n```\n\n## Errors\n\n| Error | Trigger | Notable properties |\n| --- | --- | --- |\n| `TempoError` | Base Tempo error | `instanceof TempoError` narrows every subtype |\n| `TempoInvalidInputError` | Invalid parse, duration, or fixed-threshold input | Extends `TempoError` |\n| `TempoInvalidTzError` | Invalid IANA zone or offset | Extends `TempoError` |\n| `TempoMissingTzError` | Wall time without required `timeZone` | Extends `TempoError` |\n| `TempoUnsupportedInputError` | Non-Temporal input passed to conversion | Extends `TempoError` |\n",
|
|
6
|
+
"usage": "---\ntitle: Tempo — Usage Guide\ndescription: Parse explicit Temporal values, resolve wall-clock time, compare ranges, and format dates with Tempo.\n---\n\n[[toc]]\n\n## Basic Usage\n\nParse ISO input with a declared target. Convert plain values with `timeZone` before treating them as an instant.\n\n```ts\nimport { format, inTimeZone, parse, shift, toInstant } from '@vielzeug/tempo';\n\nconst local = parse('2026-03-21T10:15:30', { as: 'plainDateTime' });\nconst instant = toInstant(local, { timeZone: 'America/New_York' });\nconst reminder = shift(instant, { minutes: -15 }, { timeZone: 'America/New_York' });\n\nformat(inTimeZone(reminder, 'America/New_York'), { locale: 'en-US', pattern: 'short' });\n```\n\n## Parse ISO Values\n\nChoose the value your boundary actually represents. Tempo does not auto-detect ISO strings.\n\n```ts\nimport { parse } from '@vielzeug/tempo';\n\nconst occurredAt = parse('2026-03-21T10:15:30Z', { as: 'instant' });\nconst meeting = parse('2026-03-21T10:15:30+01:00[Europe/Berlin]', { as: 'zonedDateTime' });\nconst localStart = parse('2026-03-21T10:15:30', { as: 'plainDateTime' });\nconst birthday = parse('2026-03-21', { as: 'plainDate' });\n```\n\n## Convert Timezones\n\nUse `inTimeZone()` to project an absolute value. Use `toInstant()` only when resolving a wall-clock value.\n\n```ts\nimport { inTimeZone, parse, toInstant } from '@vielzeug/tempo';\n\nconst local = parse('2026-11-01T01:30:00', { as: 'plainDateTime' });\nconst firstOccurrence = toInstant(local, {\n disambiguation: 'earlier',\n timeZone: 'America/New_York',\n});\n\nconst berlin = inTimeZone(firstOccurrence, 'Europe/Berlin');\n```\n\n## Calculate and Compare\n\nUse object inputs for operations with multiple time values.\n\n```ts\nimport { clamp, contains, difference, parse } from '@vielzeug/tempo';\n\nconst start = parse('2026-03-21T10:00:00Z', { as: 'instant' });\nconst end = parse('2026-03-21T12:00:00Z', { as: 'instant' });\nconst value = parse('2026-03-21T13:00:00Z', { as: 'instant' });\n\nconst duration = difference({ end, largestUnit: 'hour', start });\nconst isScheduled = contains({ end, start, value });\nconst bounded = clamp({ end, start, value });\n```\n\n## Classify Expiry\n\nUse fixed elapsed-time thresholds in milliseconds or larger units. Handle `null` as the unclassified state instead of adding a far-future catch-all.\n\n```ts\nimport { classifyExpiry, parse } from '@vielzeug/tempo';\n\nconst status = classifyExpiry({\n relativeTo: parse('2026-06-01T00:00:00Z', { as: 'instant' }),\n thresholds: {\n expired: { days: 0 },\n critical: { days: 3 },\n warning: { days: 14 },\n },\n value: parse('2026-06-04T00:00:00Z', { as: 'instant' }),\n});\n\nconst label = status ?? 'safe';\n```\n\n## Format Values\n\nUse `format()` for UI, `formatInstant()` for transport, and `formatZoned()` for a zoned ISO string.\n\n```ts\nimport { format, formatInstant, formatRelative, formatZoned, parse } from '@vielzeug/tempo';\n\nconst instant = parse('2026-03-21T10:15:30Z', { as: 'instant' });\n\nformat(instant, { locale: 'en-GB', pattern: 'short', timeZone: 'UTC' });\nformatInstant(instant);\nformatZoned(instant, { timeZone: 'Europe/Berlin' });\nformatRelative(instant, { base: parse('2026-03-21T09:15:30Z', { as: 'instant' }) });\n```\n\n## Generate Calendar Sequences\n\nUse zoned inputs for date sequences so the timezone is inferred.\n\n```ts\nimport { dateRange, parse, recurrence } from '@vielzeug/tempo';\n\nconst start = parse('2026-03-01T00:00:00[UTC]', { as: 'zonedDateTime' });\nconst end = parse('2026-03-31T00:00:00[UTC]', { as: 'zonedDateTime' });\n\nconst days = [...dateRange(start, end, { days: 1 })];\nconst meetings = [...recurrence(start, { count: 4, frequency: 'weekly' })];\n```\n\n## Testing\n\nPin the reference instant for deterministic expiry tests.\n\n```ts\nimport { classifyExpiry, parse } from '@vielzeug/tempo';\n\nconst relativeTo = parse('2026-06-01T00:00:00Z', { as: 'instant' });\nconst value = parse('2026-05-31T00:00:00Z', { as: 'instant' });\n\nclassifyExpiry({ relativeTo, thresholds: { expired: { days: 0 } }, value });\n```\n\n## Framework Integration\n\nPass ISO strings through component props. Parse and format at the rendering boundary.\n\n::: code-group\n\n```tsx [React]\nimport { format, parse } from '@vielzeug/tempo';\n\nconst label = format(parse(iso, { as: 'instant' }), { locale: 'en-US', pattern: 'medium', timeZone: 'UTC' });\n```\n\n```vue [Vue 3]\n<script setup lang=\"ts\">\nimport { format, parse } from '@vielzeug/tempo';\n\nconst props = defineProps<{ iso: string }>();\nconst label = format(parse(props.iso, { as: 'instant' }), { locale: 'en-US', pattern: 'medium', timeZone: 'UTC' });\n</script>\n```\n\n```svelte [Svelte]\n<script lang=\"ts\">\n import { format, parse } from '@vielzeug/tempo';\n export let iso: string;\n $: label = format(parse(iso, { as: 'instant' }), { locale: 'en-US', pattern: 'medium', timeZone: 'UTC' });\n</script>\n```\n\n:::\n\n## Working with Other Vielzeug Libraries\n\n### With Rune\n\nWrite stable UTC timestamps to structured logs.\n\n```ts\nimport { formatInstant, nowInstant } from '@vielzeug/tempo';\n\nlogger.info({ timestamp: formatInstant(nowInstant()) }, 'server started');\n```\n\n### With Vault\n\nCalculate an explicit instant before storing an expiring record.\n\n```ts\nimport { now, shift } from '@vielzeug/tempo';\n\nconst expiresAt = shift(now({ timeZone: 'UTC' }), { minutes: 30 }).toInstant();\n```\n\n## Best Practices\n\n- Parse each string with its actual temporal meaning.\n- Pass `timeZone` when converting a plain date or plain date-time.\n- Use `disambiguation` for DST overlap and gap handling.\n- Pass named fields to `difference()`, `contains()`, `clamp()`, and `classifyExpiry()`.\n- Use fixed duration units for expiry thresholds.\n- Store instants for transport and database values.\n- Use `formatInstant()` for machine output and `format()` for user-facing text.\n",
|
|
7
|
+
"examples": "---\ntitle: Tempo — Examples\ndescription: Practical examples and recipes for tempo.\n---\n\n## Examples\n\n- [DST-Safe Arithmetic](./examples/dst-safe-arithmetic.md)\n- [Locale Formatting](./examples/locale-formatting.md)\n- [Timezone Conversion](./examples/timezone-conversion.md)\n- [Expiry Classification](./examples/expiry-classification.md)\n- [Date Ranges and Recurrence](./examples/date-ranges-and-recurrence.md)\n"
|
|
8
|
+
},
|
|
9
|
+
"examples": [
|
|
10
|
+
{
|
|
11
|
+
"id": "meeting-duration",
|
|
12
|
+
"code": "import { classifyExpiry, contains, difference, format, inTimeZone, parse, shift, toInstant } from '@vielzeug/tempo'\n\nconst local = parse('2026-03-21T10:00:00', { as: 'plainDateTime' })\nconst start = toInstant(local, { timeZone: 'America/New_York' })\nconst end = shift(start, { hours: 2 }, { timeZone: 'America/New_York' }).toInstant()\nconst check = parse('2026-03-21T11:00:00Z', { as: 'instant' })\n\nconsole.log('Duration:', difference({ end, largestUnit: 'hour', start }).toString())\nconsole.log('Contains check:', contains({ end, start, value: check }))\nconsole.log('New York:', format(inTimeZone(start, 'America/New_York'), { locale: 'en-US', pattern: 'short' }))\nconsole.log('Expiry:', classifyExpiry({ thresholds: { soon: { days: 3 } }, value: end }))",
|
|
13
|
+
"name": "Explicit Parsing and Timezone Arithmetic"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"typeSignatures": {
|
|
17
|
+
"Temporal": "export { Temporal } from '@js-temporal/polyfill';",
|
|
18
|
+
"inTimeZone": "export { inTimeZone, toInstant } from './_convert';",
|
|
19
|
+
"toInstant": "export { inTimeZone, toInstant } from './_convert';",
|
|
20
|
+
"endOf": "export { endOf, startOf } from './boundary';",
|
|
21
|
+
"startOf": "export { endOf, startOf } from './boundary';",
|
|
22
|
+
"classifyExpiry": "export { classifyExpiry, timeDiff } from './classify';",
|
|
23
|
+
"timeDiff": "export { classifyExpiry, timeDiff } from './classify';",
|
|
24
|
+
"clamp": "export { clamp, contains, isAfter, isBefore, isSame } from './compare';",
|
|
25
|
+
"contains": "export { clamp, contains, isAfter, isBefore, isSame } from './compare';",
|
|
26
|
+
"isAfter": "export { clamp, contains, isAfter, isBefore, isSame } from './compare';",
|
|
27
|
+
"isBefore": "export { clamp, contains, isAfter, isBefore, isSame } from './compare';",
|
|
28
|
+
"isSame": "export { clamp, contains, isAfter, isBefore, isSame } from './compare';",
|
|
29
|
+
"difference": "export { difference, isValid, now, nowInstant, parse, shift } from './core';",
|
|
30
|
+
"isValid": "export { difference, isValid, now, nowInstant, parse, shift } from './core';",
|
|
31
|
+
"now": "export { difference, isValid, now, nowInstant, parse, shift } from './core';",
|
|
32
|
+
"nowInstant": "export { difference, isValid, now, nowInstant, parse, shift } from './core';",
|
|
33
|
+
"parse": "export { difference, isValid, now, nowInstant, parse, shift } from './core';",
|
|
34
|
+
"shift": "export { difference, isValid, now, nowInstant, parse, shift } from './core';",
|
|
35
|
+
"TempoError": "export {\n TempoError,\n TempoInvalidInputError,\n TempoInvalidTzError,\n TempoMissingTzError,\n TempoUnsupportedInputError,\n} from './errors';",
|
|
36
|
+
"TempoInvalidInputError": "export {\n TempoError,\n TempoInvalidInputError,\n TempoInvalidTzError,\n TempoMissingTzError,\n TempoUnsupportedInputError,\n} from './errors';",
|
|
37
|
+
"TempoInvalidTzError": "export {\n TempoError,\n TempoInvalidInputError,\n TempoInvalidTzError,\n TempoMissingTzError,\n TempoUnsupportedInputError,\n} from './errors';",
|
|
38
|
+
"TempoMissingTzError": "export {\n TempoError,\n TempoInvalidInputError,\n TempoInvalidTzError,\n TempoMissingTzError,\n TempoUnsupportedInputError,\n} from './errors';",
|
|
39
|
+
"TempoUnsupportedInputError": "export {\n TempoError,\n TempoInvalidInputError,\n TempoInvalidTzError,\n TempoMissingTzError,\n TempoUnsupportedInputError,\n} from './errors';",
|
|
40
|
+
"format": "export {\n format,\n formatDuration,\n formatInstant,\n formatParts,\n formatRange,\n formatRangeParts,\n formatRelative,\n formatZoned,\n humanize,\n parseDuration,\n} from './format';",
|
|
41
|
+
"formatDuration": "export {\n format,\n formatDuration,\n formatInstant,\n formatParts,\n formatRange,\n formatRangeParts,\n formatRelative,\n formatZoned,\n humanize,\n parseDuration,\n} from './format';",
|
|
42
|
+
"formatInstant": "export {\n format,\n formatDuration,\n formatInstant,\n formatParts,\n formatRange,\n formatRangeParts,\n formatRelative,\n formatZoned,\n humanize,\n parseDuration,\n} from './format';",
|
|
43
|
+
"formatParts": "export {\n format,\n formatDuration,\n formatInstant,\n formatParts,\n formatRange,\n formatRangeParts,\n formatRelative,\n formatZoned,\n humanize,\n parseDuration,\n} from './format';",
|
|
44
|
+
"formatRange": "export {\n format,\n formatDuration,\n formatInstant,\n formatParts,\n formatRange,\n formatRangeParts,\n formatRelative,\n formatZoned,\n humanize,\n parseDuration,\n} from './format';",
|
|
45
|
+
"formatRangeParts": "export {\n format,\n formatDuration,\n formatInstant,\n formatParts,\n formatRange,\n formatRangeParts,\n formatRelative,\n formatZoned,\n humanize,\n parseDuration,\n} from './format';",
|
|
46
|
+
"formatRelative": "export {\n format,\n formatDuration,\n formatInstant,\n formatParts,\n formatRange,\n formatRangeParts,\n formatRelative,\n formatZoned,\n humanize,\n parseDuration,\n} from './format';",
|
|
47
|
+
"formatZoned": "export {\n format,\n formatDuration,\n formatInstant,\n formatParts,\n formatRange,\n formatRangeParts,\n formatRelative,\n formatZoned,\n humanize,\n parseDuration,\n} from './format';",
|
|
48
|
+
"humanize": "export {\n format,\n formatDuration,\n formatInstant,\n formatParts,\n formatRange,\n formatRangeParts,\n formatRelative,\n formatZoned,\n humanize,\n parseDuration,\n} from './format';",
|
|
49
|
+
"parseDuration": "export {\n format,\n formatDuration,\n formatInstant,\n formatParts,\n formatRange,\n formatRangeParts,\n formatRelative,\n formatZoned,\n humanize,\n parseDuration,\n} from './format';",
|
|
50
|
+
"dateRange": "export { dateRange, recurrence } from './range';",
|
|
51
|
+
"recurrence": "export { dateRange, recurrence } from './range';",
|
|
52
|
+
"AbsoluteTime": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
53
|
+
"BoundaryOptions": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
54
|
+
"BoundaryUnit": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
55
|
+
"CalendarUnit": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
56
|
+
"ClampInput": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
57
|
+
"ClassifyExpiryInput": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
58
|
+
"CompareOptions": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
59
|
+
"ContainsInput": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
60
|
+
"DifferenceInput": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
61
|
+
"Disambiguation": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
62
|
+
"DisambiguationOptions": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
63
|
+
"DurationFormatOptions": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
64
|
+
"ExpiryThresholds": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
65
|
+
"FixedDuration": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
66
|
+
"FormatOptions": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
67
|
+
"FormatPattern": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
68
|
+
"ParseAs": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
69
|
+
"RecurrenceRule": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
70
|
+
"RelativeFormatOptions": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
71
|
+
"RelativeTimeInput": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
72
|
+
"ShiftOptions": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
73
|
+
"TempoUnit": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
74
|
+
"TimeDiffResult": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
75
|
+
"TimeDiffUnit": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
76
|
+
"TimeInput": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
77
|
+
"TimeZoneOptions": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
78
|
+
"WallTime": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';",
|
|
79
|
+
"WeekStartDay": "export type {\n AbsoluteTime,\n BoundaryOptions,\n BoundaryUnit,\n CalendarUnit,\n ClampInput,\n ClassifyExpiryInput,\n CompareOptions,\n ContainsInput,\n DifferenceInput,\n Disambiguation,\n DisambiguationOptions,\n DurationFormatOptions,\n ExpiryThresholds,\n FixedDuration,\n FormatOptions,\n FormatPattern,\n ParseAs,\n RecurrenceRule,\n RelativeFormatOptions,\n RelativeTimeInput,\n ShiftOptions,\n TempoUnit,\n TimeDiffResult,\n TimeDiffUnit,\n TimeInput,\n TimeZoneOptions,\n WallTime,\n WeekStartDay,\n} from './types';"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"apiSource": "export { VaultDisposedError, VaultError, VaultMigrationError, VaultQuotaError, VaultScopeError } from './errors';\nexport { scheduleExpiredPrune } from './prune';\nexport type { QueryBuilder } from './query';\nexport { isExpired, ttl } from './ttl';\nexport type {\n AnySchema,\n BaseAdapterOptions,\n DebugInfo,\n DebugStats,\n IterableVaultStore,\n KeyOf,\n MetricsEvent,\n Observer,\n RecordOf,\n RecordValidator,\n SchemaEntry,\n TableValidators,\n TransactionalVaultStore,\n Unsubscribe,\n VaultKey,\n VaultLogger,\n VaultStore,\n} from './types';\nexport { table } from './types';\n",
|
|
3
|
+
"docs": {
|
|
4
|
+
"index": "---\ntitle: Vault — Typed storage\ndescription: Typed browser storage and opt-in driver-neutral SQLite with portable keys, TTL, observation, and transactions.\npackage: vault\ncategory: Storage\nkeywords: [storage, indexeddb, localstorage, sessionstorage, sqlite, ttl, browser, node, deno]\nrelated: [courier, forge, ripple]\nexports: [table, ttl, scheduleExpiredPrune, isExpired, createMemory, createLocalStorage, createSessionStorage, createIndexedDB, createSQLite]\nenvironments: [browser, node, deno]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"vault\" />\n\n## Why Vault?\n\nVault gives browser and SQLite persistence one typed schema while keeping backend guarantees explicit. Use `VaultStore` for portable CRUD and observation; choose IndexedDB or the opt-in SQLite subpath when you need atomic transactions or lazy iteration.\n\n```ts\n// Before\nlocalStorage.setItem('theme', JSON.stringify({ value: 'dark' }));\nconst theme = JSON.parse(localStorage.getItem('theme') ?? '{}').value;\n\n// After\nawait store.put('preferences', { id: 'theme', value: 'dark' });\nconst theme = await store.get('preferences', 'theme');\n```\n\n| Feature | Vault | Raw Web Storage | Dexie |\n| --- | --- | --- | --- |\n| Bundle size | <PackageInfo package=\"vault\" type=\"size\" /> | Browser built-in | Extra dependency |\n| Runtime dependencies | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Typed schema and keys | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Application-defined | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n| Portable Memory/Web Storage API | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"triangle-alert\" size=\"16\"></ore-icon> | IndexedDB only |\n| Explicit atomic transactions | IndexedDB capability | <ore-icon name=\"x\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n| Driver-neutral SQLite | Opt-in subpath | <ore-icon name=\"x\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n\n<div class=\"decision-callout\">\n\n**Use Vault when** you need typed browser persistence or application-owned SQLite with one portable CRUD API and explicit storage capabilities.\n\n**Consider raw Web Storage when** you only persist one or two unstructured values. **Consider Dexie when** you need a broader IndexedDB ecosystem.\n\n</div>\n\n## Installation\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/vault\n```\n\n```sh [npm]\nnpm install @vielzeug/vault\n```\n\n```sh [yarn]\nyarn add @vielzeug/vault\n```\n\n:::\n\n## Quick Start\n\nDefine a schema, create a portable store, and dispose it with its owner.\n\n```ts\nimport { table } from '@vielzeug/vault';\nimport { createLocalStorage } from '@vielzeug/vault/local-storage';\n\nconst store = createLocalStorage({\n name: 'app-v2',\n schema: { preferences: table<{ id: string; theme: 'dark' | 'light' }>('id') },\n});\n\ntry {\n await store.put('preferences', { id: 'theme', theme: 'dark' });\n console.log(await store.get('preferences', 'theme'));\n} finally {\n await store.dispose();\n}\n```\n\n## Features\n\n<div class=\"features-grid\">\n\n- `table()` defines typed records with portable string or number keys.\n- `/memory`, `/local-storage`, and `/session-storage` return portable `VaultStore` instances without loading other adapters.\n- `observe()` emits current and changed table snapshots.\n- `ttl` creates validated expiration durations.\n- `/indexeddb` returns `IndexedDbVaultStore` with `batch()` and `iterate()`.\n- `createSQLite()` is an opt-in, driver-neutral subpath for Node, Bun, and Deno SQLite drivers.\n- `/indexeddb` also exports `defineMigration()` for schema upgrades.\n- `scheduleExpiredPrune()` removes stale TTL entries on an owned schedule.\n\n</div>\n\n## Documentation\n\n<div class=\"doc-links\">\n\n- [Usage Guide](./usage.md)\n- [API Reference](./api.md)\n- [Examples](./examples.md)\n- [Migration Guide](./migration.md)\n\n</div>\n\n## See Also\n\n<div class=\"see-also\">\n\n- [Forge](../forge/index.md) saves and restores form drafts through Vault stores.\n- [Ripple](../ripple/index.md) owns application state that can persist through Vault.\n- [Courier](../courier/index.md) can populate persistent cache data.\n\n</div>\n\n<!-- markdownlint-enable MD025 MD033 MD060 -->\n",
|
|
5
|
+
"api": "---\ntitle: Vault — API Reference\ndescription: Reference for Vault schemas, adapter entry points, storage capabilities, SQLite drivers, and errors.\n---\n\n[[toc]]\n\n## API Overview\n\n| Symbol | Purpose | Execution mode | Common gotcha |\n| --- | --- | --- | --- |\n| `createMemory()` | In-memory portable store | Async API | Import from `/memory` |\n| `createLocalStorage()` / `createSessionStorage()` | Web Storage-backed portable stores | Async API | Available only where the corresponding Web API exists |\n| `createIndexedDB()` | Browser transactions and cursor iteration | Async API | Import from `/indexeddb` |\n| `createSQLite()` | Driver-neutral SQLite store | Async API over a synchronous driver | Import from `/sqlite` |\n| `table()` | Typed record schema | Sync | The key field must be a string or finite number |\n| `ttl` | Valid expiration durations | Sync | Durations must be positive |\n| `scheduleExpiredPrune()` | Periodic TTL cleanup | Sync setup, async work | Pass `disposalSignal` to auto-cancel |\n\n## Package Entry Points\n\n| Import | Purpose |\n| --- | --- |\n| `@vielzeug/vault` | Adapter-free schemas, TTL, errors, pruning, queries, and shared types |\n| `@vielzeug/vault/memory` | `createMemory` |\n| `@vielzeug/vault/local-storage` | `createLocalStorage` |\n| `@vielzeug/vault/session-storage` | `createSessionStorage` |\n| `@vielzeug/vault/indexeddb` | `createIndexedDB`, `defineMigration`, migrations, and IndexedDB-only types |\n| `@vielzeug/vault/sqlite` | `createSQLite`, the SQLite driver protocol types, and `TransactionContext` |\n\n## Schemas and TTL\n\n### `table()`\n\n```ts\nfunction table<T extends object, Key extends keyof T & string = keyof T & string>(\n key: Key & (T[Key] extends VaultKey ? unknown : never),\n options?: { defaultTtl?: number; indexes?: readonly (keyof T & string)[] },\n): SchemaEntry<T, Key>;\n```\n\nDefines a typed table and its primary-key field.\n\n| Parameter | Description |\n| --- | --- |\n| `key` | A record field whose values are `string` or finite `number` keys |\n| `options.defaultTtl` | Per-table default TTL in milliseconds |\n| `options.indexes` | IndexedDB secondary index fields |\n\n**Returns:** A `SchemaEntry` describing the table.\n\n```ts\nimport { table, ttl } from '@vielzeug/vault';\n\nconst users = table<{ id: number; email: string }>('id', {\n indexes: ['email'],\n defaultTtl: ttl.days(7),\n});\n```\n\n---\n\n### `ttl`\n\n```ts\nconst ttl: {\n days(n: number): number;\n hours(n: number): number;\n minutes(n: number): number;\n ms(n: number): number;\n seconds(n: number): number;\n};\n```\n\nCreates a finite, positive duration in milliseconds for writes and table defaults.\n\n**Returns:** `number`.\n\n```ts\nimport { ttl } from '@vielzeug/vault';\n\nconst cacheLifetime = ttl.minutes(5);\n```\n\n---\n\n### `isExpired()`\n\n```ts\nfunction isExpired(expiresAt: number | undefined): boolean;\n```\n\nReports whether an expiration timestamp has passed.\n\n**Returns:** `true` when `expiresAt` is defined and no later than the current time.\n\n```ts\nimport { isExpired } from '@vielzeug/vault';\n\nif (isExpired(record.expiresAt)) console.log('expired');\n```\n\n## Factories\n\nAll factory options accept `schema`, plus optional `validators`, `logger`, and `onMetrics`. The root entry does not export any factory.\n\n### `createMemory()`\n\n```ts\nfunction createMemory<S extends AnySchema>(options: {\n name?: string;\n schema: S;\n} & BaseAdapterOptions<S>): VaultStore<S>;\n```\n\nCreates an in-memory portable store. A `name` enables same-origin `BroadcastChannel` observation between memory stores when the platform provides it.\n\n| Parameter | Description |\n| --- | --- |\n| `schema` | Tables created by `table()` |\n| `name` | Optional shared memory-store namespace |\n\n**Returns:** `VaultStore<S>`.\n\n```ts\nimport { table } from '@vielzeug/vault';\nimport { createMemory } from '@vielzeug/vault/memory';\n\nconst store = createMemory({ schema: { users: table<{ id: number; name: string }>('id') } });\n```\n\n---\n\n### `createLocalStorage()`\n\n```ts\nfunction createLocalStorage<S extends AnySchema>(options: {\n name: string;\n onQuotaExceeded?: (table: keyof S, error: VaultQuotaError) => 'ignore' | 'throw';\n schema: S;\n} & BaseAdapterOptions<S>): VaultStore<S>;\n```\n\nCreates a namespaced `localStorage` store.\n\n| Parameter | Description |\n| --- | --- |\n| `name` | Required storage namespace |\n| `onQuotaExceeded` | Handles a Web Storage quota error; returning `'ignore'` drops that write |\n| `schema` | Tables created by `table()` |\n\n**Returns:** `VaultStore<S>`.\n\n```ts\nimport { table } from '@vielzeug/vault';\nimport { createLocalStorage } from '@vielzeug/vault/local-storage';\n\nconst store = createLocalStorage({ name: 'app', schema: { settings: table<{ id: string }>('id') } });\n```\n\n---\n\n### `createSessionStorage()`\n\n```ts\nfunction createSessionStorage<S extends AnySchema>(options: {\n name: string;\n onQuotaExceeded?: (table: keyof S, error: VaultQuotaError) => 'ignore' | 'throw';\n schema: S;\n} & BaseAdapterOptions<S>): VaultStore<S>;\n```\n\nCreates a namespaced `sessionStorage` store. Its options and return type match `createLocalStorage()`.\n\n**Returns:** `VaultStore<S>`.\n\n```ts\nimport { table } from '@vielzeug/vault';\nimport { createSessionStorage } from '@vielzeug/vault/session-storage';\n\nconst store = createSessionStorage({ name: 'checkout', schema: { cart: table<{ id: string }>('id') } });\n```\n\n---\n\n### `createIndexedDB()`\n\n```ts\nfunction createIndexedDB<S extends AnySchema>(options: {\n migrate?: MigrationFn;\n name: string;\n schema: S;\n version?: number;\n} & BaseAdapterOptions<S>): IndexedDbVaultStore<S>;\n```\n\nCreates an IndexedDB store with atomic batches, lazy cursor iteration, and optional schema migrations.\n\n| Parameter | Description |\n| --- | --- |\n| `name` | Required database name |\n| `schema` | Tables and IndexedDB secondary indexes |\n| `version` | Positive schema version; defaults to `1` |\n| `migrate` | Synchronous upgrade callback for version changes |\n\n**Returns:** `IndexedDbVaultStore<S>`.\n\n```ts\nimport { table } from '@vielzeug/vault';\nimport { createIndexedDB } from '@vielzeug/vault/indexeddb';\n\nconst store = createIndexedDB({ name: 'app', schema: { users: table<{ id: number }>('id') } });\n```\n\n---\n\n### `createSQLite()`\n\n```ts\nfunction createSQLite<S extends AnySchema>(options: SQLiteVaultOptions<S>): SQLiteVaultStore<S>;\n```\n\nCreates a namespaced SQLite store with atomic batches and keyset-paginated iteration. It accepts an application-provided positional-parameter driver and never opens or imports a runtime driver.\n\n| Parameter | Description |\n| --- | --- |\n| `database` | Caller-provided `SQLiteDatabase` connection |\n| `name` | Namespace within the connection |\n| `schema`, `validators`, `logger`, `onMetrics` | Shared factory options |\n| `closeOnDispose` | Closes the connection during disposal; defaults to `false` |\n\n**Returns:** `SQLiteVaultStore<S>`.\n\n```ts\nimport { DatabaseSync } from 'node:sqlite';\n\nimport { table } from '@vielzeug/vault';\nimport { createSQLite } from '@vielzeug/vault/sqlite';\n\nconst store = createSQLite({\n database: new DatabaseSync(':memory:'),\n name: 'tests',\n schema: { users: table<{ id: number; name: string }>('id') },\n});\n```\n\nNode `DatabaseSync`, Bun `Database`, and Deno `jsr:@db/sqlite` `Database` satisfy the protocol. Values must be JSON-compatible plain objects. During a `batch()` callback, calls on every Vault store sharing that connection reject; use `tx.*` instead.\n\n## Store Capabilities\n\n### `VaultStore`\n\n```ts\ninterface VaultStore<S extends AnySchema> {\n clear<K extends keyof S & string>(table: K): Promise<void>;\n count<K extends keyof S & string>(table: K): Promise<number>;\n delete<K extends keyof S & string>(table: K, key: KeyOf<S, K>): Promise<boolean>;\n deleteMany<K extends keyof S & string>(table: K, keys: KeyOf<S, K>[]): Promise<number>;\n entries<K extends keyof S & string>(table: K): Promise<Array<[KeyOf<S, K>, RecordOf<S, K>]>>;\n get<K extends keyof S & string>(table: K, key: KeyOf<S, K>): Promise<RecordOf<S, K> | undefined>;\n getAll<K extends keyof S & string>(table: K): Promise<RecordOf<S, K>[]>;\n getMany<K extends keyof S & string>(table: K, keys: KeyOf<S, K>[]): Promise<Array<RecordOf<S, K> | undefined>>;\n getOrDefault<K extends keyof S & string>(table: K, key: KeyOf<S, K>, defaultFn: () => RecordOf<S, K>, ttl?: number): Promise<RecordOf<S, K>>;\n has<K extends keyof S & string>(table: K, key: KeyOf<S, K>): Promise<boolean>;\n isEmpty<K extends keyof S & string>(table: K): Promise<boolean>;\n keys<K extends keyof S & string>(table: K, filter?: (record: RecordOf<S, K>) => boolean): Promise<KeyOf<S, K>[]>;\n put<K extends keyof S & string>(table: K, value: RecordOf<S, K>, ttl?: number): Promise<void>;\n putAll<K extends keyof S & string>(table: K, values: RecordOf<S, K>[], ttl?: number): Promise<void>;\n query<K extends keyof S & string>(table: K): QueryBuilder<RecordOf<S, K>>;\n update<K extends keyof S & string>(table: K, key: KeyOf<S, K>, changes: Partial<RecordOf<S, K>>, ttl?: number): Promise<RecordOf<S, K> | undefined>;\n upsert<K extends keyof S & string>(table: K, key: KeyOf<S, K>, fn: (existing: RecordOf<S, K> | undefined) => RecordOf<S, K>, ttl?: number): Promise<RecordOf<S, K>>;\n pruneExpired(): Promise<Record<keyof S & string, number>>;\n debug(): Promise<DebugInfo<S>>;\n observe<K extends keyof S & string>(table: K, listener: Observer<RecordOf<S, K>>, options?: { immediate?: boolean; signal?: AbortSignal }): Unsubscribe;\n dispose(): Promise<void>;\n readonly disposed: boolean;\n readonly disposalSignal: AbortSignal;\n [Symbol.asyncDispose](): Promise<void>;\n}\n```\n\nThe portable store API is returned by every factory. `observe()` emits the current table snapshot by default and then emits after mutations.\n\n---\n\n### `batch()`\n\n```ts\ninterface TransactionalVaultStore<S extends AnySchema> extends VaultStore<S> {\n batch<K extends keyof S & string, R>(\n tables: readonly K[],\n fn: (tx: TransactionContext<S, K>) => Promise<R>,\n ): Promise<R>;\n}\n```\n\nRuns a scoped atomic callback. `IndexedDbVaultStore` and `SQLiteVaultStore` provide it.\n\n| Parameter | Description |\n| --- | --- |\n| `tables` | Tables the transaction may access |\n| `fn` | Async callback that uses only the supplied `tx` context |\n\n**Returns:** The callback result after commit.\n\n```ts\nawait store.batch(['users'], async (tx) => {\n await tx.put('users', { id: 1, name: 'Ada' });\n});\n```\n\n---\n\n### `iterate()`\n\n```ts\ninterface IterableVaultStore<S extends AnySchema> extends VaultStore<S> {\n iterate<K extends keyof S & string>(table: K): AsyncIterable<RecordOf<S, K>>;\n}\n```\n\nLazily yields table records. `IndexedDbVaultStore` uses a cursor; `SQLiteVaultStore` uses keyset pagination.\n\n**Returns:** An `AsyncIterable` of records.\n\n```ts\nfor await (const user of store.iterate('users')) console.log(user);\n```\n\n## Queries, Pruning, and Migrations\n\n### `QueryBuilder`\n\n```ts\ninterface QueryBuilder<T extends object, N extends T = T> {\n between(field: string, lower: number | string, upper: number | string): QueryBuilder<T, N>;\n count(): Promise<number>;\n delete(): Promise<number>;\n equals<K extends keyof T & string, V extends T[K]>(field: K, value: V): QueryBuilder<T & Record<K, V>>;\n exists(): Promise<boolean>;\n filter(fn: (value: N, index: number, array: N[]) => boolean): QueryBuilder<T, N>;\n first(): Promise<N | undefined>;\n limit(n: number): QueryBuilder<T, N>;\n offset(n: number): QueryBuilder<T, N>;\n orderBy<K extends keyof T>(field: K, direction?: 'asc' | 'desc'): QueryBuilder<T, N>;\n startsWith(field: keyof T, prefix: string, options?: { ignoreCase?: boolean }): QueryBuilder<T, N>;\n toArray(): Promise<N[]>;\n}\n```\n\nBuilds a lazy table query. `count()` ignores `limit()`, `offset()`, and `orderBy()` — it always returns the full filtered-set size.\n\n```ts\nconst page = await store.query('users').startsWith('name', 'A').orderBy('name').limit(20).toArray();\n```\n\n---\n\n### `scheduleExpiredPrune()`\n\n```ts\nfunction scheduleExpiredPrune<S extends AnySchema>(\n adapter: Pick<VaultStore<S>, 'pruneExpired'>,\n options: {\n interval: number;\n onError?: (error: unknown) => void;\n signal?: AbortSignal;\n },\n): () => void;\n```\n\nSchedules `pruneExpired()` at a finite, positive interval. Pass `signal: store.disposalSignal` to auto-cancel when the store is torn down.\n\n**Returns:** A stop function.\n\n```ts\nimport { scheduleExpiredPrune, ttl } from '@vielzeug/vault';\n\nconst stop = scheduleExpiredPrune(store, {\n interval: ttl.hours(1),\n signal: store.disposalSignal,\n});\nstop();\n```\n\n---\n\n### `defineMigration()`\n\n```ts\nfunction defineMigration(steps: MigrationStep[]): MigrationFn;\n```\n\nBuilds an idempotent IndexedDB migration callback from schema-change steps.\n\n**Returns:** An IndexedDB `MigrationFn`.\n\n```ts\nimport { defineMigration } from '@vielzeug/vault/indexeddb';\n\nconst migrate = defineMigration([{ field: 'email', table: 'users', type: 'addIndex' }]);\n```\n\n## Types\n\n```ts\ntype VaultKey = number | string;\ntype Unsubscribe = () => void;\ntype Observer<T> = (records: T[]) => void;\ntype AnySchema = Record<string, {\n defaultTtl?: number;\n indexes?: readonly string[];\n key: string;\n}>;\ntype SchemaEntry<T extends object, Key extends keyof T & string = keyof T & string> =\n T[Key] extends VaultKey ? {\n defaultTtl?: number;\n indexes?: readonly (keyof T & string)[];\n key: Key;\n } : never;\ntype RecordOf<S extends AnySchema, K extends keyof S> =\n S[K] extends SchemaEntry<infer R, infer _Key> ? R : never;\ntype KeyOf<S extends AnySchema, K extends keyof S> =\n Extract<S[K] extends SchemaEntry<infer R, infer Key> ? R[Key] : never, VaultKey>;\n```\n\n```ts\ntype BaseAdapterOptions<S extends AnySchema> = {\n logger?: VaultLogger;\n onMetrics?: (event: MetricsEvent) => void;\n schema: S;\n validators?: TableValidators<S>;\n};\n\ntype VaultLogger = {\n error(message: string, context?: Error | Record<string, unknown>): void;\n};\n\ntype RecordValidator<T> = {\n parse(value: unknown): T;\n};\n\ntype TableValidators<S extends AnySchema> = {\n [K in keyof S]?: RecordValidator<RecordOf<S, K>>;\n};\n\ntype MetricsEvent = {\n duration: number;\n operation: 'batch' | 'clear' | 'count' | 'delete' | 'deleteMany' | 'entries' | 'get' | 'getAll' |\n 'getMany' | 'getOrDefault' | 'has' | 'isEmpty' | 'keys' | 'put' | 'putAll' | 'query' |\n 'queryDelete' | 'update' | 'upsert';\n table: string;\n};\n\ntype DebugStats = { expiredCount: number; recordCount: number };\ntype DebugInfo<S extends AnySchema> = { tables: Array<{ name: keyof S & string } & DebugStats> };\n```\n\n```ts\ninterface IndexedDbVaultStore<S extends AnySchema>\n extends TransactionalVaultStore<S>, IterableVaultStore<S> {}\n\ntype MigrationContext = {\n db: IDBDatabase;\n newVersion: number | null;\n oldVersion: number;\n tx: IDBTransaction;\n};\n\ntype MigrationFn = (ctx: MigrationContext) => void;\n\ntype MigrationStep =\n | { field: string; table: string; type: 'addIndex' }\n | { field: string; table: string; type: 'removeIndex' }\n | { name: string; type: 'addTable' }\n | { name: string; type: 'removeTable' };\n```\n\nImport `MigrationContext`, `MigrationFn`, and `MigrationStep` from `@vielzeug/vault/indexeddb`.\n\n```ts\ntype SQLiteParameter = null | number | string;\n\ninterface SQLiteStatement {\n all(...parameters: SQLiteParameter[]): readonly Record<string, unknown>[];\n finalize?(): void;\n get(...parameters: SQLiteParameter[]): Record<string, unknown> | undefined;\n run(...parameters: SQLiteParameter[]): unknown;\n}\n\ninterface SQLiteDatabase {\n close?(): void;\n exec(sql: string): void;\n prepare(sql: string): SQLiteStatement;\n}\n\ntype SQLiteVaultOptions<S extends AnySchema> = BaseAdapterOptions<S> & {\n closeOnDispose?: boolean;\n database: SQLiteDatabase;\n name: string;\n};\n\ninterface SQLiteVaultStore<S extends AnySchema>\n extends TransactionalVaultStore<S>, IterableVaultStore<S> {}\n```\n\n`TransactionContext` has the same CRUD, query, and TTL methods as `VaultStore`, narrowed to the tables declared in `batch()`. Import it from `@vielzeug/vault/indexeddb` or `@vielzeug/vault/sqlite`.\n\n## Errors\n\n| Error | Trigger |\n| --- | --- |\n| `VaultError` | Any Vault-originated validation, serialization, storage, or query error |\n| `VaultDisposedError` | An operation after the store or observer hub is disposed |\n| `VaultScopeError` | An IndexedDB transaction accesses a table outside its declared batch scope |\n| `VaultQuotaError` | A LocalStorage or SessionStorage write exceeds the browser quota |\n| `VaultMigrationError` | An IndexedDB migration callback throws |\n\nEvery listed error extends `VaultError`.\n",
|
|
6
|
+
"usage": "---\ntitle: Vault — Usage Guide\ndescription: Persist typed browser or SQLite data, observe table snapshots, and use atomic transactions.\n---\n\n[[toc]]\n\n## Basic Usage\n\nCreate a portable store with one schema and write a typed row.\n\n```ts\nimport { table } from '@vielzeug/vault';\nimport { createLocalStorage } from '@vielzeug/vault/local-storage';\n\ninterface Preference {\n id: string;\n theme: 'dark' | 'light';\n}\n\nconst store = createLocalStorage({\n name: 'app-v2',\n schema: { preferences: table<Preference>('id') },\n});\n\nawait store.put('preferences', { id: 'theme', theme: 'dark' });\nconsole.log(await store.get('preferences', 'theme'));\n```\n\n## Create a Portable Store\n\nMemory, LocalStorage, and SessionStorage return `VaultStore`. They share portable string/number keys, CRUD methods, queries, TTL, and `observe()`. Vault keeps record values and expiry metadata separate; the physical storage layout is adapter-specific.\n\nThe root entry is adapter-free. Import `createMemory` from `@vielzeug/vault/memory`, `createLocalStorage` from `@vielzeug/vault/local-storage`, or `createSessionStorage` from `@vielzeug/vault/session-storage`. Import each adapter from its focused subpath so unused backends stay out of the bundle.\n\nUse a new storage name when upgrading from Vault 1. Old key and envelope formats are not read by Vault 2.\n\n```ts\nconst store = createLocalStorage({\n name: 'app-v2',\n schema: { preferences: table<Preference>('id') },\n});\n```\n\n## Read and Change Records\n\nUse `update()` for an existing row and `upsert()` when the row may not exist.\n\n```ts\nconst updated = await store.update('preferences', 'theme', { theme: 'light' });\n\nawait store.upsert('preferences', 'locale', (current) => ({\n id: 'locale',\n theme: current?.theme ?? 'dark',\n}));\n\nconsole.log(updated);\n```\n\n`update()` returns `undefined` for a missing key. `upsert()` always writes the record returned by its callback.\n\n## Query Records\n\nBuild a query from a table, then finish it with a terminal method. `count()` ignores pagination, which makes it suitable for page controls.\n\n```ts\nconst query = store.query('preferences').startsWith('id', 'theme');\nconst preferences = await query.orderBy('id').limit(10).toArray();\nconst total = await query.count();\n\nconsole.log({ preferences, total });\n```\n\nMemory and Web Storage queries scan the table. IndexedDB can use declared secondary indexes, while SQLite pushes primary-key equality, range, and case-sensitive prefix filters to the database.\n\n## Use TTL and Pruning\n\nUse `ttl.*` helpers for expiring rows. Schedule pruning when stale rows can accumulate without reads.\n\n```ts\nimport { scheduleExpiredPrune, ttl } from '@vielzeug/vault';\n\nawait store.put('preferences', { id: 'temporary', theme: 'dark' }, ttl.hours(1));\nconst stopPrune = scheduleExpiredPrune(store, {\n interval: ttl.hours(6),\n signal: store.disposalSignal,\n});\n\nstopPrune();\n```\n\n## Observe a Table\n\nUse `observe()` for current and future snapshots. Tie subscription lifetime to an `AbortSignal` when a component or request owns it.\n\n```ts\nconst controller = new AbortController();\n\nstore.observe('preferences', (preferences) => {\n console.log(preferences);\n}, { signal: controller.signal });\n\ncontroller.abort();\n```\n\n## Use IndexedDB for Browser Transactions\n\nChoose IndexedDB when browser storage needs multiple writes to commit together or cursor iteration.\n\n```ts\nimport { table } from '@vielzeug/vault';\nimport { createIndexedDB } from '@vielzeug/vault/indexeddb';\n\nconst db = createIndexedDB({\n name: 'app-v2',\n schema: { events: table<{ id: number; type: string }>('id') },\n});\n\nawait db.batch(['events'], async (tx) => {\n await tx.put('events', { id: 1, type: 'opened' });\n await tx.put('events', { id: 2, type: 'saved' });\n});\n```\n\nOnly await `tx.*` operations inside a batch callback. Do not await timers, fetches, or other external asynchronous work; IndexedDB can commit an inactive transaction.\n\n## Use SQLite Outside the Browser\n\nImport SQLite from the opt-in subpath so the browser root stays free of runtime drivers. Vault never opens a connection or configures its SQLite process behavior for you.\n\n```ts\nimport { DatabaseSync } from 'node:sqlite';\n\nimport { table } from '@vielzeug/vault';\nimport { createSQLite } from '@vielzeug/vault/sqlite';\n\nconst database = new DatabaseSync('app.db', { timeout: 5_000 });\nconst store = createSQLite({\n database,\n name: 'app-v2',\n schema: { events: table<{ id: number; type: string }>('id') },\n});\n\nawait store.batch(['events'], async (tx) => {\n await tx.put('events', { id: 1, type: 'opened' });\n await tx.put('events', { id: 2, type: 'saved' });\n});\n```\n\nNode's `node:sqlite` API is experimental. Bun's `bun:sqlite` `Database` satisfies the same positional `exec()` and `prepare()` contract; configure WAL from your application when the deployment needs it. Deno does not include SQLite, but `jsr:@db/sqlite`'s `Database` satisfies the same contract when its FFI, filesystem, and environment permissions are granted.\n\nSQLite stores serialize all access through the injected connection. `batch()` starts `BEGIN IMMEDIATE` and rolls back callback failures. While its callback runs, calls on any store sharing that connection reject rather than waiting behind the transaction; use `tx.*` instead. The underlying drivers are synchronous, so move large scans and writes to a worker or isolate when event-loop latency matters.\n\n## Store SQLite Values and Observe Changes\n\nSQLite accepts JSON-compatible plain-object records only. Circular values, `bigint`, dates, class instances, functions, and non-finite numbers are rejected before writing. Number and string primary keys remain distinct.\n\n`observe()` sees mutations written through Vault stores sharing the same injected connection after a commit. It cannot detect direct SQL changes, writes from another process, or writes through another connection. The connection belongs to the caller by default; use `closeOnDispose: true` only when the store owns it.\n\n## Handle IndexedDB Schema Migrations\n\nDeclare IndexedDB indexes in the schema. Use `migrate` only for IndexedDB version upgrades and mirror Vault’s fixed `value.<field>` index path.\n\n```ts\nimport { table } from '@vielzeug/vault';\nimport { createIndexedDB, type MigrationFn } from '@vielzeug/vault/indexeddb';\n\nconst schema = { users: table<{ id: number; name: string }>('id', { indexes: ['name'] }) };\nconst migrate: MigrationFn = ({ db, oldVersion, tx }) => {\n if (oldVersion < 2 && db.objectStoreNames.contains('users')) {\n tx.objectStore('users').createIndex('name', 'value.name');\n }\n};\n\ncreateIndexedDB({ name: 'app-v2', migrate, schema, version: 2 });\n```\n\n## Framework Integration\n\n::: code-group\n\n```ts [React]\nimport { useEffect, useState } from 'react';\n\nimport type { AnySchema, RecordOf, VaultStore } from '@vielzeug/vault';\n\nexport function useTable<S extends AnySchema, K extends keyof S & string>(store: VaultStore<S>, table: K) {\n const [rows, setRows] = useState<RecordOf<S, K>[]>([]);\n\n useEffect(() => store.observe(table, setRows), [store, table]);\n return rows;\n}\n```\n\n```ts [Vue 3]\nimport { onUnmounted, shallowRef } from 'vue';\n\nimport type { AnySchema, RecordOf, VaultStore } from '@vielzeug/vault';\n\nexport function useTable<S extends AnySchema, K extends keyof S & string>(store: VaultStore<S>, table: K) {\n const rows = shallowRef<RecordOf<S, K>[]>([]);\n const stop = store.observe(table, (next) => (rows.value = next));\n\n onUnmounted(stop);\n return rows;\n}\n```\n\n```ts [Svelte]\nimport { readable } from 'svelte/store';\n\nimport type { AnySchema, RecordOf, VaultStore } from '@vielzeug/vault';\n\nexport function tableStore<S extends AnySchema, K extends keyof S & string>(store: VaultStore<S>, table: K) {\n return readable<RecordOf<S, K>[]>([], (set) => store.observe(table, set));\n}\n```\n\n:::\n\n## Working with Other Vielzeug Libraries\n\nUse Forge’s Vault helpers for explicit form-draft persistence. Keep Ripple signals as application state and persist selected changes through Vault writes.\n\n## Best Practices\n\n- Define one schema per storage namespace.\n- Use string or finite-number primary keys only.\n- Choose a new namespace for Vault 1 storage unless you migrate it yourself.\n- Use `observe()` for table snapshots.\n- Use IndexedDB or SQLite for atomic work.\n- Keep external asynchronous work outside `batch()` callbacks.\n- Use `ttl.*` instead of raw durations.\n- Keep SQLite scans and writes off latency-sensitive event loops, and dispose stores with their owner.\n- Dispose stores when their owner ends.\n",
|
|
7
|
+
"examples": "---\ntitle: Vault — Examples\ndescription: Portable storage, observation, transactions, iteration, and SQLite.\n---\n\n- [CRUD](./examples/crud.md)\n- [TTL](./examples/ttl.md)\n- [Querying](./examples/querying.md)\n- [Reactive observation](./examples/reactive.md)\n- [IndexedDB iteration](./examples/iterate.md)\n- [IndexedDB batch transactions](./examples/batch.md)\n- [SQLite transactions and iteration](./examples/sqlite.md)\n- [Plugin validation](./examples/plugins.md)\n"
|
|
8
|
+
},
|
|
9
|
+
"examples": [
|
|
10
|
+
{
|
|
11
|
+
"id": "basic-setup",
|
|
12
|
+
"code": "import { table } from '@vielzeug/vault'\nimport { createLocalStorage } from '@vielzeug/vault/local-storage'\n\nconst schema = {\n users: table('id'),\n}\n\nconst db = createLocalStorage({ name: 'demo', schema })\n\nawait db.put('users', { id: 1, name: 'Alice', email: 'alice@example.com' })\nawait db.put('users', { id: 2, name: 'Bob', email: 'bob@example.com' })\n\nconsole.log('Get user 1:', await db.get('users', 1))\nconsole.log('All users:', await db.getAll('users'))\nconsole.log('Count:', await db.query('users').count())",
|
|
13
|
+
"name": "Basic Setup - Initialize Vault"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "bulk-operations",
|
|
17
|
+
"code": "import { table } from '@vielzeug/vault'\nimport { createLocalStorage } from '@vielzeug/vault/local-storage'\n\nconst schema = {\n items: table('id'),\n}\n\nconst db = createLocalStorage({ name: 'bulk-demo', schema })\n\nconst items = Array.from({ length: 10 }, (_, index) => ({\n id: index + 1,\n value: +(Math.random() * 1000).toFixed(2),\n}))\n\nawait db.putAll('items', items)\nconsole.log('Inserted', items.length, 'items')\n\n// getMany — fetch multiple by key in one call (missing keys return undefined)\nconst [first, missing, third] = await db.getMany('items', [1, 99, 3])\nconsole.log('getMany [1, 99, 3]:', first?.id, missing, third?.id)\n\n// deleteMany — remove multiple by key, returns count deleted\nconst deleted = await db.deleteMany('items', [1, 2, 3, 99])\nconsole.log('deleteMany [1,2,3,99] deleted:', deleted) // 3 (99 did not exist)\n\n// query-based delete for filter-driven removal\nconst queryDeleted = await db.query('items').filter((item) => item.id <= 6).delete()\nconsole.log('Query-deleted items with id ≤ 6:', queryDeleted)\n\nconsole.log('Remaining count:', await db.query('items').count())\nconsole.log('First remaining item:', await db.query('items').orderBy('id', 'asc').first())",
|
|
18
|
+
"name": "Bulk Operations"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "cache-first",
|
|
22
|
+
"code": "import { table, ttl } from '@vielzeug/vault'\nimport { createLocalStorage } from '@vielzeug/vault/local-storage'\n\nconst db = createLocalStorage({ name: 'cache-demo', schema: { cache: table('id') } })\n\nasync function getOrComputeConfig() {\n return db.getOrDefault('cache', 'config', () => ({\n id: 'config',\n data: 'computed value',\n fetchedAt: Date.now(),\n }), ttl.minutes(5))\n}\n\nconst first = await getOrComputeConfig()\nconst second = await getOrComputeConfig()\nconsole.log('Same cached record:', first.fetchedAt === second.fetchedAt)",
|
|
23
|
+
"name": "Cache-First with getOrDefault"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "crud-operations",
|
|
27
|
+
"code": "import { table } from '@vielzeug/vault'\nimport { createLocalStorage } from '@vielzeug/vault/local-storage'\n\nconst schema = {\n users: table('id'),\n}\n\nconst db = createLocalStorage({ name: 'demo', schema })\n\nawait db.put('users', { id: 1, name: 'Alice', email: 'alice@example.com', age: 25 })\nawait db.put('users', { id: 2, name: 'Bob', email: 'bob@example.com', age: 30 })\nconsole.log('Created 2 users')\n\nconsole.log('Get user 1:', await db.get('users', 1))\nconsole.log('Count:', await db.count('users'))\nconsole.log('isEmpty before clear:', await db.isEmpty('users')) // false\n\nawait db.update('users', 1, { age: 26, name: 'Alice Smith' })\nconsole.log('Updated user 1:', await db.get('users', 1))\n\nconsole.log('Deleted user 2:', await db.delete('users', 2))\nconsole.log('Remaining users:', await db.getAll('users'))\n\nawait db.clear('users')\nconsole.log('isEmpty after clear:', await db.isEmpty('users')) // true",
|
|
28
|
+
"name": "CRUD Operations"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "indexed-db",
|
|
32
|
+
"code": "import { table, ttl } from '@vielzeug/vault'\nimport { createIndexedDB } from '@vielzeug/vault/indexeddb'\n\nconst schema = {\n logs: table('id'),\n}\n\n// createIndexedDB returns IndexedDbVaultStore with transactions and cursor iteration\nconst db = createIndexedDB({\n name: 'app-logs',\n schema,\n version: 1,\n})\n\nawait db.putAll('logs', [\n { id: 1, level: 'info', message: 'App started', ts: Date.now() - 3000 },\n { id: 2, level: 'warn', message: 'Slow query detected', ts: Date.now() - 2000 },\n { id: 3, level: 'error', message: 'Request failed', ts: Date.now() - 1000 },\n { id: 4, level: 'info', message: 'Request succeeded', ts: Date.now() },\n], ttl.hours(1))\n\n// batch() is atomic on IndexedDB — all writes commit or none do\nawait db.batch(['logs'], async (tx) => {\n await tx.put('logs', { id: 5, level: 'info', message: 'Batch committed', ts: Date.now() })\n await tx.deleteMany('logs', [1, 2]) // remove old entries in the same transaction\n})\n\n// iterate() — cursor-based streaming, only on IndexedDbVaultStore\n// the full table is never loaded into memory at once\nconst messages = []\nfor await (const entry of db.iterate('logs')) {\n messages.push(entry.message)\n}\nconsole.log('Streamed via iterate():', messages)\n\nconst errors = await db.query('logs').equals('level', 'error').toArray()\nconsole.log('Errors:', errors.map((e) => e.message))\nconsole.log('Total logs:', await db.query('logs').count())\n\nconst info = await db.debug()\nfor (const t of info.tables) {\n console.log(t.name + ':', t.recordCount, 'live,', t.expiredCount, 'expired')\n}\n\nawait db.dispose()",
|
|
33
|
+
"name": "IndexedDB — Atomic Batch & iterate()"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "prune-schedule",
|
|
37
|
+
"code": "import { scheduleExpiredPrune, table, ttl } from '@vielzeug/vault'\nimport { createMemory } from '@vielzeug/vault/memory'\n\n// scheduleExpiredPrune runs pruneExpired() on an interval.\n// Pass disposalSignal to auto-cancel when the store is torn down.\n\nconst schema = { sessions: table('token') }\nconst db = createMemory({ schema })\n\nconst stop = scheduleExpiredPrune(db, {\n interval: ttl.minutes(15),\n signal: db.disposalSignal,\n onError: (err) => console.error('[vault] prune failed:', err),\n})\n\n// Write a session that expires in 1 ms\nawait db.put('sessions', { token: 'abc', user: 1 }, ttl.ms(1))\nawait db.put('sessions', { token: 'def', user: 2 }) // no TTL — permanent\n\nconsole.log('before prune:', await db.count('sessions')) // 2 (lazy eviction: both exist physically)\n\n// Manual prune to demonstrate the API\nawait new Promise((resolve) => setTimeout(resolve, 5))\nconst pruned = await db.pruneExpired()\nconsole.log('pruned:', pruned.sessions) // 1 (the expired session)\nconsole.log('after prune:', await db.count('sessions')) // 1\n\n// stop() before dispose, or rely on disposalSignal auto-cancel\nstop()\nawait db.dispose()",
|
|
38
|
+
"name": "TTL — scheduleExpiredPrune with disposalSignal"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"id": "query-builder",
|
|
42
|
+
"code": "import { table } from '@vielzeug/vault'\nimport { createLocalStorage } from '@vielzeug/vault/local-storage'\n\nconst schema = {\n products: table('id'),\n}\n\nconst db = createLocalStorage({ name: 'shop', schema })\n\nawait db.putAll('products', [\n { id: 1, name: 'Laptop', price: 999, category: 'electronics', inStock: true },\n { id: 2, name: 'Mouse', price: 29, category: 'electronics', inStock: true },\n { id: 3, name: 'Desk', price: 299, category: 'furniture', inStock: false },\n { id: 4, name: 'Chair', price: 199, category: 'furniture', inStock: true },\n { id: 5, name: 'Monitor', price: 399, category: 'electronics', inStock: true },\n])\n\nconst pageSize = 2\nconst pageIndex = 0\n\n// Build a base query — reuse it for both the page slice and the total count\nconst q = db\n .query('products')\n .equals('category', 'electronics')\n .filter((p) => p.inStock)\n .orderBy('price', 'asc')\n\n// count() ignores limit/offset/orderBy — returns the full filtered set size\nconst page = await q.limit(pageSize).offset(pageIndex * pageSize).toArray()\nconst total = await q.count()\n\nconsole.log('Page:', page.map((p) => p.name))\nconsole.log('Total matching:', total)\nconsole.log('Page 1 of', Math.ceil(total / pageSize))\n\n// startsWith with case-insensitive flag\nconst mice = await db.query('products').startsWith('name', 'm', { ignoreCase: true }).toArray()\nconsole.log('Starts with m:', mice.map((p) => p.name))\n\n// predicate delete\nconst removed = await db.query('products').filter((p) => !p.inStock).delete()\nconsole.log('Removed out-of-stock:', removed)\n\n// first()\nconst cheapest = await db.query('products').orderBy('price', 'asc').first()\nconsole.log('Cheapest:', cheapest?.name, cheapest?.price)",
|
|
43
|
+
"name": "Query Builder — Filters, Pagination, count"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "reactive-observe",
|
|
47
|
+
"code": "import { table } from '@vielzeug/vault'\nimport { createMemory } from '@vielzeug/vault/memory'\n\nconst db = createMemory({ schema: { users: table('id') } })\nconst snapshots = []\nconst stop = db.observe('users', (users) => snapshots.push(users.map((user) => user.name)))\n\nawait Promise.resolve()\nawait db.put('users', { id: 1, name: 'Ada' })\nawait Promise.resolve()\n\nconsole.log(snapshots) // [[], ['Ada']]\nstop()\nawait db.dispose()",
|
|
48
|
+
"name": "Reactive — observe()"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "ttl-expiration",
|
|
52
|
+
"code": "import { table, ttl } from '@vielzeug/vault'\nimport { createLocalStorage } from '@vielzeug/vault/local-storage'\n\nconst schema = {\n cache: table('id'),\n}\n\nconst db = createLocalStorage({ name: 'cache-demo', schema })\n\n// ttl helpers produce finite, positive millisecond durations\nawait db.put('cache', { id: 'short', data: 'Expires in 1 second' }, ttl.seconds(1))\nawait db.put('cache', { id: 'long', data: 'Expires in 5 minutes' }, ttl.minutes(5))\nconsole.log('Stored records with TTL')\nconsole.log('Immediate read:', await db.get('cache', 'short'))\n\nawait new Promise((resolve) => setTimeout(resolve, 1500))\nconsole.log('After 1.5s:', await db.get('cache', 'short')) // expired — undefined\nconsole.log('Long-lived still here:', await db.get('cache', 'long'))\n\nconsole.log('ttl helpers:', {\n '100ms': ttl.ms(100),\n '5 minutes': ttl.minutes(5),\n '2 hours': ttl.hours(2),\n '7 days': ttl.days(7),\n})",
|
|
53
|
+
"name": "TTL & Expiration"
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
"typeSignatures": {
|
|
57
|
+
"VaultDisposedError": "export { VaultDisposedError, VaultError, VaultMigrationError, VaultQuotaError, VaultScopeError } from './errors';",
|
|
58
|
+
"VaultError": "export { VaultDisposedError, VaultError, VaultMigrationError, VaultQuotaError, VaultScopeError } from './errors';",
|
|
59
|
+
"VaultMigrationError": "export { VaultDisposedError, VaultError, VaultMigrationError, VaultQuotaError, VaultScopeError } from './errors';",
|
|
60
|
+
"VaultQuotaError": "export { VaultDisposedError, VaultError, VaultMigrationError, VaultQuotaError, VaultScopeError } from './errors';",
|
|
61
|
+
"VaultScopeError": "export { VaultDisposedError, VaultError, VaultMigrationError, VaultQuotaError, VaultScopeError } from './errors';",
|
|
62
|
+
"scheduleExpiredPrune": "export { scheduleExpiredPrune } from './prune';",
|
|
63
|
+
"QueryBuilder": "export type { QueryBuilder } from './query';",
|
|
64
|
+
"isExpired": "export { isExpired, ttl } from './ttl';",
|
|
65
|
+
"ttl": "export { isExpired, ttl } from './ttl';",
|
|
66
|
+
"AnySchema": "export type {\n AnySchema,\n BaseAdapterOptions,\n DebugInfo,\n DebugStats,\n IterableVaultStore,\n KeyOf,\n MetricsEvent,\n Observer,\n RecordOf,\n RecordValidator,\n SchemaEntry,\n TableValidators,\n TransactionalVaultStore,\n Unsubscribe,\n VaultKey,\n VaultLogger,\n VaultStore,\n} from './types';",
|
|
67
|
+
"BaseAdapterOptions": "export type {\n AnySchema,\n BaseAdapterOptions,\n DebugInfo,\n DebugStats,\n IterableVaultStore,\n KeyOf,\n MetricsEvent,\n Observer,\n RecordOf,\n RecordValidator,\n SchemaEntry,\n TableValidators,\n TransactionalVaultStore,\n Unsubscribe,\n VaultKey,\n VaultLogger,\n VaultStore,\n} from './types';",
|
|
68
|
+
"DebugInfo": "export type {\n AnySchema,\n BaseAdapterOptions,\n DebugInfo,\n DebugStats,\n IterableVaultStore,\n KeyOf,\n MetricsEvent,\n Observer,\n RecordOf,\n RecordValidator,\n SchemaEntry,\n TableValidators,\n TransactionalVaultStore,\n Unsubscribe,\n VaultKey,\n VaultLogger,\n VaultStore,\n} from './types';",
|
|
69
|
+
"DebugStats": "export type {\n AnySchema,\n BaseAdapterOptions,\n DebugInfo,\n DebugStats,\n IterableVaultStore,\n KeyOf,\n MetricsEvent,\n Observer,\n RecordOf,\n RecordValidator,\n SchemaEntry,\n TableValidators,\n TransactionalVaultStore,\n Unsubscribe,\n VaultKey,\n VaultLogger,\n VaultStore,\n} from './types';",
|
|
70
|
+
"IterableVaultStore": "export type {\n AnySchema,\n BaseAdapterOptions,\n DebugInfo,\n DebugStats,\n IterableVaultStore,\n KeyOf,\n MetricsEvent,\n Observer,\n RecordOf,\n RecordValidator,\n SchemaEntry,\n TableValidators,\n TransactionalVaultStore,\n Unsubscribe,\n VaultKey,\n VaultLogger,\n VaultStore,\n} from './types';",
|
|
71
|
+
"KeyOf": "export type {\n AnySchema,\n BaseAdapterOptions,\n DebugInfo,\n DebugStats,\n IterableVaultStore,\n KeyOf,\n MetricsEvent,\n Observer,\n RecordOf,\n RecordValidator,\n SchemaEntry,\n TableValidators,\n TransactionalVaultStore,\n Unsubscribe,\n VaultKey,\n VaultLogger,\n VaultStore,\n} from './types';",
|
|
72
|
+
"MetricsEvent": "export type {\n AnySchema,\n BaseAdapterOptions,\n DebugInfo,\n DebugStats,\n IterableVaultStore,\n KeyOf,\n MetricsEvent,\n Observer,\n RecordOf,\n RecordValidator,\n SchemaEntry,\n TableValidators,\n TransactionalVaultStore,\n Unsubscribe,\n VaultKey,\n VaultLogger,\n VaultStore,\n} from './types';",
|
|
73
|
+
"Observer": "export type {\n AnySchema,\n BaseAdapterOptions,\n DebugInfo,\n DebugStats,\n IterableVaultStore,\n KeyOf,\n MetricsEvent,\n Observer,\n RecordOf,\n RecordValidator,\n SchemaEntry,\n TableValidators,\n TransactionalVaultStore,\n Unsubscribe,\n VaultKey,\n VaultLogger,\n VaultStore,\n} from './types';",
|
|
74
|
+
"RecordOf": "export type {\n AnySchema,\n BaseAdapterOptions,\n DebugInfo,\n DebugStats,\n IterableVaultStore,\n KeyOf,\n MetricsEvent,\n Observer,\n RecordOf,\n RecordValidator,\n SchemaEntry,\n TableValidators,\n TransactionalVaultStore,\n Unsubscribe,\n VaultKey,\n VaultLogger,\n VaultStore,\n} from './types';",
|
|
75
|
+
"RecordValidator": "export type {\n AnySchema,\n BaseAdapterOptions,\n DebugInfo,\n DebugStats,\n IterableVaultStore,\n KeyOf,\n MetricsEvent,\n Observer,\n RecordOf,\n RecordValidator,\n SchemaEntry,\n TableValidators,\n TransactionalVaultStore,\n Unsubscribe,\n VaultKey,\n VaultLogger,\n VaultStore,\n} from './types';",
|
|
76
|
+
"SchemaEntry": "export type {\n AnySchema,\n BaseAdapterOptions,\n DebugInfo,\n DebugStats,\n IterableVaultStore,\n KeyOf,\n MetricsEvent,\n Observer,\n RecordOf,\n RecordValidator,\n SchemaEntry,\n TableValidators,\n TransactionalVaultStore,\n Unsubscribe,\n VaultKey,\n VaultLogger,\n VaultStore,\n} from './types';",
|
|
77
|
+
"TableValidators": "export type {\n AnySchema,\n BaseAdapterOptions,\n DebugInfo,\n DebugStats,\n IterableVaultStore,\n KeyOf,\n MetricsEvent,\n Observer,\n RecordOf,\n RecordValidator,\n SchemaEntry,\n TableValidators,\n TransactionalVaultStore,\n Unsubscribe,\n VaultKey,\n VaultLogger,\n VaultStore,\n} from './types';",
|
|
78
|
+
"TransactionalVaultStore": "export type {\n AnySchema,\n BaseAdapterOptions,\n DebugInfo,\n DebugStats,\n IterableVaultStore,\n KeyOf,\n MetricsEvent,\n Observer,\n RecordOf,\n RecordValidator,\n SchemaEntry,\n TableValidators,\n TransactionalVaultStore,\n Unsubscribe,\n VaultKey,\n VaultLogger,\n VaultStore,\n} from './types';",
|
|
79
|
+
"Unsubscribe": "export type {\n AnySchema,\n BaseAdapterOptions,\n DebugInfo,\n DebugStats,\n IterableVaultStore,\n KeyOf,\n MetricsEvent,\n Observer,\n RecordOf,\n RecordValidator,\n SchemaEntry,\n TableValidators,\n TransactionalVaultStore,\n Unsubscribe,\n VaultKey,\n VaultLogger,\n VaultStore,\n} from './types';",
|
|
80
|
+
"VaultKey": "export type {\n AnySchema,\n BaseAdapterOptions,\n DebugInfo,\n DebugStats,\n IterableVaultStore,\n KeyOf,\n MetricsEvent,\n Observer,\n RecordOf,\n RecordValidator,\n SchemaEntry,\n TableValidators,\n TransactionalVaultStore,\n Unsubscribe,\n VaultKey,\n VaultLogger,\n VaultStore,\n} from './types';",
|
|
81
|
+
"VaultLogger": "export type {\n AnySchema,\n BaseAdapterOptions,\n DebugInfo,\n DebugStats,\n IterableVaultStore,\n KeyOf,\n MetricsEvent,\n Observer,\n RecordOf,\n RecordValidator,\n SchemaEntry,\n TableValidators,\n TransactionalVaultStore,\n Unsubscribe,\n VaultKey,\n VaultLogger,\n VaultStore,\n} from './types';",
|
|
82
|
+
"VaultStore": "export type {\n AnySchema,\n BaseAdapterOptions,\n DebugInfo,\n DebugStats,\n IterableVaultStore,\n KeyOf,\n MetricsEvent,\n Observer,\n RecordOf,\n RecordValidator,\n SchemaEntry,\n TableValidators,\n TransactionalVaultStore,\n Unsubscribe,\n VaultKey,\n VaultLogger,\n VaultStore,\n} from './types';",
|
|
83
|
+
"table": "export { table } from './types';"
|
|
84
|
+
}
|
|
85
|
+
}
|