Files
Pranay Prakash 15ce521e51 [core] Encode start({ retention }) as an integer duration
The server reads `$retention` as a duration written as a decimal integer,
not as the name of a mode: it honors the string `'0'`, and resolves
everything else — `'none'` included — to the plan default while counting it
as `retention.unsupported`. As written the two halves disagreed and the
option would have been inert, so the wire value moves to `'0'`.

The unit that duration is measured in has deliberately not been decided
yet. It will most likely be seconds or milliseconds, chosen for
granularity, and explicitly not days. Zero is the one value that means the
same thing in every unit, which is exactly why it can ship ahead of that
decision: it commits to a shape — a number, so the namespace has somewhere
to grow — without committing to a scale.

That is also why the option is typed `0 | 'default'` rather than
`number | 'default'`. Someone writing `retention: 7` today has no unit to
have meant it in, and the server would quietly keep their data; the literal
type makes that a compile error, and a runtime guard makes it a thrown
error for untyped JS callers. The arbitrary-string pass-through goes for
the same reason — its only example, `'90d'`, baked in a unit — and callers
targeting a World with its own retention vocabulary still have the
documented escape hatch of writing `$retention` through `attributes` with
`allowReservedAttributes`.

With arbitrary strings gone, the empty-value and max-byte-length checks are
unreachable (a fixed one-byte value cannot fail either), so they go too
rather than sit as dead branches.
2026-08-27 21:49:25 -07:00
..
2026-08-21 22:17:38 -07:00

@workflow/world

Core interfaces and types for Workflow SDK storage backends.

This package defines the World interface that abstracts workflow storage, queuing, authentication, and streaming operations. Implementation packages like @workflow/world-local and @workflow/world-vercel provide concrete implementations.

Used internally by @workflow/core and world implementations. Should not be used directly in application code.

Implementation constraint: no mutable module state

A World implementation must not keep mutable state at module scope. Hold it on the World instance, or, when it is genuinely process-wide (an ID generator whose sequence must not fork, a log-once latch), on globalThis via globalSingleton() from @workflow/utils.

@workflow/world-local and @workflow/world-vercel are bundled into the host application's server build, and a bundler keys module identity on (resource, layer): Next.js alone compiles instrument, app-route, ssr and edge as separate module graphs, so one process holds one copy of every module in these packages per layer. A top-level let, or a const holding a Map, is therefore per-copy state rather than the singleton it reads as.

A world loaded at runtime through WORKFLOW_TARGET_WORLD is deduped by Node's module cache and does not have this problem today, but that is a property of how it is loaded, not of how it is written, and it has changed before (vercel/workflow#3493). scripts/lint/module-scope-state.mjs enforces the rule across every published world package; see docs/content/worlds/*/building-a-world.mdx for the author-facing version.