Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ All notable changes to `hy-event-store` are documented in this file.
the original callback reference.
- Document the supported package-name import path for WeChat Mini Programs and
add a package-root entry-point regression test.
- Add `getState(key)` for reading a declared state value without subscribing to
updates.

## 1.4.0 - 2026-08-19

Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,16 @@ async function loadData() {
<code>onState</code> invokes its callback immediately with the current value,
then again when that key changes.

Read one declared value without subscribing to updates:

~~~js
const status = store.getState("status")
~~~

<code>getState</code> does not notify listeners and throws for an undeclared key.
It returns the current value as-is; it is not a deep clone or deep read-only
snapshot.

Subscribe to several keys with <code>onStates</code>:

~~~js
Expand Down Expand Up @@ -190,6 +200,7 @@ Create a store with a required <code>state</code> object and an optional
| <code>onStates(keys, callback)</code> | Subscribe to several declared keys and immediately receive their initial snapshot. Later callbacks contain changed keys only. |
| <code>offStates(keys, callback)</code> | Remove a multi-key subscription. |
| <code>setState(key, value)</code> | Update a declared state key. |
| <code>getState(key)</code> | Read one declared state value without subscribing. |
| <code>dispatch(actionName, ...args)</code> | Run an action and return its value unchanged, including a Promise. |

## Usage notes
Expand Down
10 changes: 10 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,15 @@ async function loadData() {

<code>onState</code> 会先立即以当前值调用一次回调,之后在对应状态键变更时再次调用。

如需读取一个已声明的状态值,但不订阅更新,可以使用:

~~~js
const status = store.getState("status")
~~~

<code>getState</code> 不会通知监听器,读取未声明的键时会抛出错误。它会原样
返回当前值,不会创建深拷贝,也不是深层只读快照。

使用 <code>onStates</code> 订阅多个状态键:

~~~js
Expand Down Expand Up @@ -180,6 +189,7 @@ store.offStates(["count", "status"], renderProfile)
| <code>onStates(keys, callback)</code> | 订阅多个已声明状态键,并立即收到初始快照;后续回调只包含变化的键。 |
| <code>offStates(keys, callback)</code> | 取消多状态订阅。 |
| <code>setState(key, value)</code> | 更新已声明的状态键。 |
| <code>getState(key)</code> | 读取一个已声明的状态值,不建立订阅。 |
| <code>dispatch(actionName, ...args)</code> | 运行 action,并原样返回它的结果,包括 Promise。 |

## 使用注意事项
Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ These items need API design or compatibility research before implementation:
- Document and test CommonJS and Mini Program import paths; native ESM remains
a separate compatibility item
([#27](https://github.com/coderwhy/hy-event-store/issues/27)).
- Evaluate a read-only state getter
- Add a read-only state getter
([#26](https://github.com/coderwhy/hy-event-store/issues/26)).
- Explore an opt-in full snapshot for `onStates`
([#24](https://github.com/coderwhy/hy-event-store/issues/24)).
Expand Down
15 changes: 15 additions & 0 deletions Test/event-store.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,21 @@ test("onStates reports the initial snapshot and changed key until removed", () =
])
})

test("getState reads a declared value without notifying listeners", () => {
const store = new HYEventStore({ state: { count: 0 } })
let notifications = 0
store.onState("count", () => {
notifications += 1
})

assert.equal(store.getState("count"), 0)
assert.equal(notifications, 1)
store.setState("count", 1)
assert.equal(store.getState("count"), 1)
assert.equal(notifications, 2)
assert.throws(() => store.getState("missing"), /does not contain/)
})

test("dispatch returns synchronous action results", () => {
const store = new HYEventStore({
state: { count: 1 },
Expand Down
4 changes: 4 additions & 0 deletions Test/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,16 @@ store.onStates(["count", "status"], change => {

store.setState("count", 1)
store.setState("status", "ready")
const currentCount: number = store.getState("count")
const currentStatus: "idle" | "ready" = store.getState("status")

const nextCount: number = store.dispatch("increment", 2)
const loaded: Promise<string> = store.dispatch("load", "home")

// @ts-expect-error State keys must exist in the declared state object.
store.setState("missing", 1)
// @ts-expect-error getState only accepts declared state keys.
store.getState("missing")
// @ts-expect-error State values must match their declared key type.
store.setState("count", "1")
// @ts-expect-error Action arguments are inferred from the action signature.
Expand Down
2 changes: 2 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ export class HYEventStore<
stateValue: State[Key]
): void;

getState<Key extends StateKey<State>>(stateKey: Key): State[Key];

dispatch<ActionName extends Extract<keyof Actions, string>>(
actionName: ActionName,
...args: ActionArguments<State, Actions[ActionName]>
Expand Down
7 changes: 7 additions & 0 deletions src/event-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,13 @@ class HYEventStore {
this.state[stateKey] = stateValue
}

getState(stateKey) {
if (Object.keys(this.state).indexOf(stateKey) === -1) {
throw new Error("the state does not contain your key")
}
return this.state[stateKey]
}

dispatch(actionName, ...args) {
if (typeof actionName !== "string") {
throw new TypeError("the action name must be string type")
Expand Down