@assistant-ui/tap 0.4.5 → 0.5.0
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 +20 -17
- package/dist/core/ResourceFiber.d.ts +2 -2
- package/dist/core/ResourceFiber.d.ts.map +1 -1
- package/dist/core/ResourceFiber.js +11 -9
- package/dist/core/ResourceFiber.js.map +1 -1
- package/dist/core/createResourceRoot.d.ts +6 -0
- package/dist/core/createResourceRoot.d.ts.map +1 -0
- package/dist/core/createResourceRoot.js +32 -0
- package/dist/core/createResourceRoot.js.map +1 -0
- package/dist/core/helpers/callResourceFn.d.ts.map +1 -0
- package/dist/core/helpers/callResourceFn.js.map +1 -0
- package/dist/core/helpers/commit.d.ts +4 -0
- package/dist/core/helpers/commit.d.ts.map +1 -0
- package/dist/core/{commit.js → helpers/commit.js} +2 -2
- package/dist/core/helpers/commit.js.map +1 -0
- package/dist/core/helpers/env.d.ts.map +1 -0
- package/dist/core/helpers/env.js +3 -0
- package/dist/core/helpers/env.js.map +1 -0
- package/dist/core/{execution-context.d.ts → helpers/execution-context.d.ts} +1 -1
- package/dist/core/helpers/execution-context.d.ts.map +1 -0
- package/dist/core/helpers/execution-context.js.map +1 -0
- package/dist/core/helpers/root.d.ts +8 -0
- package/dist/core/helpers/root.d.ts.map +1 -0
- package/dist/core/helpers/root.js +52 -0
- package/dist/core/helpers/root.js.map +1 -0
- package/dist/core/resource.js +1 -1
- package/dist/core/resource.js.map +1 -1
- package/dist/core/scheduler.d.ts.map +1 -1
- package/dist/core/scheduler.js +12 -1
- package/dist/core/scheduler.js.map +1 -1
- package/dist/core/types.d.ts +25 -7
- package/dist/core/types.d.ts.map +1 -1
- package/dist/hooks/tap-effect-event.d.ts.map +1 -1
- package/dist/hooks/tap-effect-event.js +3 -2
- package/dist/hooks/tap-effect-event.js.map +1 -1
- package/dist/hooks/tap-memo.d.ts.map +1 -1
- package/dist/hooks/tap-memo.js +16 -17
- package/dist/hooks/tap-memo.js.map +1 -1
- package/dist/hooks/tap-reducer.d.ts +7 -0
- package/dist/hooks/tap-reducer.d.ts.map +1 -0
- package/dist/hooks/tap-reducer.js +87 -0
- package/dist/hooks/tap-reducer.js.map +1 -0
- package/dist/hooks/tap-resource.js +9 -9
- package/dist/hooks/tap-resource.js.map +1 -1
- package/dist/hooks/tap-resources.d.ts.map +1 -1
- package/dist/hooks/tap-resources.js +11 -11
- 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 +6 -63
- package/dist/hooks/tap-state.js.map +1 -1
- package/dist/hooks/utils/tapHook.d.ts +1 -1
- package/dist/hooks/utils/tapHook.d.ts.map +1 -1
- package/dist/hooks/utils/tapHook.js +2 -2
- package/dist/hooks/utils/tapHook.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/react/use-resource.d.ts +1 -1
- package/dist/react/use-resource.d.ts.map +1 -1
- package/dist/react/use-resource.js +14 -8
- package/dist/react/use-resource.js.map +1 -1
- package/dist/{tapSubscribableResource.d.ts → tapResourceRoot.d.ts} +3 -3
- package/dist/tapResourceRoot.d.ts.map +1 -0
- package/dist/tapResourceRoot.js +80 -0
- package/dist/tapResourceRoot.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/basic/resourceHandle.test.ts +17 -14
- package/src/__tests__/basic/tapReducer.basic.test.ts +200 -0
- package/src/__tests__/react/concurrent-mode.test.tsx +1 -4
- package/src/__tests__/strictmode/react-strictmode-behavior.test.tsx +215 -2
- package/src/__tests__/strictmode/react-strictmode-rerender-sources.test.tsx +77 -0
- package/src/__tests__/strictmode/strictmode.test.ts +82 -21
- package/src/__tests__/strictmode/tap-strictmode-rerender-sources.test.ts +67 -110
- package/src/__tests__/test-utils.ts +5 -1
- package/src/core/ResourceFiber.ts +22 -10
- package/src/core/createResourceRoot.ts +53 -0
- package/src/core/{callResourceFn.ts → helpers/callResourceFn.ts} +1 -1
- package/src/core/{commit.ts → helpers/commit.ts} +3 -3
- package/src/core/helpers/env.ts +3 -0
- package/src/core/{execution-context.ts → helpers/execution-context.ts} +1 -1
- package/src/core/helpers/root.ts +67 -0
- package/src/core/resource.ts +1 -1
- package/src/core/scheduler.ts +13 -1
- package/src/core/types.ts +27 -7
- package/src/hooks/tap-effect-event.ts +3 -2
- package/src/hooks/tap-memo.ts +24 -19
- package/src/hooks/tap-reducer.ts +148 -0
- package/src/hooks/tap-resource.ts +9 -9
- package/src/hooks/tap-resources.ts +23 -10
- package/src/hooks/tap-state.ts +11 -88
- package/src/hooks/utils/tapHook.ts +3 -3
- package/src/index.ts +3 -3
- package/src/react/use-resource.ts +24 -11
- package/src/tapResourceRoot.ts +131 -0
- package/dist/core/callResourceFn.d.ts.map +0 -1
- package/dist/core/callResourceFn.js.map +0 -1
- package/dist/core/commit.d.ts +0 -4
- package/dist/core/commit.d.ts.map +0 -1
- package/dist/core/commit.js.map +0 -1
- package/dist/core/createResource.d.ts +0 -15
- package/dist/core/createResource.d.ts.map +0 -1
- package/dist/core/createResource.js +0 -101
- package/dist/core/createResource.js.map +0 -1
- package/dist/core/env.d.ts.map +0 -1
- package/dist/core/env.js +0 -4
- package/dist/core/env.js.map +0 -1
- package/dist/core/execution-context.d.ts.map +0 -1
- package/dist/core/execution-context.js.map +0 -1
- package/dist/hooks/tap-inline-resource.d.ts +0 -3
- package/dist/hooks/tap-inline-resource.d.ts.map +0 -1
- package/dist/hooks/tap-inline-resource.js +0 -5
- package/dist/hooks/tap-inline-resource.js.map +0 -1
- package/dist/tapSubscribableResource.d.ts.map +0 -1
- package/dist/tapSubscribableResource.js +0 -60
- package/dist/tapSubscribableResource.js.map +0 -1
- package/src/core/createResource.ts +0 -155
- package/src/core/env.ts +0 -4
- package/src/hooks/tap-inline-resource.ts +0 -8
- package/src/tapSubscribableResource.ts +0 -101
- /package/dist/core/{callResourceFn.d.ts → helpers/callResourceFn.d.ts} +0 -0
- /package/dist/core/{callResourceFn.js → helpers/callResourceFn.js} +0 -0
- /package/dist/core/{env.d.ts → helpers/env.d.ts} +0 -0
- /package/dist/core/{execution-context.js → helpers/execution-context.js} +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { ResourceFiber, RenderResult } from "
|
|
1
|
+
import { ResourceFiber, RenderResult } from "../types";
|
|
2
2
|
|
|
3
|
-
export function
|
|
3
|
+
export function commitAllEffects(renderResult: RenderResult): void {
|
|
4
4
|
const errors: unknown[] = [];
|
|
5
5
|
|
|
6
|
-
for (const task of renderResult.
|
|
6
|
+
for (const task of renderResult.effectTasks) {
|
|
7
7
|
try {
|
|
8
8
|
task();
|
|
9
9
|
} catch (error) {
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Cell, ResourceFiber, ResourceFiberRoot } from "../types";
|
|
2
|
+
|
|
3
|
+
export const createResourceFiberRoot = (
|
|
4
|
+
dispatchUpdate: (cb: () => boolean) => void,
|
|
5
|
+
): ResourceFiberRoot => {
|
|
6
|
+
return {
|
|
7
|
+
version: 0,
|
|
8
|
+
committedVersion: 0,
|
|
9
|
+
dispatchUpdate,
|
|
10
|
+
changelog: [],
|
|
11
|
+
dirtyCells: [],
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const commitRoot = (root: ResourceFiberRoot): void => {
|
|
16
|
+
for (const cell of root.dirtyCells) {
|
|
17
|
+
cell.dirty = false;
|
|
18
|
+
cell.queue.clear();
|
|
19
|
+
cell.current = cell.workInProgress;
|
|
20
|
+
}
|
|
21
|
+
root.committedVersion = root.version;
|
|
22
|
+
root.changelog.length = 0;
|
|
23
|
+
root.dirtyCells.length = 0;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const setRootVersion = (
|
|
27
|
+
root: ResourceFiberRoot,
|
|
28
|
+
version: number,
|
|
29
|
+
): void => {
|
|
30
|
+
const rollback = root.version > version;
|
|
31
|
+
root.version = version;
|
|
32
|
+
if (rollback) {
|
|
33
|
+
for (const cell of root.dirtyCells) {
|
|
34
|
+
cell.dirty = false;
|
|
35
|
+
cell.queue.clear();
|
|
36
|
+
cell.workInProgress = cell.current;
|
|
37
|
+
}
|
|
38
|
+
root.dirtyCells.length = 0;
|
|
39
|
+
|
|
40
|
+
if (version === root.committedVersion) {
|
|
41
|
+
root.changelog.length = 0;
|
|
42
|
+
} else {
|
|
43
|
+
// commit happened without a useEffect update (offscreen API)
|
|
44
|
+
|
|
45
|
+
if (root.committedVersion > version)
|
|
46
|
+
throw new Error("Version is less than committed version");
|
|
47
|
+
|
|
48
|
+
while (root.committedVersion + root.changelog.length > version) {
|
|
49
|
+
root.changelog.pop();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
root.changelog.forEach((apply) => apply());
|
|
53
|
+
commitRoot(root);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const markCellDirty = (
|
|
59
|
+
fiber: ResourceFiber<any, any>,
|
|
60
|
+
cell: Cell & { type: "reducer" },
|
|
61
|
+
): void => {
|
|
62
|
+
if (!cell.dirty) {
|
|
63
|
+
cell.dirty = true;
|
|
64
|
+
fiber.markDirty?.();
|
|
65
|
+
fiber.root.dirtyCells.push(cell);
|
|
66
|
+
}
|
|
67
|
+
};
|
package/src/core/resource.ts
CHANGED
package/src/core/scheduler.ts
CHANGED
|
@@ -36,7 +36,7 @@ export class UpdateScheduler {
|
|
|
36
36
|
const scheduleFlush = () => {
|
|
37
37
|
if (flushState.isScheduled) return;
|
|
38
38
|
flushState.isScheduled = true;
|
|
39
|
-
|
|
39
|
+
scheduleMacrotask();
|
|
40
40
|
};
|
|
41
41
|
|
|
42
42
|
const flushScheduled = () => {
|
|
@@ -80,6 +80,18 @@ const flushScheduled = () => {
|
|
|
80
80
|
}
|
|
81
81
|
};
|
|
82
82
|
|
|
83
|
+
// Use MessageChannel to schedule flushes as macrotasks (like React's scheduler).
|
|
84
|
+
// This allows more state updates to batch into a single re-render.
|
|
85
|
+
const scheduleMacrotask = (() => {
|
|
86
|
+
if (typeof MessageChannel !== "undefined") {
|
|
87
|
+
const channel = new MessageChannel();
|
|
88
|
+
channel.port1.onmessage = flushScheduled;
|
|
89
|
+
return () => channel.port2.postMessage(null);
|
|
90
|
+
}
|
|
91
|
+
// Fallback for environments without MessageChannel
|
|
92
|
+
return () => setTimeout(flushScheduled, 0);
|
|
93
|
+
})();
|
|
94
|
+
|
|
83
95
|
export const flushResourcesSync = <T>(callback: () => T): T => {
|
|
84
96
|
const prev = flushState;
|
|
85
97
|
flushState = {
|
package/src/core/types.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { tapEffect } from "../hooks/tap-effect";
|
|
2
|
-
import type {
|
|
3
|
-
import type { fnSymbol } from "./callResourceFn";
|
|
2
|
+
import type { fnSymbol } from "./helpers/callResourceFn";
|
|
4
3
|
|
|
5
4
|
export type ResourceElement<R, P = any> = {
|
|
6
5
|
readonly type: Resource<R, P> & { [fnSymbol]: (props: P) => R };
|
|
@@ -18,11 +17,22 @@ export type ExtractResourceReturnType<T> =
|
|
|
18
17
|
? R
|
|
19
18
|
: never;
|
|
20
19
|
|
|
20
|
+
export interface ReducerQueueEntry {
|
|
21
|
+
readonly action: any;
|
|
22
|
+
hasEagerState: boolean;
|
|
23
|
+
eagerState: any;
|
|
24
|
+
}
|
|
25
|
+
|
|
21
26
|
export type Cell =
|
|
22
27
|
| {
|
|
23
|
-
readonly type: "
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
readonly type: "reducer";
|
|
29
|
+
readonly dispatch: (action: any) => void;
|
|
30
|
+
|
|
31
|
+
readonly queue: Set<ReducerQueueEntry>;
|
|
32
|
+
dirty: boolean;
|
|
33
|
+
workInProgress: any;
|
|
34
|
+
current: any;
|
|
35
|
+
reducer: (state: any, action: any) => any;
|
|
26
36
|
}
|
|
27
37
|
| {
|
|
28
38
|
readonly type: "effect";
|
|
@@ -39,12 +49,22 @@ export interface EffectTask {
|
|
|
39
49
|
export interface RenderResult {
|
|
40
50
|
readonly output: any;
|
|
41
51
|
readonly props: any;
|
|
42
|
-
readonly
|
|
52
|
+
readonly effectTasks: (() => void)[];
|
|
43
53
|
}
|
|
44
54
|
|
|
45
|
-
export interface
|
|
55
|
+
export interface ResourceFiberRoot {
|
|
56
|
+
version: number;
|
|
57
|
+
committedVersion: number;
|
|
58
|
+
readonly changelog: (() => void)[];
|
|
59
|
+
|
|
46
60
|
readonly dispatchUpdate: (callback: () => boolean) => void;
|
|
61
|
+
readonly dirtyCells: (Cell & { type: "reducer" })[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ResourceFiber<R, P> {
|
|
65
|
+
readonly root: ResourceFiberRoot;
|
|
47
66
|
readonly type: Resource<R, P>;
|
|
67
|
+
readonly markDirty: (() => void) | undefined;
|
|
48
68
|
readonly devStrictMode: "root" | "child" | null;
|
|
49
69
|
|
|
50
70
|
cells: Cell[];
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { tapRef } from "./tap-ref";
|
|
2
2
|
import { tapEffect } from "./tap-effect";
|
|
3
|
-
import { isDevelopment } from "../core/env";
|
|
3
|
+
import { isDevelopment } from "../core/helpers/env";
|
|
4
4
|
import { tapCallback } from "./tap-callback";
|
|
5
|
-
import { getCurrentResourceFiber } from "../core/execution-context";
|
|
5
|
+
import { getCurrentResourceFiber } from "../core/helpers/execution-context";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Creates a stable function reference that always calls the most recent version of the callback.
|
|
@@ -24,6 +24,7 @@ export function tapEffectEvent<T extends (...args: any[]) => any>(
|
|
|
24
24
|
): T {
|
|
25
25
|
const callbackRef = tapRef(callback);
|
|
26
26
|
|
|
27
|
+
// TODO this effect needs to run before all userland effects
|
|
27
28
|
tapEffect(() => {
|
|
28
29
|
callbackRef.current = callback;
|
|
29
30
|
});
|
package/src/hooks/tap-memo.ts
CHANGED
|
@@ -1,25 +1,30 @@
|
|
|
1
|
-
import { isDevelopment } from "../core/env";
|
|
2
|
-
import { getCurrentResourceFiber } from "../core/execution-context";
|
|
3
|
-
import {
|
|
1
|
+
import { isDevelopment } from "../core/helpers/env";
|
|
2
|
+
import { getCurrentResourceFiber } from "../core/helpers/execution-context";
|
|
3
|
+
import { tapReducerWithDerivedState } from "./tap-reducer";
|
|
4
4
|
import { depsShallowEqual } from "./utils/depsShallowEqual";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (fiber.devStrictMode) {
|
|
12
|
-
void fn();
|
|
13
|
-
}
|
|
14
|
-
}
|
|
6
|
+
const memoReducer = () => {
|
|
7
|
+
throw new Error("Memo reducer should not be called");
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type MemoState<T> = { value: T; deps: readonly unknown[] };
|
|
15
11
|
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
export const tapMemo = <T>(fn: () => T, deps: readonly unknown[]): T => {
|
|
13
|
+
const fiber = getCurrentResourceFiber();
|
|
14
|
+
const [state] = tapReducerWithDerivedState(
|
|
15
|
+
memoReducer,
|
|
16
|
+
(state: MemoState<T> | null): MemoState<T> => {
|
|
17
|
+
if (state && depsShallowEqual(state.deps, deps)) return state;
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
const value = fn();
|
|
20
|
+
|
|
21
|
+
if (isDevelopment && fiber.devStrictMode) {
|
|
22
|
+
void fn();
|
|
23
|
+
}
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
return { value, deps };
|
|
26
|
+
},
|
|
27
|
+
null,
|
|
28
|
+
);
|
|
29
|
+
return state.value;
|
|
25
30
|
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { isDevelopment } from "../core/helpers/env";
|
|
2
|
+
import { getCurrentResourceFiber } from "../core/helpers/execution-context";
|
|
3
|
+
import { ReducerQueueEntry, ResourceFiber } from "../core/types";
|
|
4
|
+
import { markCellDirty } from "../core/helpers/root";
|
|
5
|
+
import { tapHook } from "./utils/tapHook";
|
|
6
|
+
|
|
7
|
+
type Dispatch<A> = (action: A) => void;
|
|
8
|
+
|
|
9
|
+
const dispatchOnFiber = (
|
|
10
|
+
fiber: ResourceFiber<any, any>,
|
|
11
|
+
callback: () => (() => void) | null,
|
|
12
|
+
): void => {
|
|
13
|
+
if (fiber.renderContext) {
|
|
14
|
+
throw new Error("Resource updated during render");
|
|
15
|
+
}
|
|
16
|
+
if (fiber.isNeverMounted) {
|
|
17
|
+
throw new Error("Resource updated before mount");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
fiber.root.dispatchUpdate(() => {
|
|
21
|
+
const result = callback();
|
|
22
|
+
if (result) {
|
|
23
|
+
result();
|
|
24
|
+
fiber.root.changelog.push(result);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function tapReducerImpl<S, A, I, R extends S>(
|
|
32
|
+
reducer: (state: S, action: A) => S,
|
|
33
|
+
getDerivedState: ((state: S) => R) | undefined,
|
|
34
|
+
initialArg: S | I,
|
|
35
|
+
initFn: ((arg: I) => S) | undefined,
|
|
36
|
+
): [R, Dispatch<A>] {
|
|
37
|
+
const cell = tapHook("reducer", () => {
|
|
38
|
+
const fiber = getCurrentResourceFiber();
|
|
39
|
+
|
|
40
|
+
// First render: compute initial state
|
|
41
|
+
const initialState = initFn ? initFn(initialArg as I) : initialArg;
|
|
42
|
+
|
|
43
|
+
if (isDevelopment && fiber.devStrictMode && initFn) {
|
|
44
|
+
void initFn(initialArg as I);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
type: "reducer",
|
|
49
|
+
queue: new Set(),
|
|
50
|
+
dirty: false,
|
|
51
|
+
workInProgress: initialState,
|
|
52
|
+
current: initialState,
|
|
53
|
+
reducer,
|
|
54
|
+
dispatch: (action: A) => {
|
|
55
|
+
const entry: ReducerQueueEntry = {
|
|
56
|
+
action,
|
|
57
|
+
hasEagerState: false,
|
|
58
|
+
eagerState: undefined,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
dispatchOnFiber(fiber, () => {
|
|
62
|
+
if (fiber.root.dirtyCells.length === 0 && !entry.hasEagerState) {
|
|
63
|
+
entry.eagerState = reducer(cell.workInProgress, action);
|
|
64
|
+
entry.hasEagerState = true;
|
|
65
|
+
|
|
66
|
+
if (Object.is(cell.current, entry.eagerState)) return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return () => {
|
|
70
|
+
markCellDirty(fiber, cell);
|
|
71
|
+
cell.queue.add(entry);
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const fiber = getCurrentResourceFiber();
|
|
79
|
+
const sameReducer = reducer === cell.reducer;
|
|
80
|
+
cell.reducer = reducer;
|
|
81
|
+
|
|
82
|
+
for (const item of cell.queue) {
|
|
83
|
+
if (!item.hasEagerState || !sameReducer) {
|
|
84
|
+
item.eagerState = reducer(cell.workInProgress, item.action);
|
|
85
|
+
item.hasEagerState = true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (isDevelopment && fiber.devStrictMode) {
|
|
89
|
+
void reducer(cell.workInProgress, item.action);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
cell.workInProgress = item.eagerState;
|
|
93
|
+
}
|
|
94
|
+
cell.queue.clear();
|
|
95
|
+
|
|
96
|
+
if (getDerivedState) {
|
|
97
|
+
const derived = getDerivedState(cell.workInProgress);
|
|
98
|
+
|
|
99
|
+
if (!Object.is(derived, cell.workInProgress)) {
|
|
100
|
+
markCellDirty(fiber, cell);
|
|
101
|
+
cell.workInProgress = derived;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return [cell.workInProgress, cell.dispatch];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function tapReducer<S, A>(
|
|
109
|
+
reducer: (state: S, action: A) => S,
|
|
110
|
+
initialState: S,
|
|
111
|
+
): [S, Dispatch<A>];
|
|
112
|
+
export function tapReducer<S, A, I>(
|
|
113
|
+
reducer: (state: S, action: A) => S,
|
|
114
|
+
initialArg: I,
|
|
115
|
+
init: (arg: I) => S,
|
|
116
|
+
): [S, Dispatch<A>];
|
|
117
|
+
export function tapReducer<S, A, I>(
|
|
118
|
+
reducer: (state: S, action: A) => S,
|
|
119
|
+
initialArg: S | I,
|
|
120
|
+
init?: (arg: I) => S,
|
|
121
|
+
): [S, Dispatch<A>] {
|
|
122
|
+
return tapReducerImpl(
|
|
123
|
+
reducer,
|
|
124
|
+
undefined,
|
|
125
|
+
initialArg as S,
|
|
126
|
+
init as ((arg: S) => S) | undefined,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function tapReducerWithDerivedState<S, A, R extends S>(
|
|
131
|
+
reducer: (state: S, action: A) => S,
|
|
132
|
+
getDerivedState: (state: S) => R,
|
|
133
|
+
initialState: S,
|
|
134
|
+
): [R, Dispatch<A>];
|
|
135
|
+
export function tapReducerWithDerivedState<S, A, I, R extends S>(
|
|
136
|
+
reducer: (state: S, action: A) => S,
|
|
137
|
+
getDerivedState: (state: S) => R,
|
|
138
|
+
initialArg: I,
|
|
139
|
+
init: (arg: I) => S,
|
|
140
|
+
): [R, Dispatch<A>];
|
|
141
|
+
export function tapReducerWithDerivedState<S, A, I, R extends S>(
|
|
142
|
+
reducer: (state: S, action: A) => S,
|
|
143
|
+
getDerivedState: (state: S) => R,
|
|
144
|
+
initialArg: I,
|
|
145
|
+
init?: (arg: I) => S,
|
|
146
|
+
): [R, Dispatch<A>] {
|
|
147
|
+
return tapReducerImpl(reducer, getDerivedState, initialArg, init);
|
|
148
|
+
}
|
|
@@ -7,8 +7,8 @@ import {
|
|
|
7
7
|
commitResourceFiber,
|
|
8
8
|
} from "../core/ResourceFiber";
|
|
9
9
|
import { tapMemo } from "./tap-memo";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { tapRef } from "./tap-ref";
|
|
11
|
+
import { getCurrentResourceFiber } from "../core/helpers/execution-context";
|
|
12
12
|
|
|
13
13
|
export function tapResource<E extends ResourceElement<any, any>>(
|
|
14
14
|
element: E,
|
|
@@ -21,21 +21,21 @@ export function tapResource<E extends ResourceElement<any, any>>(
|
|
|
21
21
|
element: E,
|
|
22
22
|
propsDeps?: readonly unknown[],
|
|
23
23
|
): ExtractResourceReturnType<E> {
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
|
|
24
|
+
const parentFiber = getCurrentResourceFiber();
|
|
25
|
+
const versionRef = tapRef(0);
|
|
27
26
|
const fiber = tapMemo(() => {
|
|
28
27
|
void element.key;
|
|
29
|
-
return createResourceFiber(element.type, (
|
|
30
|
-
|
|
28
|
+
return createResourceFiber(element.type, parentFiber.root, () => {
|
|
29
|
+
versionRef.current++;
|
|
30
|
+
parentFiber.markDirty?.();
|
|
31
31
|
});
|
|
32
|
-
}, [element.type, element.key]);
|
|
32
|
+
}, [element.type, element.key, parentFiber]);
|
|
33
33
|
|
|
34
34
|
const result = propsDeps
|
|
35
35
|
? // biome-ignore lint/correctness/useExhaustiveDependencies: user provided deps instead of prop identity
|
|
36
36
|
tapMemo(
|
|
37
37
|
() => renderResourceFiber(fiber, element.props),
|
|
38
|
-
[fiber, ...propsDeps,
|
|
38
|
+
[fiber, ...propsDeps, versionRef.current],
|
|
39
39
|
)
|
|
40
40
|
: renderResourceFiber(fiber, element.props);
|
|
41
41
|
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
} from "../core/types";
|
|
7
7
|
import { tapEffect } from "./tap-effect";
|
|
8
8
|
import { tapMemo } from "./tap-memo";
|
|
9
|
-
import { tapState } from "./tap-state";
|
|
10
9
|
import { tapCallback } from "./tap-callback";
|
|
11
10
|
import {
|
|
12
11
|
createResourceFiber,
|
|
@@ -15,6 +14,8 @@ import {
|
|
|
15
14
|
commitResourceFiber,
|
|
16
15
|
} from "../core/ResourceFiber";
|
|
17
16
|
import { tapConst } from "./tap-const";
|
|
17
|
+
import { tapRef } from "./tap-ref";
|
|
18
|
+
import { getCurrentResourceFiber } from "../core/helpers/execution-context";
|
|
18
19
|
|
|
19
20
|
type FiberState = {
|
|
20
21
|
fiber: ResourceFiber<unknown, unknown>;
|
|
@@ -28,9 +29,17 @@ export function tapResources<E extends ResourceElement<any, any>>(
|
|
|
28
29
|
getElements: () => readonly E[],
|
|
29
30
|
getElementsDeps?: readonly unknown[],
|
|
30
31
|
): ExtractResourceReturnType<E>[] {
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
|
|
32
|
+
const versionRef = tapRef(0);
|
|
33
|
+
const version = versionRef.current;
|
|
34
|
+
|
|
35
|
+
const parentFiber = tapConst(getCurrentResourceFiber, []);
|
|
36
|
+
const markDirty = tapConst(
|
|
37
|
+
() => () => {
|
|
38
|
+
versionRef.current++;
|
|
39
|
+
parentFiber.markDirty?.();
|
|
40
|
+
},
|
|
41
|
+
[],
|
|
42
|
+
);
|
|
34
43
|
const fibers = tapConst(() => new Map<string | number, FiberState>(), []);
|
|
35
44
|
|
|
36
45
|
const getElementsMemo = getElementsDeps
|
|
@@ -65,9 +74,11 @@ export function tapResources<E extends ResourceElement<any, any>>(
|
|
|
65
74
|
|
|
66
75
|
let state = fibers.get(elementKey);
|
|
67
76
|
if (!state) {
|
|
68
|
-
const fiber = createResourceFiber(
|
|
69
|
-
|
|
70
|
-
|
|
77
|
+
const fiber = createResourceFiber(
|
|
78
|
+
element.type,
|
|
79
|
+
parentFiber.root,
|
|
80
|
+
markDirty,
|
|
81
|
+
);
|
|
71
82
|
const result = renderResourceFiber(fiber, element.props);
|
|
72
83
|
state = {
|
|
73
84
|
fiber,
|
|
@@ -77,9 +88,11 @@ export function tapResources<E extends ResourceElement<any, any>>(
|
|
|
77
88
|
fibers.set(elementKey, state);
|
|
78
89
|
results.push(result.output);
|
|
79
90
|
} else if (state.fiber.type !== element.type) {
|
|
80
|
-
const fiber = createResourceFiber(
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
const fiber = createResourceFiber(
|
|
92
|
+
element.type,
|
|
93
|
+
parentFiber.root,
|
|
94
|
+
markDirty,
|
|
95
|
+
);
|
|
83
96
|
const result = renderResourceFiber(fiber, element.props);
|
|
84
97
|
state.next = [fiber, result];
|
|
85
98
|
results.push(result.output);
|
package/src/hooks/tap-state.ts
CHANGED
|
@@ -1,94 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { getCurrentResourceFiber } from "../core/execution-context";
|
|
3
|
-
import { Cell, ResourceFiber } from "../core/types";
|
|
1
|
+
import { tapReducer } from "./tap-reducer";
|
|
4
2
|
|
|
5
3
|
export namespace tapState {
|
|
6
4
|
export type StateUpdater<S> = S | ((prev: S) => S);
|
|
7
5
|
}
|
|
8
6
|
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
) =>
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
const stateReducer = <S>(
|
|
8
|
+
state: S | undefined,
|
|
9
|
+
action: tapState.StateUpdater<S>,
|
|
10
|
+
): S =>
|
|
11
|
+
typeof action === "function"
|
|
12
|
+
? (action as (prev: S | undefined) => S)(state)
|
|
13
|
+
: action;
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
fiber.dispatchUpdate(callback);
|
|
20
|
-
} else if (fiber.isNeverMounted) {
|
|
21
|
-
throw new Error("Resource updated before mount");
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// TODO mark dirty
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
function getStateCell<T>(
|
|
28
|
-
initialValue: T | (() => T),
|
|
29
|
-
): Cell & { type: "state" } {
|
|
30
|
-
const fiber = getCurrentResourceFiber();
|
|
31
|
-
const index = fiber.currentIndex++;
|
|
32
|
-
|
|
33
|
-
// Check if we're trying to use more hooks than in previous renders
|
|
34
|
-
if (!fiber.isFirstRender && index >= fiber.cells.length) {
|
|
35
|
-
throw new Error(
|
|
36
|
-
"Rendered more hooks than during the previous render. " +
|
|
37
|
-
"Hooks must be called in the exact same order in every render.",
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
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;
|
|
68
|
-
const nextValue =
|
|
69
|
-
typeof updater === "function"
|
|
70
|
-
? (updater as (prev: T) => T)(currentValue)
|
|
71
|
-
: updater;
|
|
72
|
-
|
|
73
|
-
if (
|
|
74
|
-
isDevelopment &&
|
|
75
|
-
fiber.devStrictMode &&
|
|
76
|
-
typeof updater === "function"
|
|
77
|
-
) {
|
|
78
|
-
void (updater as (prev: T) => T)(currentValue);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (Object.is(currentValue, nextValue)) return false;
|
|
82
|
-
|
|
83
|
-
newCell.value = nextValue;
|
|
84
|
-
return true;
|
|
85
|
-
});
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
fiber.cells[index] = newCell;
|
|
90
|
-
return newCell;
|
|
91
|
-
}
|
|
15
|
+
const stateInit = <S>(initial: S | (() => S)): S =>
|
|
16
|
+
typeof initial === "function" ? (initial as () => S)() : initial;
|
|
92
17
|
|
|
93
18
|
export function tapState<S = undefined>(): [
|
|
94
19
|
S | undefined,
|
|
@@ -100,7 +25,5 @@ export function tapState<S>(
|
|
|
100
25
|
export function tapState<S>(
|
|
101
26
|
initial?: S | (() => S),
|
|
102
27
|
): [S | undefined, (updater: tapState.StateUpdater<S>) => void] {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return [cell.value, cell.set];
|
|
28
|
+
return tapReducer(stateReducer, initial, stateInit);
|
|
106
29
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { getCurrentResourceFiber } from "../../core/execution-context";
|
|
1
|
+
import { getCurrentResourceFiber } from "../../core/helpers/execution-context";
|
|
2
2
|
import { Cell } from "../../core/types";
|
|
3
3
|
|
|
4
|
-
export const tapHook = <T extends "
|
|
4
|
+
export const tapHook = <T extends Cell["type"]>(
|
|
5
5
|
type: T,
|
|
6
6
|
init: () => Cell,
|
|
7
7
|
): Cell & { type: T } => {
|
|
@@ -31,5 +31,5 @@ export const tapHook = <T extends "effect" | "memo">(
|
|
|
31
31
|
|
|
32
32
|
export const registerRenderMountTask = (task: () => void) => {
|
|
33
33
|
const fiber = getCurrentResourceFiber();
|
|
34
|
-
fiber.renderContext!.
|
|
34
|
+
fiber.renderContext!.effectTasks.push(task);
|
|
35
35
|
};
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { withKey } from "./core/withKey";
|
|
|
3
3
|
|
|
4
4
|
// primitive hooks
|
|
5
5
|
export { tapState } from "./hooks/tap-state";
|
|
6
|
+
export { tapReducer, tapReducerWithDerivedState } from "./hooks/tap-reducer";
|
|
6
7
|
export { tapEffect } from "./hooks/tap-effect";
|
|
7
8
|
|
|
8
9
|
// utility hooks
|
|
@@ -14,14 +15,13 @@ export { tapEffectEvent } from "./hooks/tap-effect-event";
|
|
|
14
15
|
|
|
15
16
|
// resources
|
|
16
17
|
export { tapResource } from "./hooks/tap-resource";
|
|
17
|
-
export { tapInlineResource } from "./hooks/tap-inline-resource";
|
|
18
18
|
export { tapResources } from "./hooks/tap-resources";
|
|
19
19
|
|
|
20
20
|
// subscribable
|
|
21
|
-
export {
|
|
21
|
+
export { tapResourceRoot } from "./tapResourceRoot";
|
|
22
22
|
|
|
23
23
|
// imperative
|
|
24
|
-
export {
|
|
24
|
+
export { createResourceRoot } from "./core/createResourceRoot";
|
|
25
25
|
export { flushResourcesSync } from "./core/scheduler";
|
|
26
26
|
|
|
27
27
|
// context
|