@assistant-ui/tap 0.3.6 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -23
- package/dist/core/ResourceFiber.d.ts +1 -1
- package/dist/core/ResourceFiber.d.ts.map +1 -1
- package/dist/core/ResourceFiber.js +15 -8
- package/dist/core/ResourceFiber.js.map +1 -1
- package/dist/core/commit.d.ts +1 -1
- package/dist/core/commit.d.ts.map +1 -1
- package/dist/core/commit.js +40 -50
- package/dist/core/commit.js.map +1 -1
- package/dist/core/context.d.ts +2 -2
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +2 -2
- package/dist/core/context.js.map +1 -1
- package/dist/core/createResource.d.ts +3 -2
- package/dist/core/createResource.d.ts.map +1 -1
- package/dist/core/createResource.js +48 -22
- package/dist/core/createResource.js.map +1 -1
- package/dist/core/env.d.ts +2 -0
- package/dist/core/env.d.ts.map +1 -0
- package/dist/core/env.js +3 -0
- package/dist/core/env.js.map +1 -0
- package/dist/core/execution-context.d.ts +1 -0
- package/dist/core/execution-context.d.ts.map +1 -1
- package/dist/core/execution-context.js +8 -0
- package/dist/core/execution-context.js.map +1 -1
- package/dist/core/resource.d.ts +4 -3
- package/dist/core/resource.d.ts.map +1 -1
- package/dist/core/resource.js.map +1 -1
- package/dist/core/scheduler.d.ts +1 -1
- package/dist/core/scheduler.d.ts.map +1 -1
- package/dist/core/scheduler.js +4 -1
- package/dist/core/scheduler.js.map +1 -1
- package/dist/core/types.d.ts +22 -21
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/withKey.d.ts +3 -0
- package/dist/core/withKey.d.ts.map +1 -0
- package/dist/core/withKey.js +4 -0
- package/dist/core/withKey.js.map +1 -0
- package/dist/hooks/tap-callback.d.ts.map +1 -1
- package/dist/hooks/tap-callback.js +1 -0
- package/dist/hooks/tap-callback.js.map +1 -1
- package/dist/hooks/tap-const.d.ts +2 -0
- package/dist/hooks/tap-const.d.ts.map +1 -0
- package/dist/hooks/tap-const.js +6 -0
- package/dist/hooks/tap-const.js.map +1 -0
- package/dist/hooks/tap-effect-event.d.ts.map +1 -1
- package/dist/hooks/tap-effect-event.js +11 -0
- package/dist/hooks/tap-effect-event.js.map +1 -1
- package/dist/hooks/tap-effect.d.ts.map +1 -1
- package/dist/hooks/tap-effect.js +46 -31
- package/dist/hooks/tap-effect.js.map +1 -1
- package/dist/hooks/tap-inline-resource.d.ts +2 -2
- package/dist/hooks/tap-inline-resource.d.ts.map +1 -1
- package/dist/hooks/tap-memo.d.ts.map +1 -1
- package/dist/hooks/tap-memo.js +9 -1
- package/dist/hooks/tap-memo.js.map +1 -1
- package/dist/hooks/tap-resource.d.ts +3 -3
- package/dist/hooks/tap-resource.d.ts.map +1 -1
- package/dist/hooks/tap-resource.js +17 -9
- package/dist/hooks/tap-resource.js.map +1 -1
- package/dist/hooks/tap-resources.d.ts +2 -10
- package/dist/hooks/tap-resources.d.ts.map +1 -1
- package/dist/hooks/tap-resources.js +74 -43
- package/dist/hooks/tap-resources.js.map +1 -1
- package/dist/hooks/tap-state.d.ts.map +1 -1
- package/dist/hooks/tap-state.js +37 -24
- package/dist/hooks/tap-state.js.map +1 -1
- package/dist/hooks/utils/depsShallowEqual.d.ts.map +1 -0
- package/dist/hooks/utils/depsShallowEqual.js.map +1 -0
- package/dist/hooks/utils/tapHook.d.ts +6 -0
- package/dist/hooks/utils/tapHook.d.ts.map +1 -0
- package/dist/hooks/utils/tapHook.js +24 -0
- package/dist/hooks/utils/tapHook.js.map +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/react/use-resource.d.ts +2 -2
- package/dist/react/use-resource.d.ts.map +1 -1
- package/dist/react/use-resource.js +24 -10
- package/dist/react/use-resource.js.map +1 -1
- package/package.json +10 -3
- package/src/__tests__/basic/resourceHandle.test.ts +4 -4
- package/src/__tests__/basic/tapEffect.basic.test.ts +3 -2
- package/src/__tests__/basic/tapResources.basic.test.ts +84 -64
- package/src/__tests__/basic/tapState.basic.test.ts +8 -8
- package/src/__tests__/errors/errors.effect-errors.test.ts +8 -3
- package/src/__tests__/lifecycle/lifecycle.dependencies.test.ts +3 -2
- package/src/__tests__/lifecycle/lifecycle.mount-unmount.test.ts +2 -2
- package/src/__tests__/react/concurrent-mode.test.tsx +243 -0
- package/src/__tests__/strictmode/react-strictmode-behavior.test.tsx +709 -0
- package/src/__tests__/strictmode/react-strictmode-rerender-sources.test.tsx +392 -0
- package/src/__tests__/strictmode/strictmode.test.ts +274 -0
- package/src/__tests__/strictmode/tap-strictmode-rerender-sources.test.ts +723 -0
- package/src/__tests__/test-utils.ts +8 -6
- package/src/core/ResourceFiber.ts +21 -11
- package/src/core/commit.ts +37 -57
- package/src/core/context.ts +2 -2
- package/src/core/createResource.ts +64 -25
- package/src/core/env.ts +3 -0
- package/src/core/execution-context.ts +9 -0
- package/src/core/resource.ts +9 -3
- package/src/core/scheduler.ts +4 -1
- package/src/core/types.ts +25 -26
- package/src/core/withKey.ts +8 -0
- package/src/hooks/tap-callback.ts +1 -0
- package/src/hooks/tap-const.ts +6 -0
- package/src/hooks/tap-effect-event.ts +15 -0
- package/src/hooks/tap-effect.ts +51 -38
- package/src/hooks/tap-inline-resource.ts +2 -2
- package/src/hooks/tap-memo.ts +10 -1
- package/src/hooks/tap-resource.ts +24 -20
- package/src/hooks/tap-resources.ts +86 -63
- package/src/hooks/tap-state.ts +49 -26
- package/src/hooks/utils/tapHook.ts +35 -0
- package/src/index.ts +8 -3
- package/src/react/use-resource.ts +27 -16
- package/dist/hooks/depsShallowEqual.d.ts.map +0 -1
- package/dist/hooks/depsShallowEqual.js.map +0 -1
- /package/dist/hooks/{depsShallowEqual.d.ts → utils/depsShallowEqual.d.ts} +0 -0
- /package/dist/hooks/{depsShallowEqual.js → utils/depsShallowEqual.js} +0 -0
- /package/src/hooks/{depsShallowEqual.ts → utils/depsShallowEqual.ts} +0 -0
package/src/hooks/tap-effect.ts
CHANGED
|
@@ -1,35 +1,12 @@
|
|
|
1
|
-
import { getCurrentResourceFiber } from "../core/execution-context";
|
|
2
1
|
import { Cell } from "../core/types";
|
|
2
|
+
import { depsShallowEqual } from "./utils/depsShallowEqual";
|
|
3
|
+
import { tapHook, registerRenderMountTask } from "./utils/tapHook";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (!fiber.isFirstRender && index >= fiber.cells.length) {
|
|
10
|
-
throw new Error(
|
|
11
|
-
"Rendered more hooks than during the previous render. " +
|
|
12
|
-
"Hooks must be called in the exact same order in every render.",
|
|
13
|
-
);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if (!fiber.cells[index]) {
|
|
17
|
-
// Create the effect cell
|
|
18
|
-
const cell: Cell & { type: "effect" } = {
|
|
19
|
-
type: "effect",
|
|
20
|
-
mounted: false,
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
fiber.cells[index] = cell;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const cell = fiber.cells[index];
|
|
27
|
-
if (cell.type !== "effect") {
|
|
28
|
-
throw new Error("Hook order changed between renders");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return index;
|
|
32
|
-
}
|
|
5
|
+
const newEffect = (): Cell & { type: "effect" } => ({
|
|
6
|
+
type: "effect",
|
|
7
|
+
cleanup: undefined,
|
|
8
|
+
deps: null, // null means the effect has never been run
|
|
9
|
+
});
|
|
33
10
|
|
|
34
11
|
export namespace tapEffect {
|
|
35
12
|
export type Destructor = () => void;
|
|
@@ -45,15 +22,51 @@ export function tapEffect(
|
|
|
45
22
|
effect: tapEffect.EffectCallback,
|
|
46
23
|
deps?: readonly unknown[],
|
|
47
24
|
): void {
|
|
48
|
-
const
|
|
25
|
+
const cell = tapHook("effect", newEffect);
|
|
26
|
+
|
|
27
|
+
if (deps && cell.deps && depsShallowEqual(cell.deps, deps)) return;
|
|
28
|
+
if (cell.deps !== null && !!deps !== !!cell.deps)
|
|
29
|
+
throw new Error(
|
|
30
|
+
"tapEffect called with and without dependencies across re-renders",
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
registerRenderMountTask(() => {
|
|
34
|
+
const errors: unknown[] = [];
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
cell.cleanup?.();
|
|
38
|
+
} catch (error) {
|
|
39
|
+
errors.push(error);
|
|
40
|
+
} finally {
|
|
41
|
+
cell.cleanup = undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const cleanup = effect();
|
|
46
|
+
|
|
47
|
+
if (cleanup !== undefined && typeof cleanup !== "function") {
|
|
48
|
+
throw new Error(
|
|
49
|
+
"An effect function must either return a cleanup function or nothing. " +
|
|
50
|
+
`Received: ${typeof cleanup}`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
cell.cleanup = cleanup;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
errors.push(error);
|
|
57
|
+
}
|
|
49
58
|
|
|
50
|
-
|
|
51
|
-
const cellIndex = getEffectCell();
|
|
59
|
+
cell.deps = deps;
|
|
52
60
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
if (errors.length > 0) {
|
|
62
|
+
if (errors.length === 1) {
|
|
63
|
+
throw errors[0];
|
|
64
|
+
} else {
|
|
65
|
+
for (const error of errors) {
|
|
66
|
+
console.error(error);
|
|
67
|
+
}
|
|
68
|
+
throw new AggregateError(errors, "Errors during commit");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
58
71
|
});
|
|
59
72
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ExtractResourceReturnType, ResourceElement } from "../core/types";
|
|
2
2
|
import { callResourceFn } from "../core/callResourceFn";
|
|
3
3
|
|
|
4
4
|
export function tapInlineResource<E extends ResourceElement<any, any>>(
|
|
5
5
|
element: E,
|
|
6
|
-
):
|
|
6
|
+
): ExtractResourceReturnType<E> {
|
|
7
7
|
return callResourceFn(element.type, element.props);
|
|
8
8
|
}
|
package/src/hooks/tap-memo.ts
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
|
+
import { isDevelopment } from "../core/env";
|
|
2
|
+
import { getCurrentResourceFiber } from "../core/execution-context";
|
|
1
3
|
import { tapRef } from "./tap-ref";
|
|
2
|
-
import { depsShallowEqual } from "./depsShallowEqual";
|
|
4
|
+
import { depsShallowEqual } from "./utils/depsShallowEqual";
|
|
3
5
|
|
|
4
6
|
export const tapMemo = <T>(fn: () => T, deps: readonly unknown[]) => {
|
|
5
7
|
const dataRef = tapRef<{ value: T; deps: readonly unknown[] }>();
|
|
6
8
|
if (!dataRef.current) {
|
|
9
|
+
if (isDevelopment) {
|
|
10
|
+
const fiber = getCurrentResourceFiber();
|
|
11
|
+
if (fiber.devStrictMode) {
|
|
12
|
+
void fn();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
7
16
|
dataRef.current = { value: fn(), deps };
|
|
8
17
|
}
|
|
9
18
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ExtractResourceReturnType, ResourceElement } from "../core/types";
|
|
2
2
|
import { tapEffect } from "./tap-effect";
|
|
3
3
|
import {
|
|
4
4
|
createResourceFiber,
|
|
@@ -8,37 +8,41 @@ import {
|
|
|
8
8
|
} from "../core/ResourceFiber";
|
|
9
9
|
import { tapMemo } from "./tap-memo";
|
|
10
10
|
import { tapState } from "./tap-state";
|
|
11
|
+
import { tapConst } from "./tap-const";
|
|
11
12
|
|
|
12
13
|
export function tapResource<E extends ResourceElement<any, any>>(
|
|
13
14
|
element: E,
|
|
14
|
-
):
|
|
15
|
+
): ExtractResourceReturnType<E>;
|
|
15
16
|
export function tapResource<E extends ResourceElement<any, any>>(
|
|
16
17
|
element: E,
|
|
17
|
-
|
|
18
|
-
):
|
|
18
|
+
propsDeps: readonly unknown[],
|
|
19
|
+
): ExtractResourceReturnType<E>;
|
|
19
20
|
export function tapResource<E extends ResourceElement<any, any>>(
|
|
20
21
|
element: E,
|
|
21
|
-
|
|
22
|
-
):
|
|
23
|
-
const [
|
|
24
|
-
const
|
|
25
|
-
() => createResourceFiber(element.type, () => rerender({})),
|
|
26
|
-
[element.type],
|
|
27
|
-
);
|
|
22
|
+
propsDeps?: readonly unknown[],
|
|
23
|
+
): ExtractResourceReturnType<E> {
|
|
24
|
+
const [version, setVersion] = tapState(0);
|
|
25
|
+
const rerender = tapConst(() => () => setVersion((v) => v + 1), []);
|
|
28
26
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
() =>
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
const fiber = tapMemo(() => {
|
|
28
|
+
void element.key;
|
|
29
|
+
return createResourceFiber(element.type, (callback) => {
|
|
30
|
+
if (callback()) rerender();
|
|
31
|
+
});
|
|
32
|
+
}, [element.type, element.key]);
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
const result = propsDeps
|
|
35
|
+
? // biome-ignore lint/correctness/useExhaustiveDependencies: user provided deps instead of prop identity
|
|
36
|
+
tapMemo(
|
|
37
|
+
() => renderResourceFiber(fiber, element.props),
|
|
38
|
+
[fiber, ...propsDeps, version],
|
|
39
|
+
)
|
|
40
|
+
: renderResourceFiber(fiber, element.props);
|
|
38
41
|
|
|
42
|
+
tapEffect(() => () => unmountResourceFiber(fiber), [fiber]);
|
|
39
43
|
tapEffect(() => {
|
|
40
44
|
commitResourceFiber(fiber, result);
|
|
41
45
|
}, [fiber, result]);
|
|
42
46
|
|
|
43
|
-
return result.
|
|
47
|
+
return result.output;
|
|
44
48
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
ExtractResourceReturnType,
|
|
3
3
|
RenderResult,
|
|
4
4
|
ResourceElement,
|
|
5
5
|
ResourceFiber,
|
|
@@ -14,99 +14,122 @@ import {
|
|
|
14
14
|
renderResourceFiber,
|
|
15
15
|
commitResourceFiber,
|
|
16
16
|
} from "../core/ResourceFiber";
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
import { tapConst } from "./tap-const";
|
|
18
|
+
|
|
19
|
+
type FiberState = {
|
|
20
|
+
fiber: ResourceFiber<unknown, unknown>;
|
|
21
|
+
next:
|
|
22
|
+
| RenderResult
|
|
23
|
+
| [ResourceFiber<unknown, unknown>, RenderResult]
|
|
24
|
+
| "delete";
|
|
23
25
|
};
|
|
24
26
|
|
|
25
|
-
export function tapResources<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
>
|
|
29
|
-
map: M,
|
|
30
|
-
getElement: (t: M[keyof M], key: keyof M) => E,
|
|
31
|
-
getElementDeps: any[],
|
|
32
|
-
): { [K in keyof M]: ExtractResourceOutput<E> } {
|
|
33
|
-
type R = ExtractResourceOutput<E>;
|
|
27
|
+
export function tapResources<E extends ResourceElement<any, any>>(
|
|
28
|
+
getElements: () => readonly E[],
|
|
29
|
+
getElementsDeps?: readonly unknown[],
|
|
30
|
+
): ExtractResourceReturnType<E>[] {
|
|
34
31
|
const [version, setVersion] = tapState(0);
|
|
35
|
-
const rerender =
|
|
32
|
+
const rerender = tapConst(() => () => setVersion((v) => v + 1), []);
|
|
36
33
|
|
|
37
|
-
|
|
38
|
-
const [fibers] = tapState(() => new Map<K, ResourceFiber<R, any>>());
|
|
34
|
+
const fibers = tapConst(() => new Map<string | number, FiberState>(), []);
|
|
39
35
|
|
|
40
|
-
const
|
|
36
|
+
const getElementsMemo = getElementsDeps
|
|
37
|
+
? // biome-ignore lint/correctness/useExhaustiveDependencies: library code
|
|
38
|
+
tapCallback(getElements, getElementsDeps)
|
|
39
|
+
: getElements;
|
|
41
40
|
|
|
42
41
|
// Process each element
|
|
43
42
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
remove: [],
|
|
47
|
-
add: [],
|
|
48
|
-
commit: [],
|
|
49
|
-
return: {} as Record<K, R>,
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
// Create/update fibers and render
|
|
53
|
-
for (const key in map) {
|
|
54
|
-
const value = map[key as K];
|
|
55
|
-
const element = getElementMemo(value, key);
|
|
43
|
+
const res = tapMemo(() => {
|
|
44
|
+
void version;
|
|
56
45
|
|
|
57
|
-
|
|
46
|
+
const elementsArray = getElementsMemo();
|
|
47
|
+
const seenKeys = new Set<string | number>();
|
|
48
|
+
const results: any[] = [];
|
|
49
|
+
let newCount = 0;
|
|
58
50
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
51
|
+
// Create/update fibers and render
|
|
52
|
+
for (let i = 0; i < elementsArray.length; i++) {
|
|
53
|
+
const element = elementsArray[i]!;
|
|
54
|
+
|
|
55
|
+
const elementKey = element.key;
|
|
56
|
+
if (elementKey === undefined) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`tapResources did not provide a key for array at index ${i}`,
|
|
59
|
+
);
|
|
64
60
|
}
|
|
65
61
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
62
|
+
if (seenKeys.has(elementKey))
|
|
63
|
+
throw new Error(`Duplicate key ${elementKey} in tapResources`);
|
|
64
|
+
seenKeys.add(elementKey);
|
|
65
|
+
|
|
66
|
+
let state = fibers.get(elementKey);
|
|
67
|
+
if (!state) {
|
|
68
|
+
const fiber = createResourceFiber(element.type, (callback) => {
|
|
69
|
+
if (callback()) rerender();
|
|
70
|
+
});
|
|
71
|
+
const result = renderResourceFiber(fiber, element.props);
|
|
72
|
+
state = {
|
|
73
|
+
fiber,
|
|
74
|
+
next: result,
|
|
75
|
+
};
|
|
76
|
+
newCount++;
|
|
77
|
+
fibers.set(elementKey, state);
|
|
78
|
+
results.push(result.output);
|
|
79
|
+
} else if (state.fiber.type !== element.type) {
|
|
80
|
+
const fiber = createResourceFiber(element.type, (callback) => {
|
|
81
|
+
if (callback()) rerender();
|
|
82
|
+
});
|
|
83
|
+
const result = renderResourceFiber(fiber, element.props);
|
|
84
|
+
state.next = [fiber, result];
|
|
85
|
+
results.push(result.output);
|
|
86
|
+
} else {
|
|
87
|
+
state.next = renderResourceFiber(state.fiber, element.props);
|
|
88
|
+
results.push(state.next.output);
|
|
89
|
+
}
|
|
71
90
|
}
|
|
72
91
|
|
|
73
92
|
// Clean up removed fibers (only if there might be stale ones)
|
|
74
|
-
if (
|
|
75
|
-
fibers.size >
|
|
76
|
-
results.commit.length - results.add.length + results.remove.length
|
|
77
|
-
) {
|
|
93
|
+
if (fibers.size > results.length - newCount) {
|
|
78
94
|
for (const key of fibers.keys()) {
|
|
79
|
-
if (!(key
|
|
80
|
-
|
|
95
|
+
if (!seenKeys.has(key)) {
|
|
96
|
+
fibers.get(key)!.next = "delete";
|
|
81
97
|
}
|
|
82
98
|
}
|
|
83
99
|
}
|
|
84
100
|
|
|
85
101
|
return results;
|
|
86
|
-
}, [
|
|
102
|
+
}, [getElementsMemo, version]);
|
|
87
103
|
|
|
88
104
|
// Cleanup on unmount
|
|
89
105
|
tapEffect(() => {
|
|
90
106
|
return () => {
|
|
91
107
|
for (const key of fibers.keys()) {
|
|
92
|
-
|
|
93
|
-
|
|
108
|
+
const fiber = fibers.get(key)!.fiber;
|
|
109
|
+
unmountResourceFiber(fiber);
|
|
94
110
|
}
|
|
95
111
|
};
|
|
96
112
|
}, []);
|
|
97
113
|
|
|
98
114
|
tapEffect(() => {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
115
|
+
res; // as a performance optimization, we only run if the results have changed
|
|
116
|
+
|
|
117
|
+
for (const [key, state] of fibers.entries()) {
|
|
118
|
+
if (state.next === "delete") {
|
|
119
|
+
if (state.fiber.isMounted) {
|
|
120
|
+
unmountResourceFiber(state.fiber);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
fibers.delete(key);
|
|
124
|
+
} else if (Array.isArray(state.next)) {
|
|
125
|
+
unmountResourceFiber(state.fiber);
|
|
126
|
+
state.fiber = state.next[0];
|
|
127
|
+
commitResourceFiber(state.fiber, state.next[1]);
|
|
128
|
+
} else {
|
|
129
|
+
commitResourceFiber(state.fiber, state.next);
|
|
130
|
+
}
|
|
108
131
|
}
|
|
109
|
-
}, [
|
|
132
|
+
}, [res]);
|
|
110
133
|
|
|
111
|
-
return
|
|
134
|
+
return res;
|
|
112
135
|
}
|
package/src/hooks/tap-state.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isDevelopment } from "../core/env";
|
|
1
2
|
import { getCurrentResourceFiber } from "../core/execution-context";
|
|
2
3
|
import { Cell, ResourceFiber } from "../core/types";
|
|
3
4
|
|
|
@@ -5,17 +6,22 @@ export namespace tapState {
|
|
|
5
6
|
export type StateUpdater<S> = S | ((prev: S) => S);
|
|
6
7
|
}
|
|
7
8
|
|
|
8
|
-
const
|
|
9
|
+
const dispatchOnFiber = (
|
|
10
|
+
fiber: ResourceFiber<any, any>,
|
|
11
|
+
callback: () => boolean,
|
|
12
|
+
) => {
|
|
9
13
|
if (fiber.renderContext) {
|
|
10
14
|
throw new Error("Resource updated during render");
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
if (fiber.isMounted) {
|
|
14
18
|
// Only schedule rerender if currently mounted
|
|
15
|
-
fiber.
|
|
19
|
+
fiber.dispatchUpdate(callback);
|
|
16
20
|
} else if (fiber.isNeverMounted) {
|
|
17
21
|
throw new Error("Resource updated before mount");
|
|
18
22
|
}
|
|
23
|
+
|
|
24
|
+
// TODO mark dirty
|
|
19
25
|
};
|
|
20
26
|
|
|
21
27
|
function getStateCell<T>(
|
|
@@ -32,39 +38,56 @@ function getStateCell<T>(
|
|
|
32
38
|
);
|
|
33
39
|
}
|
|
34
40
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
const cell = fiber.cells[index];
|
|
42
|
+
if (cell) {
|
|
43
|
+
if (cell.type !== "state")
|
|
44
|
+
throw new Error("Hook order changed between renders");
|
|
45
|
+
|
|
46
|
+
return cell as Cell & { type: "state" };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const value =
|
|
50
|
+
typeof initialValue === "function"
|
|
51
|
+
? (initialValue as () => T)()
|
|
52
|
+
: initialValue;
|
|
53
|
+
|
|
54
|
+
if (
|
|
55
|
+
isDevelopment &&
|
|
56
|
+
fiber.devStrictMode &&
|
|
57
|
+
typeof initialValue === "function"
|
|
58
|
+
) {
|
|
59
|
+
void (initialValue as () => T)();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const newCell: Cell & { type: "state" } = {
|
|
63
|
+
type: "state",
|
|
64
|
+
value,
|
|
65
|
+
set: (updater: tapState.StateUpdater<T>) => {
|
|
66
|
+
dispatchOnFiber(fiber, () => {
|
|
67
|
+
const currentValue = newCell.value;
|
|
47
68
|
const nextValue =
|
|
48
69
|
typeof updater === "function"
|
|
49
70
|
? (updater as (prev: T) => T)(currentValue)
|
|
50
71
|
: updater;
|
|
51
72
|
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
|
|
73
|
+
if (
|
|
74
|
+
isDevelopment &&
|
|
75
|
+
fiber.devStrictMode &&
|
|
76
|
+
typeof updater === "function"
|
|
77
|
+
) {
|
|
78
|
+
void (updater as (prev: T) => T)(currentValue);
|
|
55
79
|
}
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
80
|
|
|
59
|
-
|
|
60
|
-
}
|
|
81
|
+
if (Object.is(currentValue, nextValue)) return false;
|
|
61
82
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
83
|
+
newCell.value = nextValue;
|
|
84
|
+
return true;
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
};
|
|
66
88
|
|
|
67
|
-
|
|
89
|
+
fiber.cells[index] = newCell;
|
|
90
|
+
return newCell;
|
|
68
91
|
}
|
|
69
92
|
|
|
70
93
|
export function tapState<S = undefined>(): [
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { getCurrentResourceFiber } from "../../core/execution-context";
|
|
2
|
+
import { Cell } from "../../core/types";
|
|
3
|
+
|
|
4
|
+
export const tapHook = <T extends "effect" | "memo">(
|
|
5
|
+
type: T,
|
|
6
|
+
init: () => Cell,
|
|
7
|
+
): Cell & { type: T } => {
|
|
8
|
+
const fiber = getCurrentResourceFiber();
|
|
9
|
+
const index = fiber.currentIndex++;
|
|
10
|
+
|
|
11
|
+
if (!fiber.isFirstRender && index >= fiber.cells.length) {
|
|
12
|
+
// Check if we're trying to use more hooks than in previous renders
|
|
13
|
+
throw new Error(
|
|
14
|
+
"Rendered more hooks than during the previous render. " +
|
|
15
|
+
"Hooks must be called in the exact same order in every render.",
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let cell = fiber.cells[index];
|
|
20
|
+
if (!cell) {
|
|
21
|
+
cell = init();
|
|
22
|
+
fiber.cells[index] = cell;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (cell.type !== type) {
|
|
26
|
+
throw new Error("Hook order changed between renders");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return cell as Cell & { type: T };
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const registerRenderMountTask = (task: () => void) => {
|
|
33
|
+
const fiber = getCurrentResourceFiber();
|
|
34
|
+
fiber.renderContext!.commitTasks.push(task);
|
|
35
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { resource } from "./core/resource";
|
|
2
|
+
export { withKey } from "./core/withKey";
|
|
2
3
|
|
|
3
4
|
// primitive hooks
|
|
4
5
|
export { tapState } from "./hooks/tap-state";
|
|
@@ -6,6 +7,7 @@ export { tapEffect } from "./hooks/tap-effect";
|
|
|
6
7
|
|
|
7
8
|
// utility hooks
|
|
8
9
|
export { tapRef } from "./hooks/tap-ref";
|
|
10
|
+
export { tapConst } from "./hooks/tap-const";
|
|
9
11
|
export { tapMemo } from "./hooks/tap-memo";
|
|
10
12
|
export { tapCallback } from "./hooks/tap-callback";
|
|
11
13
|
export { tapEffectEvent } from "./hooks/tap-effect-event";
|
|
@@ -17,15 +19,18 @@ export { tapResources } from "./hooks/tap-resources";
|
|
|
17
19
|
|
|
18
20
|
// imperative
|
|
19
21
|
export { createResource } from "./core/createResource";
|
|
20
|
-
export {
|
|
22
|
+
export { flushResourcesSync } from "./core/scheduler";
|
|
21
23
|
|
|
22
24
|
// context
|
|
23
|
-
export {
|
|
25
|
+
export {
|
|
26
|
+
createResourceContext,
|
|
27
|
+
tap,
|
|
28
|
+
withContextProvider,
|
|
29
|
+
} from "./core/context";
|
|
24
30
|
|
|
25
31
|
// types
|
|
26
32
|
export type {
|
|
27
33
|
Resource,
|
|
28
34
|
ContravariantResource,
|
|
29
35
|
ResourceElement,
|
|
30
|
-
ExtractResourceOutput,
|
|
31
36
|
} from "./core/types";
|
|
@@ -1,35 +1,46 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
|
|
2
|
+
import type { ExtractResourceReturnType, ResourceElement } from "../core/types";
|
|
3
3
|
import {
|
|
4
4
|
createResourceFiber,
|
|
5
5
|
unmountResourceFiber,
|
|
6
6
|
renderResourceFiber,
|
|
7
7
|
commitResourceFiber,
|
|
8
8
|
} from "../core/ResourceFiber";
|
|
9
|
+
import { isDevelopment } from "../core/env";
|
|
9
10
|
|
|
10
|
-
const
|
|
11
|
-
(
|
|
11
|
+
const useDevStrictMode = () => {
|
|
12
|
+
if (!isDevelopment) return null;
|
|
12
13
|
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
const count = useRef(0);
|
|
15
|
+
const isFirstRender = count.current === 0;
|
|
16
|
+
useState(() => count.current++);
|
|
17
|
+
if (count.current !== 2) return null;
|
|
18
|
+
return isFirstRender ? ("child" as const) : ("root" as const);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const resourceReducer = (version: number, callback: () => boolean) => {
|
|
22
|
+
return version + (callback() ? 1 : 0);
|
|
23
|
+
};
|
|
16
24
|
|
|
17
25
|
export function useResource<E extends ResourceElement<any, any>>(
|
|
18
26
|
element: E,
|
|
19
|
-
):
|
|
20
|
-
const [,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
): ExtractResourceReturnType<E> {
|
|
28
|
+
const [, dispatch] = useReducer(resourceReducer, 0);
|
|
29
|
+
|
|
30
|
+
const devStrictMode = useDevStrictMode();
|
|
31
|
+
|
|
32
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: user provided deps instead of prop identity
|
|
33
|
+
const fiber = useMemo(() => {
|
|
34
|
+
return createResourceFiber(element.type, dispatch, devStrictMode);
|
|
35
|
+
}, [element.type, element.key]);
|
|
25
36
|
|
|
26
37
|
const result = renderResourceFiber(fiber, element.props);
|
|
27
|
-
|
|
38
|
+
useLayoutEffect(() => {
|
|
28
39
|
return () => unmountResourceFiber(fiber);
|
|
29
40
|
}, [fiber]);
|
|
30
|
-
|
|
41
|
+
useLayoutEffect(() => {
|
|
31
42
|
commitResourceFiber(fiber, result);
|
|
32
43
|
});
|
|
33
44
|
|
|
34
|
-
return result.
|
|
45
|
+
return result.output;
|
|
35
46
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"depsShallowEqual.d.ts","sourceRoot":"","sources":["../../src/hooks/depsShallowEqual.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,GAC3B,GAAG,SAAS,OAAO,EAAE,EACrB,GAAG,SAAS,OAAO,EAAE,YAOtB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"depsShallowEqual.js","sourceRoot":"","sources":["../../src/hooks/depsShallowEqual.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC9B,CAAqB,EACrB,CAAqB,EACrB,EAAE;IACF,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACxC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC;IAC3C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|