@demlik/tea

中文文档
0.27.0发布于 2026/11/09MITTypeScriptES Module构建来源已验证近 30 天下载 4.1万

TEA-faithful state machine substrate — one package, every host adapter. Core + subs + testing, plus React, Durable Object, Chrome extension, Node, in-memory, devtools, and property-testing modules as subpaths.

原始使用文档
文档目录6 节
  1. Install
  2. Quickstart
  3. Two engines, one machine
  4. React
  5. Durability
  6. Documentation

@demlik/tea

A TEA / Elm-Architecture TypeScript library for durable, replayable state machines — one pure reducer, every host adapter.

Install

pnpm add @demlik/tea

The one runtime dependency is @standard-schema/spec, a types-only package installed for you. Cmd.define takes any Standard Schema, so bring the schema library you already use (zod as it is, or Effect Schema through the Cmd.make that @demlik/tea/effect exports). The host and testing adapters ride on optional peers you add only for the surface you use: react / react-dom (the ./react hooks), ws (Node WebSocket subs), fast-check (the ./pbt property-testing helpers), vitest (the testing utilities), effect (the ./effect engine), and @opentelemetry/api (the ./otel spans).

Quickstart

A machine is plain data: an init and an update map keyed by Msg type. run drives it, and when it has effects you hand run the interpret map that performs them; dispatch folds a Msg; getState reads the current Model. You name the Model and the Msg union once, under types; everything else is inferred from the machine itself. Each of these words has an entry in the glossary.

import { defineMachine } from "@demlik/tea";
import { run } from "@demlik/tea/promise";

type State = { readonly count: number };
type Msg = { readonly type: "increment" } | { readonly type: "reset" };

const counter = defineMachine({
  types: { model: {} as State, msg: {} as Msg },
  init: (loaded) => (loaded !== null ? [loaded, []] : [{ count: 0 }, []]),
  update: {
    increment: (s) => [{ count: s.count + 1 }, []],
    reset: () => [{ count: 0 }, []],
  },
});

const runtime = await run(counter, {}).ready;
await runtime.dispatch({ type: "increment" });
runtime.getState(); // { count: 1 }

Two engines, one machine

The core is @demlik/tea, and each engine has its own entry point. A machine file imports only the core, so it runs unchanged on either engine.

  • @demlik/tea — the core: defineMachine, Cmd, replay and the types. It imports no engine.
  • @demlik/tea/promise — the Promise engine. Handlers return Promises.
  • @demlik/tea/effect — the Effect engine (stable, Effect v4). Handlers return Effects, services come from your Layers, the handle's verbs return Effects with typed errors, and closing the scope interrupts whatever is in flight.

Run a machine on the Promise engine and on the Effect engine build on the same machine file. Run many machines under one parent starts child machines on the Effect engine with spawn, tell and stop. Coming from 0.15? The migration guide shows every removed or reshaped API, before and after.

React

useMachine from @demlik/tea/react returns [state, send]; send(msg) returns undefined immediately, and the run's onError hears step failures once. A Msg held at unmount or sent after stop reaches the sink as DispatchDiscardedError under phase: "discard". useRuntime(run, { onError }) renders a booted run from either engine, or a hand-built handle with a Promise-returning dispatch, with the same [state, send] contract. Its optional sink hears discards and hand-built dispatch failures; a run keeps its own step-error sink. useMachine takes either engine's run. For the Effect engine pass runIn(runtime) from @demlik/tea/effect, with your ManagedRuntime; its interpret cells return an Effect and its subscribe runners a Stream, and the lifecycle (one run per mount under Strict Mode, Subs paused on an <Activity> hide, the run stopped on unmount) is the same. useRuntime(run, (model) => model.views[id]) reads one part and re-renders on runtime updates only when that selected value changes (Object.is).

useBackgroundRuntime from @demlik/tea/extension keeps { state, dispatch }, with a void-returning dispatch. Its optional onError hears transport failures under phase: "follow-up"; background step failures go to the background run. The context Provider and useDispatch follow the same contract.

Read the state for the outcome. Code that previously awaited a hook's dispatch should remove the await; host code that needs to wait should own the run and use its engine's dispatch, idle, or done.

Drive a machine from React shows click handlers, lifecycle behavior, and error sinks.

Durability

With fileStore or doStore, state is saved before commands run. Restarting loads it. agent.run resumes pending work automatically; custom machines handle a boot message. An effect can run again if its result was not saved before the crash.

Make a machine durable and understand restore versus replay.

Documentation

The four Diátaxis quadrants live in docs/:

  • Tutorials — learning-oriented lessons that take you through the library by building a real machine.
  • How-to guides — goal-oriented directions for getting a specific job done.
  • Reference — information-oriented API description, generated per public module.
  • Explanation — understanding-oriented discussion of how it works and why it is shaped this way.

The glossary defines each word those pages use: Model, Msg, Cmd, Sub, Store and the rest.

Two more surfaces sit behind those, for people working on the library rather than using it: .patterns/ is how the code is shaped, and .decisions/ is why it was shaped that way.

版本与发布记录

@demlik/tea 共发布 30 个版本,首次发布于 2026/06/03;过去 12 个月发布了 30 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.27.0latest2026/11/092 天前
0.26.02026/11/083 天前
0.25.02026/11/083 天前
0.24.02026/11/083 天前
0.23.02026/11/047 天前
0.22.02026/11/029 天前

关于 @demlik/tea 的常见问题

@demlik/tea 是什么?

TEA-faithful state machine substrate — one package, every host adapter. Core + subs + testing, plus React, Durable Object, Chrome extension, Node, in-memory, devtools, and property-testing modules as subpaths.

如何安装 @demlik/tea?

在项目目录运行 npm install @demlik/tea,或使用 pnpm add / yarn add / bun add。

@demlik/tea 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@demlik/tea 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@demlik/tea 可以商用吗?

@demlik/tea 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@demlik/tea 的下载量有多少?

根据 npm 官方下载统计,@demlik/tea 在 2026/10/12 – 2026/11/10 这 30 天内被下载 40,934 次,日均约 1,364 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@demlik/tea 还在维护吗?

最新版本 0.27.0 发布于 2026/11/09(2 天前);至今共发布 30 个版本,其中过去 12 个月发布了 30 个;首次发布于 2026/06/03。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。