Skip to content

Commit 879264b

Browse files
committed
feat(utils): export utility functions via dedicated subpath
- Move util.ts content to src/util/selectors.ts for better organization - Add util subpath export in package.json for direct utility imports - Update internal imports to use the new structure - Add utilities documentation to README with usage examples
1 parent 9a9ea1e commit 879264b

File tree

8 files changed

+81
-57
lines changed

8 files changed

+81
-57
lines changed

.github/workflows/publish.yml

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,6 @@ jobs:
1717
build-and-publish:
1818
# 任务运行的虚拟机环境
1919
runs-on: ubuntu-latest
20-
21-
# 启用手动发布审批,需在 GitHub "Settings > Environments" 中配置。
22-
# 此名称 ('npm-publish') 必须与 npm Provenance 设置中的环境名完全一致。
23-
environment: npm-publish
24-
2520

2621
steps:
2722
# 检出仓库代码
@@ -44,6 +39,9 @@ jobs:
4439
node-version: '22.13.0'
4540
# 关键:为 pnpm 设置缓存
4641
cache: 'pnpm'
42+
# 配置OIDC认证url
43+
registry-url: 'https://registry.npmjs.org/'
44+
4745

4846
# 安装依赖
4947
- name: Install dependencies
@@ -68,9 +66,27 @@ jobs:
6866
run: pnpm build
6967

7068

69+
- name: Update npm
70+
run: npm install -g npm@latest
7171
# 发布到 npm
7272
- name: Publish to npm
73-
run: pnpm publish --no-git-checks
73+
id: publish_step # 给这一步一个ID,方便后面引用
74+
run: npm publish --access public --no-git-checks
75+
continue-on-error: true # 即使发布失败,也继续执行下一步来打印日志
76+
77+
# 如果发布失败,打印详细的 npm 调试日志
78+
- name: Dump npm debug log on failure
79+
if: steps.publish_step.outcome == 'failure' # 仅在上面一步失败时运行
80+
run: |
81+
echo "npm publish failed. Dumping debug log:"
82+
# a-zA-Z0-9 是为了找到最新的日志文件
83+
LOG_FILE=$(find /home/runner/.npm/_logs/ -type f -name "*Z-debug-*.log" | sort -r | head -n 1)
84+
if [ -f "$LOG_FILE" ]; then
85+
cat "$LOG_FILE"
86+
else
87+
echo "Debug log file not found."
88+
fi
89+
exit 1 # 打印完日志后,让整个 job 失败
7490
7591
# 生成 changelog
7692
- run: pnpm dlx changelogithub --no-group

README.md

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,36 @@ A hook for child components to access the store. It returns an object containing
222222
- `store`: The Zustand store instance. You can subscribe to state slices with selector hooks (e.g., `store.use.myState()`).
223223
- All functions from your `methods` object are spread onto the return value for direct access (e.g., `const { myMethod } = useBridgedStore()`).
224224

225+
## Utilities
226+
227+
You can also import utility functions directly from the `util` subpath:
228+
229+
### `createSelectors(store)`
230+
231+
A utility function that adds selector hooks to a Zustand store instance. This allows you to subscribe to specific state slices with `store.use.myState()` syntax.
232+
233+
```typescript
234+
import { createSelectors } from 'hook-store-bridge/util'
235+
import { createStore } from 'zustand'
236+
237+
const store = createStore<{ count: number }>(() => ({ count: 0 }))
238+
const storeWithSelectors = createSelectors(store)
239+
240+
// Now you can use selector hooks
241+
const count = storeWithSelectors.use.count()
242+
```
243+
244+
### `WithSelectors<S>`
245+
246+
A TypeScript utility type that adds selector capabilities to a store type.
247+
248+
```typescript
249+
import type { WithSelectors } from 'hook-store-bridge/util'
250+
import type { StoreApi } from 'zustand'
251+
252+
type MyStore = WithSelectors<StoreApi<{ count: number }>>
253+
```
254+
225255
## Custom Store Configuration
226256
227257
You can override the default Zustand setup by providing a `createStoreConfig` function. This is useful for adding middleware (like Redux DevTools) or integrating a different state library.
@@ -234,14 +264,15 @@ See the [source code](src/store.ts) for the default implementation and type defi
234264
import { createHookBridge } from 'hook-store-bridge'
235265
import { createStore } from 'zustand'
236266
import { devtools } from 'zustand/middleware'
237-
import { createSelectors } from './utils' // Your internal selector utility
267+
import { createSelectors } from 'hook-store-bridge/util'
238268
import { useMyStoreLogic } from './useMyStoreLogic'
239269

240270
export const { useBridgedStore, StoreProvider } = createHookBridge({
241271
useStoreLogic: useMyStoreLogic,
242272
createStoreConfig: () => ({
243273
createStore: (initState) => {
244-
const store = createStore(devtools(() => initState, { name: 'MyStore' }))
274+
const store = createStore<ReturnType<typeof useMyStoreLogic>['tracked']>()(
275+
devtools(() => initState, { name: 'MyStore' }))
245276
return createSelectors(store)
246277
},
247278
updateState: (store, newState) => {

examples/react-router-app/app/routes/+count/storeBridge.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { createHookBridge } from 'hook-store-bridge'
22
import { useCounts } from './useCount'
3-
43
export const { useBridgedStore, StoreProvider } = createHookBridge({
54
useStoreLogic: () => {
65
const {

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hook-store-bridge",
3-
"version": "1.1.0",
3+
"version": "1.1.1",
44
"description": "A utility to bridge React hook state into Zustand or other state management libraries.",
55
"type": "module",
66
"main": "./dist/index.js",
@@ -10,6 +10,10 @@
1010
".": {
1111
"import": "./dist/index.js",
1212
"types": "./dist/index.d.ts"
13+
},
14+
"./util": {
15+
"import": "./dist/util/index.js",
16+
"types": "./dist/util/index.d.ts"
1317
}
1418
},
1519
"files": [

src/util.ts

Lines changed: 0 additions & 46 deletions
This file was deleted.

src/util/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './selectors'

src/util/selectors.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { StoreApi } from 'zustand'
2+
import { useStore } from 'zustand/react'
3+
4+
export type WithSelectors<S> = S extends { getState: () => infer T }
5+
? S & { use: { [K in keyof T]: () => T[K] } }
6+
: never
7+
8+
export const createSelectors = <S extends StoreApi<Record<string, unknown>>>(
9+
_store: S,
10+
) => {
11+
const store = _store as WithSelectors<typeof _store>
12+
store.use = {}
13+
for (const k of Object.keys(store.getState())) {
14+
;(store.use as any)[k] = () =>
15+
useStore(_store, (s) => s[k as keyof typeof s])
16+
}
17+
18+
return store
19+
}

tsdown.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { defineConfig } from 'tsdown'
22

33
export default defineConfig({
4-
entry: ['./src/index.ts'],
4+
entry: ['./src/index.ts', './src/util/index.ts'],
55
format: ['es'],
66
clean: true,
77
dts: true,

0 commit comments

Comments
 (0)