{"_id":"@b-jones-rfd/tea-set","_rev":"5-be0347830e67eaf399330183a45c6bca","name":"@b-jones-rfd/tea-set","dist-tags":{"latest":"2.0.0"},"versions":{"0.0.1":{"name":"@b-jones-rfd/tea-set","version":"0.0.1","keywords":[],"author":"B-Jones-RFD","license":"Apache-2.0","_id":"@b-jones-rfd/tea-set@0.0.1","maintainers":[{"name":"b-jones-rfd","email":"b.jones.rfd.82@gmail.com"}],"dist":{"shasum":"3a756cbe3b81c8b28d39de3efdf7e2a8d96d0a34","tarball":"https://registry.npmjs.org/@b-jones-rfd/tea-set/-/tea-set-0.0.1.tgz","fileCount":7,"integrity":"sha512-lx/Eqw0VLYhIj8pkJlGek3bRpvB1f54yxLBVRUD9CkgLBddQv8j38Ur/Q/ibP9rkoou/A/Tx3F2ZtWV9jBFCjQ==","signatures":[{"sig":"MEUCIQCd1OewROY5omR+6mMPWyryJc0FFHdxaj/5LNxLVFOUKAIgJppYqDHjbT7webnrwWpCB617LXMICh/Sl8SyyxzL0tw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":12880},"main":"./dist/index.js","type":"module","types":"./dist/index.d.mts","module":"./dist/index.mjs","engines":{"node":">=24"},"private":false,"scripts":{"ci":"pnpm run lint && pnpm run test && pnpm run build","dev":"vitest","lint":"tsc","test":"vitest run","build":"tsup","release":"pnpm run build && pnpm publish -r --no-git-checks"},"_npmUser":{"name":"b-jones-rfd","email":"b.jones.rfd.82@gmail.com"},"devEngines":{"packageManager":{"name":"pnpm","onFail":"download","version":"^11.13.0"}},"repository":{"url":"git+https://github.com/B-Jones-RFD/tea-set.git","type":"git"},"description":"TEA implementation utilities","directories":{},"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","vitest":"^4.1.10","prettier":"^3.9.6","typescript":"6.0.3","@types/node":"^26.2.0","@changesets/cli":"^3.0.0"},"_npmOperationalInternal":{"tmp":"tmp/tea-set_0.0.1_1787006759380_0.8755118687274994","host":"s3://npm-registry-packages-npm-production"}},"0.0.2":{"name":"@b-jones-rfd/tea-set","version":"0.0.2","keywords":[],"author":"B-Jones-RFD","license":"Apache-2.0","_id":"@b-jones-rfd/tea-set@0.0.2","maintainers":[{"name":"b-jones-rfd","email":"b.jones.rfd.82@gmail.com"}],"dist":{"shasum":"48916379bfbd226e13fb8f5daa21267a02934e57","tarball":"https://registry.npmjs.org/@b-jones-rfd/tea-set/-/tea-set-0.0.2.tgz","fileCount":8,"integrity":"sha512-pC8avh1AXQlo0HMet8h4GMoGnIFJ0sosR5czibYPAJAmmGJH1vYLMKgilruSUiMMqitKbPBI7HRTMNqCEAZr3A==","signatures":[{"sig":"MEUCIQDvd4zJFjYVetCOYmbOErsEvePsyNK+bGtHz5RTR9U8wQIgJCf0EOfI4VilcRFDOf1Ic1HSAxctRRmDiVuJIpeTXYM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@b-jones-rfd%2ftea-set@0.0.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":12965},"main":"./dist/index.js","type":"module","types":"./dist/index.d.mts","module":"./dist/index.mjs","engines":{"node":">=24"},"private":false,"scripts":{"ci":"pnpm run lint && pnpm run test && pnpm run build","dev":"vitest","lint":"tsc","test":"vitest run","build":"tsup","release":"pnpm run build && pnpm publish -r --no-git-checks"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:8636d2f8-8508-42eb-ab4c-ee8310857a60"}},"devEngines":{"packageManager":{"name":"pnpm","onFail":"download","version":"^11.13.0"}},"repository":{"url":"git+https://github.com/B-Jones-RFD/tea-set.git","type":"git"},"description":"TEA implementation utilities","directories":{},"_nodeVersion":"24.19.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","vitest":"^4.1.10","prettier":"^3.9.6","typescript":"6.0.3","@types/node":"^26.2.0","@changesets/cli":"^3.0.0"},"_npmOperationalInternal":{"tmp":"tmp/tea-set_0.0.2_1787012047487_0.5172079342327591","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"@b-jones-rfd/tea-set","version":"1.0.0","keywords":[],"author":"B-Jones-RFD","license":"Apache-2.0","_id":"@b-jones-rfd/tea-set@1.0.0","maintainers":[{"name":"b-jones-rfd","email":"b.jones.rfd.82@gmail.com"}],"dist":{"shasum":"2d14f039c9844589190735ed7d76ffdfbe3eca27","tarball":"https://registry.npmjs.org/@b-jones-rfd/tea-set/-/tea-set-1.0.0.tgz","fileCount":9,"integrity":"sha512-BJEpMdG7tRRtzZx4iHRCZBBSgcN0GIvAyh3Jgc8FLvbQ1e35R0iHMytpp7dHwQqzfTRyFTH3x4Vo15y3e5aSLQ==","signatures":[{"sig":"MEUCIElU82MSx0NXkrKl2VAB4mjl3s1KYK9v6r1VFDZf1CqlAiEA4DmUAWgqTYk0rHkl5tTrPWH4jL66toWMhIQC1gafNGk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@b-jones-rfd%2ftea-set@1.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":62973},"main":"./dist/index.js","type":"module","types":"./dist/index.d.mts","module":"./dist/index.mjs","engines":{"node":">=24"},"private":false,"scripts":{"ci":"pnpm run lint && pnpm run test && pnpm run build","dev":"vitest","lint":"tsc","test":"vitest run","build":"tsup","release":"pnpm run build && pnpm publish -r --no-git-checks"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:8636d2f8-8508-42eb-ab4c-ee8310857a60"}},"devEngines":{"packageManager":{"name":"pnpm","onFail":"download","version":"^11.13.0"}},"repository":{"url":"git+https://github.com/B-Jones-RFD/tea-set.git","type":"git"},"description":"TEA implementation utilities","directories":{},"_nodeVersion":"24.19.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","vitest":"^4.1.10","prettier":"^3.9.6","typescript":"6.0.3","@types/node":"^26.2.0","@changesets/cli":"^3.0.0"},"_npmOperationalInternal":{"tmp":"tmp/tea-set_1.0.0_1787017446724_0.3918348254069224","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@b-jones-rfd/tea-set","version":"1.0.1","keywords":["elm","elm-architecture","tea","typescript","state-management","functional","cmd","subscriptions","maybe","result","remote-data"],"author":"B-Jones-RFD","license":"Apache-2.0","_id":"@b-jones-rfd/tea-set@1.0.1","maintainers":[{"name":"b-jones-rfd","email":"b.jones.rfd.82@gmail.com"}],"homepage":"https://github.com/B-Jones-RFD/tea-set#readme","bugs":{"url":"https://github.com/B-Jones-RFD/tea-set/issues"},"dist":{"shasum":"8009e7cf69d6f59ea2db0d7eb2a4ad6d6c8332ae","tarball":"https://registry.npmjs.org/@b-jones-rfd/tea-set/-/tea-set-1.0.1.tgz","fileCount":5,"integrity":"sha512-zAzzDmVVytT3C5iFjWQuKw5PzwWgCXW2W9X5ZLsHrgDfVoJMFlpK3YNGFXpuplMTKkpWEOuihbwTSRFa1/Nxbw==","signatures":[{"sig":"MEUCIQDq7J6fu5SDHyjzcKvslWWa4Rl+l+LU8WZETgg9M8Do1AIgOymhnyNxOYIMrhg1tzHrrsLYa3F7GPrXEjc0oPEcfR8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEQCIDesaFWv/Gd03mpOOBfIqzr2vmnnf9DHyLmXXEM1DfPnAiAQ4xMTMYUwXLnoCZXfD9S3uficZmvlKE/j8wHalVy9kQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@b-jones-rfd%2ftea-set@1.0.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":81237},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=24"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"private":false,"scripts":{"ci":"pnpm run lint && pnpm run test && pnpm run build","dev":"vitest","lint":"tsc -p tsconfig.test.json","test":"vitest run","build":"tsdown","release":"pnpm run build && pnpm publish --provenance --no-git-checks","version":"changeset version","examples":"pnpm run build && node examples/run.ts","changeset":"changeset"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:8636d2f8-8508-42eb-ab4c-ee8310857a60"}},"devEngines":{"packageManager":{"name":"pnpm","onFail":"download","version":"^11.13.0"}},"repository":{"url":"git+https://github.com/B-Jones-RFD/tea-set.git","type":"git"},"description":"Utilities for writing programs in The Elm Architecture in TypeScript","directories":{},"sideEffects":false,"_nodeVersion":"26.0.0","publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.23.0","vitest":"^5.0.1","prettier":"^3.9.7","typescript":"^7.0.2","@types/node":"^26.6.1","@changesets/cli":"^3.0.3"},"_npmOperationalInternal":{"tmp":"tmp/tea-set_1.0.1_1789695041465_0.8890329438206812","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"_id":"@b-jones-rfd/tea-set@2.0.0","bugs":{"url":"https://github.com/B-Jones-RFD/tea-set/issues"},"dist":{"shasum":"6148491cb703f0d3666233120b694257e45f6ae5","tarball":"https://registry.npmjs.org/@b-jones-rfd/tea-set/-/tea-set-2.0.0.tgz","fileCount":5,"integrity":"sha512-kfKKwzJaeq6Fhw26zhyyVbGmoBU7CKwniKKG4sUD2RlTmmjSBbzWNeHCX8l8p5b78EoAl6xQmaUWeKKZawB1bw==","signatures":[{"sig":"MEYCIQCWlUgk3RdMrRJe6aceOiLiqBeVUydkUHvmIiIJVQxZJwIhAPNSbSRGakJ6R/AsbBl6z0OtPFP1mE4bL/H6Qlpmcgvl","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDjH7DyOxFQ6n6jGNY38zi1sWVYKPcRVdV7QgAty+Nb/AIgXvvw3eK7K8j00foaH8BXIwjSSwRg5Ey07SGT2MyqHPg="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@b-jones-rfd%2ftea-set@2.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":81237},"main":"./dist/index.js","name":"@b-jones-rfd/tea-set","type":"module","types":"./dist/index.d.ts","author":"B-Jones-RFD","engines":{"node":">=24"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"license":"Apache-2.0","private":false,"scripts":{"ci":"pnpm run lint && pnpm run test && pnpm run build","dev":"vitest","lint":"tsc -p tsconfig.test.json","test":"vitest run","build":"tsdown","release":"pnpm run build && pnpm publish --provenance --no-git-checks","version":"changeset version","examples":"pnpm run build && node examples/run.ts","changeset":"changeset"},"version":"2.0.0","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:8636d2f8-8508-42eb-ab4c-ee8310857a60"}},"homepage":"https://github.com/B-Jones-RFD/tea-set#readme","keywords":["elm","elm-architecture","tea","typescript","state-management","functional","cmd","subscriptions","maybe","result","remote-data"],"devEngines":{"packageManager":{"name":"pnpm","onFail":"download","version":"^11.13.0"}},"repository":{"url":"git+https://github.com/B-Jones-RFD/tea-set.git","type":"git"},"description":"Utilities for writing programs in The Elm Architecture in TypeScript","directories":{},"maintainers":[{"name":"b-jones-rfd","email":"b.jones.rfd.82@gmail.com"}],"sideEffects":false,"_nodeVersion":"26.0.0","publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.23.0","vitest":"^5.0.1","prettier":"^3.9.7","typescript":"^7.0.2","@types/node":"^26.6.1","@changesets/cli":"^3.0.3"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/tea-set_2.0.0_1789695648874_0.00850670888466487"}}},"time":{"created":"2026-08-17T22:45:59.185Z","modified":"2026-09-18T01:40:49.259Z","0.0.1":"2026-08-17T22:45:59.522Z","0.0.2":"2026-08-18T00:14:07.624Z","1.0.0":"2026-08-18T01:44:06.872Z","1.0.1":"2026-09-18T01:30:41.578Z","2.0.0":"2026-09-18T01:40:48.947Z"},"bugs":{"url":"https://github.com/B-Jones-RFD/tea-set/issues"},"author":"B-Jones-RFD","license":"Apache-2.0","homepage":"https://github.com/B-Jones-RFD/tea-set#readme","keywords":["elm","elm-architecture","tea","typescript","state-management","functional","cmd","subscriptions","maybe","result","remote-data"],"repository":{"url":"git+https://github.com/B-Jones-RFD/tea-set.git","type":"git"},"description":"Utilities for writing programs in The Elm Architecture in TypeScript","maintainers":[{"name":"b-jones-rfd","email":"b.jones.rfd.82@gmail.com"}],"readme":"# tea-set\n\nUtilities for writing programs in **The Elm Architecture** in TypeScript: `Cmd`, `Sub`, `Task`, `Maybe`, `Result`, `RemoteData`, parent/child composition, and a small runtime (`element`) that drives the `init` / `update` / `subscriptions` / `view` loop.\n\n- **Framework-agnostic.** `view` is a callback you supply; render with whatever you like.\n- **Zero runtime dependencies.** ESM with TypeScript declarations.\n- **Commands are data.** A `Cmd` describes work; nothing runs until the runtime performs it, so `update` stays pure and testable.\n- **Elm names.** If you know `Cmd.batch`, `Task.attempt`, `Maybe.withDefault`, `Sub.map`, you already know the API.\n\n## Install\n\n```sh\npnpm add @b-jones-rfd/tea-set\n```\n\nRequires Node ≥ 24 (or any modern browser); `AbortSignal` is used for cancellation.\n\n## A complete program\n\n```ts\nimport { Cmd, Sub, element, assertNever } from '@b-jones-rfd/tea-set'\nimport type { Program } from '@b-jones-rfd/tea-set'\n\ntype Model = { count: number; ticking: boolean }\ntype Msg = { kind: 'Incremented' } | { kind: 'TickingToggled' } | { kind: 'Ticked' }\n\nconst everyMs = <Msg>(ms: number, toMsg: () => Msg): Sub<Msg> =>\n  Sub.fromSource('time/every', { ms }, ({ ms }, dispatch) => {\n    const id = setInterval(() => dispatch(toMsg()), ms)\n    return () => clearInterval(id)\n  })\n\nconst program: Program<void, Model, Msg> = {\n  init: () => [{ count: 0, ticking: false }, Cmd.none],\n\n  update: (msg, model) => {\n    switch (msg.kind) {\n      case 'Incremented':\n      case 'Ticked':\n        return [{ ...model, count: model.count + 1 }, Cmd.none]\n      case 'TickingToggled':\n        return [{ ...model, ticking: !model.ticking }, Cmd.none]\n      default:\n        return assertNever(msg) // add a variant to Msg and this stops compiling\n    }\n  },\n\n  // Declared, never started by hand: the runtime starts, restarts and stops sources\n  // as the value returned here changes after each update.\n  subscriptions: (model) => (model.ticking ? everyMs(1000, () => ({ kind: 'Ticked' })) : Sub.none),\n\n  view: (model, dispatch) => {\n    document.body.textContent = `count: ${model.count}`\n    document.body.onclick = () => dispatch({ kind: 'Incremented' })\n  },\n}\n\nconst app = element({ program })\napp.dispatch({ kind: 'TickingToggled' }) // an \"incoming port\": outside code can send a Msg\n// later: app.stop() — tears down subscriptions and aborts in-flight tasks\n```\n\n`init(flags)` and `update(msg, model)` both return `[Model, Cmd<Msg>]`, Elm's `(Model, Cmd Msg)`.\n\n## Effects as tasks\n\nWrap Promise-returning work in a `Task`; turn it into a `Cmd` with `Task.attempt` (may fail, delivers a `Result`) or `Task.perform` (cannot fail).\n\n```ts\nimport { Task, RemoteData, Loading } from '@b-jones-rfd/tea-set'\nimport type { Result } from '@b-jones-rfd/tea-set'\n\ntype User = { id: string; name: string }\ntype HttpError = { kind: 'Network'; reason: string }\n\nconst searchUsers = (query: string): Task<HttpError, User[]> =>\n  Task.fromPromise(\n    async (signal) => {\n      const res = await fetch(`/api/users?q=${encodeURIComponent(query)}`, { signal })\n      if (!res.ok) throw new Error(`HTTP ${res.status}`)\n      return (await res.json()) as User[]\n    },\n    (e) => ({ kind: 'Network', reason: e instanceof Error ? e.message : String(e) })\n  )\n\n// in update:\ncase 'QueryChanged':\n  return [\n    { ...model, results: Loading },\n    Task.attempt((result: Result<HttpError, User[]>): Msg => ({ kind: 'Received', result }), searchUsers(msg.query)),\n  ]\ncase 'Received':\n  return [{ ...model, results: RemoteData.fromResult(msg.result) }, Cmd.none]\n```\n\nThe runtime passes its `AbortSignal` to every task. `stop()` aborts it: in-flight work is cancelled and any result arriving afterwards is discarded — no `Msg` is dispatched. Thrown errors and rejections inside a task become `Err` via the `onError` mapper you give `fromPromise`.\n\n`Cmd.effect((dispatch) => …)` is the escape hatch for effects that don't fit a task.\n\n## Parent / child composition\n\nA child's `Msg` is split with `ChildMsg<Internal, External>`: `Internal` messages it handles itself, `External` events it raises for its parent. The child never learns the parent's `Msg` type.\n\n```ts\n// search-box.ts (child)\nimport { Cmd, Internal, raise } from '@b-jones-rfd/tea-set'\nimport type { ChildMsg } from '@b-jones-rfd/tea-set'\n\nexport type InternalMsg = { kind: 'QueryChanged'; query: string } | { kind: 'Chose'; user: User }\nexport type ExternalMsg = { kind: 'UserChosen'; user: User }\nexport type Msg = ChildMsg<InternalMsg, ExternalMsg>\n\nexport const update = (msg: Msg, model: Model): [Model, Cmd<Msg>] => {\n  if (msg.kind === 'External') return [model, Cmd.none] // addressed to the parent\n  switch (msg.msg.kind) {\n    case 'Chose':\n      return [{ ...model, open: false }, raise({ kind: 'UserChosen', user: msg.msg.user })]\n    // …\n  }\n}\n```\n\n```ts\n// app.ts (parent)\nimport { Cmd, Sub, Internal, translate, updateChild } from '@b-jones-rfd/tea-set'\nimport type { Translator } from '@b-jones-rfd/tea-set'\nimport * as SearchBox from './search-box'\n\ntype Msg = { kind: 'SearchMsg'; msg: SearchBox.Msg } | { kind: 'UrlChanged'; path: string }\n\nconst searchTranslator: Translator<SearchBox.InternalMsg, SearchBox.ExternalMsg, Msg> = {\n  onInternal: (msg) => ({ kind: 'SearchMsg', msg: Internal(msg) }),\n  onExternal: (event) => ({ kind: 'UrlChanged', path: `/users/${event.user.id}` }),\n}\nconst toParent = translate(searchTranslator)\n\nconst search = updateChild({\n  get: (model: Model) => model.search,\n  set: (model, search) => ({ ...model, search }),\n  translator: searchTranslator,\n  update: SearchBox.update,\n})\n\n// update:            case 'SearchMsg': return search(msg.msg, model)\n// subscriptions:     Sub.map('search', toParent, SearchBox.subscriptions(model.search))\n// view (Html.map):   SearchBox.view(model.search, (msg) => dispatch(toParent(msg)))\n```\n\n`Sub.map` namespaces subscription keys (`search::keyboard/keydown`), so two instances of the same child never collapse into one subscription. `delegate(f, [model, cmd])` is the lower-level piece `updateChild` is built on.\n\n## API\n\nEvery namespace below is also a type of the same name (`const m: Maybe<number> = Maybe.map(…)`).\n\n| Namespace | Members |\n|---|---|\n| `Cmd` | `none`, `batch`, `map`, `effect` |\n| `Sub` | `none`, `batch`, `map(namespace, f, sub)`, `fromSource(key, params, start)` |\n| `Task` | `succeed`, `fail`, `fromPromise`, `map`, `mapError`, `andThen`, `map2`, `sequence`, `perform`, `attempt` |\n| `Maybe` | `map`, `map2`, `andThen`, `withDefault`, `isJust`, `isNothing`, `fromNullable`, `toNullable` |\n| `Result` | `map`, `mapError`, `andThen`, `withDefault`, `toMaybe`, `fromMaybe`, `isOk`, `isErr` |\n| `RemoteData` | `map`, `mapError`, `withDefault`, `fromResult`, `toMaybe`, `isSuccess`, `isLoading` |\n\n| Constructors | |\n|---|---|\n| `Just`, `Nothing` | `Maybe` |\n| `Ok`, `Err` | `Result` |\n| `NotAsked`, `Loading`, `Failure`, `Success` | `RemoteData` |\n| `Internal`, `External` | `ChildMsg` |\n\n| Function | |\n|---|---|\n| `element({ program, flags?, onError? })` | Start a program; returns `{ dispatch, stop }` |\n| `translate(translator)` | `ChildMsg → ParentMsg` |\n| `delegate(f, [model, cmd])` | Map a child's `Cmd` into the parent's |\n| `updateChild({ get, set, translator, update })` | A parent-shaped update step for one child |\n| `raise(event)` | A `Cmd` that raises an `External` event |\n| `assertNever(x)` | Exhaustiveness check for `switch` |\n\nTypes: `Program`, `Dispatch`, `ChildMsg`, `Translator`, `ElementOptions`, `ErrorContext`, `ErrorStep`, `Running`, `SubSource`, `CmdLeaf`, `Teardown`.\n\n### Testing helpers\n\nBecause a `Cmd` is data, `update` can be tested without running anything:\n\n```ts\nimport { flattenCmd, runCmd } from '@b-jones-rfd/tea-set'\n\nconst [model, cmd] = update({ kind: 'QueryChanged', query: 'ada' }, initialModel)\nflattenCmd(cmd)           // → [{ kind: 'Attempt', task, toMsg }] — inspect without performing\nawait runCmd(cmd)         // → the Msgs it dispatches, once every task has settled\n```\n\n`runCmd` rejects with a `CmdError { step, cause }` if an effect or task fails. `flattenSubs(sub)` lists a subscription's `Source` leaves.\n\n## Runtime semantics\n\n- After each `update`: `view` is rendered, subscriptions are reconciled, then the returned `Cmd` is performed.\n- Messages dispatched while `update` runs (from `view`, an effect, or a subscription) are queued and processed in order, never recursively.\n- Subscriptions are reconciled by `key`: new keys start, missing keys are torn down, and a key whose `params` changed (structural equality on primitives, arrays, plain objects and `Date`s) is restarted. Put anything else — class instances, `Map`s — into the key string instead.\n- **Errors.** If `update`, `view`, `subscriptions`, a source's `start`/teardown, a `Cmd.effect`, or a `perform`ed task fails, the runtime calls `onError(error, { step, msg })` if you supplied one, otherwise rethrows. The model is left as it was before the failing step. A throwing `init` always propagates from `element`.\n- `stop()` tears down every subscription, aborts the program's `AbortSignal`, and ignores later `dispatch` calls.\n\n## Examples\n\n[`examples/`](./examples) contains a counter, a search-box child, and a parent app composing it, runnable under Node with `pnpm run examples`. They are type-checked with the tests but not published.\n\n## License\n\nApache-2.0\n","readmeFilename":""}