@assistant-ui/tap 0.6.0 → 0.7.1
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 +9 -6
- package/dist/core/ResourceFiber.d.ts +5 -5
- package/dist/core/ResourceFiber.d.ts.map +1 -1
- package/dist/core/ResourceFiber.js +26 -18
- package/dist/core/ResourceFiber.js.map +1 -1
- package/dist/core/createTapRoot.d.ts +9 -0
- package/dist/core/createTapRoot.d.ts.map +1 -0
- package/dist/core/createTapRoot.js +27 -0
- package/dist/core/createTapRoot.js.map +1 -0
- package/dist/core/helpers/commit.d.ts +1 -1
- package/dist/core/helpers/commit.d.ts.map +1 -1
- package/dist/core/helpers/commit.js +6 -1
- package/dist/core/helpers/commit.js.map +1 -1
- package/dist/core/helpers/execution-context.d.ts +4 -5
- package/dist/core/helpers/execution-context.d.ts.map +1 -1
- package/dist/core/helpers/execution-context.js +1 -7
- package/dist/core/helpers/execution-context.js.map +1 -1
- package/dist/core/helpers/root.d.ts +3 -2
- package/dist/core/helpers/root.d.ts.map +1 -1
- package/dist/core/helpers/root.js +19 -15
- package/dist/core/helpers/root.js.map +1 -1
- package/dist/core/react-dispatcher.d.ts.map +1 -1
- package/dist/core/react-dispatcher.js +17 -16
- package/dist/core/react-dispatcher.js.map +1 -1
- package/dist/core/resource.d.ts +2 -4
- package/dist/core/resource.d.ts.map +1 -1
- package/dist/core/resource.js +5 -10
- package/dist/core/resource.js.map +1 -1
- package/dist/core/scheduler.d.ts +2 -2
- package/dist/core/scheduler.d.ts.map +1 -1
- package/dist/core/scheduler.js +2 -2
- package/dist/core/scheduler.js.map +1 -1
- package/dist/core/types.d.ts +27 -25
- package/dist/core/types.d.ts.map +1 -1
- package/dist/hooks/useResource.d.ts +2 -2
- package/dist/hooks/useResource.d.ts.map +1 -1
- package/dist/hooks/useResource.js +14 -20
- package/dist/hooks/useResource.js.map +1 -1
- package/dist/hooks/useResources.d.ts +1 -1
- package/dist/hooks/useResources.d.ts.map +1 -1
- package/dist/hooks/useResources.js +18 -27
- package/dist/hooks/useResources.js.map +1 -1
- package/dist/hooks/useTapHost.d.ts +21 -0
- package/dist/hooks/useTapHost.d.ts.map +1 -0
- package/dist/hooks/useTapHost.js +30 -0
- package/dist/hooks/useTapHost.js.map +1 -0
- package/dist/hooks/useTapRoot.d.ts +18 -0
- package/dist/hooks/useTapRoot.d.ts.map +1 -0
- package/dist/hooks/useTapRoot.js +77 -0
- package/dist/hooks/useTapRoot.js.map +1 -0
- package/dist/hooks/utils/depsShallowEqual.d.ts.map +1 -1
- package/dist/hooks/utils/depsShallowEqual.js +5 -2
- package/dist/hooks/utils/depsShallowEqual.js.map +1 -1
- package/dist/hooks/utils/useCell.d.ts +2 -2
- package/dist/hooks/utils/useCell.d.ts.map +1 -1
- package/dist/hooks/utils/useCell.js.map +1 -1
- package/dist/hooks/utils/useDevStrictMode.d.ts +5 -0
- package/dist/hooks/utils/useDevStrictMode.d.ts.map +1 -0
- package/dist/hooks/utils/useDevStrictMode.js +25 -0
- package/dist/hooks/utils/useDevStrictMode.js.map +1 -0
- package/dist/hooks/utils/useRenderMemo.d.ts +5 -0
- package/dist/hooks/utils/useRenderMemo.d.ts.map +1 -0
- package/dist/hooks/utils/useRenderMemo.js +25 -0
- package/dist/hooks/utils/useRenderMemo.js.map +1 -0
- package/dist/hooks/utils/useResourceFiberHostUtils.d.ts +10 -0
- package/dist/hooks/utils/useResourceFiberHostUtils.d.ts.map +1 -0
- package/dist/hooks/utils/useResourceFiberHostUtils.js +46 -0
- package/dist/hooks/utils/useResourceFiberHostUtils.js.map +1 -0
- package/dist/index.d.ts +7 -4
- package/dist/index.js +7 -4
- package/dist/{hooks → react-hooks}/index.d.ts +6 -6
- package/dist/{hooks → react-hooks}/index.js +5 -5
- package/dist/{hooks → react-hooks}/use.d.ts +1 -1
- package/dist/{hooks → react-hooks}/use.d.ts.map +1 -1
- package/dist/{hooks → react-hooks}/use.js +1 -1
- package/dist/react-hooks/use.js.map +1 -0
- package/dist/{hooks → react-hooks}/useCallback.d.ts +1 -1
- package/dist/react-hooks/useCallback.d.ts.map +1 -0
- package/dist/{hooks → react-hooks}/useCallback.js +1 -1
- package/dist/react-hooks/useCallback.js.map +1 -0
- package/dist/{hooks → react-hooks}/useEffect.d.ts +1 -1
- package/dist/react-hooks/useEffect.d.ts.map +1 -0
- package/dist/react-hooks/useEffect.js +35 -0
- package/dist/react-hooks/useEffect.js.map +1 -0
- package/dist/{hooks → react-hooks}/useEffectEvent.d.ts +1 -1
- package/dist/react-hooks/useEffectEvent.d.ts.map +1 -0
- package/dist/{hooks → react-hooks}/useEffectEvent.js +2 -2
- package/dist/react-hooks/useEffectEvent.js.map +1 -0
- package/dist/{hooks → react-hooks}/useMemo.d.ts +1 -1
- package/dist/react-hooks/useMemo.d.ts.map +1 -0
- package/dist/{hooks → react-hooks}/useMemo.js +3 -3
- package/dist/react-hooks/useMemo.js.map +1 -0
- package/dist/{hooks → react-hooks}/useMemoCache.d.ts +1 -1
- package/dist/react-hooks/useMemoCache.d.ts.map +1 -0
- package/dist/{hooks → react-hooks}/useMemoCache.js +1 -1
- package/dist/react-hooks/useMemoCache.js.map +1 -0
- package/dist/react-hooks/useReducer.d.ts +9 -0
- package/dist/react-hooks/useReducer.d.ts.map +1 -0
- package/dist/react-hooks/useReducer.js +120 -0
- package/dist/react-hooks/useReducer.js.map +1 -0
- package/dist/{hooks → react-hooks}/useRef.d.ts +1 -1
- package/dist/react-hooks/useRef.d.ts.map +1 -0
- package/dist/{hooks → react-hooks}/useRef.js +1 -1
- package/dist/react-hooks/useRef.js.map +1 -0
- package/dist/{hooks → react-hooks}/useState.d.ts +1 -1
- package/dist/react-hooks/useState.d.ts.map +1 -0
- package/dist/{hooks → react-hooks}/useState.js +3 -3
- package/dist/react-hooks/useState.js.map +1 -0
- package/dist/react-shim/index.d.ts +8 -10
- package/dist/react-shim/index.d.ts.map +1 -1
- package/dist/react-shim/index.js +19 -19
- package/dist/react-shim/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/basic/resourceHandle.test.ts +32 -22
- package/src/__tests__/basic/tapEffect.basic.test.ts +8 -8
- package/src/__tests__/basic/tapReducer.basic.test.ts +16 -14
- package/src/__tests__/basic/tapResources.basic.test.ts +19 -16
- package/src/__tests__/basic/tapState.basic.test.ts +11 -11
- package/src/__tests__/bench/hosts.bench.tsx +124 -0
- package/src/__tests__/bench/tree.bench.tsx +166 -0
- package/src/__tests__/errors/errors.effect-errors.test.ts +12 -13
- package/src/__tests__/errors/errors.render-errors.test.ts +65 -22
- package/src/__tests__/lifecycle/lifecycle.dependencies.test.ts +19 -19
- package/src/__tests__/lifecycle/lifecycle.mount-unmount.test.ts +14 -14
- package/src/__tests__/parity/describeParity.tsx +217 -0
- package/src/__tests__/parity/parity.adversarial.test.tsx +375 -0
- package/src/__tests__/parity/parity.basics.test.tsx +281 -0
- package/src/__tests__/parity/parity.divergences.test.tsx +208 -0
- package/src/__tests__/parity/parity.smoke.test.tsx +43 -0
- package/src/__tests__/react/concurrent-mode.test.tsx +10 -6
- package/src/__tests__/react/concurrent-pending-updates.test.tsx +351 -0
- package/src/__tests__/react/concurrent-render-phase.test.tsx +350 -0
- package/src/__tests__/react/react-shim.test.tsx +1 -1
- package/src/__tests__/react/useResource.test.tsx +41 -26
- package/src/__tests__/react/useTapHost.test.tsx +233 -0
- package/src/__tests__/react-dispatcher.test.ts +4 -4
- package/src/__tests__/rules/rules.hook-count.test.ts +21 -21
- package/src/__tests__/rules/rules.hook-order.test.ts +17 -17
- package/src/__tests__/strictmode/strictmode-parity.test.tsx +420 -0
- package/src/__tests__/strictmode/strictmode.test.ts +39 -209
- package/src/__tests__/test-utils.ts +33 -23
- package/src/core/ResourceFiber.ts +43 -35
- package/src/core/createTapRoot.ts +45 -0
- package/src/core/helpers/commit.ts +12 -2
- package/src/core/helpers/execution-context.ts +4 -13
- package/src/core/helpers/root.ts +24 -12
- package/src/core/react-dispatcher.ts +14 -13
- package/src/core/resource.ts +5 -20
- package/src/core/scheduler.ts +1 -1
- package/src/core/types.ts +27 -21
- package/src/hooks/useResource.ts +18 -27
- package/src/hooks/useResources.ts +18 -42
- package/src/hooks/useTapHost.ts +60 -0
- package/src/hooks/useTapRoot.ts +135 -0
- package/src/hooks/utils/depsShallowEqual.ts +12 -2
- package/src/hooks/utils/useCell.ts +2 -2
- package/src/hooks/utils/useDevStrictMode.ts +34 -0
- package/src/hooks/utils/useRenderMemo.ts +27 -0
- package/src/hooks/utils/useResourceFiberHostUtils.ts +61 -0
- package/src/index.ts +6 -3
- package/src/{hooks → react-hooks}/index.ts +4 -4
- package/src/react-hooks/useEffect.ts +58 -0
- package/src/{hooks → react-hooks}/useMemo.ts +1 -1
- package/src/react-hooks/useReducer.ts +254 -0
- package/src/{hooks → react-hooks}/useState.ts +2 -2
- package/src/react-shim/index.ts +24 -13
- package/dist/core/createResourceRoot.d.ts +0 -11
- package/dist/core/createResourceRoot.d.ts.map +0 -1
- package/dist/core/createResourceRoot.js +0 -31
- package/dist/core/createResourceRoot.js.map +0 -1
- package/dist/core/helpers/callResourceFn.d.ts +0 -1
- package/dist/core/helpers/callResourceFn.js +0 -19
- package/dist/core/helpers/callResourceFn.js.map +0 -1
- package/dist/hooks/use.js.map +0 -1
- package/dist/hooks/useCallback.d.ts.map +0 -1
- package/dist/hooks/useCallback.js.map +0 -1
- package/dist/hooks/useEffect.d.ts.map +0 -1
- package/dist/hooks/useEffect.js +0 -40
- package/dist/hooks/useEffect.js.map +0 -1
- package/dist/hooks/useEffectEvent.d.ts.map +0 -1
- package/dist/hooks/useEffectEvent.js.map +0 -1
- package/dist/hooks/useMemo.d.ts.map +0 -1
- package/dist/hooks/useMemo.js.map +0 -1
- package/dist/hooks/useMemoCache.d.ts.map +0 -1
- package/dist/hooks/useMemoCache.js.map +0 -1
- package/dist/hooks/useReducer.d.ts +0 -21
- package/dist/hooks/useReducer.d.ts.map +0 -1
- package/dist/hooks/useReducer.js +0 -81
- package/dist/hooks/useReducer.js.map +0 -1
- package/dist/hooks/useRef.d.ts.map +0 -1
- package/dist/hooks/useRef.js.map +0 -1
- package/dist/hooks/useResourceRoot.d.ts +0 -20
- package/dist/hooks/useResourceRoot.d.ts.map +0 -1
- package/dist/hooks/useResourceRoot.js +0 -77
- package/dist/hooks/useResourceRoot.js.map +0 -1
- package/dist/hooks/useState.d.ts.map +0 -1
- package/dist/hooks/useState.js.map +0 -1
- package/dist/react/hooks.d.ts +0 -25
- package/dist/react/hooks.d.ts.map +0 -1
- package/dist/react/hooks.js +0 -69
- package/dist/react/hooks.js.map +0 -1
- package/src/__tests__/strictmode/react-strictmode-behavior.test.tsx +0 -920
- package/src/__tests__/strictmode/react-strictmode-rerender-sources.test.tsx +0 -488
- package/src/__tests__/strictmode/tap-strictmode-rerender-sources.test.ts +0 -687
- package/src/core/createResourceRoot.ts +0 -53
- package/src/core/helpers/callResourceFn.ts +0 -21
- package/src/hooks/useEffect.ts +0 -72
- package/src/hooks/useReducer.ts +0 -160
- package/src/hooks/useResourceRoot.ts +0 -130
- package/src/react/hooks.ts +0 -112
- /package/src/{hooks → react-hooks}/use.ts +0 -0
- /package/src/{hooks → react-hooks}/useCallback.ts +0 -0
- /package/src/{hooks → react-hooks}/useEffectEvent.ts +0 -0
- /package/src/{hooks → react-hooks}/useMemoCache.ts +0 -0
- /package/src/{hooks → react-hooks}/useRef.ts +0 -0
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { isDevelopment } from "./env";
|
|
2
1
|
import type { ResourceFiber } from "../types";
|
|
3
2
|
|
|
4
3
|
let currentResourceFiber: ResourceFiber<any, any> | null = null;
|
|
5
4
|
|
|
6
|
-
export function withResourceFiber<R,
|
|
7
|
-
fiber: ResourceFiber<R,
|
|
5
|
+
export function withResourceFiber<R, A extends readonly unknown[]>(
|
|
6
|
+
fiber: ResourceFiber<R, A>,
|
|
8
7
|
fn: () => void,
|
|
9
8
|
): void {
|
|
10
9
|
fiber.currentIndex = 0;
|
|
@@ -27,21 +26,13 @@ export function withResourceFiber<R, P>(
|
|
|
27
26
|
currentResourceFiber = previousContext;
|
|
28
27
|
}
|
|
29
28
|
}
|
|
30
|
-
export function getCurrentResourceFiber(): ResourceFiber<unknown
|
|
29
|
+
export function getCurrentResourceFiber(): ResourceFiber<unknown> {
|
|
31
30
|
if (!currentResourceFiber) {
|
|
32
31
|
throw new Error("No resource fiber available");
|
|
33
32
|
}
|
|
34
33
|
return currentResourceFiber;
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
export function peekResourceFiber(): ResourceFiber<unknown
|
|
36
|
+
export function peekResourceFiber(): ResourceFiber<unknown> | null {
|
|
38
37
|
return currentResourceFiber;
|
|
39
38
|
}
|
|
40
|
-
|
|
41
|
-
export function getDevStrictMode(enable: boolean) {
|
|
42
|
-
if (!isDevelopment) return null;
|
|
43
|
-
if (currentResourceFiber?.devStrictMode)
|
|
44
|
-
return currentResourceFiber.isFirstRender ? "child" : "root";
|
|
45
|
-
|
|
46
|
-
return enable ? "root" : null;
|
|
47
|
-
}
|
package/src/core/helpers/root.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
Cell,
|
|
3
|
+
ChangelogRecord,
|
|
4
|
+
ResourceFiber,
|
|
5
|
+
ResourceFiberRoot,
|
|
6
|
+
} from "../types";
|
|
2
7
|
|
|
3
8
|
export const createResourceFiberRoot = (
|
|
4
9
|
dispatchUpdate: (cb: () => boolean) => void,
|
|
@@ -8,19 +13,17 @@ export const createResourceFiberRoot = (
|
|
|
8
13
|
committedVersion: 0,
|
|
9
14
|
dispatchUpdate,
|
|
10
15
|
changelog: [],
|
|
11
|
-
dirtyCells:
|
|
16
|
+
dirtyCells: new Set(),
|
|
12
17
|
};
|
|
13
18
|
};
|
|
14
19
|
|
|
15
20
|
export const commitRoot = (root: ResourceFiberRoot): void => {
|
|
16
21
|
for (const cell of root.dirtyCells) {
|
|
17
|
-
cell.dirty = false;
|
|
18
|
-
cell.queue.clear();
|
|
19
22
|
cell.current = cell.workInProgress;
|
|
20
23
|
}
|
|
21
24
|
root.committedVersion = root.version;
|
|
22
25
|
root.changelog.length = 0;
|
|
23
|
-
root.dirtyCells.
|
|
26
|
+
root.dirtyCells.clear();
|
|
24
27
|
};
|
|
25
28
|
|
|
26
29
|
export const setRootVersion = (
|
|
@@ -31,11 +34,13 @@ export const setRootVersion = (
|
|
|
31
34
|
root.version = version;
|
|
32
35
|
if (rollback) {
|
|
33
36
|
for (const cell of root.dirtyCells) {
|
|
34
|
-
cell.
|
|
35
|
-
|
|
37
|
+
if (cell.queue !== null) {
|
|
38
|
+
for (const record of cell.queue) record.queued = false;
|
|
39
|
+
cell.queue = null;
|
|
40
|
+
}
|
|
36
41
|
cell.workInProgress = cell.current;
|
|
37
42
|
}
|
|
38
|
-
root.dirtyCells.
|
|
43
|
+
root.dirtyCells.clear();
|
|
39
44
|
|
|
40
45
|
if (version === root.committedVersion) {
|
|
41
46
|
root.changelog.length = 0;
|
|
@@ -49,19 +54,26 @@ export const setRootVersion = (
|
|
|
49
54
|
root.changelog.pop();
|
|
50
55
|
}
|
|
51
56
|
|
|
52
|
-
root.changelog.forEach(
|
|
57
|
+
root.changelog.forEach(applyChangelogRecord);
|
|
53
58
|
commitRoot(root);
|
|
54
59
|
}
|
|
55
60
|
}
|
|
56
61
|
};
|
|
57
62
|
|
|
63
|
+
export const applyChangelogRecord = (record: ChangelogRecord): void => {
|
|
64
|
+
markCellDirty(record.fiber, record.cell);
|
|
65
|
+
if (!record.queued) {
|
|
66
|
+
record.queued = true;
|
|
67
|
+
(record.cell.queue ??= []).push(record);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
58
71
|
export const markCellDirty = (
|
|
59
72
|
fiber: ResourceFiber<any, any>,
|
|
60
73
|
cell: Cell & { type: "reducer" },
|
|
61
74
|
): void => {
|
|
62
|
-
if (!cell
|
|
63
|
-
cell.dirty = true;
|
|
75
|
+
if (!fiber.root.dirtyCells.has(cell)) {
|
|
64
76
|
fiber.markDirty?.();
|
|
65
|
-
fiber.root.dirtyCells.
|
|
77
|
+
fiber.root.dirtyCells.add(cell);
|
|
66
78
|
}
|
|
67
79
|
};
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { useState } from "../react-hooks/useState";
|
|
4
|
+
import { useReducer } from "../react-hooks/useReducer";
|
|
5
|
+
import { useRef } from "../react-hooks/useRef";
|
|
6
|
+
import { useMemo } from "../react-hooks/useMemo";
|
|
7
|
+
import { useCallback } from "../react-hooks/useCallback";
|
|
8
|
+
import { useEffect } from "../react-hooks/useEffect";
|
|
9
|
+
import { useEffectEvent } from "../react-hooks/useEffectEvent";
|
|
10
|
+
import { use } from "../react-hooks/use";
|
|
11
|
+
import { useMemoCache } from "../react-hooks/useMemoCache";
|
|
11
12
|
|
|
12
13
|
// The dispatcher React reads while a resource renders, so hooks imported from
|
|
13
14
|
// "react" route to tap with no build step. Hooks tap has no equivalent for are
|
|
@@ -30,10 +31,10 @@ const tapDispatcher = {
|
|
|
30
31
|
|
|
31
32
|
// React's live dispatcher slot differs by version: React 19 exposes it as `H` on
|
|
32
33
|
// the client internals object; React 18 as `ReactCurrentDispatcher.current`.
|
|
34
|
+
const ReactRuntime = React as any;
|
|
33
35
|
const internals =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
(React as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
|
|
36
|
+
ReactRuntime.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE ??
|
|
37
|
+
ReactRuntime.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
|
|
37
38
|
|
|
38
39
|
const slot: { current: unknown } | null =
|
|
39
40
|
internals == null
|
package/src/core/resource.ts
CHANGED
|
@@ -1,22 +1,7 @@
|
|
|
1
|
-
import type { ResourceElement } from "./types";
|
|
2
|
-
import { fnSymbol } from "./helpers/callResourceFn";
|
|
1
|
+
import type { Resource, ResourceElement } from "./types";
|
|
3
2
|
|
|
4
|
-
export function resource<R
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export function resource<R, P>(
|
|
9
|
-
fn: (props?: P) => R,
|
|
10
|
-
): (props?: P) => ResourceElement<R, P | undefined>;
|
|
11
|
-
export function resource<R, P = undefined>(fn: (props: P) => R) {
|
|
12
|
-
const type = (props?: P) => {
|
|
13
|
-
return {
|
|
14
|
-
type,
|
|
15
|
-
props: props!,
|
|
16
|
-
} satisfies ResourceElement<R, P>;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
type[fnSymbol] = fn;
|
|
20
|
-
|
|
21
|
-
return type;
|
|
3
|
+
export function resource<R, A extends readonly unknown[]>(
|
|
4
|
+
hook: (...args: A) => R,
|
|
5
|
+
): Resource<R, A> {
|
|
6
|
+
return (...args: A): ResourceElement<R, A> => ({ hook, args });
|
|
22
7
|
}
|
package/src/core/scheduler.ts
CHANGED
|
@@ -92,7 +92,7 @@ const scheduleMacrotask = (() => {
|
|
|
92
92
|
return () => setTimeout(flushScheduled, 0);
|
|
93
93
|
})();
|
|
94
94
|
|
|
95
|
-
export const
|
|
95
|
+
export const flushTapSync = <T>(callback: () => T): T => {
|
|
96
96
|
const prev = flushState;
|
|
97
97
|
flushState = {
|
|
98
98
|
schedulers: new Set([]),
|
package/src/core/types.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
readonly type: Resource<R, P>;
|
|
5
|
-
readonly props: P;
|
|
1
|
+
export type ResourceElement<R, A extends readonly unknown[] = any[]> = {
|
|
2
|
+
readonly hook: (...args: A) => R;
|
|
3
|
+
readonly args: Readonly<A>;
|
|
6
4
|
readonly key?: string | number;
|
|
7
5
|
};
|
|
8
6
|
|
|
9
|
-
export type Resource<R,
|
|
10
|
-
|
|
7
|
+
export type Resource<R, A extends readonly unknown[] = any[]> = (
|
|
8
|
+
...args: A
|
|
9
|
+
) => ResourceElement<R, A>;
|
|
10
|
+
export type ContravariantResource<R, A extends readonly unknown[] = any[]> = (
|
|
11
|
+
...args: A
|
|
12
|
+
) => ResourceElement<R>;
|
|
11
13
|
|
|
12
14
|
export type ExtractResourceReturnType<T> =
|
|
13
15
|
T extends ResourceElement<infer R, any>
|
|
@@ -16,10 +18,13 @@ export type ExtractResourceReturnType<T> =
|
|
|
16
18
|
? R
|
|
17
19
|
: never;
|
|
18
20
|
|
|
19
|
-
export interface
|
|
21
|
+
export interface ChangelogRecord {
|
|
22
|
+
readonly fiber: ResourceFiber<any, any>;
|
|
23
|
+
readonly cell: Cell & { type: "reducer" };
|
|
20
24
|
readonly action: any;
|
|
21
25
|
hasEagerState: boolean;
|
|
22
26
|
eagerState: any;
|
|
27
|
+
queued: boolean;
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
export type Cell =
|
|
@@ -27,48 +32,49 @@ export type Cell =
|
|
|
27
32
|
readonly type: "reducer";
|
|
28
33
|
readonly dispatch: (action: any) => void;
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
queue: ChangelogRecord[] | null;
|
|
36
|
+
renderQueue: any[] | null;
|
|
37
|
+
|
|
32
38
|
workInProgress: any;
|
|
33
39
|
current: any;
|
|
34
40
|
reducer: (state: any, action: any) => any;
|
|
35
41
|
}
|
|
36
42
|
| {
|
|
37
43
|
readonly type: "effect";
|
|
38
|
-
cleanup:
|
|
44
|
+
cleanup: (() => void) | undefined;
|
|
39
45
|
deps: readonly unknown[] | null | undefined;
|
|
40
46
|
};
|
|
41
47
|
|
|
42
48
|
export interface EffectTask {
|
|
43
|
-
readonly
|
|
44
|
-
readonly
|
|
45
|
-
readonly cell: Cell & { type: "effect" };
|
|
49
|
+
readonly cleanup: () => void;
|
|
50
|
+
readonly setup: () => void;
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
export interface RenderResult {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
readonly effectTasks: (() => void)[];
|
|
54
|
+
value: any;
|
|
55
|
+
effectTasks: EffectTask[];
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
export interface ResourceFiberRoot {
|
|
55
59
|
version: number;
|
|
56
60
|
committedVersion: number;
|
|
57
|
-
readonly changelog:
|
|
61
|
+
readonly changelog: ChangelogRecord[];
|
|
58
62
|
|
|
59
63
|
readonly dispatchUpdate: (callback: () => boolean) => void;
|
|
60
|
-
readonly dirtyCells:
|
|
64
|
+
readonly dirtyCells: Set<Cell & { type: "reducer" }>;
|
|
61
65
|
}
|
|
62
66
|
|
|
63
|
-
export interface ResourceFiber<R,
|
|
67
|
+
export interface ResourceFiber<R, A extends readonly unknown[] = any[]> {
|
|
64
68
|
readonly root: ResourceFiberRoot;
|
|
65
|
-
readonly
|
|
69
|
+
readonly hook: (...args: A) => R;
|
|
66
70
|
readonly markDirty: (() => void) | undefined;
|
|
67
71
|
readonly devStrictMode: "root" | "child" | null;
|
|
68
72
|
|
|
69
73
|
cells: Cell[];
|
|
70
74
|
currentIndex: number;
|
|
71
75
|
|
|
76
|
+
renderPendingCells: Set<Cell & { type: "reducer" }> | null;
|
|
77
|
+
|
|
72
78
|
renderContext: RenderResult | undefined; // set during render
|
|
73
79
|
|
|
74
80
|
isMounted: boolean;
|
package/src/hooks/useResource.ts
CHANGED
|
@@ -1,49 +1,40 @@
|
|
|
1
1
|
import type { ExtractResourceReturnType, ResourceElement } from "../core/types";
|
|
2
|
-
import { useEffect } from "./useEffect";
|
|
3
2
|
import {
|
|
4
|
-
createResourceFiber,
|
|
5
3
|
unmountResourceFiber,
|
|
6
4
|
renderResourceFiber,
|
|
7
5
|
commitResourceFiber,
|
|
8
6
|
} from "../core/ResourceFiber";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
7
|
+
import { useResourceFiberHost } from "./utils/useResourceFiberHostUtils";
|
|
8
|
+
import { useEffect, useMemo } from "react";
|
|
9
|
+
import { useRenderMemo } from "./utils/useRenderMemo";
|
|
12
10
|
|
|
13
|
-
export function useResource<E extends ResourceElement<any, any>>(
|
|
11
|
+
export function useResource<E extends ResourceElement<any, any[]>>(
|
|
14
12
|
element: E,
|
|
15
13
|
): ExtractResourceReturnType<E>;
|
|
16
|
-
export function useResource<E extends ResourceElement<any, any>>(
|
|
14
|
+
export function useResource<E extends ResourceElement<any, any[]>>(
|
|
17
15
|
element: E,
|
|
18
|
-
|
|
16
|
+
argsDeps: readonly unknown[],
|
|
19
17
|
): ExtractResourceReturnType<E>;
|
|
20
|
-
export function useResource<E extends ResourceElement<any, any>>(
|
|
18
|
+
export function useResource<E extends ResourceElement<any, any[]>>(
|
|
21
19
|
element: E,
|
|
22
|
-
|
|
20
|
+
argsDeps?: readonly unknown[],
|
|
23
21
|
): ExtractResourceReturnType<E> {
|
|
24
|
-
const
|
|
25
|
-
const versionRef = useRef(0);
|
|
22
|
+
const { version, createFiber } = useResourceFiberHost();
|
|
26
23
|
const fiber = useMemo(() => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
versionRef.current++;
|
|
30
|
-
parentFiber.markDirty?.();
|
|
31
|
-
});
|
|
32
|
-
}, [element.type, element.key, parentFiber]);
|
|
24
|
+
return createFiber(element.hook, element.key);
|
|
25
|
+
}, [element.hook, element.key, createFiber]);
|
|
33
26
|
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
)
|
|
41
|
-
: renderResourceFiber(fiber, element.props);
|
|
27
|
+
const identity = argsDeps ?? [element.args];
|
|
28
|
+
|
|
29
|
+
const result = useRenderMemo(
|
|
30
|
+
() => renderResourceFiber(fiber, element.args),
|
|
31
|
+
[fiber, version, ...identity],
|
|
32
|
+
);
|
|
42
33
|
|
|
43
34
|
useEffect(() => () => unmountResourceFiber(fiber), [fiber]);
|
|
44
35
|
useEffect(() => {
|
|
45
36
|
commitResourceFiber(fiber, result);
|
|
46
37
|
}, [fiber, result]);
|
|
47
38
|
|
|
48
|
-
return result.
|
|
39
|
+
return result.value;
|
|
49
40
|
}
|
|
@@ -4,41 +4,24 @@ import type {
|
|
|
4
4
|
ResourceElement,
|
|
5
5
|
ResourceFiber,
|
|
6
6
|
} from "../core/types";
|
|
7
|
-
import { useEffect } from "./useEffect";
|
|
8
|
-
import { useMemo } from "./useMemo";
|
|
9
|
-
import { useCallback } from "./useCallback";
|
|
10
7
|
import {
|
|
11
|
-
createResourceFiber,
|
|
12
8
|
unmountResourceFiber,
|
|
13
9
|
renderResourceFiber,
|
|
14
10
|
commitResourceFiber,
|
|
15
11
|
} from "../core/ResourceFiber";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
12
|
+
import { useResourceFiberHost } from "./utils/useResourceFiberHostUtils";
|
|
13
|
+
import { useCallback, useEffect, useMemo } from "react";
|
|
14
|
+
import { useRenderMemo } from "./utils/useRenderMemo";
|
|
18
15
|
|
|
19
16
|
type FiberState = {
|
|
20
|
-
fiber: ResourceFiber<unknown
|
|
21
|
-
next:
|
|
22
|
-
| RenderResult
|
|
23
|
-
| [ResourceFiber<unknown, unknown>, RenderResult]
|
|
24
|
-
| "delete";
|
|
17
|
+
fiber: ResourceFiber<unknown>;
|
|
18
|
+
next: RenderResult | [ResourceFiber<unknown>, RenderResult] | "delete";
|
|
25
19
|
};
|
|
26
20
|
|
|
27
|
-
export function useResources<E extends ResourceElement<any, any>>(
|
|
21
|
+
export function useResources<E extends ResourceElement<any, any[]>>(
|
|
28
22
|
getElements: () => readonly E[],
|
|
29
23
|
getElementsDeps?: readonly unknown[],
|
|
30
24
|
): ExtractResourceReturnType<E>[] {
|
|
31
|
-
const versionRef = useRef(0);
|
|
32
|
-
const version = versionRef.current;
|
|
33
|
-
|
|
34
|
-
const parentFiber = useMemo(() => getCurrentResourceFiber(), []);
|
|
35
|
-
const markDirty = useMemo(
|
|
36
|
-
() => () => {
|
|
37
|
-
versionRef.current++;
|
|
38
|
-
parentFiber.markDirty?.();
|
|
39
|
-
},
|
|
40
|
-
[parentFiber],
|
|
41
|
-
);
|
|
42
25
|
const fibers = useMemo(() => new Map<string | number, FiberState>(), []);
|
|
43
26
|
|
|
44
27
|
const getElementsMemo = getElementsDeps
|
|
@@ -48,7 +31,8 @@ export function useResources<E extends ResourceElement<any, any>>(
|
|
|
48
31
|
|
|
49
32
|
// Process each element
|
|
50
33
|
|
|
51
|
-
const
|
|
34
|
+
const { version, createFiber } = useResourceFiberHost();
|
|
35
|
+
const res = useRenderMemo(() => {
|
|
52
36
|
void version;
|
|
53
37
|
|
|
54
38
|
const elementsArray = getElementsMemo();
|
|
@@ -73,31 +57,23 @@ export function useResources<E extends ResourceElement<any, any>>(
|
|
|
73
57
|
|
|
74
58
|
let state = fibers.get(elementKey);
|
|
75
59
|
if (!state) {
|
|
76
|
-
const fiber =
|
|
77
|
-
|
|
78
|
-
parentFiber.root,
|
|
79
|
-
markDirty,
|
|
80
|
-
);
|
|
81
|
-
const result = renderResourceFiber(fiber, element.props);
|
|
60
|
+
const fiber = createFiber(element.hook, element.key);
|
|
61
|
+
const result = renderResourceFiber(fiber, element.args);
|
|
82
62
|
state = {
|
|
83
63
|
fiber,
|
|
84
64
|
next: result,
|
|
85
65
|
};
|
|
86
66
|
newCount++;
|
|
87
67
|
fibers.set(elementKey, state);
|
|
88
|
-
results.push(result.
|
|
89
|
-
} else if (state.fiber.
|
|
90
|
-
const fiber =
|
|
91
|
-
|
|
92
|
-
parentFiber.root,
|
|
93
|
-
markDirty,
|
|
94
|
-
);
|
|
95
|
-
const result = renderResourceFiber(fiber, element.props);
|
|
68
|
+
results.push(result.value);
|
|
69
|
+
} else if (state.fiber.hook !== element.hook) {
|
|
70
|
+
const fiber = createFiber(element.hook, element.key);
|
|
71
|
+
const result = renderResourceFiber(fiber, element.args);
|
|
96
72
|
state.next = [fiber, result];
|
|
97
|
-
results.push(result.
|
|
73
|
+
results.push(result.value);
|
|
98
74
|
} else {
|
|
99
|
-
state.next = renderResourceFiber(state.fiber, element.
|
|
100
|
-
results.push(state.next.
|
|
75
|
+
state.next = renderResourceFiber(state.fiber, element.args);
|
|
76
|
+
results.push(state.next.value);
|
|
101
77
|
}
|
|
102
78
|
}
|
|
103
79
|
|
|
@@ -111,7 +87,7 @@ export function useResources<E extends ResourceElement<any, any>>(
|
|
|
111
87
|
}
|
|
112
88
|
|
|
113
89
|
return results;
|
|
114
|
-
}, [getElementsMemo,
|
|
90
|
+
}, [getElementsMemo, fibers, createFiber, version]);
|
|
115
91
|
|
|
116
92
|
// Cleanup on unmount
|
|
117
93
|
useEffect(() => {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
unmountResourceFiber,
|
|
3
|
+
renderResourceFiber,
|
|
4
|
+
commitResourceFiber,
|
|
5
|
+
} from "../core/ResourceFiber";
|
|
6
|
+
import { useResourceFiberHost } from "./utils/useResourceFiberHostUtils";
|
|
7
|
+
import { useEffect, useMemo } from "react";
|
|
8
|
+
|
|
9
|
+
export namespace useTapHost {
|
|
10
|
+
export interface Result<R> {
|
|
11
|
+
/**
|
|
12
|
+
* The current render output of the host resource.
|
|
13
|
+
*/
|
|
14
|
+
value: R;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Commits the host's pending render result. Pass to a deps-less
|
|
18
|
+
* useEffect in a descendant component to land the commit ahead of
|
|
19
|
+
* the descendants' own effects; the first instance to run wins. A
|
|
20
|
+
* plain callback (not a hook) so React Compiler can compile the
|
|
21
|
+
* consumer; its identity changes on every host render.
|
|
22
|
+
*/
|
|
23
|
+
effects: () => void;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const useHostRender = <R>(render: () => R): R => render();
|
|
28
|
+
|
|
29
|
+
export const useTapHost = <R>(callback: () => R): useTapHost.Result<R> => {
|
|
30
|
+
const { createFiber } = useResourceFiberHost();
|
|
31
|
+
const fiber = useMemo(
|
|
32
|
+
() => createFiber(useHostRender<R>, undefined),
|
|
33
|
+
[createFiber],
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const render = renderResourceFiber(fiber, [callback]);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
return () => {
|
|
40
|
+
unmountResourceFiber(fiber);
|
|
41
|
+
};
|
|
42
|
+
}, [fiber]);
|
|
43
|
+
|
|
44
|
+
let renderCommitted = false;
|
|
45
|
+
const effects = () => {
|
|
46
|
+
// !isMounted: StrictMode/Activity replays effects without a re-render
|
|
47
|
+
// after unmounting the fiber; the replay must recommit it.
|
|
48
|
+
if (renderCommitted && fiber.isMounted) return;
|
|
49
|
+
renderCommitted = true;
|
|
50
|
+
|
|
51
|
+
commitResourceFiber(fiber, render);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
useEffect(effects);
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
value: render.value as R,
|
|
58
|
+
effects,
|
|
59
|
+
};
|
|
60
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import {
|
|
2
|
+
commitResourceFiber,
|
|
3
|
+
createResourceFiber,
|
|
4
|
+
renderResourceFiber,
|
|
5
|
+
unmountResourceFiber,
|
|
6
|
+
} from "../core/ResourceFiber";
|
|
7
|
+
import { UpdateScheduler } from "../core/scheduler";
|
|
8
|
+
import { isDevelopment } from "../core/helpers/env";
|
|
9
|
+
import {
|
|
10
|
+
commitRoot,
|
|
11
|
+
createResourceFiberRoot,
|
|
12
|
+
setRootVersion,
|
|
13
|
+
} from "../core/helpers/root";
|
|
14
|
+
import { useEffect, useEffectEvent, useMemo, useRef } from "react";
|
|
15
|
+
import { useDevStrictMode } from "./utils/useDevStrictMode";
|
|
16
|
+
|
|
17
|
+
export namespace useTapRoot {
|
|
18
|
+
export type Unsubscribe = () => void;
|
|
19
|
+
|
|
20
|
+
export interface Root<R> {
|
|
21
|
+
/**
|
|
22
|
+
* Get the current value of the root.
|
|
23
|
+
*/
|
|
24
|
+
getValue(): R;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Subscribe to the root.
|
|
28
|
+
*/
|
|
29
|
+
subscribe(listener: () => void): Unsubscribe;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const useHostRoot = <R>(render: () => R): R => render();
|
|
34
|
+
|
|
35
|
+
export const useTapRoot = <R>(render: () => R): useTapRoot.Root<R> => {
|
|
36
|
+
const scheduler = useMemo(
|
|
37
|
+
() => new UpdateScheduler(() => handleUpdate()),
|
|
38
|
+
[],
|
|
39
|
+
);
|
|
40
|
+
const queue = useMemo(() => [] as (() => boolean)[], []);
|
|
41
|
+
|
|
42
|
+
const getDevStrictMode = useDevStrictMode();
|
|
43
|
+
const fiber = useMemo(() => {
|
|
44
|
+
const root = createResourceFiberRoot((callback) => {
|
|
45
|
+
if (!scheduler.isDirty && !callback()) return;
|
|
46
|
+
|
|
47
|
+
setRootVersion(root, root.committedVersion + root.changelog.length);
|
|
48
|
+
queue.push(callback);
|
|
49
|
+
scheduler.markDirty();
|
|
50
|
+
});
|
|
51
|
+
return createResourceFiber(
|
|
52
|
+
useHostRoot<R>,
|
|
53
|
+
root,
|
|
54
|
+
undefined,
|
|
55
|
+
getDevStrictMode(),
|
|
56
|
+
);
|
|
57
|
+
}, [queue, scheduler, getDevStrictMode]);
|
|
58
|
+
|
|
59
|
+
const drainedCount = fiber.root.version - fiber.root.committedVersion;
|
|
60
|
+
const render2 = renderResourceFiber(fiber, [render]);
|
|
61
|
+
|
|
62
|
+
const isMountedRef = useRef(false);
|
|
63
|
+
const committedArgsRef = useRef([render] as const);
|
|
64
|
+
const valueRef = useRef<R>(render2.value);
|
|
65
|
+
const subscribers = useMemo(() => new Set<() => void>(), []);
|
|
66
|
+
|
|
67
|
+
const publish = (output: R) => {
|
|
68
|
+
if (scheduler.isDirty || valueRef.current === output) return;
|
|
69
|
+
valueRef.current = output;
|
|
70
|
+
subscribers.forEach((listener) => listener());
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const handleUpdate = useEffectEvent(() => {
|
|
74
|
+
setRootVersion(fiber.root, fiber.root.committedVersion);
|
|
75
|
+
|
|
76
|
+
queue.forEach((callback) => {
|
|
77
|
+
if (isDevelopment && fiber.devStrictMode) {
|
|
78
|
+
callback();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
callback();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
setRootVersion(
|
|
85
|
+
fiber.root,
|
|
86
|
+
fiber.root.committedVersion + fiber.root.changelog.length,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
if (isDevelopment && fiber.devStrictMode) {
|
|
90
|
+
void renderResourceFiber(fiber, committedArgsRef.current);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const render = renderResourceFiber(fiber, committedArgsRef.current);
|
|
94
|
+
|
|
95
|
+
if (scheduler.isDirty)
|
|
96
|
+
throw new Error("Scheduler is dirty, this should never happen");
|
|
97
|
+
|
|
98
|
+
commitRoot(fiber.root);
|
|
99
|
+
queue.length = 0;
|
|
100
|
+
|
|
101
|
+
if (isMountedRef.current) {
|
|
102
|
+
commitResourceFiber(fiber, render);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
publish(render.value);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
isMountedRef.current = true;
|
|
110
|
+
return () => {
|
|
111
|
+
isMountedRef.current = false;
|
|
112
|
+
unmountResourceFiber(fiber);
|
|
113
|
+
};
|
|
114
|
+
}, [fiber]);
|
|
115
|
+
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
committedArgsRef.current = [render];
|
|
118
|
+
commitRoot(fiber.root);
|
|
119
|
+
queue.splice(0, drainedCount);
|
|
120
|
+
commitResourceFiber(fiber, render2);
|
|
121
|
+
|
|
122
|
+
publish(render2.value);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return useMemo(
|
|
126
|
+
() => ({
|
|
127
|
+
getValue: () => valueRef.current,
|
|
128
|
+
subscribe: (listener: () => void) => {
|
|
129
|
+
subscribers.add(listener);
|
|
130
|
+
return () => subscribers.delete(listener);
|
|
131
|
+
},
|
|
132
|
+
}),
|
|
133
|
+
[subscribers],
|
|
134
|
+
);
|
|
135
|
+
};
|