@assistant-ui/tap 0.3.6 → 0.4.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 (123) hide show
  1. package/README.md +24 -23
  2. package/dist/core/ResourceFiber.d.ts +1 -1
  3. package/dist/core/ResourceFiber.d.ts.map +1 -1
  4. package/dist/core/ResourceFiber.js +15 -8
  5. package/dist/core/ResourceFiber.js.map +1 -1
  6. package/dist/core/commit.d.ts +1 -1
  7. package/dist/core/commit.d.ts.map +1 -1
  8. package/dist/core/commit.js +30 -48
  9. package/dist/core/commit.js.map +1 -1
  10. package/dist/core/context.d.ts +2 -2
  11. package/dist/core/context.d.ts.map +1 -1
  12. package/dist/core/context.js +2 -2
  13. package/dist/core/context.js.map +1 -1
  14. package/dist/core/createResource.d.ts +3 -2
  15. package/dist/core/createResource.d.ts.map +1 -1
  16. package/dist/core/createResource.js +33 -19
  17. package/dist/core/createResource.js.map +1 -1
  18. package/dist/core/env.d.ts +2 -0
  19. package/dist/core/env.d.ts.map +1 -0
  20. package/dist/core/env.js +3 -0
  21. package/dist/core/env.js.map +1 -0
  22. package/dist/core/execution-context.d.ts +1 -0
  23. package/dist/core/execution-context.d.ts.map +1 -1
  24. package/dist/core/execution-context.js +8 -0
  25. package/dist/core/execution-context.js.map +1 -1
  26. package/dist/core/resource.d.ts +3 -3
  27. package/dist/core/resource.d.ts.map +1 -1
  28. package/dist/core/resource.js.map +1 -1
  29. package/dist/core/scheduler.d.ts +1 -1
  30. package/dist/core/scheduler.d.ts.map +1 -1
  31. package/dist/core/scheduler.js +1 -1
  32. package/dist/core/scheduler.js.map +1 -1
  33. package/dist/core/types.d.ts +22 -21
  34. package/dist/core/types.d.ts.map +1 -1
  35. package/dist/core/types.js +1 -1
  36. package/dist/core/types.js.map +1 -1
  37. package/dist/core/withKey.d.ts +3 -0
  38. package/dist/core/withKey.d.ts.map +1 -0
  39. package/dist/core/withKey.js +4 -0
  40. package/dist/core/withKey.js.map +1 -0
  41. package/dist/hooks/tap-callback.d.ts.map +1 -1
  42. package/dist/hooks/tap-callback.js +1 -0
  43. package/dist/hooks/tap-callback.js.map +1 -1
  44. package/dist/hooks/tap-const.d.ts +2 -0
  45. package/dist/hooks/tap-const.d.ts.map +1 -0
  46. package/dist/hooks/tap-const.js +6 -0
  47. package/dist/hooks/tap-const.js.map +1 -0
  48. package/dist/hooks/tap-effect-event.d.ts.map +1 -1
  49. package/dist/hooks/tap-effect-event.js +11 -0
  50. package/dist/hooks/tap-effect-event.js.map +1 -1
  51. package/dist/hooks/tap-effect.d.ts.map +1 -1
  52. package/dist/hooks/tap-effect.js +43 -31
  53. package/dist/hooks/tap-effect.js.map +1 -1
  54. package/dist/hooks/tap-inline-resource.d.ts +2 -2
  55. package/dist/hooks/tap-inline-resource.d.ts.map +1 -1
  56. package/dist/hooks/tap-memo.js +1 -1
  57. package/dist/hooks/tap-memo.js.map +1 -1
  58. package/dist/hooks/tap-resource.d.ts +3 -3
  59. package/dist/hooks/tap-resource.d.ts.map +1 -1
  60. package/dist/hooks/tap-resource.js +17 -9
  61. package/dist/hooks/tap-resource.js.map +1 -1
  62. package/dist/hooks/tap-resources.d.ts +2 -10
  63. package/dist/hooks/tap-resources.d.ts.map +1 -1
  64. package/dist/hooks/tap-resources.js +74 -43
  65. package/dist/hooks/tap-resources.js.map +1 -1
  66. package/dist/hooks/tap-state.d.ts.map +1 -1
  67. package/dist/hooks/tap-state.js +37 -24
  68. package/dist/hooks/tap-state.js.map +1 -1
  69. package/dist/hooks/utils/depsShallowEqual.d.ts.map +1 -0
  70. package/dist/hooks/utils/depsShallowEqual.js.map +1 -0
  71. package/dist/hooks/utils/tapHook.d.ts +6 -0
  72. package/dist/hooks/utils/tapHook.d.ts.map +1 -0
  73. package/dist/hooks/utils/tapHook.js +24 -0
  74. package/dist/hooks/utils/tapHook.js.map +1 -0
  75. package/dist/index.d.ts +5 -3
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +4 -2
  78. package/dist/index.js.map +1 -1
  79. package/dist/react/use-resource.d.ts +2 -2
  80. package/dist/react/use-resource.d.ts.map +1 -1
  81. package/dist/react/use-resource.js +24 -10
  82. package/dist/react/use-resource.js.map +1 -1
  83. package/package.json +8 -1
  84. package/src/__tests__/basic/resourceHandle.test.ts +4 -4
  85. package/src/__tests__/basic/tapEffect.basic.test.ts +3 -2
  86. package/src/__tests__/basic/tapResources.basic.test.ts +84 -64
  87. package/src/__tests__/basic/tapState.basic.test.ts +8 -8
  88. package/src/__tests__/errors/errors.effect-errors.test.ts +8 -3
  89. package/src/__tests__/lifecycle/lifecycle.dependencies.test.ts +3 -2
  90. package/src/__tests__/lifecycle/lifecycle.mount-unmount.test.ts +2 -2
  91. package/src/__tests__/react/concurrent-mode.test.tsx +243 -0
  92. package/src/__tests__/strictmode/react-strictmode-behavior.test.tsx +709 -0
  93. package/src/__tests__/strictmode/react-strictmode-rerender-sources.test.tsx +392 -0
  94. package/src/__tests__/strictmode/strictmode.test.ts +270 -0
  95. package/src/__tests__/strictmode/tap-strictmode-rerender-sources.test.ts +723 -0
  96. package/src/__tests__/test-utils.ts +8 -6
  97. package/src/core/ResourceFiber.ts +21 -11
  98. package/src/core/commit.ts +29 -58
  99. package/src/core/context.ts +2 -2
  100. package/src/core/createResource.ts +46 -22
  101. package/src/core/env.ts +3 -0
  102. package/src/core/execution-context.ts +9 -0
  103. package/src/core/resource.ts +6 -3
  104. package/src/core/scheduler.ts +1 -1
  105. package/src/core/types.ts +25 -26
  106. package/src/core/withKey.ts +8 -0
  107. package/src/hooks/tap-callback.ts +1 -0
  108. package/src/hooks/tap-const.ts +6 -0
  109. package/src/hooks/tap-effect-event.ts +15 -0
  110. package/src/hooks/tap-effect.ts +48 -38
  111. package/src/hooks/tap-inline-resource.ts +2 -2
  112. package/src/hooks/tap-memo.ts +1 -1
  113. package/src/hooks/tap-resource.ts +24 -20
  114. package/src/hooks/tap-resources.ts +86 -63
  115. package/src/hooks/tap-state.ts +49 -26
  116. package/src/hooks/utils/tapHook.ts +35 -0
  117. package/src/index.ts +8 -3
  118. package/src/react/use-resource.ts +27 -16
  119. package/dist/hooks/depsShallowEqual.d.ts.map +0 -1
  120. package/dist/hooks/depsShallowEqual.js.map +0 -1
  121. /package/dist/hooks/{depsShallowEqual.d.ts → utils/depsShallowEqual.d.ts} +0 -0
  122. /package/dist/hooks/{depsShallowEqual.js → utils/depsShallowEqual.js} +0 -0
  123. /package/src/hooks/{depsShallowEqual.ts → utils/depsShallowEqual.ts} +0 -0
@@ -1,15 +1,17 @@
1
+ import { isDevelopment } from "../core/env.js";
1
2
  import { getCurrentResourceFiber } from "../core/execution-context.js";
2
- const rerender = (fiber) => {
3
+ const dispatchOnFiber = (fiber, callback) => {
3
4
  if (fiber.renderContext) {
4
5
  throw new Error("Resource updated during render");
5
6
  }
6
7
  if (fiber.isMounted) {
7
8
  // Only schedule rerender if currently mounted
8
- fiber.scheduleRerender();
9
+ fiber.dispatchUpdate(callback);
9
10
  }
10
11
  else if (fiber.isNeverMounted) {
11
12
  throw new Error("Resource updated before mount");
12
13
  }
14
+ // TODO mark dirty
13
15
  };
14
16
  function getStateCell(initialValue) {
15
17
  const fiber = getCurrentResourceFiber();
@@ -19,32 +21,43 @@ function getStateCell(initialValue) {
19
21
  throw new Error("Rendered more hooks than during the previous render. " +
20
22
  "Hooks must be called in the exact same order in every render.");
21
23
  }
22
- if (!fiber.cells[index]) {
23
- // Initialize the value immediately
24
- const value = typeof initialValue === "function"
25
- ? initialValue()
26
- : initialValue;
27
- const cell = {
28
- type: "state",
29
- value,
30
- set: (updater) => {
31
- const currentValue = cell.value;
24
+ const cell = fiber.cells[index];
25
+ if (cell) {
26
+ if (cell.type !== "state")
27
+ throw new Error("Hook order changed between renders");
28
+ return cell;
29
+ }
30
+ const value = typeof initialValue === "function"
31
+ ? initialValue()
32
+ : initialValue;
33
+ if (isDevelopment &&
34
+ fiber.devStrictMode &&
35
+ typeof initialValue === "function") {
36
+ void initialValue();
37
+ }
38
+ const newCell = {
39
+ type: "state",
40
+ value,
41
+ set: (updater) => {
42
+ dispatchOnFiber(fiber, () => {
43
+ const currentValue = newCell.value;
32
44
  const nextValue = typeof updater === "function"
33
45
  ? updater(currentValue)
34
46
  : updater;
35
- if (!Object.is(currentValue, nextValue)) {
36
- cell.value = nextValue;
37
- rerender(fiber);
47
+ if (isDevelopment &&
48
+ fiber.devStrictMode &&
49
+ typeof updater === "function") {
50
+ void updater(currentValue);
38
51
  }
39
- },
40
- };
41
- fiber.cells[index] = cell;
42
- }
43
- const cell = fiber.cells[index];
44
- if (cell.type !== "state") {
45
- throw new Error("Hook order changed between renders");
46
- }
47
- return cell;
52
+ if (Object.is(currentValue, nextValue))
53
+ return false;
54
+ newCell.value = nextValue;
55
+ return true;
56
+ });
57
+ },
58
+ };
59
+ fiber.cells[index] = newCell;
60
+ return newCell;
48
61
  }
49
62
  export function tapState(initial) {
50
63
  const cell = getStateCell(initial);
@@ -1 +1 @@
1
- {"version":3,"file":"tap-state.js","sourceRoot":"","sources":["../../src/hooks/tap-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,qCAAkC;AAOpE,MAAM,QAAQ,GAAG,CAAC,KAA8B,EAAE,EAAE;IAClD,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QACpB,8CAA8C;QAC9C,KAAK,CAAC,gBAAgB,EAAE,CAAC;IAC3B,CAAC;SAAM,IAAI,KAAK,CAAC,cAAc,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IACnD,CAAC;AACH,CAAC,CAAC;AAEF,SAAS,YAAY,CACnB,YAA2B;IAE3B,MAAM,KAAK,GAAG,uBAAuB,EAAE,CAAC;IACxC,MAAM,KAAK,GAAG,KAAK,CAAC,YAAY,EAAE,CAAC;IAEnC,mEAAmE;IACnE,IAAI,CAAC,KAAK,CAAC,aAAa,IAAI,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QACxD,MAAM,IAAI,KAAK,CACb,uDAAuD;YACrD,+DAA+D,CAClE,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QACxB,mCAAmC;QACnC,MAAM,KAAK,GACT,OAAO,YAAY,KAAK,UAAU;YAChC,CAAC,CAAE,YAAwB,EAAE;YAC7B,CAAC,CAAC,YAAY,CAAC;QAEnB,MAAM,IAAI,GAA6B;YACrC,IAAI,EAAE,OAAO;YACb,KAAK;YACL,GAAG,EAAE,CAAC,OAAiC,EAAE,EAAE;gBACzC,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC;gBAChC,MAAM,SAAS,GACb,OAAO,OAAO,KAAK,UAAU;oBAC3B,CAAC,CAAE,OAA0B,CAAC,YAAY,CAAC;oBAC3C,CAAC,CAAC,OAAO,CAAC;gBAEd,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,YAAY,EAAE,SAAS,CAAC,EAAE,CAAC;oBACxC,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;oBACvB,QAAQ,CAAC,KAAK,CAAC,CAAC;gBAClB,CAAC;YACH,CAAC;SACF,CAAC;QAEF,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IAC5B,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAChC,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IAED,OAAO,IAAgC,CAAC;AAC1C,CAAC;AASD,MAAM,UAAU,QAAQ,CACtB,OAAuB;IAEvB,MAAM,IAAI,GAAG,YAAY,CAAC,OAAwB,CAAC,CAAC;IAEpD,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;AAChC,CAAC"}
1
+ {"version":3,"file":"tap-state.js","sourceRoot":"","sources":["../../src/hooks/tap-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,uBAAoB;AAC5C,OAAO,EAAE,uBAAuB,EAAE,qCAAkC;AAOpE,MAAM,eAAe,GAAG,CACtB,KAA8B,EAC9B,QAAuB,EACvB,EAAE;IACF,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QACpB,8CAA8C;QAC9C,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;IACjC,CAAC;SAAM,IAAI,KAAK,CAAC,cAAc,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IACnD,CAAC;IAED,kBAAkB;AACpB,CAAC,CAAC;AAEF,SAAS,YAAY,CACnB,YAA2B;IAE3B,MAAM,KAAK,GAAG,uBAAuB,EAAE,CAAC;IACxC,MAAM,KAAK,GAAG,KAAK,CAAC,YAAY,EAAE,CAAC;IAEnC,mEAAmE;IACnE,IAAI,CAAC,KAAK,CAAC,aAAa,IAAI,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QACxD,MAAM,IAAI,KAAK,CACb,uDAAuD;YACrD,+DAA+D,CAClE,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAChC,IAAI,IAAI,EAAE,CAAC;QACT,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;YACvB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;QAExD,OAAO,IAAgC,CAAC;IAC1C,CAAC;IAED,MAAM,KAAK,GACT,OAAO,YAAY,KAAK,UAAU;QAChC,CAAC,CAAE,YAAwB,EAAE;QAC7B,CAAC,CAAC,YAAY,CAAC;IAEnB,IACE,aAAa;QACb,KAAK,CAAC,aAAa;QACnB,OAAO,YAAY,KAAK,UAAU,EAClC,CAAC;QACD,KAAM,YAAwB,EAAE,CAAC;IACnC,CAAC;IAED,MAAM,OAAO,GAA6B;QACxC,IAAI,EAAE,OAAO;QACb,KAAK;QACL,GAAG,EAAE,CAAC,OAAiC,EAAE,EAAE;YACzC,eAAe,CAAC,KAAK,EAAE,GAAG,EAAE;gBAC1B,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC;gBACnC,MAAM,SAAS,GACb,OAAO,OAAO,KAAK,UAAU;oBAC3B,CAAC,CAAE,OAA0B,CAAC,YAAY,CAAC;oBAC3C,CAAC,CAAC,OAAO,CAAC;gBAEd,IACE,aAAa;oBACb,KAAK,CAAC,aAAa;oBACnB,OAAO,OAAO,KAAK,UAAU,EAC7B,CAAC;oBACD,KAAM,OAA0B,CAAC,YAAY,CAAC,CAAC;gBACjD,CAAC;gBAED,IAAI,MAAM,CAAC,EAAE,CAAC,YAAY,EAAE,SAAS,CAAC;oBAAE,OAAO,KAAK,CAAC;gBAErD,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC;gBAC1B,OAAO,IAAI,CAAC;YACd,CAAC,CAAC,CAAC;QACL,CAAC;KACF,CAAC;IAEF,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC;IAC7B,OAAO,OAAO,CAAC;AACjB,CAAC;AASD,MAAM,UAAU,QAAQ,CACtB,OAAuB;IAEvB,MAAM,IAAI,GAAG,YAAY,CAAC,OAAwB,CAAC,CAAC;IAEpD,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;AAChC,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"depsShallowEqual.d.ts","sourceRoot":"","sources":["../../../src/hooks/utils/depsShallowEqual.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,GAC3B,GAAG,SAAS,OAAO,EAAE,EACrB,GAAG,SAAS,OAAO,EAAE,YAOtB,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"depsShallowEqual.js","sourceRoot":"","sources":["../../../src/hooks/utils/depsShallowEqual.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC9B,CAAqB,EACrB,CAAqB,EACrB,EAAE;IACF,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACxC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC;IAC3C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { Cell } from "../../core/types.js";
2
+ export declare const tapHook: <T extends "effect" | "memo">(type: T, init: () => Cell) => Cell & {
3
+ type: T;
4
+ };
5
+ export declare const registerRenderMountTask: (task: () => void) => void;
6
+ //# sourceMappingURL=tapHook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tapHook.d.ts","sourceRoot":"","sources":["../../../src/hooks/utils/tapHook.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,4BAAyB;AAExC,eAAO,MAAM,OAAO,GAAI,CAAC,SAAS,QAAQ,GAAG,MAAM,EACjD,MAAM,CAAC,EACP,MAAM,MAAM,IAAI,KACf,IAAI,GAAG;IAAE,IAAI,EAAE,CAAC,CAAA;CAuBlB,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,MAAM,MAAM,IAAI,SAGvD,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { getCurrentResourceFiber } from "../../core/execution-context.js";
2
+ export const tapHook = (type, init) => {
3
+ const fiber = getCurrentResourceFiber();
4
+ const index = fiber.currentIndex++;
5
+ if (!fiber.isFirstRender && index >= fiber.cells.length) {
6
+ // Check if we're trying to use more hooks than in previous renders
7
+ throw new Error("Rendered more hooks than during the previous render. " +
8
+ "Hooks must be called in the exact same order in every render.");
9
+ }
10
+ let cell = fiber.cells[index];
11
+ if (!cell) {
12
+ cell = init();
13
+ fiber.cells[index] = cell;
14
+ }
15
+ if (cell.type !== type) {
16
+ throw new Error("Hook order changed between renders");
17
+ }
18
+ return cell;
19
+ };
20
+ export const registerRenderMountTask = (task) => {
21
+ const fiber = getCurrentResourceFiber();
22
+ fiber.renderContext.commitTasks.push(task);
23
+ };
24
+ //# sourceMappingURL=tapHook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tapHook.js","sourceRoot":"","sources":["../../../src/hooks/utils/tapHook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,wCAAqC;AAGvE,MAAM,CAAC,MAAM,OAAO,GAAG,CACrB,IAAO,EACP,IAAgB,EACI,EAAE;IACtB,MAAM,KAAK,GAAG,uBAAuB,EAAE,CAAC;IACxC,MAAM,KAAK,GAAG,KAAK,CAAC,YAAY,EAAE,CAAC;IAEnC,IAAI,CAAC,KAAK,CAAC,aAAa,IAAI,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QACxD,mEAAmE;QACnE,MAAM,IAAI,KAAK,CACb,uDAAuD;YACrD,+DAA+D,CAClE,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC9B,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,GAAG,IAAI,EAAE,CAAC;QACd,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IAC5B,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IAED,OAAO,IAA0B,CAAC;AACpC,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,IAAgB,EAAE,EAAE;IAC1D,MAAM,KAAK,GAAG,uBAAuB,EAAE,CAAC;IACxC,KAAK,CAAC,aAAc,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC9C,CAAC,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  export { resource } from "./core/resource.js";
2
+ export { withKey } from "./core/withKey.js";
2
3
  export { tapState } from "./hooks/tap-state.js";
3
4
  export { tapEffect } from "./hooks/tap-effect.js";
4
5
  export { tapRef } from "./hooks/tap-ref.js";
6
+ export { tapConst } from "./hooks/tap-const.js";
5
7
  export { tapMemo } from "./hooks/tap-memo.js";
6
8
  export { tapCallback } from "./hooks/tap-callback.js";
7
9
  export { tapEffectEvent } from "./hooks/tap-effect-event.js";
@@ -9,7 +11,7 @@ export { tapResource } from "./hooks/tap-resource.js";
9
11
  export { tapInlineResource } from "./hooks/tap-inline-resource.js";
10
12
  export { tapResources } from "./hooks/tap-resources.js";
11
13
  export { createResource } from "./core/createResource.js";
12
- export { flushSync } from "./core/scheduler.js";
13
- export { createContext, tapContext, withContextProvider } from "./core/context.js";
14
- export type { Resource, ContravariantResource, ResourceElement, ExtractResourceOutput, } from "./core/types.js";
14
+ export { flushResourcesSync } from "./core/scheduler.js";
15
+ export { createResourceContext, tap, withContextProvider, } from "./core/context.js";
16
+ export type { Resource, ContravariantResource, ResourceElement, } from "./core/types.js";
15
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,2BAAwB;AAG3C,OAAO,EAAE,QAAQ,EAAE,6BAA0B;AAC7C,OAAO,EAAE,SAAS,EAAE,8BAA2B;AAG/C,OAAO,EAAE,MAAM,EAAE,2BAAwB;AACzC,OAAO,EAAE,OAAO,EAAE,4BAAyB;AAC3C,OAAO,EAAE,WAAW,EAAE,gCAA6B;AACnD,OAAO,EAAE,cAAc,EAAE,oCAAiC;AAG1D,OAAO,EAAE,WAAW,EAAE,gCAA6B;AACnD,OAAO,EAAE,iBAAiB,EAAE,uCAAoC;AAChE,OAAO,EAAE,YAAY,EAAE,iCAA8B;AAGrD,OAAO,EAAE,cAAc,EAAE,iCAA8B;AACvD,OAAO,EAAE,SAAS,EAAE,4BAAyB;AAG7C,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,mBAAmB,EAAE,0BAAuB;AAGhF,YAAY,EACV,QAAQ,EACR,qBAAqB,EACrB,eAAe,EACf,qBAAqB,GACtB,wBAAqB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,2BAAwB;AAC3C,OAAO,EAAE,OAAO,EAAE,0BAAuB;AAGzC,OAAO,EAAE,QAAQ,EAAE,6BAA0B;AAC7C,OAAO,EAAE,SAAS,EAAE,8BAA2B;AAG/C,OAAO,EAAE,MAAM,EAAE,2BAAwB;AACzC,OAAO,EAAE,QAAQ,EAAE,6BAA0B;AAC7C,OAAO,EAAE,OAAO,EAAE,4BAAyB;AAC3C,OAAO,EAAE,WAAW,EAAE,gCAA6B;AACnD,OAAO,EAAE,cAAc,EAAE,oCAAiC;AAG1D,OAAO,EAAE,WAAW,EAAE,gCAA6B;AACnD,OAAO,EAAE,iBAAiB,EAAE,uCAAoC;AAChE,OAAO,EAAE,YAAY,EAAE,iCAA8B;AAGrD,OAAO,EAAE,cAAc,EAAE,iCAA8B;AACvD,OAAO,EAAE,kBAAkB,EAAE,4BAAyB;AAGtD,OAAO,EACL,qBAAqB,EACrB,GAAG,EACH,mBAAmB,GACpB,0BAAuB;AAGxB,YAAY,EACV,QAAQ,EACR,qBAAqB,EACrB,eAAe,GAChB,wBAAqB"}
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  export { resource } from "./core/resource.js";
2
+ export { withKey } from "./core/withKey.js";
2
3
  // primitive hooks
3
4
  export { tapState } from "./hooks/tap-state.js";
4
5
  export { tapEffect } from "./hooks/tap-effect.js";
5
6
  // utility hooks
6
7
  export { tapRef } from "./hooks/tap-ref.js";
8
+ export { tapConst } from "./hooks/tap-const.js";
7
9
  export { tapMemo } from "./hooks/tap-memo.js";
8
10
  export { tapCallback } from "./hooks/tap-callback.js";
9
11
  export { tapEffectEvent } from "./hooks/tap-effect-event.js";
@@ -13,7 +15,7 @@ export { tapInlineResource } from "./hooks/tap-inline-resource.js";
13
15
  export { tapResources } from "./hooks/tap-resources.js";
14
16
  // imperative
15
17
  export { createResource } from "./core/createResource.js";
16
- export { flushSync } from "./core/scheduler.js";
18
+ export { flushResourcesSync } from "./core/scheduler.js";
17
19
  // context
18
- export { createContext, tapContext, withContextProvider } from "./core/context.js";
20
+ export { createResourceContext, tap, withContextProvider, } from "./core/context.js";
19
21
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,2BAAwB;AAE3C,kBAAkB;AAClB,OAAO,EAAE,QAAQ,EAAE,6BAA0B;AAC7C,OAAO,EAAE,SAAS,EAAE,8BAA2B;AAE/C,gBAAgB;AAChB,OAAO,EAAE,MAAM,EAAE,2BAAwB;AACzC,OAAO,EAAE,OAAO,EAAE,4BAAyB;AAC3C,OAAO,EAAE,WAAW,EAAE,gCAA6B;AACnD,OAAO,EAAE,cAAc,EAAE,oCAAiC;AAE1D,YAAY;AACZ,OAAO,EAAE,WAAW,EAAE,gCAA6B;AACnD,OAAO,EAAE,iBAAiB,EAAE,uCAAoC;AAChE,OAAO,EAAE,YAAY,EAAE,iCAA8B;AAErD,aAAa;AACb,OAAO,EAAE,cAAc,EAAE,iCAA8B;AACvD,OAAO,EAAE,SAAS,EAAE,4BAAyB;AAE7C,UAAU;AACV,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,mBAAmB,EAAE,0BAAuB"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,2BAAwB;AAC3C,OAAO,EAAE,OAAO,EAAE,0BAAuB;AAEzC,kBAAkB;AAClB,OAAO,EAAE,QAAQ,EAAE,6BAA0B;AAC7C,OAAO,EAAE,SAAS,EAAE,8BAA2B;AAE/C,gBAAgB;AAChB,OAAO,EAAE,MAAM,EAAE,2BAAwB;AACzC,OAAO,EAAE,QAAQ,EAAE,6BAA0B;AAC7C,OAAO,EAAE,OAAO,EAAE,4BAAyB;AAC3C,OAAO,EAAE,WAAW,EAAE,gCAA6B;AACnD,OAAO,EAAE,cAAc,EAAE,oCAAiC;AAE1D,YAAY;AACZ,OAAO,EAAE,WAAW,EAAE,gCAA6B;AACnD,OAAO,EAAE,iBAAiB,EAAE,uCAAoC;AAChE,OAAO,EAAE,YAAY,EAAE,iCAA8B;AAErD,aAAa;AACb,OAAO,EAAE,cAAc,EAAE,iCAA8B;AACvD,OAAO,EAAE,kBAAkB,EAAE,4BAAyB;AAEtD,UAAU;AACV,OAAO,EACL,qBAAqB,EACrB,GAAG,EACH,mBAAmB,GACpB,0BAAuB"}
@@ -1,3 +1,3 @@
1
- import { ExtractResourceOutput, ResourceElement } from "../core/types.js";
2
- export declare function useResource<E extends ResourceElement<any, any>>(element: E): ExtractResourceOutput<E>;
1
+ import type { ExtractResourceReturnType, ResourceElement } from "../core/types.js";
2
+ export declare function useResource<E extends ResourceElement<any, any>>(element: E): ExtractResourceReturnType<E>;
3
3
  //# sourceMappingURL=use-resource.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-resource.d.ts","sourceRoot":"","sources":["../../src/react/use-resource.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,yBAAsB;AAevE,wBAAgB,WAAW,CAAC,CAAC,SAAS,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,EAC7D,OAAO,EAAE,CAAC,GACT,qBAAqB,CAAC,CAAC,CAAC,CAgB1B"}
1
+ {"version":3,"file":"use-resource.d.ts","sourceRoot":"","sources":["../../src/react/use-resource.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,yBAAyB,EAAE,eAAe,EAAE,yBAAsB;AAuBhF,wBAAgB,WAAW,CAAC,CAAC,SAAS,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,EAC7D,OAAO,EAAE,CAAC,GACT,yBAAyB,CAAC,CAAC,CAAC,CAmB9B"}
@@ -1,19 +1,33 @@
1
- import { useEffect, useLayoutEffect, useMemo, useState } from "react";
1
+ import { useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
2
2
  import { createResourceFiber, unmountResourceFiber, renderResourceFiber, commitResourceFiber, } from "../core/ResourceFiber.js";
3
- const shouldAvoidLayoutEffect = globalThis.__ASSISTANT_UI_DISABLE_LAYOUT_EFFECT__ === true;
4
- const useIsomorphicLayoutEffect = shouldAvoidLayoutEffect
5
- ? useEffect
6
- : useLayoutEffect;
3
+ import { isDevelopment } from "../core/env.js";
4
+ const useDevStrictMode = () => {
5
+ if (!isDevelopment)
6
+ return null;
7
+ const count = useRef(0);
8
+ const isFirstRender = count.current === 0;
9
+ useState(() => count.current++);
10
+ if (count.current !== 2)
11
+ return null;
12
+ return isFirstRender ? "child" : "root";
13
+ };
14
+ const resourceReducer = (version, callback) => {
15
+ return version + (callback() ? 1 : 0);
16
+ };
7
17
  export function useResource(element) {
8
- const [, rerender] = useState({});
9
- const fiber = useMemo(() => createResourceFiber(element.type, () => rerender({})), [element.type]);
18
+ const [, dispatch] = useReducer(resourceReducer, 0);
19
+ const devStrictMode = useDevStrictMode();
20
+ // biome-ignore lint/correctness/useExhaustiveDependencies: user provided deps instead of prop identity
21
+ const fiber = useMemo(() => {
22
+ return createResourceFiber(element.type, dispatch, devStrictMode);
23
+ }, [element.type, element.key]);
10
24
  const result = renderResourceFiber(fiber, element.props);
11
- useIsomorphicLayoutEffect(() => {
25
+ useLayoutEffect(() => {
12
26
  return () => unmountResourceFiber(fiber);
13
27
  }, [fiber]);
14
- useIsomorphicLayoutEffect(() => {
28
+ useLayoutEffect(() => {
15
29
  commitResourceFiber(fiber, result);
16
30
  });
17
- return result.state;
31
+ return result.output;
18
32
  }
19
33
  //# sourceMappingURL=use-resource.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-resource.js","sourceRoot":"","sources":["../../src/react/use-resource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEtE,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,GACpB,iCAA8B;AAE/B,MAAM,uBAAuB,GAC1B,UAAkB,CAAC,sCAAsC,KAAK,IAAI,CAAC;AAEtE,MAAM,yBAAyB,GAAG,uBAAuB;IACvD,CAAC,CAAC,SAAS;IACX,CAAC,CAAC,eAAe,CAAC;AAEpB,MAAM,UAAU,WAAW,CACzB,OAAU;IAEV,MAAM,CAAC,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,mBAAmB,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,EAC3D,CAAC,OAAO,CAAC,IAAI,CAAC,CACf,CAAC;IAEF,MAAM,MAAM,GAAG,mBAAmB,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACzD,yBAAyB,CAAC,GAAG,EAAE;QAC7B,OAAO,GAAG,EAAE,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAC3C,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IACZ,yBAAyB,CAAC,GAAG,EAAE;QAC7B,mBAAmB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC,KAAK,CAAC;AACtB,CAAC"}
1
+ {"version":3,"file":"use-resource.js","sourceRoot":"","sources":["../../src/react/use-resource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAE/E,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,GACpB,iCAA8B;AAC/B,OAAO,EAAE,aAAa,EAAE,uBAAoB;AAE5C,MAAM,gBAAgB,GAAG,GAAG,EAAE;IAC5B,IAAI,CAAC,aAAa;QAAE,OAAO,IAAI,CAAC;IAEhC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACxB,MAAM,aAAa,GAAG,KAAK,CAAC,OAAO,KAAK,CAAC,CAAC;IAC1C,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IAChC,IAAI,KAAK,CAAC,OAAO,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACrC,OAAO,aAAa,CAAC,CAAC,CAAE,OAAiB,CAAC,CAAC,CAAE,MAAgB,CAAC;AAChE,CAAC,CAAC;AAEF,MAAM,eAAe,GAAG,CAAC,OAAe,EAAE,QAAuB,EAAE,EAAE;IACnE,OAAO,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACxC,CAAC,CAAC;AAEF,MAAM,UAAU,WAAW,CACzB,OAAU;IAEV,MAAM,CAAC,EAAE,QAAQ,CAAC,GAAG,UAAU,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;IAEpD,MAAM,aAAa,GAAG,gBAAgB,EAAE,CAAC;IAEzC,uGAAuG;IACvG,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE;QACzB,OAAO,mBAAmB,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;IACpE,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IAEhC,MAAM,MAAM,GAAG,mBAAmB,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACzD,eAAe,CAAC,GAAG,EAAE;QACnB,OAAO,GAAG,EAAE,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAC3C,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IACZ,eAAe,CAAC,GAAG,EAAE;QACnB,mBAAmB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC,MAAM,CAAC;AACvB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/tap",
3
- "version": "0.3.6",
3
+ "version": "0.4.0",
4
4
  "description": "Zero-dependency reactive state management inspired by React hooks",
5
5
  "keywords": [
6
6
  "state-management",
@@ -46,8 +46,15 @@
46
46
  }
47
47
  },
48
48
  "devDependencies": {
49
+ "@testing-library/dom": "^10.4.1",
50
+ "@testing-library/react": "^16.3.1",
49
51
  "@types/react": "^19.2.9",
52
+ "@types/node": "^25.0.3",
53
+ "@types/react-dom": "^19.2.3",
50
54
  "@vitest/ui": "^4.0.17",
55
+ "react": "19.2.3",
56
+ "react-dom": "19.2.3",
57
+ "jsdom": "^27.3.0",
51
58
  "vitest": "^4.0.17",
52
59
  "@assistant-ui/x-buildutils": "0.0.1"
53
60
  },
@@ -13,13 +13,13 @@ describe("ResourceHandle - Basic Usage", () => {
13
13
  const handle = createResource(TestResource(5));
14
14
 
15
15
  // The handle provides a const API
16
- expect(typeof handle.getState).toBe("function");
16
+ expect(typeof handle.getValue).toBe("function");
17
17
  expect(typeof handle.subscribe).toBe("function");
18
18
  expect(typeof handle.render).toBe("function");
19
19
 
20
20
  // Initial state
21
- expect(handle.getState().value).toBe(10);
22
- expect(handle.getState().propsUsed).toBe(5);
21
+ expect(handle.getValue().value).toBe(10);
22
+ expect(handle.getValue().propsUsed).toBe(5);
23
23
  });
24
24
 
25
25
  it("should allow updating props", () => {
@@ -29,7 +29,7 @@ describe("ResourceHandle - Basic Usage", () => {
29
29
  const handle = createResource(TestResource({ multiplier: 2 }));
30
30
 
31
31
  // Initial state
32
- expect(handle.getState().result).toBe(20);
32
+ expect(handle.getValue().result).toBe(20);
33
33
 
34
34
  // Can call render to update props
35
35
  expect(() => handle.render(TestResource({ multiplier: 3 }))).not.toThrow();
@@ -60,7 +60,7 @@ describe("tapEffect - Basic Functionality", () => {
60
60
  expect(cleanup).toHaveBeenCalledTimes(1);
61
61
  });
62
62
 
63
- it("should cleanup effects in reverse order", () => {
63
+ it("should cleanup effects in same order", () => {
64
64
  const cleanupOrder: string[] = [];
65
65
 
66
66
  const testFiber = createTestResource(() => {
@@ -84,7 +84,7 @@ describe("tapEffect - Basic Functionality", () => {
84
84
  manager.cleanup();
85
85
 
86
86
  // Cleanup should run in reverse order (LIFO)
87
- expect(cleanupOrder).toEqual(["third", "second", "first"]);
87
+ expect(cleanupOrder).toEqual(["first", "second", "third"]);
88
88
  });
89
89
  });
90
90
 
@@ -131,6 +131,7 @@ describe("tapEffect - Basic Functionality", () => {
131
131
  }, []);
132
132
 
133
133
  // Effect with deps - runs when deps change
134
+ // biome-ignore lint/correctness/useExhaustiveDependencies: test
134
135
  tapEffect(() => {
135
136
  effectCalls.conditional++;
136
137
  }, [props.value]);
@@ -2,6 +2,7 @@ import { describe, it, expect, afterEach } from "vitest";
2
2
  import { tapResources } from "../../hooks/tap-resources";
3
3
  import { tapState } from "../../hooks/tap-state";
4
4
  import { resource } from "../../core/resource";
5
+ import { withKey } from "../../core/withKey";
5
6
  import {
6
7
  createTestResource,
7
8
  renderTest,
@@ -59,8 +60,11 @@ describe("tapResources - Basic Functionality", () => {
59
60
  it("should render multiple resources with keys", () => {
60
61
  const testFiber = createTestResource(() => {
61
62
  const results = tapResources(
62
- { a: 10, b: 20, c: 30 },
63
- (value) => SimpleCounter({ value }),
63
+ () => [
64
+ withKey("a", SimpleCounter({ value: 10 })),
65
+ withKey("b", SimpleCounter({ value: 20 })),
66
+ withKey("c", SimpleCounter({ value: 30 })),
67
+ ],
64
68
  [],
65
69
  );
66
70
 
@@ -68,11 +72,7 @@ describe("tapResources - Basic Functionality", () => {
68
72
  });
69
73
 
70
74
  const result = renderTest(testFiber, undefined);
71
- expect(result).toEqual({
72
- a: { count: 10 },
73
- b: { count: 20 },
74
- c: { count: 30 },
75
- });
75
+ expect(result).toEqual([{ count: 10 }, { count: 20 }, { count: 30 }]);
76
76
  });
77
77
 
78
78
  it("should work with resource constructor syntax", () => {
@@ -82,15 +82,17 @@ describe("tapResources - Basic Functionality", () => {
82
82
  });
83
83
 
84
84
  const testFiber = createTestResource(() => {
85
- const items = {
86
- first: { value: 5 },
87
- second: { value: 10 },
88
- third: { value: 15 },
89
- };
85
+ const items = [
86
+ { key: "first", value: 5 },
87
+ { key: "second", value: 10 },
88
+ { key: "third", value: 15 },
89
+ ];
90
90
 
91
91
  const results = tapResources(
92
- items,
93
- (item) => Counter({ value: item.value }),
92
+ () =>
93
+ items.map((item) =>
94
+ withKey(item.key, Counter({ value: item.value })),
95
+ ),
94
96
  [],
95
97
  );
96
98
 
@@ -98,56 +100,66 @@ describe("tapResources - Basic Functionality", () => {
98
100
  });
99
101
 
100
102
  const result = renderTest(testFiber, undefined);
101
- expect(result).toEqual({
102
- first: { count: 5, double: 10 },
103
- second: { count: 10, double: 20 },
104
- third: { count: 15, double: 30 },
105
- });
103
+ expect(result).toEqual([
104
+ { count: 5, double: 10 },
105
+ { count: 10, double: 20 },
106
+ { count: 15, double: 30 },
107
+ ]);
106
108
  });
107
109
  });
108
110
 
109
111
  describe("Instance Preservation", () => {
110
112
  it("should maintain resource instances when keys remain the same", () => {
111
113
  const testFiber = createTestResource(
112
- (props: { items: Record<string, { value: number; id: string }> }) => {
113
- return tapResources(
114
- props.items,
115
- (item) => TrackingCounter({ value: item.value, id: item.id }),
116
-
117
- [],
118
- );
114
+ (props: {
115
+ items: Array<{ key: string; value: number; id: string }>;
116
+ }) => {
117
+ return tapResources(() => {
118
+ return props.items.map((item) =>
119
+ withKey(
120
+ item.key,
121
+ TrackingCounter({ value: item.value, id: item.id }),
122
+ ),
123
+ );
124
+ }, [props.items]);
119
125
  },
120
126
  );
121
127
 
122
128
  // Initial render
123
129
  const result1 = renderTest(testFiber, {
124
- items: { a: { value: 1, id: "a" }, b: { value: 2, id: "b" } },
130
+ items: [
131
+ { key: "a", value: 1, id: "a" },
132
+ { key: "b", value: 2, id: "b" },
133
+ ],
125
134
  });
126
135
 
127
136
  // Verify initial state
128
- expect(result1.a).toMatchObject({
137
+ expect(result1[0]).toMatchObject({
129
138
  id: "a",
130
139
  value: 1,
131
140
  renderCount: 1,
132
141
  });
133
- expect(result1.b).toMatchObject({
142
+ expect(result1[1]).toMatchObject({
134
143
  id: "b",
135
144
  value: 2,
136
145
  renderCount: 1,
137
146
  });
138
147
 
139
- // Re-render with same keys but different values
148
+ // Re-render with same keys but different order and values
140
149
  const result2 = renderTest(testFiber, {
141
- items: { b: { value: 20, id: "b" }, a: { value: 10, id: "a" } },
150
+ items: [
151
+ { key: "b", value: 20, id: "b" },
152
+ { key: "a", value: 10, id: "a" },
153
+ ],
142
154
  });
143
155
 
144
- // Verify instances are preserved
145
- expect(result2.b).toMatchObject({
156
+ // Verify instances are preserved (renderCount should be 2)
157
+ expect(result2[0]).toMatchObject({
146
158
  id: "b",
147
159
  value: 20,
148
160
  renderCount: 2,
149
161
  });
150
- expect(result2.a).toMatchObject({
162
+ expect(result2[1]).toMatchObject({
151
163
  id: "a",
152
164
  value: 10,
153
165
  renderCount: 2,
@@ -158,65 +170,73 @@ describe("tapResources - Basic Functionality", () => {
158
170
  describe("Dynamic List Management", () => {
159
171
  it("should handle adding and removing resources", () => {
160
172
  const testFiber = createTestResource(
161
- (props: { items: Record<string, number> }) => {
162
- const results = tapResources(
163
- props.items,
164
- (value) => SimpleCounter({ value }),
165
-
166
- [],
167
- );
173
+ (props: { items: Array<{ key: string; value: number }> }) => {
174
+ const results = tapResources(() => {
175
+ return props.items.map((item) =>
176
+ withKey(item.key, SimpleCounter({ value: item.value })),
177
+ );
178
+ }, [props.items]);
168
179
  return results;
169
180
  },
170
181
  );
171
182
 
172
183
  // Initial render with 3 items
173
- const result1 = renderTest(testFiber, { items: { a: 0, b: 10, c: 20 } });
174
- expect(result1).toEqual({
175
- a: { count: 0 },
176
- b: { count: 10 },
177
- c: { count: 20 },
184
+ const result1 = renderTest(testFiber, {
185
+ items: [
186
+ { key: "a", value: 0 },
187
+ { key: "b", value: 10 },
188
+ { key: "c", value: 20 },
189
+ ],
178
190
  });
191
+ expect(result1).toEqual([{ count: 0 }, { count: 10 }, { count: 20 }]);
179
192
 
180
193
  // Remove middle item
181
- const result2 = renderTest(testFiber, { items: { a: 0, c: 10 } });
182
- expect(result2).toEqual({
183
- a: { count: 0 },
184
- c: { count: 10 },
194
+ const result2 = renderTest(testFiber, {
195
+ items: [
196
+ { key: "a", value: 0 },
197
+ { key: "c", value: 10 },
198
+ ],
185
199
  });
200
+ expect(result2).toEqual([{ count: 0 }, { count: 10 }]);
186
201
 
187
202
  // Add new item
188
- const result3 = renderTest(testFiber, { items: { a: 0, c: 10, d: 20 } });
189
- expect(result3).toEqual({
190
- a: { count: 0 },
191
- c: { count: 10 },
192
- d: { count: 20 },
203
+ const result3 = renderTest(testFiber, {
204
+ items: [
205
+ { key: "a", value: 0 },
206
+ { key: "c", value: 10 },
207
+ { key: "d", value: 20 },
208
+ ],
193
209
  });
210
+ expect(result3).toEqual([{ count: 0 }, { count: 10 }, { count: 20 }]);
194
211
  });
195
212
 
196
213
  it("should handle changing resource types for the same key", () => {
197
214
  const testFiber = createTestResource((props: { useCounter: boolean }) => {
198
215
  const results = tapResources(
199
- { item: props.useCounter },
200
- (useCounter) =>
201
- useCounter
202
- ? StatefulCounter({ initial: 42 })
203
- : Display({ text: "Hello" }),
204
- [],
216
+ () => [
217
+ withKey(
218
+ "item",
219
+ props.useCounter
220
+ ? StatefulCounter({ initial: 42 })
221
+ : Display({ text: "Hello" }),
222
+ ),
223
+ ],
224
+ [props.useCounter],
205
225
  );
206
226
  return results;
207
227
  });
208
228
 
209
229
  // Start with Counter
210
230
  const result1 = renderTest(testFiber, { useCounter: true });
211
- expect(result1).toEqual({ item: { count: 42 } });
231
+ expect(result1).toEqual([{ count: 42 }]);
212
232
 
213
233
  // Switch to Display
214
234
  const result2 = renderTest(testFiber, { useCounter: false });
215
- expect(result2).toEqual({ item: { type: "display", text: "Hello" } });
235
+ expect(result2).toEqual([{ type: "display", text: "Hello" }]);
216
236
 
217
237
  // Switch back to Counter (new instance)
218
238
  const result3 = renderTest(testFiber, { useCounter: true });
219
- expect(result3).toEqual({ item: { count: 42 } });
239
+ expect(result3).toEqual([{ count: 42 }]);
220
240
  });
221
241
  });
222
242
  });