diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 181b5ea..58477b6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -24,4 +24,5 @@ jobs:
cache: npm
- run: npm ci
- run: npm test
+ - run: npm run test:types
- run: npm pack --dry-run
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7401543..dc2739f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,14 @@
All notable changes to `hy-event-store` are documented in this file.
-## 1.4.0 (unreleased)
+## Unreleased
+
+### Added
+
+- Add first-party TypeScript declarations for event payloads, state keys,
+ actions, and action return values, plus compile-time regression coverage.
+
+## 1.4.0 - 2026-08-19
### Added
diff --git a/README.md b/README.md
index 73b7816..eaf1083 100644
--- a/README.md
+++ b/README.md
@@ -26,12 +26,30 @@ Mini Program build workflows.
npm install hy-event-store
~~~
-The package currently exposes a CommonJS entry point:
+The package exposes a CommonJS entry point and ships first-party TypeScript
+declarations:
~~~js
const { HYEventBus, HYEventStore } = require("hy-event-store")
~~~
+TypeScript users can add event and state types without changing the runtime API:
+
+~~~ts
+const bus = new HYEventBus<{ ready: []; signedIn: [user: { name: string }] }>()
+bus.on("signedIn", user => console.log(user.name))
+
+const store = new HYEventStore({
+ state: { count: 0 },
+ actions: {
+ increment(state, amount: number) {
+ state.count += amount
+ return state.count
+ }
+ }
+})
+~~~
+
## Quick start
### Event bus
@@ -181,9 +199,9 @@ The distributed package is CommonJS and has no runtime dependencies. CI
validates the package on Node.js 18 through 26. Browser and Mini Program
projects should use their normal npm-package build or bundling workflow.
-Native ESM exports, TypeScript declarations, a read-only state selector, and
-an opt-in full snapshot mode for onStates are planned design
-improvements; see the [roadmap](ROADMAP.md).
+Native ESM exports, a read-only state selector, and an opt-in full snapshot
+mode for onStates are planned design improvements; see the
+[roadmap](ROADMAP.md).
## Development
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 4a3e50d..d66e3fb 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -22,12 +22,29 @@
npm install hy-event-store
~~~
-当前包提供 CommonJS 入口:
+当前包提供 CommonJS 入口,并内置第一方 TypeScript 声明:
~~~js
const { HYEventBus, HYEventStore } = require("hy-event-store")
~~~
+TypeScript 用户无需改变运行时 API,即可补充事件和状态类型:
+
+~~~ts
+const bus = new HYEventBus<{ ready: []; signedIn: [user: { name: string }] }>()
+bus.on("signedIn", user => console.log(user.name))
+
+const store = new HYEventStore({
+ state: { count: 0 },
+ actions: {
+ increment(state, amount: number) {
+ state.count += amount
+ return state.count
+ }
+ }
+})
+~~~
+
## 快速开始
### 事件总线
@@ -164,7 +181,7 @@ store.offStates(["count", "status"], renderProfile)
发布包为 CommonJS,且没有运行时依赖。CI 在 Node.js 18 至 26 上验证。浏览器和小程序项目应通过各自的 npm 包构建或打包流程使用本包。
-原生 ESM 导出、TypeScript 声明、只读状态选择器与 onStates 的完整快照模式属于后续设计项,详见[路线图](ROADMAP.md)。
+原生 ESM 导出、只读状态选择器与 onStates 的完整快照模式属于后续设计项,详见[路线图](ROADMAP.md)。
## 开发
diff --git a/ROADMAP.md b/ROADMAP.md
index ac7a5ad..8a84108 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -21,7 +21,7 @@ keeping the public API compatible.
These items need API design or compatibility research before implementation:
-- Add first-party TypeScript declarations
+- Add first-party TypeScript declarations and compile-time coverage
([#25](https://github.com/coderwhy/hy-event-store/issues/25)).
- Document and test CommonJS, ESM, and Mini Program import paths
([#27](https://github.com/coderwhy/hy-event-store/issues/27)).
diff --git a/Test/types.test.ts b/Test/types.test.ts
new file mode 100644
index 0000000..3dba997
--- /dev/null
+++ b/Test/types.test.ts
@@ -0,0 +1,57 @@
+import { HYEventBus, HYEventStore } from ".."
+
+type AppEvents = {
+ ready: []
+ signedIn: [user: { id: string; name: string }]
+}
+
+const bus = new HYEventBus()
+
+bus.on("signedIn", user => {
+ user.id.toUpperCase()
+ user.name.toUpperCase()
+})
+bus.emit("ready")
+bus.emit("signedIn", { id: "1", name: "coderwhy" })
+
+// @ts-expect-error An event payload must match its declared event tuple.
+bus.emit("signedIn", "coderwhy")
+
+const store = new HYEventStore({
+ state: {
+ count: 0,
+ status: "idle" as "idle" | "ready"
+ },
+ actions: {
+ increment(state, amount: number) {
+ state.count += amount
+ return state.count
+ },
+ async load(state, label: string) {
+ state.status = "ready"
+ return `${label}:${state.status}`
+ }
+ }
+})
+
+store.onState("count", count => {
+ count.toFixed()
+})
+
+store.onStates(["count", "status"], change => {
+ change.count?.toFixed()
+ change.status?.toUpperCase()
+})
+
+store.setState("count", 1)
+store.setState("status", "ready")
+
+const nextCount: number = store.dispatch("increment", 2)
+const loaded: Promise = store.dispatch("load", "home")
+
+// @ts-expect-error State keys must exist in the declared state object.
+store.setState("missing", 1)
+// @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.
+store.dispatch("increment", "2")
diff --git a/index.d.ts b/index.d.ts
new file mode 100644
index 0000000..0f4f5ca
--- /dev/null
+++ b/index.d.ts
@@ -0,0 +1,106 @@
+export type EventMap = Record;
+
+export type EventCallback = (
+ ...payload: Payload
+) => void;
+
+export class HYEventBus {
+ on>(
+ eventName: EventName,
+ eventCallback: EventCallback,
+ thisArg?: unknown
+ ): this;
+
+ once>(
+ eventName: EventName,
+ eventCallback: EventCallback,
+ thisArg?: unknown
+ ): this;
+
+ emit>(
+ eventName: EventName,
+ ...payload: Events[EventName]
+ ): this;
+
+ off>(
+ eventName: EventName,
+ eventCallback: EventCallback
+ ): this;
+
+ clear(): this;
+
+ hasEvent(eventName: string): boolean;
+}
+
+export type StoreAction<
+ State extends object,
+ Args extends unknown[] = unknown[],
+ Result = unknown
+> = (state: State, ...args: Args) => Result;
+
+export type StoreActions = Record<
+ string,
+ StoreAction
+>;
+
+export interface HYEventStoreOptions<
+ State extends object,
+ Actions extends StoreActions = Record
+> {
+ state: State;
+ actions?: Actions & StoreActions;
+}
+
+type StateKey = Extract;
+type StateChange> = Partial<
+ Pick
+>;
+type ActionArguments = Action extends (
+ state: State,
+ ...args: infer Args
+) => unknown
+ ? Args
+ : never;
+type ActionResult = Action extends (...args: any[]) => infer Result
+ ? Result
+ : never;
+
+export class HYEventStore<
+ State extends object,
+ Actions extends StoreActions = Record
+> {
+ constructor(options: HYEventStoreOptions);
+
+ state: State;
+ actions: Actions;
+
+ onState>(
+ stateKey: Key,
+ stateCallback: (value: State[Key]) => void
+ ): void;
+
+ offState>(
+ stateKey: Key,
+ stateCallback: (value: State[Key]) => void
+ ): void;
+
+ onStates[]>(
+ stateKeys: Keys,
+ stateCallback: (change: StateChange) => void
+ ): void;
+
+ offStates[]>(
+ stateKeys: Keys,
+ stateCallback: (change: StateChange) => void
+ ): void;
+
+ setState>(
+ stateKey: Key,
+ stateValue: State[Key]
+ ): void;
+
+ dispatch>(
+ actionName: ActionName,
+ ...args: ActionArguments
+ ): ActionResult;
+}
diff --git a/package-lock.json b/package-lock.json
index 8ba9dac..d9eac33 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,7 +7,24 @@
"": {
"name": "hy-event-store",
"version": "1.4.0",
- "license": "MIT"
+ "license": "MIT",
+ "devDependencies": {
+ "typescript": "^5.7.3"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
}
}
}
diff --git a/package.json b/package.json
index 0ee2a68..adf64aa 100644
--- a/package.json
+++ b/package.json
@@ -3,14 +3,20 @@
"version": "1.4.0",
"description": "A zero-dependency event bus and global state store for Vue, React, Mini Programs, and vanilla JavaScript.",
"main": "src/index.js",
+ "types": "index.d.ts",
"files": [
"src",
"CHANGELOG.md",
- "README.zh-CN.md"
+ "README.zh-CN.md",
+ "index.d.ts"
],
"scripts": {
"test": "node --test Test/*.test.js",
- "test:coverage": "node --test --experimental-test-coverage Test/*.test.js"
+ "test:coverage": "node --test --experimental-test-coverage Test/*.test.js",
+ "test:types": "tsc --noEmit --strict --skipLibCheck --module commonjs --moduleResolution node --target es2020 Test/types.test.ts"
+ },
+ "devDependencies": {
+ "typescript": "^5.7.3"
},
"author": "coderwhy",
"license": "MIT",