@assistant-ui/tap 0.5.14 → 0.6.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.
Files changed (181) hide show
  1. package/README.md +9 -8
  2. package/dist/core/ResourceFiber.d.ts.map +1 -1
  3. package/dist/core/ResourceFiber.js +3 -2
  4. package/dist/core/ResourceFiber.js.map +1 -1
  5. package/dist/core/context.d.ts +13 -6
  6. package/dist/core/context.d.ts.map +1 -1
  7. package/dist/core/context.js +19 -6
  8. package/dist/core/context.js.map +1 -1
  9. package/dist/core/createResourceRoot.d.ts +2 -1
  10. package/dist/core/createResourceRoot.d.ts.map +1 -1
  11. package/dist/core/createResourceRoot.js +2 -2
  12. package/dist/core/createResourceRoot.js.map +1 -1
  13. package/dist/core/helpers/execution-context.d.ts +2 -1
  14. package/dist/core/helpers/execution-context.d.ts.map +1 -1
  15. package/dist/core/helpers/execution-context.js +4 -1
  16. package/dist/core/helpers/execution-context.js.map +1 -1
  17. package/dist/core/react-dispatcher.d.ts +12 -0
  18. package/dist/core/react-dispatcher.d.ts.map +1 -0
  19. package/dist/core/react-dispatcher.js +62 -0
  20. package/dist/core/react-dispatcher.js.map +1 -0
  21. package/dist/core/scheduler.js +1 -1
  22. package/dist/core/scheduler.js.map +1 -1
  23. package/dist/core/types.d.ts +3 -3
  24. package/dist/hooks/index.d.ts +13 -0
  25. package/dist/hooks/index.js +13 -0
  26. package/dist/hooks/use.d.ts +9 -0
  27. package/dist/hooks/use.d.ts.map +1 -0
  28. package/dist/hooks/use.js +14 -0
  29. package/dist/hooks/use.js.map +1 -0
  30. package/dist/hooks/useCallback.d.ts +5 -0
  31. package/dist/hooks/useCallback.d.ts.map +1 -0
  32. package/dist/hooks/useCallback.js +9 -0
  33. package/dist/hooks/useCallback.js.map +1 -0
  34. package/dist/hooks/useEffect.d.ts +10 -0
  35. package/dist/hooks/useEffect.d.ts.map +1 -0
  36. package/dist/hooks/{tap-effect.js → useEffect.js} +7 -7
  37. package/dist/hooks/useEffect.js.map +1 -0
  38. package/dist/hooks/{tap-effect-event.d.ts → useEffectEvent.d.ts} +5 -5
  39. package/dist/hooks/useEffectEvent.d.ts.map +1 -0
  40. package/dist/hooks/{tap-effect-event.js → useEffectEvent.js} +12 -12
  41. package/dist/hooks/useEffectEvent.js.map +1 -0
  42. package/dist/hooks/useMemo.d.ts +5 -0
  43. package/dist/hooks/useMemo.d.ts.map +1 -0
  44. package/dist/hooks/{tap-memo.js → useMemo.js} +6 -6
  45. package/dist/hooks/useMemo.js.map +1 -0
  46. package/dist/hooks/useMemoCache.d.ts +10 -0
  47. package/dist/hooks/useMemoCache.d.ts.map +1 -0
  48. package/dist/hooks/useMemoCache.js +21 -0
  49. package/dist/hooks/useMemoCache.js.map +1 -0
  50. package/dist/hooks/useReducer.d.ts +21 -0
  51. package/dist/hooks/useReducer.d.ts.map +1 -0
  52. package/dist/hooks/{tap-reducer.js → useReducer.js} +10 -10
  53. package/dist/hooks/useReducer.js.map +1 -0
  54. package/dist/hooks/useRef.d.ts +11 -0
  55. package/dist/hooks/useRef.d.ts.map +1 -0
  56. package/dist/hooks/useRef.js +10 -0
  57. package/dist/hooks/useRef.js.map +1 -0
  58. package/dist/{react/use-resource.d.ts → hooks/useResource.d.ts} +3 -2
  59. package/dist/hooks/useResource.d.ts.map +1 -0
  60. package/dist/hooks/{tap-resource.js → useResource.js} +12 -12
  61. package/dist/hooks/useResource.js.map +1 -0
  62. package/dist/hooks/useResourceRoot.d.ts +20 -0
  63. package/dist/hooks/useResourceRoot.d.ts.map +1 -0
  64. package/dist/{tapResourceRoot.js → hooks/useResourceRoot.js} +30 -26
  65. package/dist/hooks/useResourceRoot.js.map +1 -0
  66. package/dist/hooks/{tap-resources.d.ts → useResources.d.ts} +4 -4
  67. package/dist/hooks/useResources.d.ts.map +1 -0
  68. package/dist/hooks/{tap-resources.js → useResources.js} +28 -23
  69. package/dist/hooks/useResources.js.map +1 -0
  70. package/dist/hooks/useState.d.ts +9 -0
  71. package/dist/hooks/useState.d.ts.map +1 -0
  72. package/dist/hooks/useState.js +11 -0
  73. package/dist/hooks/useState.js.map +1 -0
  74. package/dist/hooks/utils/useCell.d.ts +10 -0
  75. package/dist/hooks/utils/useCell.d.ts.map +1 -0
  76. package/dist/hooks/utils/{tapHook.js → useCell.js} +4 -4
  77. package/dist/hooks/utils/{tapHook.js.map → useCell.js.map} +1 -1
  78. package/dist/index.d.ts +3 -13
  79. package/dist/index.js +3 -13
  80. package/dist/react/hooks.d.ts +25 -0
  81. package/dist/react/hooks.d.ts.map +1 -0
  82. package/dist/react/hooks.js +69 -0
  83. package/dist/react/hooks.js.map +1 -0
  84. package/dist/react-shim/index.d.ts +19 -0
  85. package/dist/react-shim/index.d.ts.map +1 -0
  86. package/dist/react-shim/index.js +28 -0
  87. package/dist/react-shim/index.js.map +1 -0
  88. package/package.json +13 -16
  89. package/react-shim/package.json +4 -0
  90. package/src/__tests__/basic/resourceHandle.test.ts +7 -3
  91. package/src/__tests__/basic/tapEffect.basic.test.ts +19 -19
  92. package/src/__tests__/basic/tapReducer.basic.test.ts +14 -14
  93. package/src/__tests__/basic/tapResources.basic.test.ts +19 -14
  94. package/src/__tests__/basic/tapState.basic.test.ts +20 -20
  95. package/src/__tests__/errors/errors.effect-errors.test.ts +21 -21
  96. package/src/__tests__/errors/errors.render-errors.test.ts +18 -18
  97. package/src/__tests__/lifecycle/lifecycle.dependencies.test.ts +25 -25
  98. package/src/__tests__/lifecycle/lifecycle.mount-unmount.test.ts +17 -18
  99. package/src/__tests__/react/concurrent-mode.test.tsx +7 -7
  100. package/src/__tests__/react/react-shim.test.tsx +65 -0
  101. package/src/__tests__/react/useResource.test.tsx +172 -0
  102. package/src/__tests__/react-dispatcher.test.ts +74 -0
  103. package/src/__tests__/rules/rules.hook-count.test.ts +30 -29
  104. package/src/__tests__/rules/rules.hook-order.test.ts +27 -27
  105. package/src/__tests__/strictmode/react-strictmode-behavior.test.tsx +1 -1
  106. package/src/__tests__/strictmode/strictmode.test.ts +42 -42
  107. package/src/__tests__/strictmode/tap-strictmode-rerender-sources.test.ts +55 -55
  108. package/src/__tests__/test-utils.ts +2 -2
  109. package/src/core/ResourceFiber.ts +4 -1
  110. package/src/core/context.ts +31 -9
  111. package/src/core/createResourceRoot.ts +4 -4
  112. package/src/core/helpers/execution-context.ts +4 -0
  113. package/src/core/react-dispatcher.ts +78 -0
  114. package/src/core/scheduler.ts +1 -1
  115. package/src/core/types.ts +3 -3
  116. package/src/hooks/index.ts +12 -0
  117. package/src/hooks/use.ts +13 -0
  118. package/src/hooks/useCallback.ts +9 -0
  119. package/src/hooks/{tap-effect.ts → useEffect.ts} +9 -9
  120. package/src/hooks/{tap-effect-event.ts → useEffectEvent.ts} +9 -9
  121. package/src/hooks/{tap-memo.ts → useMemo.ts} +3 -3
  122. package/src/hooks/useMemoCache.ts +25 -0
  123. package/src/hooks/{tap-reducer.ts → useReducer.ts} +23 -11
  124. package/src/hooks/useRef.ts +16 -0
  125. package/src/hooks/{tap-resource.ts → useResource.ts} +13 -12
  126. package/src/{tapResourceRoot.ts → hooks/useResourceRoot.ts} +26 -27
  127. package/src/hooks/{tap-resources.ts → useResources.ts} +21 -22
  128. package/src/hooks/useState.ts +29 -0
  129. package/src/hooks/utils/{tapHook.ts → useCell.ts} +1 -1
  130. package/src/index.ts +4 -24
  131. package/src/react/hooks.ts +112 -0
  132. package/src/react-shim/index.ts +64 -0
  133. package/dist/hooks/tap-callback.d.ts +0 -5
  134. package/dist/hooks/tap-callback.d.ts.map +0 -1
  135. package/dist/hooks/tap-callback.js +0 -9
  136. package/dist/hooks/tap-callback.js.map +0 -1
  137. package/dist/hooks/tap-const.d.ts +0 -5
  138. package/dist/hooks/tap-const.d.ts.map +0 -1
  139. package/dist/hooks/tap-const.js +0 -10
  140. package/dist/hooks/tap-const.js.map +0 -1
  141. package/dist/hooks/tap-effect-event.d.ts.map +0 -1
  142. package/dist/hooks/tap-effect-event.js.map +0 -1
  143. package/dist/hooks/tap-effect.d.ts +0 -10
  144. package/dist/hooks/tap-effect.d.ts.map +0 -1
  145. package/dist/hooks/tap-effect.js.map +0 -1
  146. package/dist/hooks/tap-memo.d.ts +0 -5
  147. package/dist/hooks/tap-memo.d.ts.map +0 -1
  148. package/dist/hooks/tap-memo.js.map +0 -1
  149. package/dist/hooks/tap-reducer.d.ts +0 -9
  150. package/dist/hooks/tap-reducer.d.ts.map +0 -1
  151. package/dist/hooks/tap-reducer.js.map +0 -1
  152. package/dist/hooks/tap-ref.d.ts +0 -11
  153. package/dist/hooks/tap-ref.d.ts.map +0 -1
  154. package/dist/hooks/tap-ref.js +0 -10
  155. package/dist/hooks/tap-ref.js.map +0 -1
  156. package/dist/hooks/tap-resource.d.ts +0 -8
  157. package/dist/hooks/tap-resource.d.ts.map +0 -1
  158. package/dist/hooks/tap-resource.js.map +0 -1
  159. package/dist/hooks/tap-resources.d.ts.map +0 -1
  160. package/dist/hooks/tap-resources.js.map +0 -1
  161. package/dist/hooks/tap-state.d.ts +0 -9
  162. package/dist/hooks/tap-state.d.ts.map +0 -1
  163. package/dist/hooks/tap-state.js +0 -11
  164. package/dist/hooks/tap-state.js.map +0 -1
  165. package/dist/hooks/utils/tapHook.d.ts +0 -10
  166. package/dist/hooks/utils/tapHook.d.ts.map +0 -1
  167. package/dist/react/index.d.ts +0 -2
  168. package/dist/react/index.js +0 -2
  169. package/dist/react/use-resource.d.ts.map +0 -1
  170. package/dist/react/use-resource.js +0 -46
  171. package/dist/react/use-resource.js.map +0 -1
  172. package/dist/tapResourceRoot.d.ts +0 -20
  173. package/dist/tapResourceRoot.d.ts.map +0 -1
  174. package/dist/tapResourceRoot.js.map +0 -1
  175. package/react/package.json +0 -5
  176. package/src/hooks/tap-callback.ts +0 -9
  177. package/src/hooks/tap-const.ts +0 -6
  178. package/src/hooks/tap-ref.ts +0 -16
  179. package/src/hooks/tap-state.ts +0 -29
  180. package/src/react/index.ts +0 -1
  181. package/src/react/use-resource.ts +0 -61
@@ -1,28 +1,50 @@
1
+ import type { Context as ReactContext } from "react";
2
+
1
3
  const contextValue: unique symbol = Symbol("tap.Context");
2
- type Context<T> = {
4
+ type TapContext<T> = {
3
5
  [contextValue]: T;
4
6
  };
5
7
 
6
- export const createResourceContext = <T>(defaultValue: T): Context<T> => {
8
+ const asTap = <T>(context: ReactContext<T>): TapContext<T> =>
9
+ context as unknown as TapContext<T>;
10
+
11
+ // A tap resource context is typed as a React `Context<T>` purely so React's
12
+ // `use(context)` accepts it (the type is erased, so this adds no runtime react
13
+ // dependency). At runtime it is only a branded `{ [contextValue]: T }` and is
14
+ // not a usable React context — `use()` routes it to `useResourceContext()` via the brand.
15
+ /**
16
+ * @deprecated experimental — the resource context API is not yet stable and may
17
+ * change or be removed in a future release.
18
+ */
19
+ export const createResourceContext = <T>(defaultValue: T): ReactContext<T> => {
7
20
  return {
8
21
  [contextValue]: defaultValue,
9
- };
22
+ } as unknown as ReactContext<T>;
23
+ };
24
+
25
+ export const isResourceContext = (value: unknown): boolean => {
26
+ return typeof value === "object" && value !== null && contextValue in value;
10
27
  };
11
28
 
29
+ /**
30
+ * @deprecated experimental — the resource context API is not yet stable and may
31
+ * change or be removed in a future release.
32
+ */
12
33
  export const withContextProvider = <T, TResult>(
13
- context: Context<T>,
34
+ context: ReactContext<T>,
14
35
  value: T,
15
36
  fn: () => TResult,
16
37
  ) => {
17
- const previousValue = context[contextValue];
18
- context[contextValue] = value;
38
+ const tapContext = asTap(context);
39
+ const previousValue = tapContext[contextValue];
40
+ tapContext[contextValue] = value;
19
41
  try {
20
42
  return fn();
21
43
  } finally {
22
- context[contextValue] = previousValue;
44
+ tapContext[contextValue] = previousValue;
23
45
  }
24
46
  };
25
47
 
26
- export const tap = <T>(context: Context<T>) => {
27
- return context[contextValue];
48
+ export const useResourceContext = <T>(context: ReactContext<T>) => {
49
+ return asTap(context)[contextValue];
28
50
  };
@@ -5,17 +5,17 @@ import {
5
5
  renderResourceFiber,
6
6
  commitResourceFiber,
7
7
  } from "./ResourceFiber";
8
- import { tapResourceRoot } from "../tapResourceRoot";
8
+ import { useResourceRoot } from "../hooks/useResourceRoot";
9
9
  import { resource } from "./resource";
10
10
  import { isDevelopment } from "./helpers/env";
11
11
  import { flushResourcesSync, UpdateScheduler } from "./scheduler";
12
12
  import { createResourceFiberRoot } from "./helpers/root";
13
13
 
14
- const SubscribableResource = resource(tapResourceRoot);
14
+ const SubscribableResource = resource(useResourceRoot);
15
15
 
16
16
  export const createResourceRoot = () => {
17
17
  const fiber = createResourceFiber<
18
- tapResourceRoot.SubscribableResource<any>,
18
+ useResourceRoot.SubscribableResource<any>,
19
19
  ResourceElement<any>
20
20
  >(
21
21
  SubscribableResource,
@@ -44,7 +44,7 @@ export const createResourceRoot = () => {
44
44
 
45
45
  flushResourcesSync(() => commitResourceFiber(fiber, render));
46
46
 
47
- return render.output;
47
+ return render.output as useResourceRoot.SubscribableResource<R>;
48
48
  },
49
49
  unmount: () => {
50
50
  unmountResourceFiber(fiber);
@@ -34,6 +34,10 @@ export function getCurrentResourceFiber(): ResourceFiber<unknown, unknown> {
34
34
  return currentResourceFiber;
35
35
  }
36
36
 
37
+ export function peekResourceFiber(): ResourceFiber<unknown, unknown> | null {
38
+ return currentResourceFiber;
39
+ }
40
+
37
41
  export function getDevStrictMode(enable: boolean) {
38
42
  if (!isDevelopment) return null;
39
43
  if (currentResourceFiber?.devStrictMode)
@@ -0,0 +1,78 @@
1
+ import * as React from "react";
2
+ import { useState } from "../hooks/useState";
3
+ import { useReducer } from "../hooks/useReducer";
4
+ import { useRef } from "../hooks/useRef";
5
+ import { useMemo } from "../hooks/useMemo";
6
+ import { useCallback } from "../hooks/useCallback";
7
+ import { useEffect } from "../hooks/useEffect";
8
+ import { useEffectEvent } from "../hooks/useEffectEvent";
9
+ import { use } from "../hooks/use";
10
+ import { useMemoCache } from "../hooks/useMemoCache";
11
+
12
+ // The dispatcher React reads while a resource renders, so hooks imported from
13
+ // "react" route to tap with no build step. Hooks tap has no equivalent for are
14
+ // intentionally absent: calling one throws, which is the intended "unsupported
15
+ // in a resource" signal.
16
+ const tapDispatcher = {
17
+ useState,
18
+ useReducer,
19
+ useRef,
20
+ useMemo,
21
+ useCallback,
22
+ useEffect,
23
+ useLayoutEffect: useEffect,
24
+ useInsertionEffect: useEffect,
25
+ useEffectEvent,
26
+ useContext: use,
27
+ use,
28
+ useMemoCache,
29
+ };
30
+
31
+ // React's live dispatcher slot differs by version: React 19 exposes it as `H` on
32
+ // the client internals object; React 18 as `ReactCurrentDispatcher.current`.
33
+ const internals =
34
+ (React as any)
35
+ .__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE ??
36
+ (React as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
37
+
38
+ const slot: { current: unknown } | null =
39
+ internals == null
40
+ ? null
41
+ : "H" in internals
42
+ ? {
43
+ get current() {
44
+ return internals.H;
45
+ },
46
+ set current(d) {
47
+ internals.H = d;
48
+ },
49
+ }
50
+ : "ReactCurrentDispatcher" in internals
51
+ ? {
52
+ get current() {
53
+ return internals.ReactCurrentDispatcher.current;
54
+ },
55
+ set current(d) {
56
+ internals.ReactCurrentDispatcher.current = d;
57
+ },
58
+ }
59
+ : null;
60
+
61
+ /**
62
+ * Runs a resource body with tap's React dispatcher installed, so real React
63
+ * hooks called inside it (`import { useState } from "react"`) route to tap, then
64
+ * restores the previous dispatcher. If React's internal dispatcher slot can't be
65
+ * found (an unsupported React version), the body runs unchanged and `react`
66
+ * hooks inside it keep throwing React's "invalid hook call".
67
+ */
68
+ export function withReactDispatcher<T>(render: () => T): T {
69
+ if (!slot) return render();
70
+
71
+ const previous = slot.current;
72
+ slot.current = tapDispatcher;
73
+ try {
74
+ return render();
75
+ } finally {
76
+ slot.current = previous;
77
+ }
78
+ }
@@ -53,7 +53,7 @@ const flushScheduled = () => {
53
53
  if (flushDepth > MAX_FLUSH_LIMIT) {
54
54
  throw new Error(
55
55
  `Maximum update depth exceeded. This can happen when a resource ` +
56
- `repeatedly calls setState inside tapEffect.`,
56
+ `repeatedly calls setState inside useEffect.`,
57
57
  );
58
58
  }
59
59
 
package/src/core/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { tapEffect } from "../hooks/tap-effect";
1
+ import type { useEffect } from "../hooks/useEffect";
2
2
 
3
3
  export type ResourceElement<R, P = any> = {
4
4
  readonly type: Resource<R, P>;
@@ -35,12 +35,12 @@ export type Cell =
35
35
  }
36
36
  | {
37
37
  readonly type: "effect";
38
- cleanup: tapEffect.Destructor | undefined;
38
+ cleanup: useEffect.Destructor | undefined;
39
39
  deps: readonly unknown[] | null | undefined;
40
40
  };
41
41
 
42
42
  export interface EffectTask {
43
- readonly effect: tapEffect.EffectCallback;
43
+ readonly effect: useEffect.EffectCallback;
44
44
  readonly deps: readonly unknown[] | undefined;
45
45
  readonly cell: Cell & { type: "effect" };
46
46
  }
@@ -0,0 +1,12 @@
1
+ export { useState } from "./useState";
2
+ export { useReducer, useReducerWithDerivedState } from "./useReducer";
3
+ export { useRef } from "./useRef";
4
+ export { useMemo } from "./useMemo";
5
+ export { useCallback } from "./useCallback";
6
+ export { useEffect } from "./useEffect";
7
+ export { useEffectEvent } from "./useEffectEvent";
8
+ export { use } from "./use";
9
+ export { useMemoCache } from "./useMemoCache";
10
+ export { useResource } from "./useResource";
11
+ export { useResources } from "./useResources";
12
+ export { useResourceRoot } from "./useResourceRoot";
@@ -0,0 +1,13 @@
1
+ import { isResourceContext, useResourceContext } from "../core/context";
2
+
3
+ /**
4
+ * Reads a resource context from inside a resource render, the tap equivalent of
5
+ * React's `use(Context)`. Only resource contexts are supported.
6
+ */
7
+ export const use = (usable: unknown): unknown => {
8
+ if (!isResourceContext(usable))
9
+ throw new Error(
10
+ "A tap resource's `use()` only accepts a resource context.",
11
+ );
12
+ return useResourceContext(usable as never);
13
+ };
@@ -0,0 +1,9 @@
1
+ import { useMemo } from "./useMemo";
2
+
3
+ export const useCallback = <T extends (...args: any[]) => any>(
4
+ fn: T,
5
+ deps: readonly unknown[],
6
+ ): T => {
7
+ // oxlint-disable-next-line react/exhaustive-deps -- user-provided dep array forwarded verbatim
8
+ return useMemo(() => fn, deps);
9
+ };
@@ -1,6 +1,6 @@
1
1
  import type { Cell } from "../core/types";
2
2
  import { depsShallowEqual } from "./utils/depsShallowEqual";
3
- import { tapHook, registerRenderMountTask } from "./utils/tapHook";
3
+ import { useCell, registerRenderMountTask } from "./utils/useCell";
4
4
 
5
5
  const newEffect = (): Cell & { type: "effect" } => ({
6
6
  type: "effect",
@@ -8,26 +8,26 @@ const newEffect = (): Cell & { type: "effect" } => ({
8
8
  deps: null, // null means the effect has never been run
9
9
  });
10
10
 
11
- export namespace tapEffect {
11
+ export namespace useEffect {
12
12
  export type Destructor = () => void;
13
13
  export type EffectCallback = () => Destructor | undefined;
14
14
  }
15
15
 
16
- export function tapEffect(effect: tapEffect.EffectCallback): void;
17
- export function tapEffect(
18
- effect: tapEffect.EffectCallback,
16
+ export function useEffect(effect: useEffect.EffectCallback): void;
17
+ export function useEffect(
18
+ effect: useEffect.EffectCallback,
19
19
  deps: readonly unknown[],
20
20
  ): void;
21
- export function tapEffect(
22
- effect: tapEffect.EffectCallback,
21
+ export function useEffect(
22
+ effect: useEffect.EffectCallback,
23
23
  deps?: readonly unknown[],
24
24
  ): void {
25
- const cell = tapHook("effect", newEffect);
25
+ const cell = useCell("effect", newEffect);
26
26
 
27
27
  if (deps && cell.deps && depsShallowEqual(cell.deps, deps)) return;
28
28
  if (cell.deps !== null && !!deps !== !!cell.deps)
29
29
  throw new Error(
30
- "tapEffect called with and without dependencies across re-renders",
30
+ "useEffect called with and without dependencies across re-renders",
31
31
  );
32
32
 
33
33
  registerRenderMountTask(() => {
@@ -1,7 +1,7 @@
1
- import { tapRef } from "./tap-ref";
2
- import { tapEffect } from "./tap-effect";
1
+ import { useRef } from "./useRef";
2
+ import { useEffect } from "./useEffect";
3
3
  import { isDevelopment } from "../core/helpers/env";
4
- import { tapCallback } from "./tap-callback";
4
+ import { useCallback } from "./useCallback";
5
5
  import { getCurrentResourceFiber } from "../core/helpers/execution-context";
6
6
 
7
7
  /**
@@ -13,27 +13,27 @@ import { getCurrentResourceFiber } from "../core/helpers/execution-context";
13
13
  *
14
14
  * @example
15
15
  * ```typescript
16
- * const handleClick = tapEffectEvent((value: string) => {
16
+ * const handleClick = useEffectEvent((value: string) => {
17
17
  * console.log(value);
18
18
  * });
19
19
  * // handleClick reference is stable, but always calls the latest version
20
20
  * ```
21
21
  */
22
- export function tapEffectEvent<T extends (...args: any[]) => any>(
22
+ export function useEffectEvent<T extends (...args: any[]) => any>(
23
23
  callback: T,
24
24
  ): T {
25
- const callbackRef = tapRef(callback);
25
+ const callbackRef = useRef(callback);
26
26
 
27
27
  // TODO this effect needs to run before all userland effects
28
- tapEffect(() => {
28
+ useEffect(() => {
29
29
  callbackRef.current = callback;
30
30
  });
31
31
 
32
32
  const fiber = getCurrentResourceFiber();
33
- return tapCallback(
33
+ return useCallback(
34
34
  ((...args: Parameters<T>) => {
35
35
  if (isDevelopment && fiber.renderContext)
36
- throw new Error("tapEffectEvent cannot be called during render");
36
+ throw new Error("useEffectEvent cannot be called during render");
37
37
  return callbackRef.current(...args);
38
38
  }) as T,
39
39
  [fiber],
@@ -1,6 +1,6 @@
1
1
  import { isDevelopment } from "../core/helpers/env";
2
2
  import { getCurrentResourceFiber } from "../core/helpers/execution-context";
3
- import { tapReducerWithDerivedState } from "./tap-reducer";
3
+ import { useReducerWithDerivedState } from "./useReducer";
4
4
  import { depsShallowEqual } from "./utils/depsShallowEqual";
5
5
 
6
6
  const memoReducer = () => {
@@ -9,9 +9,9 @@ const memoReducer = () => {
9
9
 
10
10
  type MemoState<T> = { value: T; deps: readonly unknown[] };
11
11
 
12
- export const tapMemo = <T>(fn: () => T, deps: readonly unknown[]): T => {
12
+ export const useMemo = <T>(fn: () => T, deps: readonly unknown[]): T => {
13
13
  const fiber = getCurrentResourceFiber();
14
- const [state] = tapReducerWithDerivedState(
14
+ const [state] = useReducerWithDerivedState(
15
15
  memoReducer,
16
16
  (state: MemoState<T> | null): MemoState<T> => {
17
17
  if (state && depsShallowEqual(state.deps, deps)) return state;
@@ -0,0 +1,25 @@
1
+ import { useReducerWithDerivedState } from "./useReducer";
2
+
3
+ const MEMO_CACHE_SENTINEL = Symbol.for("react.memo_cache_sentinel");
4
+
5
+ /**
6
+ * Backs React Compiler's memo cache. Compiled output allocates it through
7
+ * `react/compiler-runtime`'s `c(size)` (`ReactSharedInternals.H.useMemoCache(size)`);
8
+ * a tap ref persists the array so compiled resources run without `"use no memo"`.
9
+ */
10
+ export const useMemoCache = (size: number): unknown[] => {
11
+ // clone the memo value once per render
12
+ let cloned = false;
13
+ const [cache] = useReducerWithDerivedState(
14
+ () => [] as unknown[],
15
+ (arr) => {
16
+ if (cloned) return arr;
17
+ cloned = true;
18
+ return [...arr];
19
+ },
20
+ size,
21
+ (length) => Array.from({ length }).fill(MEMO_CACHE_SENTINEL),
22
+ );
23
+
24
+ return cache;
25
+ };
@@ -2,7 +2,7 @@ import { isDevelopment } from "../core/helpers/env";
2
2
  import { getCurrentResourceFiber } from "../core/helpers/execution-context";
3
3
  import type { ReducerQueueEntry, ResourceFiber } from "../core/types";
4
4
  import { markCellDirty } from "../core/helpers/root";
5
- import { tapHook } from "./utils/tapHook";
5
+ import { useCell } from "./utils/useCell";
6
6
 
7
7
  type Dispatch<A> = (action: A) => void;
8
8
 
@@ -28,13 +28,13 @@ const dispatchOnFiber = (
28
28
  });
29
29
  };
30
30
 
31
- function tapReducerImpl<S, A, I, R extends S>(
31
+ function useReducerImpl<S, A, I, R extends S>(
32
32
  reducer: (state: S, action: A) => S,
33
33
  getDerivedState: ((state: S) => R) | undefined,
34
34
  initialArg: S | I,
35
35
  initFn: ((arg: I) => S) | undefined,
36
36
  ): [R, Dispatch<A>] {
37
- const cell = tapHook("reducer", () => {
37
+ const cell = useCell("reducer", () => {
38
38
  const fiber = getCurrentResourceFiber();
39
39
 
40
40
  // First render: compute initial state
@@ -105,21 +105,21 @@ function tapReducerImpl<S, A, I, R extends S>(
105
105
  return [cell.workInProgress, cell.dispatch];
106
106
  }
107
107
 
108
- export function tapReducer<S, A>(
108
+ export function useReducer<S, A>(
109
109
  reducer: (state: S, action: A) => S,
110
110
  initialState: S,
111
111
  ): [S, Dispatch<A>];
112
- export function tapReducer<S, A, I>(
112
+ export function useReducer<S, A, I>(
113
113
  reducer: (state: S, action: A) => S,
114
114
  initialArg: I,
115
115
  init: (arg: I) => S,
116
116
  ): [S, Dispatch<A>];
117
- export function tapReducer<S, A, I>(
117
+ export function useReducer<S, A, I>(
118
118
  reducer: (state: S, action: A) => S,
119
119
  initialArg: S | I,
120
120
  init?: (arg: I) => S,
121
121
  ): [S, Dispatch<A>] {
122
- return tapReducerImpl(
122
+ return useReducerImpl(
123
123
  reducer,
124
124
  undefined,
125
125
  initialArg as S,
@@ -127,22 +127,34 @@ export function tapReducer<S, A, I>(
127
127
  );
128
128
  }
129
129
 
130
- export function tapReducerWithDerivedState<S, A, R extends S>(
130
+ /**
131
+ * @deprecated experimental — a `getDerivedStateFromProps` replacement for
132
+ * resources: adjust state in response to props without setting during render.
133
+ * Tap-only for now (call it inside a resource render, not a React component) and
134
+ * may change before stabilizing.
135
+ */
136
+ export function useReducerWithDerivedState<S, A, R extends S>(
131
137
  reducer: (state: S, action: A) => S,
132
138
  getDerivedState: (state: S) => R,
133
139
  initialState: S,
134
140
  ): [R, Dispatch<A>];
135
- export function tapReducerWithDerivedState<S, A, I, R extends S>(
141
+ /**
142
+ * @deprecated experimental — a `getDerivedStateFromProps` replacement for
143
+ * resources: adjust state in response to props without setting during render.
144
+ * Tap-only for now (call it inside a resource render, not a React component) and
145
+ * may change before stabilizing.
146
+ */
147
+ export function useReducerWithDerivedState<S, A, I, R extends S>(
136
148
  reducer: (state: S, action: A) => S,
137
149
  getDerivedState: (state: S) => R,
138
150
  initialArg: I,
139
151
  init: (arg: I) => S,
140
152
  ): [R, Dispatch<A>];
141
- export function tapReducerWithDerivedState<S, A, I, R extends S>(
153
+ export function useReducerWithDerivedState<S, A, I, R extends S>(
142
154
  reducer: (state: S, action: A) => S,
143
155
  getDerivedState: (state: S) => R,
144
156
  initialArg: I,
145
157
  init?: (arg: I) => S,
146
158
  ): [R, Dispatch<A>] {
147
- return tapReducerImpl(reducer, getDerivedState, initialArg, init);
159
+ return useReducerImpl(reducer, getDerivedState, initialArg, init);
148
160
  }
@@ -0,0 +1,16 @@
1
+ import { useState } from "./useState";
2
+
3
+ export namespace useRef {
4
+ export interface RefObject<T> {
5
+ current: T;
6
+ }
7
+ }
8
+
9
+ export function useRef<T>(initialValue: T): useRef.RefObject<T>;
10
+ export function useRef<T = undefined>(): useRef.RefObject<T | undefined>;
11
+ export function useRef<T>(initialValue?: T): useRef.RefObject<T | undefined> {
12
+ const [state] = useState(() => ({
13
+ current: initialValue,
14
+ }));
15
+ return state;
16
+ }
@@ -1,29 +1,29 @@
1
1
  import type { ExtractResourceReturnType, ResourceElement } from "../core/types";
2
- import { tapEffect } from "./tap-effect";
2
+ import { useEffect } from "./useEffect";
3
3
  import {
4
4
  createResourceFiber,
5
5
  unmountResourceFiber,
6
6
  renderResourceFiber,
7
7
  commitResourceFiber,
8
8
  } from "../core/ResourceFiber";
9
- import { tapMemo } from "./tap-memo";
10
- import { tapRef } from "./tap-ref";
9
+ import { useMemo } from "./useMemo";
10
+ import { useRef } from "./useRef";
11
11
  import { getCurrentResourceFiber } from "../core/helpers/execution-context";
12
12
 
13
- export function tapResource<E extends ResourceElement<any, any>>(
13
+ export function useResource<E extends ResourceElement<any, any>>(
14
14
  element: E,
15
15
  ): ExtractResourceReturnType<E>;
16
- export function tapResource<E extends ResourceElement<any, any>>(
16
+ export function useResource<E extends ResourceElement<any, any>>(
17
17
  element: E,
18
18
  propsDeps: readonly unknown[],
19
19
  ): ExtractResourceReturnType<E>;
20
- export function tapResource<E extends ResourceElement<any, any>>(
20
+ export function useResource<E extends ResourceElement<any, any>>(
21
21
  element: E,
22
22
  propsDeps?: readonly unknown[],
23
23
  ): ExtractResourceReturnType<E> {
24
24
  const parentFiber = getCurrentResourceFiber();
25
- const versionRef = tapRef(0);
26
- const fiber = tapMemo(() => {
25
+ const versionRef = useRef(0);
26
+ const fiber = useMemo(() => {
27
27
  void element.key;
28
28
  return createResourceFiber(element.type, parentFiber.root, () => {
29
29
  versionRef.current++;
@@ -32,15 +32,16 @@ export function tapResource<E extends ResourceElement<any, any>>(
32
32
  }, [element.type, element.key, parentFiber]);
33
33
 
34
34
  const result = propsDeps
35
- ? tapMemo(
35
+ ? // oxlint-disable-next-line react/rules-of-hooks -- propsDeps presence is fixed per call site, so the conditional call order is stable
36
+ useMemo(
36
37
  () => renderResourceFiber(fiber, element.props),
37
- // oxlint-disable-next-line tap-hooks/exhaustive-deps -- props identity replaced by user-provided deps
38
+ // oxlint-disable-next-line react/exhaustive-deps -- props identity replaced by user-provided deps
38
39
  [fiber, ...propsDeps, versionRef.current],
39
40
  )
40
41
  : renderResourceFiber(fiber, element.props);
41
42
 
42
- tapEffect(() => () => unmountResourceFiber(fiber), [fiber]);
43
- tapEffect(() => {
43
+ useEffect(() => () => unmountResourceFiber(fiber), [fiber]);
44
+ useEffect(() => {
44
45
  commitResourceFiber(fiber, result);
45
46
  }, [fiber, result]);
46
47