Skip to content

Commit 4ba9626

Browse files
committed
feat: allow hooks to be defined without execute fn
1 parent f66d513 commit 4ba9626

File tree

5 files changed

+91
-39
lines changed

5 files changed

+91
-39
lines changed

.changeset/spicy-jokes-melt.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@backhooks/core": patch
3+
---
4+
5+
Allow hooks to run without the execute function

package-lock.json

+2-19
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/package.json

+1-4
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,5 @@
1111
"ts-jest": "^29.0.5",
1212
"typescript": "^4.9.4"
1313
},
14-
"main": "dist/index.js",
15-
"dependencies": {
16-
"utility-types": "^3.10.0"
17-
}
14+
"main": "dist/index.js"
1815
}

packages/core/src/core.ts

+65-16
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
import { AsyncLocalStorage } from "node:async_hooks";
22
import * as crypto from "node:crypto";
3-
import type { Optional } from "utility-types";
43

54
const asyncLocalStorage = new AsyncLocalStorage();
65

76
let globalStore = {};
87

9-
interface CreateHookOptions<
10-
State extends Record<string, any>,
11-
ExecuteResult,
12-
HookOptions
13-
> {
8+
interface CreateHookOptions<State, ExecuteResult, HookOptions> {
149
name: string;
1510
data: () => State;
1611
execute: (state: State, options: HookOptions) => ExecuteResult;
@@ -46,30 +41,84 @@ const generateUpdateHookStateFunction = <State, ExecuteResult, HookOptions>(
4641
};
4742
};
4843

49-
export const createHook = <State, ExecuteResult, HookOptions>(
50-
options: Optional<
51-
Omit<CreateHookOptions<State, ExecuteResult, HookOptions>, "name">,
52-
"data"
53-
>
44+
type StateAndExecuteOptions<State, ExecuteResult, HookOptions> = Omit<
45+
CreateHookOptions<State, ExecuteResult, HookOptions>,
46+
"name"
47+
>;
48+
49+
type StateOnlyOptions<State, HookOptions> = Omit<
50+
StateAndExecuteOptions<State, undefined, HookOptions>,
51+
"execute"
52+
>;
53+
54+
type ExecuteOnlyOptions<ExecuteResult, HookOptions> = Omit<
55+
StateAndExecuteOptions<undefined, ExecuteResult, HookOptions>,
56+
"data"
57+
>;
58+
59+
export function createHook<State, ExecuteResult, HookOptions>(
60+
options: StateAndExecuteOptions<State, ExecuteResult, HookOptions>
5461
): [
5562
(parameters?: HookOptions) => ExecuteResult,
5663
(fn: (currentState: State) => State) => void
57-
] => {
64+
];
65+
export function createHook<State, HookOptions>(
66+
options: StateOnlyOptions<State, HookOptions>
67+
): [() => State, (fn: (currentState: State) => State) => void];
68+
export function createHook<ExecuteResult, HookOptions>(
69+
options: ExecuteOnlyOptions<ExecuteResult, HookOptions>
70+
): [(parameters?: HookOptions) => ExecuteResult, (fn: () => void) => void];
71+
export function createHook<State, ExecuteResult, HookOptions>(
72+
options:
73+
| StateOnlyOptions<State, HookOptions>
74+
| ExecuteOnlyOptions<ExecuteResult, HookOptions>
75+
| StateAndExecuteOptions<State, ExecuteResult, HookOptions>
76+
) {
5877
const name = crypto.randomUUID();
59-
const data = options.data || (() => ({} as State));
78+
if ("data" in options && !("execute" in options)) {
79+
const execute = (state: State) => state;
80+
return [
81+
generateHookFunction({
82+
name,
83+
data: options.data,
84+
execute,
85+
}),
86+
generateUpdateHookStateFunction({
87+
name,
88+
data: options.data,
89+
execute,
90+
}),
91+
];
92+
}
93+
if ("data" in options) {
94+
return [
95+
generateHookFunction({
96+
name,
97+
data: options.data,
98+
execute: options.execute,
99+
}),
100+
generateUpdateHookStateFunction({
101+
name,
102+
data: options.data,
103+
execute: options.execute,
104+
}),
105+
];
106+
}
107+
108+
const data = () => undefined;
60109
return [
61110
generateHookFunction({
62-
...options,
63111
name,
64112
data,
113+
execute: options.execute,
65114
}),
66115
generateUpdateHookStateFunction({
67-
...options,
68116
name,
69117
data,
118+
execute: options.execute,
70119
}),
71120
];
72-
};
121+
}
73122

74123
export const runHookContext = async <T>(
75124
fn: () => Promise<T> | T

packages/core/tests/core.spec.ts

+18
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,24 @@ test("it should be able to create a hook without name", async () => {
104104
expect(result).toBe("ok");
105105
});
106106

107+
test("it should be able to create hooks without data or execute", async () => {
108+
const [useHookWithoutData] = createHook({
109+
execute(state) {
110+
return state;
111+
},
112+
});
113+
expect(useHookWithoutData()).toBeUndefined();
114+
115+
const [useHookWithoutExecute] = createHook({
116+
data() {
117+
return {
118+
foo: "bar",
119+
};
120+
},
121+
});
122+
expect(useHookWithoutExecute().foo).toBe("bar");
123+
});
124+
107125
test("it should be able to reset the global context", async () => {
108126
const random = useRandom();
109127
const random2 = useRandom();

0 commit comments

Comments
 (0)