@e280/strata

中文文档
0.5.7发布于 2026/11/07MITTypeScriptES Module构建来源已验证近 30 天下载 3332

state management

原始使用文档
文档目录23 节
  1. get in loser, we're managing state
  2. 🚦 strata signals
  3. 🚦 signal
  4. 🚦 derived
  5. 🚦 effects
  6. 🚦 batch
  7. 🚦 types
  8. 🔮 strata prism
  9. 🔮 prism and lenses
  10. 🔮 chrono for time travel
  11. 🔮 persistence to localStorage
  12. ⌛ strata wait
  13. ⌛ good things come to those who wait
  14. ⌛ waitFormal is persnickety belt-and-suspenders mode
  15. ⌛ wait, there's more
  16. 🪄 strata tracker
  17. 🪄 invent your own novel state concept
  18. 🪄 integrate your ui framework for auto-rerendering
  19. ⚛️ react bindings
  20. ⚛️ setup your strata.ts module
  21. ⚛️ component enables fully automatic reactive re-rendering
  22. ⚛️ useTracked for a manual hands-on approach (plays nicer w…
  23. ⚛️ useSignal for local component state (and useDerived )



⛏️ strata

get in loser, we're managing state

📦 npm install @e280/strata
✨ it's basically about automagically rerendering ui when data changes
🦝 powers auto-reactivity in our view library @e280/sly
🧩 easily extensible, your custom state primitives can participate
🧙‍♂️ probably my tenth state management library, lol
🧑‍💻 a project by https://e280.org/

🚦 #signals, sweet little bundles of state
🔮 #prism, bigger centralized state trees
⌛ #wait, async helpers for loading spinners
🪄 #tracker, reactivity hub for making custom state
⚛️ #react, optional bindings for react



🚦 strata signals

reactive bundles of joy

import {signal, derived, effect, batch} from "@e280/strata"

🚦 signal

  • a signal holds a value
    const $count = signal(1)
    

    we kinda like the $ convention for signals

  • read the signal
    $count() // 1
    
  • write the signal
    $count(2)
    
  • 🆕 get/set alternative syntax
    $count.get() // 2
    $count.set(3) // 3
    
  • 🆕 .on to attach listener fns
    $count.on(n => console.log(n))
    

🚦 derived

  • combine signals like a formula
    const $alpha = signal(1)
    const $bravo = signal(10)
    const $product = derived(() => $alpha() * $bravo())
    
    $product() // 10
    $alpha(2)
    $product() // 🪄 20
    
  • btw,
    derived also gives you .get() and .on().

🚦 effects

  • effects run immediately, then again when relevant signals change
    effect(() => console.log($count()))
      // 3
      // the system detects '$count' is relevant
    
    $count(4)
      // 4
      // when $count is changed, the effect fn is run
    
  • you can pass 2nd responder fn, which does not run at first
    effect(() => $count(), count => console.log(count))
      // *nothing is initially logged*
    
    $count(5)
      // 5
      // when $count is changed, the responder fn is run
    
  • btw,
    effect returns a dispose fn if you need to stop it.

🚦 batch

  • optimize multiple writes into one fat update
    // call downstream effects only once
    batch(() => {
      $count(6)
      $count(7)
      $count(8)
    })
    

🚦 types

  • Signal<Value> — it's a signal fn
  • Derived<Value> — it's a derived fn
  • Valuable<Value> — could be Signal<Value> or Derived<Value>



🔮 strata prism

persistent app-level state

  • single-source-of-truth state tree
  • no spooky-dookie proxy magic — just god's honest javascript
  • immutable except for mutate(fn) calls
  • use many lenses, efficient reactivity
  • chrono provides undo/redo history
  • persistence, localstorage, cross-tab sync

🔮 prism and lenses

  • import prism
    import {Prism} from "@e280/strata"
    
  • prism is a state tree
    const prism = new Prism({
      snacks: {
        peanuts: 8,
        bag: ["popcorn", "butter"],
        person: {
          name: "chase",
          incredi: true,
        },
      },
    })
    
  • create lenses, which are views into state subtrees
    const snacks = prism.lens(state => state.snacks)
    const person = snacks.lens(state => state.person)
    
    • you can lens another lens
  • lens.state is a cloned mutable snapshot with chill typings
    snacks.state.peanuts // 8
    person.state.name // "chase"
    
    snacks.state.peanuts++
      // ⛔ attempted state mutation: uselessly changed temp snapshot
    
  • lens.frozen provides a deep-frozen immutable snapshot with strict typings
    snacks.frozen.peanuts // 8 (readonly)
    person.frozen.name // "chase" (readonly)
    
    snacks.frozen.peanuts++
      // ⛔ attempted frozen mutation: throw errors
    
  • only formal mutations can actually change state
    snacks.mutate(state => state.peanuts++)
      // ✅ formal mutations to change state
    
    snacks.state.peanuts // 9
    
  • array mutations are unironically based, actually
    snacks.mutate(state => state.bag.push("salt"))
    
  • effects can automatically react to changes
    effect(() => console.log(snacks.state.peanuts))
    
    snacks.mutate(state => state.peanuts++)
      // effect is run
    
    • it's the reading of the .state getter that allows effect to know what's relevant.

🔮 chrono for time travel

  • import stuff
    import {Chrono, chronicle} from "@e280/strata"
    
  • create a chronicle in your state
    const prism = new Prism({
    
        // chronicle stores history
        //        👇
      snacks: chronicle({
        peanuts: 8,
        bag: ["popcorn", "butter"],
        person: {
          name: "chase",
          incredi: true,
        },
      }),
    })
    
    • big-brain moment: the whole chronicle itself is stored in the state.. serializable.. think persistence — user can close their project, reopen, and their undo/redo history is still chillin' — brat girl summer
  • create a chrono-wrapped lens to interact with your chronicle
    const snacks = new Chrono(64, prism.lens(state => state.snacks))
      //                      👆
      // how many past snapshots to store
    
  • mutations will advance history, and undo/redo works
    snacks.mutate(s => s.peanuts = 101)
    
    snacks.undo()
      // back to 8 peanuts
    
    snacks.redo()
      // forward to 101 peanuts
    
  • check how many undoable or redoable steps are available
    snacks.undoable // 1
    snacks.redoable // 0
    
  • you can make sub-lenses of a chrono, all their mutations advance history too
  • plz pinky-swear right now, that you won't create a chrono under a lens under another chrono 💀

🔮 persistence to localStorage

  • import prism
    import {Vault, LocalStore} from "@e280/strata"
    
  • create a local storage store
    const store = new LocalStore("myAppState")
    
  • make a vault for your prism
    const vault = new Vault({
      prism,
      store,
      version: 1, // 👈 bump this when you break your state schema!
    })
    
  • cross-tab sync (load on storage events)
    store.onStorageEvent(vault.load)
    
  • initial load
    await vault.load()
    



⌛ strata wait

tiny async state helpers

it's about "wait states" like pending, ok, err.
wait extends stz's ok/err toolkit, and it's mostly for showing little loading spinners in your ui.

⌛ good things come to those who wait

  • import stuff
    import {ok, err, nap} from "@e280/stz"
    import {wait, waitFormal} from "@e280/strata"
    
  • wrap any async operation and get a Wait (which is a derived signal)
    // wrap any async operation in a fancy wait
    const $wait = wait(async() => {
      await nap(100)
      if (Math.random() > 0.5) return 123
      else throw new Error("bad luck!")
    })
    
    • btw you can pass a promise instead of an async fn
  • check if it's done
    effect(() => console.log($wait().done))
      // false -- sorry bro, its not ready yet
      // true -- it's ready!
    
  • okay, we can actually await for the result
    const result = await $wait.result
    
    if (result.ok)
      console.log(result.value)
        // 123
    else
      console.error(result.error)
        // Error: bad luck!
    

⌛ waitFormal is persnickety belt-and-suspenders mode

  • you can get super explicit about the types
    const $wait = waitFormal<number, "unlucky" | "bad roll">(async() => {
      if (Math.random() > 0.5)
        return ok(123)
    
      if (Math.random() < 0.01)
        return err("unlucky")
    
      else
        return err("bad roll")
    })
    

⌛ wait, there's more

  • maker
    makeWaitState<number>() // pending
    makeWaitState(ok(123))
    makeWaitState(err("uh oh"))
    
  • status checkers
    isWaitPending($wait())
    isWaitDone($wait()) // ok or err
    isWaitOk($wait())
    isWaitErr($wait())
    
  • value grabbers
    waitGet($wait()) // 123 | undefined
    waitGot($wait()) // 123 (or throws an error)
    
    waitGetErr($wait()) // "bad roll" | undefined
    waitGotErr($wait()) // "bad roll" (or throws an error)
    
  • quick selector
    const text = waitSelect($wait(), {
      pending: () => "still loading...",
      ok: value => `ready: ${value}`,
      err: error => `ack! ${error}`,
    })
    



🪄 strata tracker

reactivity integration hub

import {tracker} from "@e280/strata/tracker"

this is the inner sanctum of strata. use the tracker to jack into the reactivity system, you can make anything fully strata-compatible and you'll be reactin' and triggerin' with the best of 'em. the tracker is also what you'll need if you're trying to create bindings for your own frontend framework to trigger your ui to rerender and stuff.

🪄 invent your own novel state concept

  • let's invent a very simple thing, so you can see how simple the tracker really is.
    export class BoomerSignal<Value> {
      constructor(private value: Value) {}
    
      get() {
        tracker.read(this) // 🪄 inform tracker our thing was accessed
        return this.value
      }
      
      set(value: Value) {
        this.value = value
        tracker.write(this) // 🪄 inform tracker our thing was changed
      }
    }
    
  • boom, that's it! now we have a new reactive thing we can use, and it'll rerender our ui or whatever.
    const $count = new BoomerSignal(1)
    
    effect(() => console.log($count.get()))
      // 1
    
    $count.set(2)
      // 2
    

🪄 integrate your ui framework for auto-rerendering

  • use tracker.observe to check what is touched by a fn
  • use tracker.subscribe to subscribe to the seen items that observe returns
  • see the tracker.ts source code



⚛️ react bindings

⚛️ setup your strata.ts module

import * as react from "react"
import {reactBindings} from "@e280/strata"

export const {
  component,
  useTracked,
  useOnce,
  useSignal,
  useDerived,
} = reactBindings(react)

⚛️ component enables fully automatic reactive re-rendering

import {signal} from "@e280/strata"
import {component} from "./strata.js"

const $count = signal(0)

export const MyCounter = component(() => {
  const add = () => $count($count() + 1)
  return <button onClick={add}>{$count()}</button>
})

⚛️ useTracked for a manual hands-on approach (plays nicer with hmr)

import {signal} from "@e280/strata"
import {useTracked} from "./strata.js"

const $count = signal(0)

export const MyCounter = () => {
  const count = useTracked(() => $count())
  const add = () => $count($count() + 1)
  return <button onClick={add}>{count}</button>
}

⚛️ useSignal for local component state (and useDerived)

import {useSignal} from "./strata.js"

export const MyCounter = () => {
  const $count = useSignal(0)
  const add = () => $count($count() + 1)
  return <button onClick={add}>{$count()}</button>
}



🧑‍💻 https://e280.org/

版本与发布记录

@e280/strata 共发布 69 个版本,首次发布于 2025/06/24;过去 12 个月发布了 34 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.5.7latest2026/11/076 天前
0.5.62026/10/111 个月前
0.5.52026/10/101 个月前
0.5.42026/10/101 个月前
0.5.32026/10/081 个月前
0.5.22026/09/181 个月前

其他发布通道:next → 0.4.0-next.8。

关于 @e280/strata 的常见问题

@e280/strata 是什么?

state management

如何安装 @e280/strata?

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

@e280/strata 支持 TypeScript 吗?

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

@e280/strata 支持 ESM 还是 CommonJS?

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

@e280/strata 可以商用吗?

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

@e280/strata 的下载量有多少?

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

@e280/strata 还在维护吗?

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