@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.
Files changed (215) hide show
  1. package/README.md +9 -6
  2. package/dist/core/ResourceFiber.d.ts +5 -5
  3. package/dist/core/ResourceFiber.d.ts.map +1 -1
  4. package/dist/core/ResourceFiber.js +26 -18
  5. package/dist/core/ResourceFiber.js.map +1 -1
  6. package/dist/core/createTapRoot.d.ts +9 -0
  7. package/dist/core/createTapRoot.d.ts.map +1 -0
  8. package/dist/core/createTapRoot.js +27 -0
  9. package/dist/core/createTapRoot.js.map +1 -0
  10. package/dist/core/helpers/commit.d.ts +1 -1
  11. package/dist/core/helpers/commit.d.ts.map +1 -1
  12. package/dist/core/helpers/commit.js +6 -1
  13. package/dist/core/helpers/commit.js.map +1 -1
  14. package/dist/core/helpers/execution-context.d.ts +4 -5
  15. package/dist/core/helpers/execution-context.d.ts.map +1 -1
  16. package/dist/core/helpers/execution-context.js +1 -7
  17. package/dist/core/helpers/execution-context.js.map +1 -1
  18. package/dist/core/helpers/root.d.ts +3 -2
  19. package/dist/core/helpers/root.d.ts.map +1 -1
  20. package/dist/core/helpers/root.js +19 -15
  21. package/dist/core/helpers/root.js.map +1 -1
  22. package/dist/core/react-dispatcher.d.ts.map +1 -1
  23. package/dist/core/react-dispatcher.js +17 -16
  24. package/dist/core/react-dispatcher.js.map +1 -1
  25. package/dist/core/resource.d.ts +2 -4
  26. package/dist/core/resource.d.ts.map +1 -1
  27. package/dist/core/resource.js +5 -10
  28. package/dist/core/resource.js.map +1 -1
  29. package/dist/core/scheduler.d.ts +2 -2
  30. package/dist/core/scheduler.d.ts.map +1 -1
  31. package/dist/core/scheduler.js +2 -2
  32. package/dist/core/scheduler.js.map +1 -1
  33. package/dist/core/types.d.ts +27 -25
  34. package/dist/core/types.d.ts.map +1 -1
  35. package/dist/hooks/useResource.d.ts +2 -2
  36. package/dist/hooks/useResource.d.ts.map +1 -1
  37. package/dist/hooks/useResource.js +14 -20
  38. package/dist/hooks/useResource.js.map +1 -1
  39. package/dist/hooks/useResources.d.ts +1 -1
  40. package/dist/hooks/useResources.d.ts.map +1 -1
  41. package/dist/hooks/useResources.js +18 -27
  42. package/dist/hooks/useResources.js.map +1 -1
  43. package/dist/hooks/useTapHost.d.ts +21 -0
  44. package/dist/hooks/useTapHost.d.ts.map +1 -0
  45. package/dist/hooks/useTapHost.js +30 -0
  46. package/dist/hooks/useTapHost.js.map +1 -0
  47. package/dist/hooks/useTapRoot.d.ts +18 -0
  48. package/dist/hooks/useTapRoot.d.ts.map +1 -0
  49. package/dist/hooks/useTapRoot.js +77 -0
  50. package/dist/hooks/useTapRoot.js.map +1 -0
  51. package/dist/hooks/utils/depsShallowEqual.d.ts.map +1 -1
  52. package/dist/hooks/utils/depsShallowEqual.js +5 -2
  53. package/dist/hooks/utils/depsShallowEqual.js.map +1 -1
  54. package/dist/hooks/utils/useCell.d.ts +2 -2
  55. package/dist/hooks/utils/useCell.d.ts.map +1 -1
  56. package/dist/hooks/utils/useCell.js.map +1 -1
  57. package/dist/hooks/utils/useDevStrictMode.d.ts +5 -0
  58. package/dist/hooks/utils/useDevStrictMode.d.ts.map +1 -0
  59. package/dist/hooks/utils/useDevStrictMode.js +25 -0
  60. package/dist/hooks/utils/useDevStrictMode.js.map +1 -0
  61. package/dist/hooks/utils/useRenderMemo.d.ts +5 -0
  62. package/dist/hooks/utils/useRenderMemo.d.ts.map +1 -0
  63. package/dist/hooks/utils/useRenderMemo.js +25 -0
  64. package/dist/hooks/utils/useRenderMemo.js.map +1 -0
  65. package/dist/hooks/utils/useResourceFiberHostUtils.d.ts +10 -0
  66. package/dist/hooks/utils/useResourceFiberHostUtils.d.ts.map +1 -0
  67. package/dist/hooks/utils/useResourceFiberHostUtils.js +46 -0
  68. package/dist/hooks/utils/useResourceFiberHostUtils.js.map +1 -0
  69. package/dist/index.d.ts +7 -4
  70. package/dist/index.js +7 -4
  71. package/dist/{hooks → react-hooks}/index.d.ts +6 -6
  72. package/dist/{hooks → react-hooks}/index.js +5 -5
  73. package/dist/{hooks → react-hooks}/use.d.ts +1 -1
  74. package/dist/{hooks → react-hooks}/use.d.ts.map +1 -1
  75. package/dist/{hooks → react-hooks}/use.js +1 -1
  76. package/dist/react-hooks/use.js.map +1 -0
  77. package/dist/{hooks → react-hooks}/useCallback.d.ts +1 -1
  78. package/dist/react-hooks/useCallback.d.ts.map +1 -0
  79. package/dist/{hooks → react-hooks}/useCallback.js +1 -1
  80. package/dist/react-hooks/useCallback.js.map +1 -0
  81. package/dist/{hooks → react-hooks}/useEffect.d.ts +1 -1
  82. package/dist/react-hooks/useEffect.d.ts.map +1 -0
  83. package/dist/react-hooks/useEffect.js +35 -0
  84. package/dist/react-hooks/useEffect.js.map +1 -0
  85. package/dist/{hooks → react-hooks}/useEffectEvent.d.ts +1 -1
  86. package/dist/react-hooks/useEffectEvent.d.ts.map +1 -0
  87. package/dist/{hooks → react-hooks}/useEffectEvent.js +2 -2
  88. package/dist/react-hooks/useEffectEvent.js.map +1 -0
  89. package/dist/{hooks → react-hooks}/useMemo.d.ts +1 -1
  90. package/dist/react-hooks/useMemo.d.ts.map +1 -0
  91. package/dist/{hooks → react-hooks}/useMemo.js +3 -3
  92. package/dist/react-hooks/useMemo.js.map +1 -0
  93. package/dist/{hooks → react-hooks}/useMemoCache.d.ts +1 -1
  94. package/dist/react-hooks/useMemoCache.d.ts.map +1 -0
  95. package/dist/{hooks → react-hooks}/useMemoCache.js +1 -1
  96. package/dist/react-hooks/useMemoCache.js.map +1 -0
  97. package/dist/react-hooks/useReducer.d.ts +9 -0
  98. package/dist/react-hooks/useReducer.d.ts.map +1 -0
  99. package/dist/react-hooks/useReducer.js +120 -0
  100. package/dist/react-hooks/useReducer.js.map +1 -0
  101. package/dist/{hooks → react-hooks}/useRef.d.ts +1 -1
  102. package/dist/react-hooks/useRef.d.ts.map +1 -0
  103. package/dist/{hooks → react-hooks}/useRef.js +1 -1
  104. package/dist/react-hooks/useRef.js.map +1 -0
  105. package/dist/{hooks → react-hooks}/useState.d.ts +1 -1
  106. package/dist/react-hooks/useState.d.ts.map +1 -0
  107. package/dist/{hooks → react-hooks}/useState.js +3 -3
  108. package/dist/react-hooks/useState.js.map +1 -0
  109. package/dist/react-shim/index.d.ts +8 -10
  110. package/dist/react-shim/index.d.ts.map +1 -1
  111. package/dist/react-shim/index.js +19 -19
  112. package/dist/react-shim/index.js.map +1 -1
  113. package/package.json +1 -1
  114. package/src/__tests__/basic/resourceHandle.test.ts +32 -22
  115. package/src/__tests__/basic/tapEffect.basic.test.ts +8 -8
  116. package/src/__tests__/basic/tapReducer.basic.test.ts +16 -14
  117. package/src/__tests__/basic/tapResources.basic.test.ts +19 -16
  118. package/src/__tests__/basic/tapState.basic.test.ts +11 -11
  119. package/src/__tests__/bench/hosts.bench.tsx +124 -0
  120. package/src/__tests__/bench/tree.bench.tsx +166 -0
  121. package/src/__tests__/errors/errors.effect-errors.test.ts +12 -13
  122. package/src/__tests__/errors/errors.render-errors.test.ts +65 -22
  123. package/src/__tests__/lifecycle/lifecycle.dependencies.test.ts +19 -19
  124. package/src/__tests__/lifecycle/lifecycle.mount-unmount.test.ts +14 -14
  125. package/src/__tests__/parity/describeParity.tsx +217 -0
  126. package/src/__tests__/parity/parity.adversarial.test.tsx +375 -0
  127. package/src/__tests__/parity/parity.basics.test.tsx +281 -0
  128. package/src/__tests__/parity/parity.divergences.test.tsx +208 -0
  129. package/src/__tests__/parity/parity.smoke.test.tsx +43 -0
  130. package/src/__tests__/react/concurrent-mode.test.tsx +10 -6
  131. package/src/__tests__/react/concurrent-pending-updates.test.tsx +351 -0
  132. package/src/__tests__/react/concurrent-render-phase.test.tsx +350 -0
  133. package/src/__tests__/react/react-shim.test.tsx +1 -1
  134. package/src/__tests__/react/useResource.test.tsx +41 -26
  135. package/src/__tests__/react/useTapHost.test.tsx +233 -0
  136. package/src/__tests__/react-dispatcher.test.ts +4 -4
  137. package/src/__tests__/rules/rules.hook-count.test.ts +21 -21
  138. package/src/__tests__/rules/rules.hook-order.test.ts +17 -17
  139. package/src/__tests__/strictmode/strictmode-parity.test.tsx +420 -0
  140. package/src/__tests__/strictmode/strictmode.test.ts +39 -209
  141. package/src/__tests__/test-utils.ts +33 -23
  142. package/src/core/ResourceFiber.ts +43 -35
  143. package/src/core/createTapRoot.ts +45 -0
  144. package/src/core/helpers/commit.ts +12 -2
  145. package/src/core/helpers/execution-context.ts +4 -13
  146. package/src/core/helpers/root.ts +24 -12
  147. package/src/core/react-dispatcher.ts +14 -13
  148. package/src/core/resource.ts +5 -20
  149. package/src/core/scheduler.ts +1 -1
  150. package/src/core/types.ts +27 -21
  151. package/src/hooks/useResource.ts +18 -27
  152. package/src/hooks/useResources.ts +18 -42
  153. package/src/hooks/useTapHost.ts +60 -0
  154. package/src/hooks/useTapRoot.ts +135 -0
  155. package/src/hooks/utils/depsShallowEqual.ts +12 -2
  156. package/src/hooks/utils/useCell.ts +2 -2
  157. package/src/hooks/utils/useDevStrictMode.ts +34 -0
  158. package/src/hooks/utils/useRenderMemo.ts +27 -0
  159. package/src/hooks/utils/useResourceFiberHostUtils.ts +61 -0
  160. package/src/index.ts +6 -3
  161. package/src/{hooks → react-hooks}/index.ts +4 -4
  162. package/src/react-hooks/useEffect.ts +58 -0
  163. package/src/{hooks → react-hooks}/useMemo.ts +1 -1
  164. package/src/react-hooks/useReducer.ts +254 -0
  165. package/src/{hooks → react-hooks}/useState.ts +2 -2
  166. package/src/react-shim/index.ts +24 -13
  167. package/dist/core/createResourceRoot.d.ts +0 -11
  168. package/dist/core/createResourceRoot.d.ts.map +0 -1
  169. package/dist/core/createResourceRoot.js +0 -31
  170. package/dist/core/createResourceRoot.js.map +0 -1
  171. package/dist/core/helpers/callResourceFn.d.ts +0 -1
  172. package/dist/core/helpers/callResourceFn.js +0 -19
  173. package/dist/core/helpers/callResourceFn.js.map +0 -1
  174. package/dist/hooks/use.js.map +0 -1
  175. package/dist/hooks/useCallback.d.ts.map +0 -1
  176. package/dist/hooks/useCallback.js.map +0 -1
  177. package/dist/hooks/useEffect.d.ts.map +0 -1
  178. package/dist/hooks/useEffect.js +0 -40
  179. package/dist/hooks/useEffect.js.map +0 -1
  180. package/dist/hooks/useEffectEvent.d.ts.map +0 -1
  181. package/dist/hooks/useEffectEvent.js.map +0 -1
  182. package/dist/hooks/useMemo.d.ts.map +0 -1
  183. package/dist/hooks/useMemo.js.map +0 -1
  184. package/dist/hooks/useMemoCache.d.ts.map +0 -1
  185. package/dist/hooks/useMemoCache.js.map +0 -1
  186. package/dist/hooks/useReducer.d.ts +0 -21
  187. package/dist/hooks/useReducer.d.ts.map +0 -1
  188. package/dist/hooks/useReducer.js +0 -81
  189. package/dist/hooks/useReducer.js.map +0 -1
  190. package/dist/hooks/useRef.d.ts.map +0 -1
  191. package/dist/hooks/useRef.js.map +0 -1
  192. package/dist/hooks/useResourceRoot.d.ts +0 -20
  193. package/dist/hooks/useResourceRoot.d.ts.map +0 -1
  194. package/dist/hooks/useResourceRoot.js +0 -77
  195. package/dist/hooks/useResourceRoot.js.map +0 -1
  196. package/dist/hooks/useState.d.ts.map +0 -1
  197. package/dist/hooks/useState.js.map +0 -1
  198. package/dist/react/hooks.d.ts +0 -25
  199. package/dist/react/hooks.d.ts.map +0 -1
  200. package/dist/react/hooks.js +0 -69
  201. package/dist/react/hooks.js.map +0 -1
  202. package/src/__tests__/strictmode/react-strictmode-behavior.test.tsx +0 -920
  203. package/src/__tests__/strictmode/react-strictmode-rerender-sources.test.tsx +0 -488
  204. package/src/__tests__/strictmode/tap-strictmode-rerender-sources.test.ts +0 -687
  205. package/src/core/createResourceRoot.ts +0 -53
  206. package/src/core/helpers/callResourceFn.ts +0 -21
  207. package/src/hooks/useEffect.ts +0 -72
  208. package/src/hooks/useReducer.ts +0 -160
  209. package/src/hooks/useResourceRoot.ts +0 -130
  210. package/src/react/hooks.ts +0 -112
  211. /package/src/{hooks → react-hooks}/use.ts +0 -0
  212. /package/src/{hooks → react-hooks}/useCallback.ts +0 -0
  213. /package/src/{hooks → react-hooks}/useEffectEvent.ts +0 -0
  214. /package/src/{hooks → react-hooks}/useMemoCache.ts +0 -0
  215. /package/src/{hooks → react-hooks}/useRef.ts +0 -0
@@ -1,9 +1,19 @@
1
+ import { isDevelopment } from "../../core/helpers/env";
2
+
1
3
  export const depsShallowEqual = (
2
4
  a: readonly unknown[],
3
5
  b: readonly unknown[],
4
6
  ) => {
5
- if (a.length !== b.length) return false;
6
- for (let i = 0; i < a.length; i++) {
7
+ if (isDevelopment && a.length !== b.length) {
8
+ console.error(
9
+ "The final argument passed to a hook changed size between renders. " +
10
+ "The order and size of this array must remain constant.\n\n" +
11
+ `Previous: [${a.join(", ")}]\n` +
12
+ `Incoming: [${b.join(", ")}]`,
13
+ );
14
+ }
15
+
16
+ for (let i = 0; i < a.length && i < b.length; i++) {
7
17
  if (!Object.is(a[i], b[i])) return false;
8
18
  }
9
19
  return true;
@@ -1,5 +1,5 @@
1
1
  import { getCurrentResourceFiber } from "../../core/helpers/execution-context";
2
- import type { Cell } from "../../core/types";
2
+ import type { Cell, EffectTask } from "../../core/types";
3
3
 
4
4
  export const useCell = <T extends Cell["type"]>(
5
5
  type: T,
@@ -29,7 +29,7 @@ export const useCell = <T extends Cell["type"]>(
29
29
  return cell as Cell & { type: T };
30
30
  };
31
31
 
32
- export const registerRenderMountTask = (task: () => void) => {
32
+ export const registerRenderMountTask = (task: EffectTask) => {
33
33
  const fiber = getCurrentResourceFiber();
34
34
  fiber.renderContext!.effectTasks.push(task);
35
35
  };
@@ -0,0 +1,34 @@
1
+ import { useRef, useState } from "react";
2
+ import { isDevelopment } from "../../core/helpers/env";
3
+ import {
4
+ getCurrentResourceFiber,
5
+ peekResourceFiber,
6
+ } from "../../core/helpers/execution-context";
7
+
8
+ const getTapDevMode = () => {
9
+ const currentResourceFiber = getCurrentResourceFiber();
10
+ if (currentResourceFiber.devStrictMode)
11
+ return currentResourceFiber.isFirstRender
12
+ ? ("child" as const)
13
+ : ("root" as const);
14
+ return null;
15
+ };
16
+
17
+ const child = () => "child" as const;
18
+ const notDevMode = () => null;
19
+
20
+ const useDevStrictModeReact = () => {
21
+ if (!isDevelopment) return notDevMode;
22
+
23
+ // oxlint-disable-next-line react/rules-of-hooks -- isDevelopment is a build-time constant, so this branch is stable per build
24
+ const count = useRef(0);
25
+ // oxlint-disable-next-line react/rules-of-hooks -- isDevelopment is a build-time constant, so this branch is stable per build
26
+ useState(() => count.current++);
27
+ if (count.current !== 2) return notDevMode;
28
+ return child;
29
+ };
30
+
31
+ export const useDevStrictMode = () => {
32
+ // oxlint-disable-next-line react-hooks/rules-of-hooks
33
+ return peekResourceFiber() ? getTapDevMode : useDevStrictModeReact();
34
+ };
@@ -0,0 +1,27 @@
1
+ import { useState, useEffect } from "react";
2
+ import { depsShallowEqual } from "./depsShallowEqual";
3
+
4
+ export const useRenderMemo = <T>(callback: () => T, deps: unknown[]) => {
5
+ const [state] = useState(() => ({
6
+ wipDeps: null as unknown[] | null,
7
+ wip: null as T,
8
+ currentDeps: null as unknown[] | null,
9
+ current: null as T,
10
+ }));
11
+
12
+ state.wipDeps = state.currentDeps;
13
+ state.wip = state.current;
14
+
15
+ useEffect(() => {
16
+ state.currentDeps = state.wipDeps;
17
+ state.current = state.wip;
18
+ });
19
+
20
+ if (state.currentDeps && depsShallowEqual(state.currentDeps, deps))
21
+ return state.current;
22
+
23
+ state.wipDeps = deps;
24
+ state.wip = callback();
25
+
26
+ return state.wip;
27
+ };
@@ -0,0 +1,61 @@
1
+ import { useRef, useMemo, useReducer, useCallback } from "react";
2
+ import {
3
+ getCurrentResourceFiber,
4
+ peekResourceFiber,
5
+ } from "../../core/helpers/execution-context";
6
+ import {
7
+ createResourceFiberRoot,
8
+ setRootVersion,
9
+ } from "../../core/helpers/root";
10
+ import { createResourceFiber } from "../../core/ResourceFiber";
11
+ import type { ResourceFiberRoot } from "../../core/types";
12
+ import { useDevStrictMode } from "./useDevStrictMode";
13
+
14
+ const useResourceFiberHostUtilsTap = () => {
15
+ const versionRef = useRef(0);
16
+ const version = versionRef.current;
17
+ const parent = getCurrentResourceFiber();
18
+ const markDirty = useMemo(
19
+ () => () => {
20
+ versionRef.current++;
21
+ parent?.markDirty?.();
22
+ },
23
+ [parent],
24
+ );
25
+
26
+ return { version, markDirty, root: parent.root };
27
+ };
28
+
29
+ const useResourceFiberHostUtilsReact = () => {
30
+ const root = useMemo<ResourceFiberRoot>(() => {
31
+ return createResourceFiberRoot((cb) => dispatch(cb));
32
+ }, []);
33
+
34
+ const [version, dispatch] = useReducer((v: number, cb: () => boolean) => {
35
+ setRootVersion(root!, v);
36
+ return v + (cb() ? 1 : 0);
37
+ }, 0);
38
+ setRootVersion(root, version);
39
+
40
+ return { root, version, markDirty: undefined };
41
+ };
42
+
43
+ export const useResourceFiberHost = () => {
44
+ const getDevMode = useDevStrictMode();
45
+ const { root, version, markDirty } = peekResourceFiber()
46
+ ? // oxlint-disable-next-line react-hooks/rules-of-hooks
47
+ useResourceFiberHostUtilsTap()
48
+ : // oxlint-disable-next-line react-hooks/rules-of-hooks
49
+ useResourceFiberHostUtilsReact();
50
+
51
+ const createFiber = useCallback(
52
+ <R, A extends readonly any[]>(
53
+ hook: (...props: A) => R,
54
+ _key: string | number | undefined,
55
+ ) => createResourceFiber(hook, root, markDirty, getDevMode()),
56
+ // oxlint-disable-next-line react-hooks/exhaustive-deps
57
+ [],
58
+ );
59
+
60
+ return { version, createFiber };
61
+ };
package/src/index.ts CHANGED
@@ -2,14 +2,17 @@ export { resource } from "./core/resource";
2
2
  export { withKey } from "./core/withKey";
3
3
 
4
4
  // imperative
5
- export { createResourceRoot } from "./core/createResourceRoot";
6
- export { flushResourcesSync } from "./core/scheduler";
5
+ export { createTapRoot } from "./core/createTapRoot";
6
+ export { flushTapSync } from "./core/scheduler";
7
7
 
8
8
  // context
9
9
  export { createResourceContext, withContextProvider } from "./core/context";
10
10
 
11
11
  // hooks
12
- export { useResource, useResources, useResourceRoot } from "./react/hooks";
12
+ export { useResource } from "./hooks/useResource";
13
+ export { useResources } from "./hooks/useResources";
14
+ export { useTapRoot } from "./hooks/useTapRoot";
15
+ export { useTapHost } from "./hooks/useTapHost";
13
16
 
14
17
  // types
15
18
  export type {
@@ -1,5 +1,5 @@
1
1
  export { useState } from "./useState";
2
- export { useReducer, useReducerWithDerivedState } from "./useReducer";
2
+ export { useReducer } from "./useReducer";
3
3
  export { useRef } from "./useRef";
4
4
  export { useMemo } from "./useMemo";
5
5
  export { useCallback } from "./useCallback";
@@ -7,6 +7,6 @@ export { useEffect } from "./useEffect";
7
7
  export { useEffectEvent } from "./useEffectEvent";
8
8
  export { use } from "./use";
9
9
  export { useMemoCache } from "./useMemoCache";
10
- export { useResource } from "./useResource";
11
- export { useResources } from "./useResources";
12
- export { useResourceRoot } from "./useResourceRoot";
10
+ export { useResource } from "../hooks/useResource";
11
+ export { useResources } from "../hooks/useResources";
12
+ export { useTapRoot } from "../hooks/useTapRoot";
@@ -0,0 +1,58 @@
1
+ import type { Cell } from "../core/types";
2
+ import { depsShallowEqual } from "../hooks/utils/depsShallowEqual";
3
+ import { useCell, registerRenderMountTask } from "../hooks/utils/useCell";
4
+
5
+ const newEffect = (): Cell & { type: "effect" } => ({
6
+ type: "effect",
7
+ cleanup: undefined,
8
+ deps: null, // null means the effect has never been run
9
+ });
10
+
11
+ export namespace useEffect {
12
+ export type Destructor = () => void;
13
+ export type EffectCallback = () => Destructor | undefined;
14
+ }
15
+
16
+ export function useEffect(effect: useEffect.EffectCallback): void;
17
+ export function useEffect(
18
+ effect: useEffect.EffectCallback,
19
+ deps: readonly unknown[],
20
+ ): void;
21
+ export function useEffect(
22
+ effect: useEffect.EffectCallback,
23
+ deps?: readonly unknown[],
24
+ ): void {
25
+ const cell = useCell("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
+ "useEffect called with and without dependencies across re-renders",
31
+ );
32
+
33
+ registerRenderMountTask({
34
+ cleanup: () => {
35
+ try {
36
+ cell.cleanup?.();
37
+ } finally {
38
+ cell.cleanup = undefined;
39
+ }
40
+ },
41
+ setup: () => {
42
+ try {
43
+ const cleanup = effect();
44
+
45
+ if (cleanup !== undefined && typeof cleanup !== "function") {
46
+ throw new Error(
47
+ "An effect function must either return a cleanup function or nothing. " +
48
+ `Received: ${typeof cleanup}`,
49
+ );
50
+ }
51
+
52
+ cell.cleanup = cleanup;
53
+ } finally {
54
+ cell.deps = deps;
55
+ }
56
+ },
57
+ });
58
+ }
@@ -1,7 +1,7 @@
1
1
  import { isDevelopment } from "../core/helpers/env";
2
2
  import { getCurrentResourceFiber } from "../core/helpers/execution-context";
3
3
  import { useReducerWithDerivedState } from "./useReducer";
4
- import { depsShallowEqual } from "./utils/depsShallowEqual";
4
+ import { depsShallowEqual } from "../hooks/utils/depsShallowEqual";
5
5
 
6
6
  const memoReducer = () => {
7
7
  throw new Error("Memo reducer should not be called");
@@ -0,0 +1,254 @@
1
+ import { isDevelopment } from "../core/helpers/env";
2
+ import {
3
+ getCurrentResourceFiber,
4
+ peekResourceFiber,
5
+ } from "../core/helpers/execution-context";
6
+ import type { Cell, ChangelogRecord, ResourceFiber } from "../core/types";
7
+ import { applyChangelogRecord, markCellDirty } from "../core/helpers/root";
8
+
9
+ type Dispatch<A> = (action: A) => void;
10
+
11
+ const dispatchOnFiber = (
12
+ fiber: ResourceFiber<any, any>,
13
+ callback: () => ChangelogRecord | null,
14
+ ): void => {
15
+ if (fiber.isNeverMounted) {
16
+ throw new Error("Resource updated before mount");
17
+ }
18
+
19
+ fiber.root.dispatchUpdate(() => {
20
+ const record = callback();
21
+ if (record) {
22
+ applyChangelogRecord(record);
23
+ fiber.root.changelog.push(record);
24
+ return true;
25
+ }
26
+ return false;
27
+ });
28
+ };
29
+
30
+ const createReducerCell = (
31
+ fiber: ResourceFiber<any, any>,
32
+ reducer: (state: any, action: any) => any,
33
+ initialArg: any,
34
+ initFn: ((arg: any) => any) | undefined,
35
+ eagerDispatch: boolean,
36
+ ): Cell & { type: "reducer" } => {
37
+ const initialState = initFn ? initFn(initialArg) : initialArg;
38
+
39
+ if (isDevelopment && fiber.devStrictMode && initFn) {
40
+ void initFn(initialArg);
41
+ }
42
+
43
+ const cell: Cell & { type: "reducer" } = {
44
+ type: "reducer",
45
+ queue: null,
46
+ renderQueue: null,
47
+ workInProgress: initialState,
48
+ current: initialState,
49
+ reducer,
50
+ dispatch: (action) => {
51
+ const currentFiber = peekResourceFiber();
52
+ if (currentFiber !== null) {
53
+ if (currentFiber !== fiber)
54
+ throw new Error(
55
+ "Cannot update a resource while rendering a different resource.",
56
+ );
57
+
58
+ (fiber.renderPendingCells ??= new Set()).add(cell);
59
+ (cell.renderQueue ??= []).push(action);
60
+ } else {
61
+ const record: ChangelogRecord = {
62
+ fiber,
63
+ cell,
64
+ action,
65
+ hasEagerState: false,
66
+ eagerState: undefined,
67
+ queued: false,
68
+ };
69
+
70
+ dispatchOnFiber(fiber, () => {
71
+ if (
72
+ eagerDispatch &&
73
+ fiber.root.dirtyCells.size === 0 &&
74
+ !record.hasEagerState
75
+ ) {
76
+ record.eagerState = reducer(cell.workInProgress, action);
77
+ record.hasEagerState = true;
78
+
79
+ if (Object.is(cell.current, record.eagerState)) return null;
80
+ }
81
+
82
+ return record;
83
+ });
84
+ }
85
+ },
86
+ };
87
+ return cell;
88
+ };
89
+
90
+ function useReducerImpl<S, A, I, R extends S>(
91
+ reducer: (state: S, action: A) => S,
92
+ getDerivedState: ((state: S) => R) | undefined,
93
+ initialArg: S | I,
94
+ initFn: ((arg: I) => S) | undefined,
95
+ eagerDispatch: boolean,
96
+ ): [R, Dispatch<A>] {
97
+ const fiber = getCurrentResourceFiber();
98
+ const index = fiber.currentIndex++;
99
+
100
+ const existing = fiber.cells[index];
101
+ let cell: Cell & { type: "reducer" };
102
+ if (existing === undefined) {
103
+ if (!fiber.isFirstRender && index >= fiber.cells.length) {
104
+ throw new Error(
105
+ "Rendered more hooks than during the previous render. " +
106
+ "Hooks must be called in the exact same order in every render.",
107
+ );
108
+ }
109
+ cell = createReducerCell(fiber, reducer, initialArg, initFn, eagerDispatch);
110
+ fiber.cells[index] = cell;
111
+ } else {
112
+ if (existing.type !== "reducer") {
113
+ throw new Error("Hook order changed between renders");
114
+ }
115
+ cell = existing;
116
+ }
117
+
118
+ const queue = cell.queue;
119
+ if (queue !== null) {
120
+ const sameReducer = reducer === cell.reducer;
121
+
122
+ // The drain consumes entries: a re-render of the same uncommitted lineage
123
+ // sees an empty queue and must not re-apply them. Rollback replays them
124
+ // into the queue via the changelog.
125
+ for (let i = 0; i < queue.length; i++) {
126
+ const item = queue[i]!;
127
+ if (!item.hasEagerState || !sameReducer) {
128
+ item.eagerState = reducer(cell.workInProgress, item.action);
129
+ item.hasEagerState = true;
130
+
131
+ if (isDevelopment && fiber.devStrictMode) {
132
+ // React keeps the strict re-invocation's result for render-computed
133
+ // actions (unlike eager-computed ones, whose ghost is discarded).
134
+ item.eagerState = reducer(cell.workInProgress, item.action);
135
+ }
136
+ } else if (isDevelopment && fiber.devStrictMode) {
137
+ void reducer(cell.workInProgress, item.action);
138
+ }
139
+
140
+ item.queued = false;
141
+ cell.workInProgress = item.eagerState;
142
+ }
143
+ cell.queue = null;
144
+ }
145
+ cell.reducer = reducer;
146
+
147
+ if (cell.renderQueue !== null || getDerivedState !== undefined) {
148
+ let derived = cell.workInProgress;
149
+ if (cell.renderQueue !== null) {
150
+ for (const action of cell.renderQueue) {
151
+ derived = reducer(derived, action);
152
+ }
153
+
154
+ cell.renderQueue = null;
155
+ fiber.renderPendingCells?.delete(cell);
156
+ }
157
+
158
+ if (getDerivedState) {
159
+ let changed;
160
+ let passes = 0;
161
+ do {
162
+ if (++passes > 25) {
163
+ throw new Error(
164
+ "Too many derivations. getDerivedState must reach a fixpoint; " +
165
+ "tap limits the number of iterations to prevent an infinite loop.",
166
+ );
167
+ }
168
+ const result = getDerivedState(derived);
169
+ changed = !Object.is(result, derived);
170
+ derived = result;
171
+ } while (changed);
172
+ }
173
+
174
+ if (!Object.is(derived, cell.workInProgress)) {
175
+ markCellDirty(fiber, cell);
176
+ cell.workInProgress = derived;
177
+ }
178
+ }
179
+
180
+ return [cell.workInProgress, cell.dispatch];
181
+ }
182
+
183
+ export function useReducer<S, A>(
184
+ reducer: (state: S, action: A) => S,
185
+ initialState: S,
186
+ ): [S, Dispatch<A>];
187
+ export function useReducer<S, A, I>(
188
+ reducer: (state: S, action: A) => S,
189
+ initialArg: I,
190
+ init: (arg: I) => S,
191
+ ): [S, Dispatch<A>];
192
+ export function useReducer<S, A, I>(
193
+ reducer: (state: S, action: A) => S,
194
+ initialArg: S | I,
195
+ init?: (arg: I) => S,
196
+ ): [S, Dispatch<A>] {
197
+ return useReducerImpl(
198
+ reducer,
199
+ undefined,
200
+ initialArg as S,
201
+ init as ((arg: S) => S) | undefined,
202
+ false,
203
+ );
204
+ }
205
+
206
+ /** @internal useState's entry point: eager dispatch, like React's basic state reducer. */
207
+ export function useEagerReducer<S, A>(
208
+ reducer: (state: S, action: A) => S,
209
+ initialState: S,
210
+ ): [S, Dispatch<A>];
211
+ export function useEagerReducer<S, A, I>(
212
+ reducer: (state: S, action: A) => S,
213
+ initialArg: I,
214
+ init: (arg: I) => S,
215
+ ): [S, Dispatch<A>];
216
+ export function useEagerReducer<S, A, I>(
217
+ reducer: (state: S, action: A) => S,
218
+ initialArg: S | I,
219
+ init?: (arg: I) => S,
220
+ ): [S, Dispatch<A>] {
221
+ return useReducerImpl(
222
+ reducer,
223
+ undefined,
224
+ initialArg as S,
225
+ init as ((arg: S) => S) | undefined,
226
+ true,
227
+ );
228
+ }
229
+
230
+ /**
231
+ * @internal Backs useMemo and useMemoCache: a reducer cell whose state is
232
+ * recomputed during render via getDerivedState. Not part of the public API;
233
+ * user-facing state adjustment during render uses render-phase updates
234
+ * (setState during render), like React.
235
+ */
236
+ export function useReducerWithDerivedState<S, A, R extends S>(
237
+ reducer: (state: S, action: A) => S,
238
+ getDerivedState: (state: S) => R,
239
+ initialState: S,
240
+ ): [R, Dispatch<A>];
241
+ export function useReducerWithDerivedState<S, A, I, R extends S>(
242
+ reducer: (state: S, action: A) => S,
243
+ getDerivedState: (state: S) => R,
244
+ initialArg: I,
245
+ init: (arg: I) => S,
246
+ ): [R, Dispatch<A>];
247
+ export function useReducerWithDerivedState<S, A, I, R extends S>(
248
+ reducer: (state: S, action: A) => S,
249
+ getDerivedState: (state: S) => R,
250
+ initialArg: I,
251
+ init?: (arg: I) => S,
252
+ ): [R, Dispatch<A>] {
253
+ return useReducerImpl(reducer, getDerivedState, initialArg, init, true);
254
+ }
@@ -1,4 +1,4 @@
1
- import { useReducer } from "./useReducer";
1
+ import { useEagerReducer } from "./useReducer";
2
2
 
3
3
  export namespace useState {
4
4
  export type StateUpdater<S> = S | ((prev: S) => S);
@@ -25,5 +25,5 @@ export function useState<S>(
25
25
  export function useState<S>(
26
26
  initial?: S | (() => S),
27
27
  ): [S | undefined, (updater: useState.StateUpdater<S>) => void] {
28
- return useReducer(stateReducer, initial, stateInit);
28
+ return useEagerReducer(stateReducer, initial, stateInit);
29
29
  }
@@ -8,9 +8,9 @@
8
8
  // This subpath ships no type declarations: the build reverts the aliased
9
9
  // specifier back to `"react"` in emitted `.d.ts`, so consumer types resolve to
10
10
  // React's own. The source-level TS2498 from the `export *` below is suppressed.
11
- import * as React from "react";
11
+ import React from "react";
12
12
  import { peekResourceFiber } from "../core/helpers/execution-context";
13
- import * as hooks from "../hooks";
13
+ import * as hooks from "../react-hooks";
14
14
  import { useResourceContext, isResourceContext } from "../core/context";
15
15
 
16
16
  // @ts-expect-error -- @types/react uses `export =`; this is valid at runtime.
@@ -18,47 +18,58 @@ export * from "react";
18
18
  export { default } from "react";
19
19
 
20
20
  const inTap = () => peekResourceFiber() !== null;
21
+ const ReactRuntime = React as any;
21
22
 
22
23
  // --- hooks with a tap equivalent: override the star-exported react hooks ---
23
24
 
24
25
  export const useState = (initialState?: any) =>
25
- inTap() ? hooks.useState(initialState) : React.useState(initialState);
26
+ inTap() ? hooks.useState(initialState) : ReactRuntime.useState(initialState);
26
27
 
27
28
  export const useReducer = (reducer: any, initialArg: any, init?: any) =>
28
29
  inTap()
29
30
  ? hooks.useReducer(reducer, initialArg, init)
30
- : React.useReducer(reducer, initialArg, init);
31
+ : ReactRuntime.useReducer(reducer, initialArg, init);
31
32
 
32
33
  export const useRef = (initialValue?: any) =>
33
- inTap() ? hooks.useRef(initialValue) : React.useRef(initialValue);
34
+ inTap() ? hooks.useRef(initialValue) : ReactRuntime.useRef(initialValue);
34
35
 
35
36
  export const useMemo = (factory: any, deps: any) =>
36
- inTap() ? hooks.useMemo(factory, deps) : React.useMemo(factory, deps);
37
+ inTap() ? hooks.useMemo(factory, deps) : ReactRuntime.useMemo(factory, deps);
37
38
 
38
39
  export const useCallback = (callback: any, deps: any) =>
39
40
  inTap()
40
41
  ? hooks.useCallback(callback, deps)
41
- : React.useCallback(callback, deps);
42
+ : ReactRuntime.useCallback(callback, deps);
42
43
 
43
44
  export const useEffect = (effect: any, deps?: any) =>
44
- inTap() ? hooks.useEffect(effect, deps) : React.useEffect(effect, deps);
45
+ inTap()
46
+ ? hooks.useEffect(effect, deps)
47
+ : ReactRuntime.useEffect(effect, deps);
45
48
 
46
49
  // tap has a single effect primitive; layout effects collapse onto it
47
50
  export const useLayoutEffect = (effect: any, deps?: any) =>
48
- inTap() ? hooks.useEffect(effect, deps) : React.useLayoutEffect(effect, deps);
51
+ inTap()
52
+ ? hooks.useEffect(effect, deps)
53
+ : ReactRuntime.useLayoutEffect(effect, deps);
49
54
 
55
+ // The non-tap fallback requires a React version that provides useEffectEvent.
50
56
  export const useEffectEvent = (callback: any) =>
51
- inTap() ? hooks.useEffectEvent(callback) : React.useEffectEvent(callback);
57
+ inTap()
58
+ ? hooks.useEffectEvent(callback)
59
+ : ReactRuntime.useEffectEvent(callback);
52
60
 
53
61
  // `use(usable)` reads tap resource context when handed a tap context (routed by
54
62
  // its brand, not by ambient render state), and falls back to React's `use`
55
- // (promises / React context) for everything else.
63
+ // (promises / React context) for everything else. The non-tap fallback requires
64
+ // React 19.
56
65
  export const use = (usable: any) =>
57
- isResourceContext(usable) ? useResourceContext(usable) : React.use(usable);
66
+ isResourceContext(usable)
67
+ ? useResourceContext(usable)
68
+ : ReactRuntime.use(usable);
58
69
 
59
70
  // `useContext(context)` reads tap resource context when handed a tap context
60
71
  // (routed by its brand), and falls back to React's `useContext` otherwise.
61
72
  export const useContext = (context: any) =>
62
73
  isResourceContext(context)
63
74
  ? useResourceContext(context)
64
- : React.useContext(context);
75
+ : ReactRuntime.useContext(context);
@@ -1,11 +0,0 @@
1
- import { ResourceElement } from "./types.js";
2
- import { useResourceRoot } from "../hooks/useResourceRoot.js";
3
-
4
- //#region src/core/createResourceRoot.d.ts
5
- declare const createResourceRoot: () => {
6
- render: <R, P>(element: ResourceElement<R, P>) => useResourceRoot.SubscribableResource<R>;
7
- unmount: () => void;
8
- };
9
- //#endregion
10
- export { createResourceRoot };
11
- //# sourceMappingURL=createResourceRoot.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"createResourceRoot.d.ts","names":[],"sources":["../../src/core/createResourceRoot.ts"],"mappings":";;;;cAea,kBAAA;iBAqBI,OAAA,EAAW,eAAA,CAAgB,CAAA,EAAG,CAAA,MAUjB,eAAA,CAAgB,oBAAA,CAAqB,CAAA"}
@@ -1,31 +0,0 @@
1
- import { resource } from "./resource.js";
2
- import { isDevelopment } from "./helpers/env.js";
3
- import { createResourceFiberRoot } from "./helpers/root.js";
4
- import { commitResourceFiber, createResourceFiber, renderResourceFiber, unmountResourceFiber } from "./ResourceFiber.js";
5
- import { UpdateScheduler, flushResourcesSync } from "./scheduler.js";
6
- import { useResourceRoot } from "../hooks/useResourceRoot.js";
7
- //#region src/core/createResourceRoot.ts
8
- const SubscribableResource = resource(useResourceRoot);
9
- const createResourceRoot = () => {
10
- const fiber = createResourceFiber(SubscribableResource, createResourceFiberRoot((callback) => {
11
- new UpdateScheduler(() => {
12
- if (callback()) throw new Error("Unexpected rerender of createResourceRoot outer fiber");
13
- return false;
14
- }).markDirty();
15
- }), void 0, isDevelopment ? "root" : null);
16
- return {
17
- render: (element) => {
18
- if (isDevelopment && fiber.devStrictMode === "root") renderResourceFiber(fiber, element);
19
- const render = renderResourceFiber(fiber, element);
20
- flushResourcesSync(() => commitResourceFiber(fiber, render));
21
- return render.output;
22
- },
23
- unmount: () => {
24
- unmountResourceFiber(fiber);
25
- }
26
- };
27
- };
28
- //#endregion
29
- export { createResourceRoot };
30
-
31
- //# sourceMappingURL=createResourceRoot.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"createResourceRoot.js","names":[],"sources":["../../src/core/createResourceRoot.ts"],"sourcesContent":["import type { ResourceElement } from \"./types\";\nimport {\n createResourceFiber,\n unmountResourceFiber,\n renderResourceFiber,\n commitResourceFiber,\n} from \"./ResourceFiber\";\nimport { useResourceRoot } from \"../hooks/useResourceRoot\";\nimport { resource } from \"./resource\";\nimport { isDevelopment } from \"./helpers/env\";\nimport { flushResourcesSync, UpdateScheduler } from \"./scheduler\";\nimport { createResourceFiberRoot } from \"./helpers/root\";\n\nconst SubscribableResource = resource(useResourceRoot);\n\nexport const createResourceRoot = () => {\n const fiber = createResourceFiber<\n useResourceRoot.SubscribableResource<any>,\n ResourceElement<any>\n >(\n SubscribableResource,\n createResourceFiberRoot((callback) => {\n new UpdateScheduler(() => {\n if (callback()) {\n throw new Error(\n \"Unexpected rerender of createResourceRoot outer fiber\",\n );\n }\n return false;\n }).markDirty();\n }),\n undefined,\n isDevelopment ? \"root\" : null,\n );\n\n return {\n render: <R, P>(element: ResourceElement<R, P>) => {\n // In strict mode, render twice to detect side effects\n if (isDevelopment && fiber.devStrictMode === \"root\") {\n void renderResourceFiber(fiber, element);\n }\n\n const render = renderResourceFiber(fiber, element);\n\n flushResourcesSync(() => commitResourceFiber(fiber, render));\n\n return render.output as useResourceRoot.SubscribableResource<R>;\n },\n unmount: () => {\n unmountResourceFiber(fiber);\n },\n };\n};\n"],"mappings":";;;;;;;AAaA,MAAM,uBAAuB,SAAS,eAAe;AAErD,MAAa,2BAA2B;CACtC,MAAM,QAAQ,oBAIZ,sBACA,yBAAyB,aAAa;EACpC,IAAI,sBAAsB;GACxB,IAAI,SAAS,GACX,MAAM,IAAI,MACR,uDACF;GAEF,OAAO;EACT,CAAC,CAAC,CAAC,UAAU;CACf,CAAC,GACD,KAAA,GACA,gBAAgB,SAAS,IAC3B;CAEA,OAAO;EACL,SAAe,YAAmC;GAEhD,IAAI,iBAAiB,MAAM,kBAAkB,QAC3C,oBAAyB,OAAO,OAAO;GAGzC,MAAM,SAAS,oBAAoB,OAAO,OAAO;GAEjD,yBAAyB,oBAAoB,OAAO,MAAM,CAAC;GAE3D,OAAO,OAAO;EAChB;EACA,eAAe;GACb,qBAAqB,KAAK;EAC5B;CACF;AACF"}
@@ -1 +0,0 @@
1
- export { };