@assistant-ui/tap 0.2.2 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +151 -20
- package/dist/__tests__/test-utils.d.ts +2 -2
- package/dist/__tests__/test-utils.d.ts.map +1 -1
- package/dist/__tests__/test-utils.js +3 -2
- package/dist/__tests__/test-utils.js.map +1 -1
- package/dist/core/ResourceFiber.d.ts +2 -2
- package/dist/core/ResourceFiber.d.ts.map +1 -1
- package/dist/core/ResourceFiber.js +4 -3
- package/dist/core/ResourceFiber.js.map +1 -1
- package/dist/core/ResourceHandle.d.ts +11 -8
- package/dist/core/ResourceHandle.d.ts.map +1 -1
- package/dist/core/ResourceHandle.js +37 -34
- package/dist/core/ResourceHandle.js.map +1 -1
- package/dist/core/callResourceFn.d.ts +2 -0
- package/dist/core/callResourceFn.d.ts.map +1 -0
- package/dist/core/{getResourceFn.js → callResourceFn.js} +6 -6
- package/dist/core/callResourceFn.js.map +1 -0
- package/dist/core/resource.d.ts +3 -2
- package/dist/core/resource.d.ts.map +1 -1
- package/dist/core/resource.js +1 -1
- package/dist/core/resource.js.map +1 -1
- package/dist/core/types.d.ts +10 -10
- package/dist/core/types.d.ts.map +1 -1
- package/dist/hooks/tap-effect-event.d.ts +17 -0
- package/dist/hooks/tap-effect-event.d.ts.map +1 -0
- package/dist/hooks/tap-effect-event.js +14 -0
- package/dist/hooks/tap-effect-event.js.map +1 -0
- package/dist/hooks/tap-effect.d.ts +6 -3
- package/dist/hooks/tap-effect.d.ts.map +1 -1
- package/dist/hooks/tap-effect.js.map +1 -1
- package/dist/hooks/tap-inline-resource.js +2 -2
- package/dist/hooks/tap-inline-resource.js.map +1 -1
- package/dist/hooks/tap-ref.d.ts +6 -4
- package/dist/hooks/tap-ref.d.ts.map +1 -1
- package/dist/hooks/tap-ref.js.map +1 -1
- package/dist/hooks/tap-resource.d.ts.map +1 -1
- package/dist/hooks/tap-resource.js +1 -2
- package/dist/hooks/tap-resource.js.map +1 -1
- package/dist/hooks/tap-resources.d.ts.map +1 -1
- package/dist/hooks/tap-resources.js +2 -6
- package/dist/hooks/tap-resources.js.map +1 -1
- package/dist/hooks/tap-state.d.ts +6 -3
- package/dist/hooks/tap-state.d.ts.map +1 -1
- package/dist/hooks/tap-state.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/react/use-resource.d.ts.map +1 -1
- package/dist/react/use-resource.js +1 -2
- package/dist/react/use-resource.js.map +1 -1
- package/package.json +14 -3
- package/dist/core/getResourceFn.d.ts +0 -2
- package/dist/core/getResourceFn.d.ts.map +0 -1
- package/dist/core/getResourceFn.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @assistant-ui/tap
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**tap** (Reactive Resources) is a zero-dependency reactive state management library that brings **React's hooks mental model to state management outside of React components**.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,9 +8,34 @@ Zero-dependency reactive state management inspired by React hooks.
|
|
|
8
8
|
npm install @assistant-ui/tap
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## What is tap?
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Instead of limiting hooks to React components, tap lets you use the same familiar hooks pattern (`useState`, `useEffect`, `useMemo`, etc.) to create self-contained, reusable units of reactive state and logic called **Resources** that can be used anywhere - in vanilla JavaScript, servers, or outside of React.
|
|
14
|
+
|
|
15
|
+
## Philosophy
|
|
16
|
+
|
|
17
|
+
- **Unified mental model**: Use the same hooks pattern everywhere
|
|
18
|
+
- **Framework agnostic**: Zero dependencies, works with or without React
|
|
19
|
+
- **Lifecycle management**: Resources handle their own cleanup automatically
|
|
20
|
+
- **Type-safe**: Full TypeScript support with proper type inference
|
|
21
|
+
|
|
22
|
+
## How It Works
|
|
23
|
+
|
|
24
|
+
tap implements a **render-commit pattern** similar to React:
|
|
25
|
+
|
|
26
|
+
### Render Phase
|
|
27
|
+
|
|
28
|
+
1. Each resource instance has a "fiber" that tracks state and effects
|
|
29
|
+
2. When a resource function runs, hooks record their data in the fiber
|
|
30
|
+
3. The library maintains an execution context to track which fiber's hooks are being called
|
|
31
|
+
4. Each hook stores its data in cells indexed by call order (enforcing React's rules)
|
|
32
|
+
|
|
33
|
+
### Commit Phase
|
|
34
|
+
|
|
35
|
+
1. After render, collected effect tasks are processed
|
|
36
|
+
2. Effects check if dependencies changed using shallow equality
|
|
37
|
+
3. Old effects are cleaned up before new ones run
|
|
38
|
+
4. Updates are batched using microtasks to prevent excessive re-renders
|
|
14
39
|
|
|
15
40
|
## Core Concepts
|
|
16
41
|
|
|
@@ -18,8 +43,9 @@ This library brings React's hooks mental model to state management outside of Re
|
|
|
18
43
|
|
|
19
44
|
Resources are self-contained units of reactive state and logic. They follow the same rules as React hooks:
|
|
20
45
|
|
|
21
|
-
- Hooks must be called in the same order every render
|
|
22
|
-
- Hooks
|
|
46
|
+
- **Hook Order**: Hooks must be called in the same order in every render
|
|
47
|
+
- **No Conditional Hooks**: Can't call hooks inside conditionals or loops
|
|
48
|
+
- **No Async Hooks**: Hooks must be called synchronously during render
|
|
23
49
|
- Resources automatically handle cleanup and lifecycle
|
|
24
50
|
|
|
25
51
|
### Creating Resources
|
|
@@ -153,26 +179,47 @@ const Timer = resource(() => {
|
|
|
153
179
|
Renders multiple resources with keys, similar to React's list rendering. All resources must have a unique `key` property.
|
|
154
180
|
|
|
155
181
|
```typescript
|
|
156
|
-
// Using with createResourceNodeConstructor
|
|
157
182
|
const TodoItem = resource((props: { text: string }) => {
|
|
158
183
|
const [completed, setCompleted] = tapState(false);
|
|
159
184
|
return { text: props.text, completed, setCompleted };
|
|
160
185
|
});
|
|
161
186
|
|
|
162
|
-
const
|
|
187
|
+
const TodoList = resource(() => {
|
|
163
188
|
const todos = [
|
|
164
|
-
{ id: "1", text: "Learn
|
|
189
|
+
{ id: "1", text: "Learn tap" },
|
|
165
190
|
{ id: "2", text: "Build something awesome" },
|
|
166
191
|
];
|
|
167
192
|
|
|
168
|
-
const
|
|
193
|
+
const todoItems = tapResources(
|
|
169
194
|
todos.map((todo) => new TodoItem({ text: todo.text }, { key: todo.id })),
|
|
170
195
|
);
|
|
171
196
|
|
|
172
|
-
return
|
|
197
|
+
return todoItems;
|
|
173
198
|
});
|
|
174
199
|
```
|
|
175
200
|
|
|
201
|
+
### `tapContext` and Context Support
|
|
202
|
+
|
|
203
|
+
Create and use context to pass values through resource boundaries without prop drilling.
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import {
|
|
207
|
+
createContext,
|
|
208
|
+
tapContext,
|
|
209
|
+
withContextProvider,
|
|
210
|
+
} from "@assistant-ui/tap";
|
|
211
|
+
|
|
212
|
+
const MyContext = createContext(defaultValue);
|
|
213
|
+
|
|
214
|
+
// Provide context
|
|
215
|
+
withContextProvider(MyContext, value, () => {
|
|
216
|
+
// Inside this function, tapContext can access the value
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Access context in a resource
|
|
220
|
+
const value = tapContext(MyContext);
|
|
221
|
+
```
|
|
222
|
+
|
|
176
223
|
## Resource Management
|
|
177
224
|
|
|
178
225
|
### `createResource`
|
|
@@ -196,21 +243,36 @@ const unsubscribe = handle.subscribe(() => {
|
|
|
196
243
|
handle.updateInput({ incrementBy: 2 });
|
|
197
244
|
|
|
198
245
|
// Cleanup
|
|
246
|
+
handle.dispose();
|
|
199
247
|
unsubscribe();
|
|
200
248
|
```
|
|
201
249
|
|
|
202
|
-
##
|
|
250
|
+
## React Integration
|
|
203
251
|
|
|
204
|
-
|
|
252
|
+
Use resources directly in React components with the `useResource` hook:
|
|
205
253
|
|
|
206
|
-
|
|
254
|
+
```typescript
|
|
255
|
+
import { useResource } from "@assistant-ui/tap/react";
|
|
256
|
+
|
|
257
|
+
function MyComponent() {
|
|
258
|
+
const state = useResource(new Counter({ incrementBy: 1 }));
|
|
259
|
+
return (
|
|
260
|
+
<div>
|
|
261
|
+
<p>Count: {state.count}</p>
|
|
262
|
+
<button onClick={state.increment}>Increment</button>
|
|
263
|
+
</div>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Design Patterns
|
|
207
269
|
|
|
208
|
-
###
|
|
270
|
+
### Automatic Cleanup
|
|
209
271
|
|
|
210
|
-
|
|
272
|
+
Resources automatically clean up after themselves when unmounted:
|
|
211
273
|
|
|
212
274
|
```typescript
|
|
213
|
-
const WebSocketResource = () => {
|
|
275
|
+
const WebSocketResource = resource(() => {
|
|
214
276
|
const [messages, setMessages] = tapState<string[]>([]);
|
|
215
277
|
|
|
216
278
|
tapEffect(() => {
|
|
@@ -225,16 +287,85 @@ const WebSocketResource = () => {
|
|
|
225
287
|
}, []);
|
|
226
288
|
|
|
227
289
|
return messages;
|
|
290
|
+
});
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### API Wrapper Pattern
|
|
294
|
+
|
|
295
|
+
A common pattern in assistant-ui is to wrap resource state in a stable API object:
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
export const tapApi = <TApi extends ApiObject & { getState: () => any }>(
|
|
299
|
+
api: TApi,
|
|
300
|
+
) => {
|
|
301
|
+
const ref = tapRef(api);
|
|
302
|
+
|
|
303
|
+
tapEffect(() => {
|
|
304
|
+
ref.current = api;
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const apiProxy = tapMemo(
|
|
308
|
+
() =>
|
|
309
|
+
new Proxy<TApi>({} as TApi, new ReadonlyApiHandler(() => ref.current)),
|
|
310
|
+
[],
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
return tapMemo(
|
|
314
|
+
() => ({
|
|
315
|
+
state: api.getState(),
|
|
316
|
+
api: apiProxy,
|
|
317
|
+
}),
|
|
318
|
+
[api.getState()],
|
|
319
|
+
);
|
|
228
320
|
};
|
|
229
321
|
```
|
|
230
322
|
|
|
231
|
-
|
|
323
|
+
## Use Cases
|
|
324
|
+
|
|
325
|
+
tap is used throughout assistant-ui for:
|
|
232
326
|
|
|
233
|
-
|
|
327
|
+
1. **State Management**: Application-wide state without Redux/Zustand
|
|
328
|
+
2. **Event Handling**: Managing event subscriptions and cleanup
|
|
329
|
+
3. **Resource Lifecycle**: Auto-cleanup of WebSockets, timers, subscriptions
|
|
330
|
+
4. **Composition**: Nested resource management (threads, messages, tools)
|
|
331
|
+
5. **Context Injection**: Passing values through resource boundaries without prop drilling
|
|
332
|
+
6. **API Wrapping**: Creating reactive API objects with `getState()` and `subscribe()`
|
|
333
|
+
|
|
334
|
+
### Example: Tools Management
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
export const Tools = resource(({ toolkit }: { toolkit?: Toolkit }) => {
|
|
338
|
+
const [state, setState] = tapState<ToolsState>(() => ({
|
|
339
|
+
tools: {},
|
|
340
|
+
}));
|
|
341
|
+
|
|
342
|
+
const modelContext = tapModelContext();
|
|
343
|
+
|
|
344
|
+
tapEffect(() => {
|
|
345
|
+
if (!toolkit) return;
|
|
346
|
+
|
|
347
|
+
// Register tools and setup subscriptions
|
|
348
|
+
const unsubscribes: (() => void)[] = [];
|
|
349
|
+
// ... registration logic
|
|
350
|
+
|
|
351
|
+
return () => unsubscribes.forEach((fn) => fn());
|
|
352
|
+
}, [toolkit, modelContext]);
|
|
353
|
+
|
|
354
|
+
return tapApi<ToolsApi>({
|
|
355
|
+
getState: () => state,
|
|
356
|
+
setToolUI,
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
```
|
|
234
360
|
|
|
235
|
-
|
|
361
|
+
## Why tap?
|
|
236
362
|
|
|
237
|
-
|
|
363
|
+
- **Reuse React knowledge**: Developers already familiar with hooks can immediately work with tap
|
|
364
|
+
- **Framework flexibility**: Core logic can work outside React components
|
|
365
|
+
- **Automatic cleanup**: No memory leaks from forgotten unsubscribes
|
|
366
|
+
- **Composability**: Resources can nest and combine naturally
|
|
367
|
+
- **Type safety**: Full TypeScript inference for state and APIs
|
|
368
|
+
- **Zero dependencies**: Lightweight and portable
|
|
238
369
|
|
|
239
370
|
## Comparison with React Hooks
|
|
240
371
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ResourceFiber } from "../core/types";
|
|
2
2
|
/**
|
|
3
3
|
* Creates a test resource fiber for unit testing.
|
|
4
4
|
* This is a low-level utility that creates a ResourceFiber directly.
|
|
5
5
|
* Sets up a rerender callback that automatically re-renders when state changes.
|
|
6
6
|
*/
|
|
7
|
-
export declare function createTestResource<R, P>(
|
|
7
|
+
export declare function createTestResource<R, P>(fn: (props: P) => R): ResourceFiber<R, P>;
|
|
8
8
|
/**
|
|
9
9
|
* Renders a test resource fiber with the given props and manages its lifecycle.
|
|
10
10
|
* - Tracks resources for cleanup
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../../src/__tests__/test-utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../../src/__tests__/test-utils.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAO9C;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,uBAa3D;AAWD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAcxE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,QAK/D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,SAGlC;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAQrE;AAMD;;;GAGG;AACH,qBAAa,cAAc,CAAC,CAAC;IACpB,SAAS,SAAK;IACd,SAAS,EAAE,CAAC,CAAC;IACpB,OAAO,CAAC,KAAK,CAA0B;gBAE3B,KAAK,EAAE,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC;IAW1C,OAAO;CAMR;AAED;;;GAGG;AACH,qBAAa,mBAAmB,CAAC,CAAC,EAAE,CAAC;IAGhB,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC;IAF7C,OAAO,CAAC,QAAQ,CAAS;gBAEN,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC;IAE7C,cAAc,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC;IAc3B,OAAO;CAOR;AAMD;;;GAGG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,SAAS,EAAE,MAAM,OAAO,EACxB,OAAO,SAAO,EACd,QAAQ,SAAK,GACZ,OAAO,CAAC,IAAI,CAAC,CAQf;AAMD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,SAAI,IAC5C,OAAO;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE;;EAIlC;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,KACnC,OAAO;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE;;;;EAQnC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/__tests__/test-utils.ts
|
|
2
|
+
import { resource } from "../core/resource.js";
|
|
2
3
|
import {
|
|
3
4
|
createResourceFiber,
|
|
4
5
|
unmountResource as unmountResourceFiber,
|
|
@@ -6,7 +7,7 @@ import {
|
|
|
6
7
|
commitResource
|
|
7
8
|
} from "../core/ResourceFiber.js";
|
|
8
9
|
import { tapState } from "../hooks/tap-state.js";
|
|
9
|
-
function createTestResource(
|
|
10
|
+
function createTestResource(fn) {
|
|
10
11
|
const rerenderCallback = () => {
|
|
11
12
|
if (activeResources.has(fiber)) {
|
|
12
13
|
const lastProps = propsMap.get(fiber);
|
|
@@ -15,7 +16,7 @@ function createTestResource(type) {
|
|
|
15
16
|
lastRenderResultMap.set(fiber, result);
|
|
16
17
|
}
|
|
17
18
|
};
|
|
18
|
-
const fiber = createResourceFiber(
|
|
19
|
+
const fiber = createResourceFiber(resource(fn), rerenderCallback);
|
|
19
20
|
return fiber;
|
|
20
21
|
}
|
|
21
22
|
var activeResources = /* @__PURE__ */ new Set();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/__tests__/test-utils.ts"],"sourcesContent":["import {\n createResourceFiber,\n unmountResource as unmountResourceFiber,\n renderResource as renderResourceFiber,\n commitResource,\n} from \"../core/ResourceFiber\";\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/__tests__/test-utils.ts"],"sourcesContent":["import { resource } from \"../core/resource\";\nimport {\n createResourceFiber,\n unmountResource as unmountResourceFiber,\n renderResource as renderResourceFiber,\n commitResource,\n} from \"../core/ResourceFiber\";\nimport { ResourceFiber } from \"../core/types\";\nimport { tapState } from \"../hooks/tap-state\";\n\n// ============================================================================\n// Resource Creation\n// ============================================================================\n\n/**\n * Creates a test resource fiber for unit testing.\n * This is a low-level utility that creates a ResourceFiber directly.\n * Sets up a rerender callback that automatically re-renders when state changes.\n */\nexport function createTestResource<R, P>(fn: (props: P) => R) {\n const rerenderCallback = () => {\n // Re-render when state changes\n if (activeResources.has(fiber)) {\n const lastProps = propsMap.get(fiber);\n const result = renderResourceFiber(fiber, lastProps);\n commitResource(fiber, result);\n lastRenderResultMap.set(fiber, result);\n }\n };\n\n const fiber = createResourceFiber(resource(fn), rerenderCallback);\n return fiber;\n}\n\n// ============================================================================\n// Resource Lifecycle Management\n// ============================================================================\n\n// Track resources for cleanup\nconst activeResources = new Set<ResourceFiber<any, any>>();\nconst propsMap = new WeakMap<ResourceFiber<any, any>, any>();\nconst lastRenderResultMap = new WeakMap<ResourceFiber<any, any>, any>();\n\n/**\n * Renders a test resource fiber with the given props and manages its lifecycle.\n * - Tracks resources for cleanup\n * - Returns the current state after render\n */\nexport function renderTest<R, P>(fiber: ResourceFiber<R, P>, props: P): R {\n propsMap.set(fiber, props);\n\n // Track resource for cleanup\n activeResources.add(fiber);\n\n // Render with new props\n const result = renderResourceFiber(fiber, props);\n commitResource(fiber, result);\n lastRenderResultMap.set(fiber, result);\n\n // Return the committed state from the result\n // This accounts for any re-renders that happened during commit\n return result.state;\n}\n\n/**\n * Unmounts a specific resource fiber and removes it from tracking.\n */\nexport function unmountResource<R, P>(fiber: ResourceFiber<R, P>) {\n if (activeResources.has(fiber)) {\n unmountResourceFiber(fiber);\n activeResources.delete(fiber);\n }\n}\n\n/**\n * Cleans up all resources. Should be called after each test.\n */\nexport function cleanupAllResources() {\n activeResources.forEach((fiber) => unmountResourceFiber(fiber));\n activeResources.clear();\n}\n\n/**\n * Gets the current committed state of a resource fiber.\n * Returns the state from the last render/commit cycle.\n */\nexport function getCommittedState<R, P>(fiber: ResourceFiber<R, P>): R {\n const lastResult = lastRenderResultMap.get(fiber);\n if (!lastResult) {\n throw new Error(\n \"No render result found for fiber. Make sure to call renderResource first.\",\n );\n }\n return lastResult.state;\n}\n\n// ============================================================================\n// Test Helpers\n// ============================================================================\n\n/**\n * Helper to subscribe to resource state changes for testing.\n * Tracks call count and latest state value.\n */\nexport class TestSubscriber<T> {\n public callCount = 0;\n public lastState: T;\n private fiber: ResourceFiber<any, any>;\n\n constructor(fiber: ResourceFiber<any, any>) {\n this.fiber = fiber;\n // Need to render once to get initial state\n const lastProps = propsMap.get(fiber) ?? undefined;\n const initialResult = renderResourceFiber(fiber, lastProps as any);\n commitResource(fiber, initialResult);\n this.lastState = initialResult.state;\n lastRenderResultMap.set(fiber, initialResult);\n activeResources.add(fiber);\n }\n\n cleanup() {\n if (activeResources.has(this.fiber)) {\n unmountResourceFiber(this.fiber);\n activeResources.delete(this.fiber);\n }\n }\n}\n\n/**\n * Helper class to manage resource lifecycle in tests with explicit control.\n * Useful when you need fine-grained control over mount/unmount timing.\n */\nexport class TestResourceManager<R, P> {\n private isActive = false;\n\n constructor(public fiber: ResourceFiber<R, P>) {}\n\n renderAndMount(props: P): R {\n if (this.isActive) {\n throw new Error(\"Resource already active\");\n }\n\n this.isActive = true;\n activeResources.add(this.fiber);\n propsMap.set(this.fiber, props);\n const result = renderResourceFiber(this.fiber, props);\n commitResource(this.fiber, result);\n lastRenderResultMap.set(this.fiber, result);\n return result.state;\n }\n\n cleanup() {\n if (this.isActive && activeResources.has(this.fiber)) {\n unmountResourceFiber(this.fiber);\n activeResources.delete(this.fiber);\n this.isActive = false;\n }\n }\n}\n\n// ============================================================================\n// Async Utilities\n// ============================================================================\n\n/**\n * Waits for the next tick of the event loop.\n * Useful for testing async state updates.\n */\nexport function waitForNextTick(): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, 0));\n}\n\n/**\n * Waits for a condition to be true with timeout.\n * Useful for testing eventual consistency.\n */\nexport async function waitFor(\n condition: () => boolean,\n timeout = 1000,\n interval = 10,\n): Promise<void> {\n const start = Date.now();\n while (!condition()) {\n if (Date.now() - start > timeout) {\n throw new Error(\"Timeout waiting for condition\");\n }\n await new Promise((resolve) => setTimeout(resolve, interval));\n }\n}\n\n// ============================================================================\n// Test Data Factories\n// ============================================================================\n\n/**\n * Creates a simple counter resource for testing.\n * Commonly used across multiple test files.\n */\nexport function createCounterResource(initialValue = 0) {\n return (props: { value?: number }) => {\n const value = props.value ?? initialValue;\n return { count: value };\n };\n}\n\n/**\n * Creates a stateful counter resource for testing.\n * Includes increment/decrement functions.\n */\nexport function createStatefulCounterResource() {\n return (props: { initial: number }) => {\n const [count, setCount] = tapState(props.initial);\n return {\n count,\n increment: () => setCount((c: number) => c + 1),\n decrement: () => setCount((c: number) => c - 1),\n };\n };\n}\n"],"mappings":";AAAA,SAAS,gBAAgB;AACzB;AAAA,EACE;AAAA,EACA,mBAAmB;AAAA,EACnB,kBAAkB;AAAA,EAClB;AAAA,OACK;AAEP,SAAS,gBAAgB;AAWlB,SAAS,mBAAyB,IAAqB;AAC5D,QAAM,mBAAmB,MAAM;AAE7B,QAAI,gBAAgB,IAAI,KAAK,GAAG;AAC9B,YAAM,YAAY,SAAS,IAAI,KAAK;AACpC,YAAM,SAAS,oBAAoB,OAAO,SAAS;AACnD,qBAAe,OAAO,MAAM;AAC5B,0BAAoB,IAAI,OAAO,MAAM;AAAA,IACvC;AAAA,EACF;AAEA,QAAM,QAAQ,oBAAoB,SAAS,EAAE,GAAG,gBAAgB;AAChE,SAAO;AACT;AAOA,IAAM,kBAAkB,oBAAI,IAA6B;AACzD,IAAM,WAAW,oBAAI,QAAsC;AAC3D,IAAM,sBAAsB,oBAAI,QAAsC;AAO/D,SAAS,WAAiB,OAA4B,OAAa;AACxE,WAAS,IAAI,OAAO,KAAK;AAGzB,kBAAgB,IAAI,KAAK;AAGzB,QAAM,SAAS,oBAAoB,OAAO,KAAK;AAC/C,iBAAe,OAAO,MAAM;AAC5B,sBAAoB,IAAI,OAAO,MAAM;AAIrC,SAAO,OAAO;AAChB;AAKO,SAAS,gBAAsB,OAA4B;AAChE,MAAI,gBAAgB,IAAI,KAAK,GAAG;AAC9B,yBAAqB,KAAK;AAC1B,oBAAgB,OAAO,KAAK;AAAA,EAC9B;AACF;AAKO,SAAS,sBAAsB;AACpC,kBAAgB,QAAQ,CAAC,UAAU,qBAAqB,KAAK,CAAC;AAC9D,kBAAgB,MAAM;AACxB;AAMO,SAAS,kBAAwB,OAA+B;AACrE,QAAM,aAAa,oBAAoB,IAAI,KAAK;AAChD,MAAI,CAAC,YAAY;AACf,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO,WAAW;AACpB;AAUO,IAAM,iBAAN,MAAwB;AAAA,EACtB,YAAY;AAAA,EACZ;AAAA,EACC;AAAA,EAER,YAAY,OAAgC;AAC1C,SAAK,QAAQ;AAEb,UAAM,YAAY,SAAS,IAAI,KAAK,KAAK;AACzC,UAAM,gBAAgB,oBAAoB,OAAO,SAAgB;AACjE,mBAAe,OAAO,aAAa;AACnC,SAAK,YAAY,cAAc;AAC/B,wBAAoB,IAAI,OAAO,aAAa;AAC5C,oBAAgB,IAAI,KAAK;AAAA,EAC3B;AAAA,EAEA,UAAU;AACR,QAAI,gBAAgB,IAAI,KAAK,KAAK,GAAG;AACnC,2BAAqB,KAAK,KAAK;AAC/B,sBAAgB,OAAO,KAAK,KAAK;AAAA,IACnC;AAAA,EACF;AACF;AAMO,IAAM,sBAAN,MAAgC;AAAA,EAGrC,YAAmB,OAA4B;AAA5B;AAAA,EAA6B;AAAA,EAFxC,WAAW;AAAA,EAInB,eAAe,OAAa;AAC1B,QAAI,KAAK,UAAU;AACjB,YAAM,IAAI,MAAM,yBAAyB;AAAA,IAC3C;AAEA,SAAK,WAAW;AAChB,oBAAgB,IAAI,KAAK,KAAK;AAC9B,aAAS,IAAI,KAAK,OAAO,KAAK;AAC9B,UAAM,SAAS,oBAAoB,KAAK,OAAO,KAAK;AACpD,mBAAe,KAAK,OAAO,MAAM;AACjC,wBAAoB,IAAI,KAAK,OAAO,MAAM;AAC1C,WAAO,OAAO;AAAA,EAChB;AAAA,EAEA,UAAU;AACR,QAAI,KAAK,YAAY,gBAAgB,IAAI,KAAK,KAAK,GAAG;AACpD,2BAAqB,KAAK,KAAK;AAC/B,sBAAgB,OAAO,KAAK,KAAK;AACjC,WAAK,WAAW;AAAA,IAClB;AAAA,EACF;AACF;AAUO,SAAS,kBAAiC;AAC/C,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,CAAC,CAAC;AACxD;AAMA,eAAsB,QACpB,WACA,UAAU,KACV,WAAW,IACI;AACf,QAAM,QAAQ,KAAK,IAAI;AACvB,SAAO,CAAC,UAAU,GAAG;AACnB,QAAI,KAAK,IAAI,IAAI,QAAQ,SAAS;AAChC,YAAM,IAAI,MAAM,+BAA+B;AAAA,IACjD;AACA,UAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,QAAQ,CAAC;AAAA,EAC9D;AACF;AAUO,SAAS,sBAAsB,eAAe,GAAG;AACtD,SAAO,CAAC,UAA8B;AACpC,UAAM,QAAQ,MAAM,SAAS;AAC7B,WAAO,EAAE,OAAO,MAAM;AAAA,EACxB;AACF;AAMO,SAAS,gCAAgC;AAC9C,SAAO,CAAC,UAA+B;AACrC,UAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,MAAM,OAAO;AAChD,WAAO;AAAA,MACL;AAAA,MACA,WAAW,MAAM,SAAS,CAAC,MAAc,IAAI,CAAC;AAAA,MAC9C,WAAW,MAAM,SAAS,CAAC,MAAc,IAAI,CAAC;AAAA,IAChD;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare function createResourceFiber<R, P>(
|
|
1
|
+
import { ResourceFiber, RenderResult, Resource } from "./types";
|
|
2
|
+
export declare function createResourceFiber<R, P>(resource: Resource<R, P>, scheduleRerender: () => void): ResourceFiber<R, P>;
|
|
3
3
|
export declare function unmountResource<R, P>(fiber: ResourceFiber<R, P>): void;
|
|
4
4
|
export declare function renderResource<R, P>(fiber: ResourceFiber<R, P>, props: P): RenderResult;
|
|
5
5
|
export declare function commitResource<R, P>(fiber: ResourceFiber<R, P>, result: RenderResult): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResourceFiber.d.ts","sourceRoot":"","sources":["../../src/core/ResourceFiber.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"ResourceFiber.d.ts","sourceRoot":"","sources":["../../src/core/ResourceFiber.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAKhE,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,CAAC,EACtC,QAAQ,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,EACxB,gBAAgB,EAAE,MAAM,IAAI,GAC3B,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAWrB;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAItE;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,CAAC,EACjC,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EAC1B,KAAK,EAAE,CAAC,GACP,YAAY,CAiBd;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,CAAC,EACjC,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EAC1B,MAAM,EAAE,YAAY,GACnB,IAAI,CAKN"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// src/core/ResourceFiber.ts
|
|
2
2
|
import { commitRender, cleanupAllEffects } from "./commit.js";
|
|
3
3
|
import { withResourceFiber } from "./execution-context.js";
|
|
4
|
-
|
|
4
|
+
import { callResourceFn } from "./callResourceFn.js";
|
|
5
|
+
function createResourceFiber(resource, scheduleRerender) {
|
|
5
6
|
return {
|
|
6
|
-
|
|
7
|
+
resource,
|
|
7
8
|
scheduleRerender,
|
|
8
9
|
cells: [],
|
|
9
10
|
currentIndex: 0,
|
|
@@ -26,7 +27,7 @@ function renderResource(fiber, props) {
|
|
|
26
27
|
withResourceFiber(fiber, () => {
|
|
27
28
|
fiber.renderContext = result;
|
|
28
29
|
try {
|
|
29
|
-
result.state = fiber.
|
|
30
|
+
result.state = callResourceFn(fiber.resource, props);
|
|
30
31
|
} finally {
|
|
31
32
|
fiber.renderContext = void 0;
|
|
32
33
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/ResourceFiber.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"sources":["../../src/core/ResourceFiber.ts"],"sourcesContent":["import { ResourceFiber, RenderResult, Resource } from \"./types\";\nimport { commitRender, cleanupAllEffects } from \"./commit\";\nimport { withResourceFiber } from \"./execution-context\";\nimport { callResourceFn } from \"./callResourceFn\";\n\nexport function createResourceFiber<R, P>(\n resource: Resource<R, P>,\n scheduleRerender: () => void,\n): ResourceFiber<R, P> {\n return {\n resource,\n scheduleRerender,\n cells: [],\n currentIndex: 0,\n renderContext: undefined,\n isFirstRender: true,\n isMounted: false,\n isNeverMounted: true,\n };\n}\n\nexport function unmountResource<R, P>(fiber: ResourceFiber<R, P>): void {\n // Clean up all effects\n fiber.isMounted = false;\n cleanupAllEffects(fiber);\n}\n\nexport function renderResource<R, P>(\n fiber: ResourceFiber<R, P>,\n props: P,\n): RenderResult {\n const result: RenderResult = {\n commitTasks: [],\n props,\n state: undefined,\n };\n\n withResourceFiber(fiber, () => {\n fiber.renderContext = result;\n try {\n result.state = callResourceFn(fiber.resource, props);\n } finally {\n fiber.renderContext = undefined;\n }\n });\n\n return result;\n}\n\nexport function commitResource<R, P>(\n fiber: ResourceFiber<R, P>,\n result: RenderResult,\n): void {\n fiber.isMounted = true;\n fiber.isNeverMounted = false;\n\n commitRender(result, fiber);\n}\n"],"mappings":";AACA,SAAS,cAAc,yBAAyB;AAChD,SAAS,yBAAyB;AAClC,SAAS,sBAAsB;AAExB,SAAS,oBACd,UACA,kBACqB;AACrB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,OAAO,CAAC;AAAA,IACR,cAAc;AAAA,IACd,eAAe;AAAA,IACf,eAAe;AAAA,IACf,WAAW;AAAA,IACX,gBAAgB;AAAA,EAClB;AACF;AAEO,SAAS,gBAAsB,OAAkC;AAEtE,QAAM,YAAY;AAClB,oBAAkB,KAAK;AACzB;AAEO,SAAS,eACd,OACA,OACc;AACd,QAAM,SAAuB;AAAA,IAC3B,aAAa,CAAC;AAAA,IACd;AAAA,IACA,OAAO;AAAA,EACT;AAEA,oBAAkB,OAAO,MAAM;AAC7B,UAAM,gBAAgB;AACtB,QAAI;AACF,aAAO,QAAQ,eAAe,MAAM,UAAU,KAAK;AAAA,IACrD,UAAE;AACA,YAAM,gBAAgB;AAAA,IACxB;AAAA,EACF,CAAC;AAED,SAAO;AACT;AAEO,SAAS,eACd,OACA,QACM;AACN,QAAM,YAAY;AAClB,QAAM,iBAAiB;AAEvB,eAAa,QAAQ,KAAK;AAC5B;","names":[]}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { ResourceElement
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { ResourceElement } from "./types";
|
|
2
|
+
export declare namespace createResource {
|
|
3
|
+
type Unsubscribe = () => void;
|
|
4
|
+
interface Handle<R, P> {
|
|
5
|
+
getState(): R;
|
|
6
|
+
subscribe(callback: () => void): Unsubscribe;
|
|
7
|
+
updateInput(props: P): void;
|
|
8
|
+
flushSync(): void;
|
|
9
|
+
dispose(): void;
|
|
10
|
+
}
|
|
8
11
|
}
|
|
9
|
-
export declare const createResource: <R, P>(element: ResourceElement<R, P>, delayMount?: boolean) =>
|
|
12
|
+
export declare const createResource: <R, P>(element: ResourceElement<R, P>, delayMount?: boolean) => createResource.Handle<R, P>;
|
|
10
13
|
//# sourceMappingURL=ResourceHandle.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResourceHandle.d.ts","sourceRoot":"","sources":["../../src/core/ResourceHandle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,
|
|
1
|
+
{"version":3,"file":"ResourceHandle.d.ts","sourceRoot":"","sources":["../../src/core/ResourceHandle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAe1C,yBAAiB,cAAc,CAAC;IAC9B,KAAY,WAAW,GAAG,MAAM,IAAI,CAAC;IAErC,UAAiB,MAAM,CAAC,CAAC,EAAE,CAAC;QAC1B,QAAQ,IAAI,CAAC,CAAC;QACd,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,WAAW,CAAC;QAC7C,WAAW,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;QAC5B,SAAS,IAAI,IAAI,CAAC;QAClB,OAAO,IAAI,IAAI,CAAC;KACjB;CACF;AA+CD,eAAO,MAAM,cAAc,GAAI,CAAC,EAAE,CAAC,EACjC,SAAS,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,EAC9B,oBAAkB,KACjB,cAAc,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CA2B5B,CAAC"}
|
|
@@ -11,40 +11,43 @@ import { tapState } from "../hooks/tap-state.js";
|
|
|
11
11
|
import { tapMemo } from "../hooks/tap-memo.js";
|
|
12
12
|
import { tapInlineResource } from "../hooks/tap-inline-resource.js";
|
|
13
13
|
import { tapEffect } from "../hooks/tap-effect.js";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
14
|
+
import { resource } from "./resource.js";
|
|
15
|
+
var HandleWrapperResource = resource(
|
|
16
|
+
({
|
|
17
|
+
element,
|
|
18
|
+
onUpdateInput,
|
|
19
|
+
onFlushSync,
|
|
20
|
+
onDispose
|
|
21
|
+
}) => {
|
|
22
|
+
const [props, setProps] = tapState(element.props);
|
|
23
|
+
const value = tapInlineResource({ type: element.type, props });
|
|
24
|
+
const subscribers = tapRef(/* @__PURE__ */ new Set()).current;
|
|
25
|
+
const valueRef = tapRef(value);
|
|
26
|
+
tapEffect(() => {
|
|
27
|
+
if (value !== valueRef.current) {
|
|
28
|
+
valueRef.current = value;
|
|
29
|
+
subscribers.forEach((callback) => callback());
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
const handle = tapMemo(
|
|
33
|
+
() => ({
|
|
34
|
+
getState: () => valueRef.current,
|
|
35
|
+
subscribe: (callback) => {
|
|
36
|
+
subscribers.add(callback);
|
|
37
|
+
return () => subscribers.delete(callback);
|
|
38
|
+
},
|
|
39
|
+
updateInput: (props2) => {
|
|
40
|
+
onUpdateInput();
|
|
41
|
+
setProps(() => props2);
|
|
42
|
+
},
|
|
43
|
+
flushSync: onFlushSync,
|
|
44
|
+
dispose: onDispose
|
|
45
|
+
}),
|
|
46
|
+
[]
|
|
47
|
+
);
|
|
48
|
+
return handle;
|
|
49
|
+
}
|
|
50
|
+
);
|
|
48
51
|
var createResource = (element, delayMount = false) => {
|
|
49
52
|
let isMounted = !delayMount;
|
|
50
53
|
const props = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/ResourceHandle.ts"],"sourcesContent":["import { ResourceElement
|
|
1
|
+
{"version":3,"sources":["../../src/core/ResourceHandle.ts"],"sourcesContent":["import { ResourceElement } from \"./types\";\nimport {\n createResourceFiber,\n unmountResource,\n renderResource,\n commitResource,\n} from \"./ResourceFiber\";\nimport { UpdateScheduler } from \"./scheduler\";\nimport { tapRef } from \"../hooks/tap-ref\";\nimport { tapState } from \"../hooks/tap-state\";\nimport { tapMemo } from \"../hooks/tap-memo\";\nimport { tapInlineResource } from \"../hooks/tap-inline-resource\";\nimport { tapEffect } from \"../hooks/tap-effect\";\nimport { resource } from \"./resource\";\n\nexport namespace createResource {\n export type Unsubscribe = () => void;\n\n export interface Handle<R, P> {\n getState(): R;\n subscribe(callback: () => void): Unsubscribe;\n updateInput(props: P): void;\n flushSync(): void;\n dispose(): void;\n }\n}\n\nconst HandleWrapperResource = resource(\n <R, P>({\n element,\n onUpdateInput,\n onFlushSync,\n onDispose,\n }: {\n element: ResourceElement<R, P>;\n onUpdateInput: () => void;\n onFlushSync: () => void;\n onDispose: () => void;\n }): createResource.Handle<R, P> => {\n const [props, setProps] = tapState(element.props);\n const value = tapInlineResource({ type: element.type, props });\n const subscribers = tapRef(new Set<() => void>()).current;\n const valueRef = tapRef(value);\n\n tapEffect(() => {\n if (value !== valueRef.current) {\n valueRef.current = value;\n subscribers.forEach((callback) => callback());\n }\n });\n\n const handle = tapMemo(\n () => ({\n getState: () => valueRef.current,\n subscribe: (callback: () => void) => {\n subscribers.add(callback);\n return () => subscribers.delete(callback);\n },\n updateInput: (props: P) => {\n onUpdateInput();\n setProps(() => props);\n },\n flushSync: onFlushSync,\n dispose: onDispose,\n }),\n [],\n );\n\n return handle;\n },\n);\n\nexport const createResource = <R, P>(\n element: ResourceElement<R, P>,\n delayMount = false,\n): createResource.Handle<R, P> => {\n let isMounted = !delayMount;\n const props = {\n element,\n onUpdateInput: () => {\n if (isMounted) return;\n isMounted = true;\n commitResource(fiber, lastRender);\n },\n onFlushSync: () => {\n scheduler.flushSync();\n },\n onDispose: () => unmountResource(fiber),\n };\n\n const scheduler = new UpdateScheduler(() => {\n lastRender = renderResource(fiber, props);\n if (isMounted) commitResource(fiber, lastRender);\n });\n\n const fiber = createResourceFiber(HandleWrapperResource<R, P>, () =>\n scheduler.markDirty(),\n );\n\n let lastRender = renderResource(fiber, props);\n if (isMounted) commitResource(fiber, lastRender);\n return lastRender.state;\n};\n"],"mappings":";AACA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,uBAAuB;AAChC,SAAS,cAAc;AACvB,SAAS,gBAAgB;AACzB,SAAS,eAAe;AACxB,SAAS,yBAAyB;AAClC,SAAS,iBAAiB;AAC1B,SAAS,gBAAgB;AAczB,IAAM,wBAAwB;AAAA,EAC5B,CAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,MAKmC;AACjC,UAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,QAAQ,KAAK;AAChD,UAAM,QAAQ,kBAAkB,EAAE,MAAM,QAAQ,MAAM,MAAM,CAAC;AAC7D,UAAM,cAAc,OAAO,oBAAI,IAAgB,CAAC,EAAE;AAClD,UAAM,WAAW,OAAO,KAAK;AAE7B,cAAU,MAAM;AACd,UAAI,UAAU,SAAS,SAAS;AAC9B,iBAAS,UAAU;AACnB,oBAAY,QAAQ,CAAC,aAAa,SAAS,CAAC;AAAA,MAC9C;AAAA,IACF,CAAC;AAED,UAAM,SAAS;AAAA,MACb,OAAO;AAAA,QACL,UAAU,MAAM,SAAS;AAAA,QACzB,WAAW,CAAC,aAAyB;AACnC,sBAAY,IAAI,QAAQ;AACxB,iBAAO,MAAM,YAAY,OAAO,QAAQ;AAAA,QAC1C;AAAA,QACA,aAAa,CAACA,WAAa;AACzB,wBAAc;AACd,mBAAS,MAAMA,MAAK;AAAA,QACtB;AAAA,QACA,WAAW;AAAA,QACX,SAAS;AAAA,MACX;AAAA,MACA,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AACF;AAEO,IAAM,iBAAiB,CAC5B,SACA,aAAa,UACmB;AAChC,MAAI,YAAY,CAAC;AACjB,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA,eAAe,MAAM;AACnB,UAAI,UAAW;AACf,kBAAY;AACZ,qBAAe,OAAO,UAAU;AAAA,IAClC;AAAA,IACA,aAAa,MAAM;AACjB,gBAAU,UAAU;AAAA,IACtB;AAAA,IACA,WAAW,MAAM,gBAAgB,KAAK;AAAA,EACxC;AAEA,QAAM,YAAY,IAAI,gBAAgB,MAAM;AAC1C,iBAAa,eAAe,OAAO,KAAK;AACxC,QAAI,UAAW,gBAAe,OAAO,UAAU;AAAA,EACjD,CAAC;AAED,QAAM,QAAQ;AAAA,IAAoB;AAAA,IAA6B,MAC7D,UAAU,UAAU;AAAA,EACtB;AAEA,MAAI,aAAa,eAAe,OAAO,KAAK;AAC5C,MAAI,UAAW,gBAAe,OAAO,UAAU;AAC/C,SAAO,WAAW;AACpB;","names":["props"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callResourceFn.d.ts","sourceRoot":"","sources":["../../src/core/callResourceFn.ts"],"names":[],"mappings":""}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
// src/core/
|
|
2
|
-
function
|
|
1
|
+
// src/core/callResourceFn.ts
|
|
2
|
+
function callResourceFn(resource, props) {
|
|
3
3
|
const fn = resource[fnSymbol];
|
|
4
4
|
if (!fn) {
|
|
5
5
|
throw new Error("ResourceElement.type is not a valid Resource");
|
|
6
6
|
}
|
|
7
|
-
return fn;
|
|
7
|
+
return fn(props);
|
|
8
8
|
}
|
|
9
9
|
var fnSymbol = Symbol("fnSymbol");
|
|
10
10
|
export {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
callResourceFn,
|
|
12
|
+
fnSymbol
|
|
13
13
|
};
|
|
14
|
-
//# sourceMappingURL=
|
|
14
|
+
//# sourceMappingURL=callResourceFn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/callResourceFn.ts"],"sourcesContent":["import { Resource } from \"./types\";\n\n/**\n * Renders a resource with the given props.\n * @internal This is for internal use only.\n */\nexport function callResourceFn<R, P>(resource: Resource<R, P>, props: P): R {\n const fn = (resource as unknown as { [fnSymbol]?: (props: P) => R })[\n fnSymbol\n ];\n if (!fn) {\n throw new Error(\"ResourceElement.type is not a valid Resource\");\n }\n return fn(props);\n}\n\n/**\n * Symbol used to store the ResourceFn in the Resource constructor.\n * @internal This is for internal use only.\n */\nexport const fnSymbol = Symbol(\"fnSymbol\");\n"],"mappings":";AAMO,SAAS,eAAqB,UAA0B,OAAa;AAC1E,QAAM,KAAM,SACV,QACF;AACA,MAAI,CAAC,IAAI;AACP,UAAM,IAAI,MAAM,8CAA8C;AAAA,EAChE;AACA,SAAO,GAAG,KAAK;AACjB;AAMO,IAAM,WAAW,OAAO,UAAU;","names":[]}
|
package/dist/core/resource.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare function resource<R
|
|
1
|
+
import { ResourceElement, Resource } from "./types";
|
|
2
|
+
export declare function resource<R>(fn: () => R): () => ResourceElement<R, undefined>;
|
|
3
|
+
export declare function resource<R, P>(fn: (props: P) => R): Resource<R, P>;
|
|
3
4
|
//# sourceMappingURL=resource.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../src/core/resource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../src/core/resource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEpD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,MAAM,eAAe,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;AAC9E,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC"}
|
package/dist/core/resource.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/resource.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"sources":["../../src/core/resource.ts"],"sourcesContent":["import { ResourceElement, Resource } from \"./types\";\nimport { fnSymbol } from \"./callResourceFn\";\nexport function resource<R>(fn: () => R): () => ResourceElement<R, undefined>;\nexport function resource<R, P>(fn: (props: P) => R): Resource<R, P>;\nexport function resource<R, P = undefined>(\n fn: (props: P) => R,\n): Resource<R, P> {\n const type = (props?: P, options?: { key?: string | number }) => {\n return {\n type,\n props: props!,\n ...(options?.key !== undefined && { key: options.key }),\n } satisfies ResourceElement<R, P>;\n };\n\n type[fnSymbol] = fn;\n\n return type;\n}\n"],"mappings":";AACA,SAAS,gBAAgB;AAGlB,SAAS,SACd,IACgB;AAChB,QAAM,OAAO,CAAC,OAAW,YAAwC;AAC/D,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,GAAI,SAAS,QAAQ,UAAa,EAAE,KAAK,QAAQ,IAAI;AAAA,IACvD;AAAA,EACF;AAEA,OAAK,QAAQ,IAAI;AAEjB,SAAO;AACT;","names":[]}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { tapEffect } from "../hooks/tap-effect";
|
|
2
|
+
import type { tapState } from "../hooks/tap-state";
|
|
3
3
|
export type ResourceElement<R, P = any> = {
|
|
4
4
|
type: Resource<R, P>;
|
|
5
5
|
props: P;
|
|
6
6
|
key?: string | number;
|
|
7
7
|
};
|
|
8
|
-
export type Resource<R, P> = (...args:
|
|
8
|
+
export type Resource<R, P> = (...args: P extends undefined ? [props?: undefined, options?: {
|
|
9
9
|
key?: string | number;
|
|
10
10
|
}] : [props: P, options?: {
|
|
11
11
|
key?: string | number;
|
|
12
12
|
}]) => ResourceElement<R, P>;
|
|
13
|
-
export type
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
export type ContravariantResource<R, P> = (props: P, options?: {
|
|
14
|
+
key?: string | number;
|
|
15
|
+
}) => ResourceElement<R, any>;
|
|
16
16
|
export type Cell = {
|
|
17
17
|
type: "state";
|
|
18
18
|
value: any;
|
|
19
|
-
set: (updater: StateUpdater<any>) => void;
|
|
19
|
+
set: (updater: tapState.StateUpdater<any>) => void;
|
|
20
20
|
} | {
|
|
21
21
|
type: "effect";
|
|
22
22
|
mounted: boolean;
|
|
23
|
-
cleanup?: Destructor | undefined;
|
|
23
|
+
cleanup?: tapEffect.Destructor | undefined;
|
|
24
24
|
deps?: readonly unknown[] | undefined;
|
|
25
25
|
};
|
|
26
26
|
export interface EffectTask {
|
|
27
|
-
effect: EffectCallback;
|
|
27
|
+
effect: tapEffect.EffectCallback;
|
|
28
28
|
deps?: readonly unknown[] | undefined;
|
|
29
29
|
cellIndex: number;
|
|
30
30
|
}
|
|
@@ -35,7 +35,7 @@ export interface RenderResult {
|
|
|
35
35
|
}
|
|
36
36
|
export interface ResourceFiber<R, P> {
|
|
37
37
|
readonly scheduleRerender: () => void;
|
|
38
|
-
readonly
|
|
38
|
+
readonly resource: Resource<R, P>;
|
|
39
39
|
cells: Cell[];
|
|
40
40
|
currentIndex: number;
|
|
41
41
|
renderContext: RenderResult | undefined;
|
package/dist/core/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD,MAAM,MAAM,eAAe,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,IAAI;IACxC,IAAI,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACrB,KAAK,EAAE,CAAC,CAAC;IACT,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,QAAQ,CAAC,CAAC,EAAE,CAAC,IAAI,CAC3B,GAAG,IAAI,EAAE,CAAC,SAAS,SAAS,GACxB,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAC,GACxD,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAC,KAChD,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAE3B,MAAM,MAAM,qBAAqB,CAAC,CAAC,EAAE,CAAC,IAAI,CACxC,KAAK,EAAE,CAAC,EACR,OAAO,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,KAChC,eAAe,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAE7B,MAAM,MAAM,IAAI,GACZ;IACE,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,GAAG,CAAC;IACX,GAAG,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC;CACpD,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,SAAS,CAAC,UAAU,GAAG,SAAS,CAAC;IAC3C,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,CAAC;CACvC,CAAC;AAEN,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,SAAS,CAAC,cAAc,CAAC;IACjC,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,CAAC;IACtC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,GAAG,CAAC;IACX,KAAK,EAAE,GAAG,CAAC;IACX,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,EAAE,CAAC;IACjC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,IAAI,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAElC,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IAErB,aAAa,EAAE,YAAY,GAAG,SAAS,CAAC;IAExC,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;IACvB,cAAc,EAAE,OAAO,CAAC;CACzB"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a stable function reference that always calls the most recent version of the callback.
|
|
3
|
+
* Similar to React's useEffectEvent hook.
|
|
4
|
+
*
|
|
5
|
+
* @param callback - The callback function to wrap
|
|
6
|
+
* @returns A stable function reference that always calls the latest callback
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* const handleClick = tapEffectEvent((value: string) => {
|
|
11
|
+
* console.log(value);
|
|
12
|
+
* });
|
|
13
|
+
* // handleClick reference is stable, but always calls the latest version
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare function tapEffectEvent<T extends (...args: any[]) => any>(callback: T): T;
|
|
17
|
+
//# sourceMappingURL=tap-effect-event.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tap-effect-event.d.ts","sourceRoot":"","sources":["../../src/hooks/tap-effect-event.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAC9D,QAAQ,EAAE,CAAC,GACV,CAAC,CAQH"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// src/hooks/tap-effect-event.ts
|
|
2
|
+
import { tapRef } from "./tap-ref.js";
|
|
3
|
+
import { tapEffect } from "./tap-effect.js";
|
|
4
|
+
function tapEffectEvent(callback) {
|
|
5
|
+
const callbackRef = tapRef(callback);
|
|
6
|
+
tapEffect(() => {
|
|
7
|
+
callbackRef.current = callback;
|
|
8
|
+
});
|
|
9
|
+
return callbackRef.current;
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
tapEffectEvent
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=tap-effect-event.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/tap-effect-event.ts"],"sourcesContent":["import { tapRef } from \"./tap-ref\";\nimport { tapEffect } from \"./tap-effect\";\n\n/**\n * Creates a stable function reference that always calls the most recent version of the callback.\n * Similar to React's useEffectEvent hook.\n *\n * @param callback - The callback function to wrap\n * @returns A stable function reference that always calls the latest callback\n *\n * @example\n * ```typescript\n * const handleClick = tapEffectEvent((value: string) => {\n * console.log(value);\n * });\n * // handleClick reference is stable, but always calls the latest version\n * ```\n */\nexport function tapEffectEvent<T extends (...args: any[]) => any>(\n callback: T,\n): T {\n const callbackRef = tapRef(callback);\n\n tapEffect(() => {\n callbackRef.current = callback;\n });\n\n return callbackRef.current;\n}\n"],"mappings":";AAAA,SAAS,cAAc;AACvB,SAAS,iBAAiB;AAiBnB,SAAS,eACd,UACG;AACH,QAAM,cAAc,OAAO,QAAQ;AAEnC,YAAU,MAAM;AACd,gBAAY,UAAU;AAAA,EACxB,CAAC;AAED,SAAO,YAAY;AACrB;","names":[]}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
export declare namespace tapEffect {
|
|
2
|
+
type Destructor = () => void;
|
|
3
|
+
type EffectCallback = () => void | Destructor;
|
|
4
|
+
}
|
|
5
|
+
export declare function tapEffect(effect: tapEffect.EffectCallback): void;
|
|
6
|
+
export declare function tapEffect(effect: tapEffect.EffectCallback, deps: readonly unknown[]): void;
|
|
4
7
|
//# sourceMappingURL=tap-effect.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tap-effect.d.ts","sourceRoot":"","sources":["../../src/hooks/tap-effect.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tap-effect.d.ts","sourceRoot":"","sources":["../../src/hooks/tap-effect.ts"],"names":[],"mappings":"AAiCA,yBAAiB,SAAS,CAAC;IACzB,KAAY,UAAU,GAAG,MAAM,IAAI,CAAC;IACpC,KAAY,cAAc,GAAG,MAAM,IAAI,GAAG,UAAU,CAAC;CACtD;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC;AAClE,wBAAgB,SAAS,CACvB,MAAM,EAAE,SAAS,CAAC,cAAc,EAChC,IAAI,EAAE,SAAS,OAAO,EAAE,GACvB,IAAI,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hooks/tap-effect.ts"],"sourcesContent":["import { getCurrentResourceFiber } from \"../core/execution-context\";\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/tap-effect.ts"],"sourcesContent":["import { getCurrentResourceFiber } from \"../core/execution-context\";\nimport { Cell } from \"../core/types\";\n\nfunction getEffectCell(): number {\n const fiber = getCurrentResourceFiber();\n const index = fiber.currentIndex++;\n\n // Check if we're trying to use more hooks than in previous renders\n if (!fiber.isFirstRender && index >= fiber.cells.length) {\n throw new Error(\n \"Rendered more hooks than during the previous render. \" +\n \"Hooks must be called in the exact same order in every render.\",\n );\n }\n\n if (!fiber.cells[index]) {\n // Create the effect cell\n const cell: Cell & { type: \"effect\" } = {\n type: \"effect\",\n mounted: false,\n };\n\n fiber.cells[index] = cell;\n }\n\n const cell = fiber.cells[index];\n if (cell.type !== \"effect\") {\n throw new Error(\"Hook order changed between renders\");\n }\n\n return index;\n}\n\nexport namespace tapEffect {\n export type Destructor = () => void;\n export type EffectCallback = () => void | Destructor;\n}\n\nexport function tapEffect(effect: tapEffect.EffectCallback): void;\nexport function tapEffect(\n effect: tapEffect.EffectCallback,\n deps: readonly unknown[],\n): void;\nexport function tapEffect(\n effect: tapEffect.EffectCallback,\n deps?: readonly unknown[],\n): void {\n const fiber = getCurrentResourceFiber();\n\n // Reserve a spot for the effect cell and get its index\n const cellIndex = getEffectCell();\n\n // Add task to render context for execution in commit phase\n fiber.renderContext!.commitTasks.push({\n effect,\n deps,\n cellIndex,\n });\n}\n"],"mappings":";AAAA,SAAS,+BAA+B;AAGxC,SAAS,gBAAwB;AAC/B,QAAM,QAAQ,wBAAwB;AACtC,QAAM,QAAQ,MAAM;AAGpB,MAAI,CAAC,MAAM,iBAAiB,SAAS,MAAM,MAAM,QAAQ;AACvD,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AAEA,MAAI,CAAC,MAAM,MAAM,KAAK,GAAG;AAEvB,UAAMA,QAAkC;AAAA,MACtC,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAEA,UAAM,MAAM,KAAK,IAAIA;AAAA,EACvB;AAEA,QAAM,OAAO,MAAM,MAAM,KAAK;AAC9B,MAAI,KAAK,SAAS,UAAU;AAC1B,UAAM,IAAI,MAAM,oCAAoC;AAAA,EACtD;AAEA,SAAO;AACT;AAYO,SAAS,UACd,QACA,MACM;AACN,QAAM,QAAQ,wBAAwB;AAGtC,QAAM,YAAY,cAAc;AAGhC,QAAM,cAAe,YAAY,KAAK;AAAA,IACpC;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACH;","names":["cell"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/hooks/tap-inline-resource.ts
|
|
2
|
-
import {
|
|
2
|
+
import { callResourceFn } from "../core/callResourceFn.js";
|
|
3
3
|
function tapInlineResource(element) {
|
|
4
|
-
return
|
|
4
|
+
return callResourceFn(element.type, element.props);
|
|
5
5
|
}
|
|
6
6
|
export {
|
|
7
7
|
tapInlineResource
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hooks/tap-inline-resource.ts"],"sourcesContent":["import { ResourceElement } from \"../core/types\";\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/tap-inline-resource.ts"],"sourcesContent":["import { ResourceElement } from \"../core/types\";\nimport { callResourceFn } from \"../core/callResourceFn\";\n\nexport function tapInlineResource<R, P>(element: ResourceElement<R, P>): R {\n return callResourceFn(element.type, element.props);\n}\n"],"mappings":";AACA,SAAS,sBAAsB;AAExB,SAAS,kBAAwB,SAAmC;AACzE,SAAO,eAAe,QAAQ,MAAM,QAAQ,KAAK;AACnD;","names":[]}
|
package/dist/hooks/tap-ref.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
1
|
+
export declare namespace tapRef {
|
|
2
|
+
interface RefObject<T> {
|
|
3
|
+
current: T;
|
|
4
|
+
}
|
|
3
5
|
}
|
|
4
|
-
export declare function tapRef<T>(initialValue: T): RefObject<T>;
|
|
5
|
-
export declare function tapRef<T = undefined>(): RefObject<T | undefined>;
|
|
6
|
+
export declare function tapRef<T>(initialValue: T): tapRef.RefObject<T>;
|
|
7
|
+
export declare function tapRef<T = undefined>(): tapRef.RefObject<T | undefined>;
|
|
6
8
|
//# sourceMappingURL=tap-ref.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tap-ref.d.ts","sourceRoot":"","sources":["../../src/hooks/tap-ref.ts"],"names":[],"mappings":"AAEA,MAAM,
|
|
1
|
+
{"version":3,"file":"tap-ref.d.ts","sourceRoot":"","sources":["../../src/hooks/tap-ref.ts"],"names":[],"mappings":"AAEA,yBAAiB,MAAM,CAAC;IACtB,UAAiB,SAAS,CAAC,CAAC;QAC1B,OAAO,EAAE,CAAC,CAAC;KACZ;CACF;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;AAChE,wBAAgB,MAAM,CAAC,CAAC,GAAG,SAAS,KAAK,MAAM,CAAC,SAAS,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hooks/tap-ref.ts"],"sourcesContent":["import { tapState } from \"./tap-state\";\n\nexport interface RefObject<T> {\n
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/tap-ref.ts"],"sourcesContent":["import { tapState } from \"./tap-state\";\n\nexport namespace tapRef {\n export interface RefObject<T> {\n current: T;\n }\n}\n\nexport function tapRef<T>(initialValue: T): tapRef.RefObject<T>;\nexport function tapRef<T = undefined>(): tapRef.RefObject<T | undefined>;\nexport function tapRef<T>(initialValue?: T): tapRef.RefObject<T | undefined> {\n const [state] = tapState(() => ({\n current: initialValue,\n }));\n return state;\n}\n"],"mappings":";AAAA,SAAS,gBAAgB;AAUlB,SAAS,OAAU,cAAmD;AAC3E,QAAM,CAAC,KAAK,IAAI,SAAS,OAAO;AAAA,IAC9B,SAAS;AAAA,EACX,EAAE;AACF,SAAO;AACT;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tap-resource.d.ts","sourceRoot":"","sources":["../../src/hooks/tap-resource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"tap-resource.d.ts","sourceRoot":"","sources":["../../src/hooks/tap-resource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAWhD,wBAAgB,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;AACrE,wBAAgB,WAAW,CAAC,CAAC,EAAE,CAAC,EAC9B,OAAO,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,EAC9B,IAAI,EAAE,SAAS,OAAO,EAAE,GACvB,CAAC,CAAC"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// src/hooks/tap-resource.ts
|
|
2
|
-
import { getResourceFn } from "../core/getResourceFn.js";
|
|
3
2
|
import { tapEffect } from "./tap-effect.js";
|
|
4
3
|
import {
|
|
5
4
|
createResourceFiber,
|
|
@@ -12,7 +11,7 @@ import { tapState } from "./tap-state.js";
|
|
|
12
11
|
function tapResource(element, deps) {
|
|
13
12
|
const [stateVersion, rerender] = tapState({});
|
|
14
13
|
const fiber = tapMemo(
|
|
15
|
-
() => createResourceFiber(
|
|
14
|
+
() => createResourceFiber(element.type, () => rerender({})),
|
|
16
15
|
[element.type]
|
|
17
16
|
);
|
|
18
17
|
const props = deps ? tapMemo(() => element.props, deps) : element.props;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hooks/tap-resource.ts"],"sourcesContent":["import { ResourceElement } from \"../core/types\";\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/tap-resource.ts"],"sourcesContent":["import { ResourceElement } from \"../core/types\";\nimport { tapEffect } from \"./tap-effect\";\nimport {\n createResourceFiber,\n unmountResource,\n renderResource,\n commitResource,\n} from \"../core/ResourceFiber\";\nimport { tapMemo } from \"./tap-memo\";\nimport { tapState } from \"./tap-state\";\n\nexport function tapResource<R, P>(element: ResourceElement<R, P>): R;\nexport function tapResource<R, P>(\n element: ResourceElement<R, P>,\n deps: readonly unknown[],\n): R;\nexport function tapResource<R, P>(\n element: ResourceElement<R, P>,\n deps?: readonly unknown[],\n): R {\n const [stateVersion, rerender] = tapState({});\n const fiber = tapMemo(\n () => createResourceFiber(element.type, () => rerender({})),\n [element.type],\n );\n\n const props = deps ? tapMemo(() => element.props, deps) : element.props;\n const result = tapMemo(\n () => renderResource(fiber, props),\n [fiber, props, stateVersion],\n );\n\n tapEffect(() => {\n return () => unmountResource(fiber);\n }, [fiber]);\n\n tapEffect(() => {\n commitResource(fiber, result);\n }, [fiber, result]);\n\n return result.state;\n}\n"],"mappings":";AACA,SAAS,iBAAiB;AAC1B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,eAAe;AACxB,SAAS,gBAAgB;AAOlB,SAAS,YACd,SACA,MACG;AACH,QAAM,CAAC,cAAc,QAAQ,IAAI,SAAS,CAAC,CAAC;AAC5C,QAAM,QAAQ;AAAA,IACZ,MAAM,oBAAoB,QAAQ,MAAM,MAAM,SAAS,CAAC,CAAC,CAAC;AAAA,IAC1D,CAAC,QAAQ,IAAI;AAAA,EACf;AAEA,QAAM,QAAQ,OAAO,QAAQ,MAAM,QAAQ,OAAO,IAAI,IAAI,QAAQ;AAClE,QAAM,SAAS;AAAA,IACb,MAAM,eAAe,OAAO,KAAK;AAAA,IACjC,CAAC,OAAO,OAAO,YAAY;AAAA,EAC7B;AAEA,YAAU,MAAM;AACd,WAAO,MAAM,gBAAgB,KAAK;AAAA,EACpC,GAAG,CAAC,KAAK,CAAC;AAEV,YAAU,MAAM;AACd,mBAAe,OAAO,MAAM;AAAA,EAC9B,GAAG,CAAC,OAAO,MAAM,CAAC;AAElB,SAAO,OAAO;AAChB;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tap-resources.d.ts","sourceRoot":"","sources":["../../src/hooks/tap-resources.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAiB,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"tap-resources.d.ts","sourceRoot":"","sources":["../../src/hooks/tap-resources.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAiB,MAAM,eAAe,CAAC;AAW/D,wBAAgB,YAAY,CAC1B,CAAC,SAAS,aAAa,CAAC,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,EAElD,QAAQ,EAAE,CAAC,GACV;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,eAAe,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK;CAAE,CAyF5E"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// src/hooks/tap-resources.ts
|
|
2
|
-
import { getResourceFn } from "../core/getResourceFn.js";
|
|
3
2
|
import { tapEffect } from "./tap-effect.js";
|
|
4
3
|
import { tapMemo } from "./tap-memo.js";
|
|
5
4
|
import { tapState } from "./tap-state.js";
|
|
@@ -38,12 +37,9 @@ function tapResources(elements) {
|
|
|
38
37
|
elementsByKey.forEach((element, key) => {
|
|
39
38
|
currentKeys.add(key);
|
|
40
39
|
let fiber = fibers.get(key);
|
|
41
|
-
if (!fiber || fiber.
|
|
40
|
+
if (!fiber || fiber.resource !== element.type) {
|
|
42
41
|
if (fiber) unmountResource(fiber);
|
|
43
|
-
fiber = createResourceFiber(
|
|
44
|
-
getResourceFn(element.type),
|
|
45
|
-
() => rerender({})
|
|
46
|
-
);
|
|
42
|
+
fiber = createResourceFiber(element.type, () => rerender({}));
|
|
47
43
|
fibers.set(key, fiber);
|
|
48
44
|
}
|
|
49
45
|
const result = renderResource(fiber, element.props);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hooks/tap-resources.ts"],"sourcesContent":["import { ResourceElement, ResourceFiber } from \"../core/types\";\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/tap-resources.ts"],"sourcesContent":["import { ResourceElement, ResourceFiber } from \"../core/types\";\nimport { tapEffect } from \"./tap-effect\";\nimport { tapMemo } from \"./tap-memo\";\nimport { tapState } from \"./tap-state\";\nimport {\n createResourceFiber,\n unmountResource,\n renderResource,\n commitResource,\n} from \"../core/ResourceFiber\";\n\nexport function tapResources<\n T extends ReadonlyArray<ResourceElement<any, any>>,\n>(\n elements: T,\n): { [K in keyof T]: T[K] extends ResourceElement<infer R, any> ? R : never } {\n // Validate keys\n const seenKeys = new Set<string | number>();\n elements.forEach((element, index) => {\n if (element.key === undefined) {\n throw new Error(\n `tapResources: All resource elements must have a key. Element at index ${index} is missing a key.`,\n );\n }\n if (seenKeys.has(element.key)) {\n throw new Error(\n `tapResources: Duplicate key \"${element.key}\" found. All keys must be unique.`,\n );\n }\n seenKeys.add(element.key);\n });\n\n const [stateVersion, rerender] = tapState({});\n\n // Create a map of current elements by key for efficient lookup\n const elementsByKey = tapMemo(\n () => new Map(elements.map((element) => [element.key!, element])),\n [elements],\n );\n\n // Track fibers persistently across renders\n const [fibers] = tapState(\n () => new Map<string | number, ResourceFiber<any, any>>(),\n );\n\n // Process each element\n const results = tapMemo(() => {\n const resultMap = new Map<string | number, any>();\n const currentKeys = new Set<string | number>();\n\n // Create/update fibers and render\n elementsByKey.forEach((element, key) => {\n currentKeys.add(key);\n\n let fiber = fibers.get(key);\n\n // Create new fiber if needed or type changed\n if (!fiber || fiber.resource !== element.type) {\n if (fiber) unmountResource(fiber);\n fiber = createResourceFiber(element.type, () => rerender({}));\n fibers.set(key, fiber);\n }\n\n // Render with current props\n const result = renderResource(fiber, element.props);\n resultMap.set(key, result);\n });\n\n // Clean up removed fibers\n fibers.forEach((fiber, key) => {\n if (!currentKeys.has(key)) {\n unmountResource(fiber);\n fibers.delete(key);\n }\n });\n\n return resultMap;\n }, [elementsByKey, stateVersion]);\n\n // Commit all renders\n tapEffect(() => {\n results.forEach((result, key) => {\n const fiber = fibers.get(key);\n if (fiber) {\n commitResource(fiber, result);\n }\n });\n }, [results, fibers]);\n\n // Cleanup on unmount\n tapEffect(() => {\n return () => {\n fibers.forEach((fiber) => {\n unmountResource(fiber);\n });\n fibers.clear();\n };\n }, [fibers]);\n\n // Return results in the same order as input elements\n return tapMemo(\n () => elements.map((element) => results.get(element.key!)?.state),\n [elements, results],\n ) as any;\n}\n"],"mappings":";AACA,SAAS,iBAAiB;AAC1B,SAAS,eAAe;AACxB,SAAS,gBAAgB;AACzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,SAAS,aAGd,UAC4E;AAE5E,QAAM,WAAW,oBAAI,IAAqB;AAC1C,WAAS,QAAQ,CAAC,SAAS,UAAU;AACnC,QAAI,QAAQ,QAAQ,QAAW;AAC7B,YAAM,IAAI;AAAA,QACR,yEAAyE,KAAK;AAAA,MAChF;AAAA,IACF;AACA,QAAI,SAAS,IAAI,QAAQ,GAAG,GAAG;AAC7B,YAAM,IAAI;AAAA,QACR,gCAAgC,QAAQ,GAAG;AAAA,MAC7C;AAAA,IACF;AACA,aAAS,IAAI,QAAQ,GAAG;AAAA,EAC1B,CAAC;AAED,QAAM,CAAC,cAAc,QAAQ,IAAI,SAAS,CAAC,CAAC;AAG5C,QAAM,gBAAgB;AAAA,IACpB,MAAM,IAAI,IAAI,SAAS,IAAI,CAAC,YAAY,CAAC,QAAQ,KAAM,OAAO,CAAC,CAAC;AAAA,IAChE,CAAC,QAAQ;AAAA,EACX;AAGA,QAAM,CAAC,MAAM,IAAI;AAAA,IACf,MAAM,oBAAI,IAA8C;AAAA,EAC1D;AAGA,QAAM,UAAU,QAAQ,MAAM;AAC5B,UAAM,YAAY,oBAAI,IAA0B;AAChD,UAAM,cAAc,oBAAI,IAAqB;AAG7C,kBAAc,QAAQ,CAAC,SAAS,QAAQ;AACtC,kBAAY,IAAI,GAAG;AAEnB,UAAI,QAAQ,OAAO,IAAI,GAAG;AAG1B,UAAI,CAAC,SAAS,MAAM,aAAa,QAAQ,MAAM;AAC7C,YAAI,MAAO,iBAAgB,KAAK;AAChC,gBAAQ,oBAAoB,QAAQ,MAAM,MAAM,SAAS,CAAC,CAAC,CAAC;AAC5D,eAAO,IAAI,KAAK,KAAK;AAAA,MACvB;AAGA,YAAM,SAAS,eAAe,OAAO,QAAQ,KAAK;AAClD,gBAAU,IAAI,KAAK,MAAM;AAAA,IAC3B,CAAC;AAGD,WAAO,QAAQ,CAAC,OAAO,QAAQ;AAC7B,UAAI,CAAC,YAAY,IAAI,GAAG,GAAG;AACzB,wBAAgB,KAAK;AACrB,eAAO,OAAO,GAAG;AAAA,MACnB;AAAA,IACF,CAAC;AAED,WAAO;AAAA,EACT,GAAG,CAAC,eAAe,YAAY,CAAC;AAGhC,YAAU,MAAM;AACd,YAAQ,QAAQ,CAAC,QAAQ,QAAQ;AAC/B,YAAM,QAAQ,OAAO,IAAI,GAAG;AAC5B,UAAI,OAAO;AACT,uBAAe,OAAO,MAAM;AAAA,MAC9B;AAAA,IACF,CAAC;AAAA,EACH,GAAG,CAAC,SAAS,MAAM,CAAC;AAGpB,YAAU,MAAM;AACd,WAAO,MAAM;AACX,aAAO,QAAQ,CAAC,UAAU;AACxB,wBAAgB,KAAK;AAAA,MACvB,CAAC;AACD,aAAO,MAAM;AAAA,IACf;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAGX,SAAO;AAAA,IACL,MAAM,SAAS,IAAI,CAAC,YAAY,QAAQ,IAAI,QAAQ,GAAI,GAAG,KAAK;AAAA,IAChE,CAAC,UAAU,OAAO;AAAA,EACpB;AACF;","names":[]}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ResourceFiber } from "../core/types";
|
|
2
|
+
export declare namespace tapState {
|
|
3
|
+
type StateUpdater<S> = S | ((prev: S) => S);
|
|
4
|
+
}
|
|
2
5
|
export declare const rerender: (fiber: ResourceFiber<any, any>) => void;
|
|
3
6
|
export declare function tapState<S = undefined>(): [
|
|
4
7
|
S | undefined,
|
|
5
|
-
(updater: StateUpdater<S>) => void
|
|
8
|
+
(updater: tapState.StateUpdater<S>) => void
|
|
6
9
|
];
|
|
7
|
-
export declare function tapState<S>(initial: S | (() => S)): [S, (updater: StateUpdater<S>) => void];
|
|
10
|
+
export declare function tapState<S>(initial: S | (() => S)): [S, (updater: tapState.StateUpdater<S>) => void];
|
|
8
11
|
//# sourceMappingURL=tap-state.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tap-state.d.ts","sourceRoot":"","sources":["../../src/hooks/tap-state.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"tap-state.d.ts","sourceRoot":"","sources":["../../src/hooks/tap-state.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,aAAa,EAAE,MAAM,eAAe,CAAC;AAEpD,yBAAiB,QAAQ,CAAC;IACxB,KAAY,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;CACpD;AAED,eAAO,MAAM,QAAQ,GAAI,OAAO,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,SAWtD,CAAC;AAmDF,wBAAgB,QAAQ,CAAC,CAAC,GAAG,SAAS,KAAK;IACzC,CAAC,GAAG,SAAS;IACb,CAAC,OAAO,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI;CAC5C,CAAC;AACF,wBAAgB,QAAQ,CAAC,CAAC,EACxB,OAAO,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GACrB,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hooks/tap-state.ts"],"sourcesContent":["import { getCurrentResourceFiber } from \"../core/execution-context\";\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/tap-state.ts"],"sourcesContent":["import { getCurrentResourceFiber } from \"../core/execution-context\";\nimport { Cell, ResourceFiber } from \"../core/types\";\n\nexport namespace tapState {\n export type StateUpdater<S> = S | ((prev: S) => S);\n}\n\nexport const rerender = (fiber: ResourceFiber<any, any>) => {\n if (fiber.renderContext) {\n throw new Error(\"Resource updated during render\");\n }\n if (fiber.isNeverMounted) {\n throw new Error(\"Resource updated before mount\");\n }\n // Only schedule rerender if currently mounted\n if (fiber.isMounted) {\n fiber.scheduleRerender();\n }\n};\n\nfunction getStateCell<T>(\n initialValue: T | (() => T),\n): Cell & { type: \"state\" } {\n const fiber = getCurrentResourceFiber();\n const index = fiber.currentIndex++;\n\n // Check if we're trying to use more hooks than in previous renders\n if (!fiber.isFirstRender && index >= fiber.cells.length) {\n throw new Error(\n \"Rendered more hooks than during the previous render. \" +\n \"Hooks must be called in the exact same order in every render.\",\n );\n }\n\n if (!fiber.cells[index]) {\n // Initialize the value immediately\n const value =\n typeof initialValue === \"function\"\n ? (initialValue as () => T)()\n : initialValue;\n\n const cell: Cell & { type: \"state\" } = {\n type: \"state\",\n value,\n set: (updater: tapState.StateUpdater<T>) => {\n const currentValue = cell.value;\n const nextValue =\n typeof updater === \"function\"\n ? (updater as (prev: T) => T)(currentValue)\n : updater;\n\n if (!Object.is(currentValue, nextValue)) {\n cell.value = nextValue;\n rerender(fiber);\n }\n },\n };\n\n fiber.cells[index] = cell;\n }\n\n const cell = fiber.cells[index];\n if (cell.type !== \"state\") {\n throw new Error(\"Hook order changed between renders\");\n }\n\n return cell as Cell & { type: \"state\" };\n}\n\nexport function tapState<S = undefined>(): [\n S | undefined,\n (updater: tapState.StateUpdater<S>) => void,\n];\nexport function tapState<S>(\n initial: S | (() => S),\n): [S, (updater: tapState.StateUpdater<S>) => void];\nexport function tapState<S>(\n initial?: S | (() => S),\n): [S | undefined, (updater: tapState.StateUpdater<S>) => void] {\n const cell = getStateCell(initial as S | (() => S));\n\n return [cell.value, cell.set];\n}\n"],"mappings":";AAAA,SAAS,+BAA+B;AAOjC,IAAM,WAAW,CAAC,UAAmC;AAC1D,MAAI,MAAM,eAAe;AACvB,UAAM,IAAI,MAAM,gCAAgC;AAAA,EAClD;AACA,MAAI,MAAM,gBAAgB;AACxB,UAAM,IAAI,MAAM,+BAA+B;AAAA,EACjD;AAEA,MAAI,MAAM,WAAW;AACnB,UAAM,iBAAiB;AAAA,EACzB;AACF;AAEA,SAAS,aACP,cAC0B;AAC1B,QAAM,QAAQ,wBAAwB;AACtC,QAAM,QAAQ,MAAM;AAGpB,MAAI,CAAC,MAAM,iBAAiB,SAAS,MAAM,MAAM,QAAQ;AACvD,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AAEA,MAAI,CAAC,MAAM,MAAM,KAAK,GAAG;AAEvB,UAAM,QACJ,OAAO,iBAAiB,aACnB,aAAyB,IAC1B;AAEN,UAAMA,QAAiC;AAAA,MACrC,MAAM;AAAA,MACN;AAAA,MACA,KAAK,CAAC,YAAsC;AAC1C,cAAM,eAAeA,MAAK;AAC1B,cAAM,YACJ,OAAO,YAAY,aACd,QAA2B,YAAY,IACxC;AAEN,YAAI,CAAC,OAAO,GAAG,cAAc,SAAS,GAAG;AACvC,UAAAA,MAAK,QAAQ;AACb,mBAAS,KAAK;AAAA,QAChB;AAAA,MACF;AAAA,IACF;AAEA,UAAM,MAAM,KAAK,IAAIA;AAAA,EACvB;AAEA,QAAM,OAAO,MAAM,MAAM,KAAK;AAC9B,MAAI,KAAK,SAAS,SAAS;AACzB,UAAM,IAAI,MAAM,oCAAoC;AAAA,EACtD;AAEA,SAAO;AACT;AASO,SAAS,SACd,SAC8D;AAC9D,QAAM,OAAO,aAAa,OAAwB;AAElD,SAAO,CAAC,KAAK,OAAO,KAAK,GAAG;AAC9B;","names":["cell"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
export { resource } from "./core/resource";
|
|
2
2
|
export { tapState } from "./hooks/tap-state";
|
|
3
3
|
export { tapEffect } from "./hooks/tap-effect";
|
|
4
|
-
export { tapRef
|
|
4
|
+
export { tapRef } from "./hooks/tap-ref";
|
|
5
5
|
export { tapMemo } from "./hooks/tap-memo";
|
|
6
6
|
export { tapCallback } from "./hooks/tap-callback";
|
|
7
|
+
export { tapEffectEvent } from "./hooks/tap-effect-event";
|
|
7
8
|
export { tapResource } from "./hooks/tap-resource";
|
|
8
9
|
export { tapInlineResource } from "./hooks/tap-inline-resource";
|
|
9
10
|
export { tapResources } from "./hooks/tap-resources";
|
|
10
|
-
export { createResource
|
|
11
|
+
export { createResource } from "./core/ResourceHandle";
|
|
11
12
|
export { createContext, tapContext, withContextProvider } from "./core/context";
|
|
12
|
-
export type {
|
|
13
|
+
export type { Resource, ContravariantResource, ResourceElement, } from "./core/types";
|
|
13
14
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAG3C,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAG/C,OAAO,EAAE,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAG3C,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAG/C,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAG1D,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAGrD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAGvD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAGhF,YAAY,EACV,QAAQ,EACR,qBAAqB,EACrB,eAAe,GAChB,MAAM,cAAc,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { tapEffect } from "./hooks/tap-effect.js";
|
|
|
5
5
|
import { tapRef } from "./hooks/tap-ref.js";
|
|
6
6
|
import { tapMemo } from "./hooks/tap-memo.js";
|
|
7
7
|
import { tapCallback } from "./hooks/tap-callback.js";
|
|
8
|
+
import { tapEffectEvent } from "./hooks/tap-effect-event.js";
|
|
8
9
|
import { tapResource } from "./hooks/tap-resource.js";
|
|
9
10
|
import { tapInlineResource } from "./hooks/tap-inline-resource.js";
|
|
10
11
|
import { tapResources } from "./hooks/tap-resources.js";
|
|
@@ -17,6 +18,7 @@ export {
|
|
|
17
18
|
tapCallback,
|
|
18
19
|
tapContext,
|
|
19
20
|
tapEffect,
|
|
21
|
+
tapEffectEvent,
|
|
20
22
|
tapInlineResource,
|
|
21
23
|
tapMemo,
|
|
22
24
|
tapRef,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { resource } from \"./core/resource\";\n\n// primitive hooks\nexport { tapState } from \"./hooks/tap-state\";\nexport { tapEffect } from \"./hooks/tap-effect\";\n\n// utility hooks\nexport { tapRef
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { resource } from \"./core/resource\";\n\n// primitive hooks\nexport { tapState } from \"./hooks/tap-state\";\nexport { tapEffect } from \"./hooks/tap-effect\";\n\n// utility hooks\nexport { tapRef } from \"./hooks/tap-ref\";\nexport { tapMemo } from \"./hooks/tap-memo\";\nexport { tapCallback } from \"./hooks/tap-callback\";\nexport { tapEffectEvent } from \"./hooks/tap-effect-event\";\n\n// resources\nexport { tapResource } from \"./hooks/tap-resource\";\nexport { tapInlineResource } from \"./hooks/tap-inline-resource\";\nexport { tapResources } from \"./hooks/tap-resources\";\n\n// imperative\nexport { createResource } from \"./core/ResourceHandle\";\n\n// context\nexport { createContext, tapContext, withContextProvider } from \"./core/context\";\n\n// types\nexport type {\n Resource,\n ContravariantResource,\n ResourceElement,\n} from \"./core/types\";\n"],"mappings":";AAAA,SAAS,gBAAgB;AAGzB,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAG1B,SAAS,cAAc;AACvB,SAAS,eAAe;AACxB,SAAS,mBAAmB;AAC5B,SAAS,sBAAsB;AAG/B,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB;AAClC,SAAS,oBAAoB;AAG7B,SAAS,sBAAsB;AAG/B,SAAS,eAAe,YAAY,2BAA2B;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-resource.d.ts","sourceRoot":"","sources":["../../src/react/use-resource.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"use-resource.d.ts","sourceRoot":"","sources":["../../src/react/use-resource.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAehD,wBAAgB,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAgBnE"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// src/react/use-resource.ts
|
|
2
2
|
import { useEffect, useLayoutEffect, useMemo, useState } from "react";
|
|
3
|
-
import { getResourceFn } from "../core/getResourceFn.js";
|
|
4
3
|
import {
|
|
5
4
|
createResourceFiber,
|
|
6
5
|
unmountResource,
|
|
@@ -12,7 +11,7 @@ var useIsomorphicLayoutEffect = shouldAvoidLayoutEffect ? useEffect : useLayoutE
|
|
|
12
11
|
function useResource(element) {
|
|
13
12
|
const [, rerender] = useState({});
|
|
14
13
|
const fiber = useMemo(
|
|
15
|
-
() => createResourceFiber(
|
|
14
|
+
() => createResourceFiber(element.type, () => rerender({})),
|
|
16
15
|
[element.type, rerender]
|
|
17
16
|
);
|
|
18
17
|
const result = renderResource(fiber, element.props);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/use-resource.ts"],"sourcesContent":["import { useEffect, useLayoutEffect, useMemo, useState } from \"react\";\nimport { ResourceElement } from \"../core/types\";\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/react/use-resource.ts"],"sourcesContent":["import { useEffect, useLayoutEffect, useMemo, useState } from \"react\";\nimport { ResourceElement } from \"../core/types\";\nimport {\n createResourceFiber,\n unmountResource,\n renderResource,\n commitResource,\n} from \"../core/ResourceFiber\";\n\nconst shouldAvoidLayoutEffect =\n (globalThis as any).__ASSISTANT_UI_DISABLE_LAYOUT_EFFECT__ === true;\n\nconst useIsomorphicLayoutEffect = shouldAvoidLayoutEffect\n ? useEffect\n : useLayoutEffect;\n\nexport function useResource<R, P>(element: ResourceElement<R, P>): R {\n const [, rerender] = useState({});\n const fiber = useMemo(\n () => createResourceFiber(element.type, () => rerender({})),\n [element.type, rerender],\n );\n\n const result = renderResource(fiber, element.props);\n useIsomorphicLayoutEffect(() => {\n return () => unmountResource(fiber);\n }, []);\n useIsomorphicLayoutEffect(() => {\n commitResource(fiber, result);\n });\n\n return result.state;\n}\n"],"mappings":";AAAA,SAAS,WAAW,iBAAiB,SAAS,gBAAgB;AAE9D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,IAAM,0BACH,WAAmB,2CAA2C;AAEjE,IAAM,4BAA4B,0BAC9B,YACA;AAEG,SAAS,YAAkB,SAAmC;AACnE,QAAM,CAAC,EAAE,QAAQ,IAAI,SAAS,CAAC,CAAC;AAChC,QAAM,QAAQ;AAAA,IACZ,MAAM,oBAAoB,QAAQ,MAAM,MAAM,SAAS,CAAC,CAAC,CAAC;AAAA,IAC1D,CAAC,QAAQ,MAAM,QAAQ;AAAA,EACzB;AAEA,QAAM,SAAS,eAAe,OAAO,QAAQ,KAAK;AAClD,4BAA0B,MAAM;AAC9B,WAAO,MAAM,gBAAgB,KAAK;AAAA,EACpC,GAAG,CAAC,CAAC;AACL,4BAA0B,MAAM;AAC9B,mBAAe,OAAO,MAAM;AAAA,EAC9B,CAAC;AAED,SAAO,OAAO;AAChB;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/tap",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Zero-dependency reactive state management inspired by React hooks",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@types/node": "^24.10.1",
|
|
22
22
|
"@types/react": "*",
|
|
23
|
-
"@vitest/ui": "^4.0.
|
|
23
|
+
"@vitest/ui": "^4.0.14",
|
|
24
24
|
"tsx": "^4.20.6",
|
|
25
25
|
"typescript": "^5.9.3",
|
|
26
|
-
"vitest": "^4.0.
|
|
26
|
+
"vitest": "^4.0.14",
|
|
27
27
|
"@assistant-ui/x-buildutils": "0.0.1"
|
|
28
28
|
},
|
|
29
29
|
"exports": {
|
|
@@ -39,6 +39,17 @@
|
|
|
39
39
|
"files": [
|
|
40
40
|
"dist"
|
|
41
41
|
],
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public",
|
|
44
|
+
"provenance": true
|
|
45
|
+
},
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "https://github.com/assistant-ui/assistant-ui/tree/main/packages/tap"
|
|
49
|
+
},
|
|
50
|
+
"bugs": {
|
|
51
|
+
"url": "https://github.com/assistant-ui/assistant-ui/issues"
|
|
52
|
+
},
|
|
42
53
|
"scripts": {
|
|
43
54
|
"build": "tsx build.mts",
|
|
44
55
|
"lint": "eslint .",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"getResourceFn.d.ts","sourceRoot":"","sources":["../../src/core/getResourceFn.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/getResourceFn.ts"],"sourcesContent":["import { ResourceFn, Resource } from \"./types\";\n\n/**\n * Get the ResourceFn from a Resource constructor.\n * @internal This is for internal use only.\n */\nexport function getResourceFn<R, P>(\n resource: Resource<R, P>,\n): ResourceFn<R, P> {\n const fn = (resource as unknown as { [fnSymbol]?: ResourceFn<R, P> })[\n fnSymbol\n ];\n if (!fn) {\n throw new Error(\"ResourceElement.type is not a valid Resource\");\n }\n return fn;\n}\n\n/**\n * Symbol used to store the ResourceFn in the Resource constructor.\n * @internal This is for internal use only.\n */\nexport const fnSymbol = Symbol(\"fnSymbol\");\n"],"mappings":";AAMO,SAAS,cACd,UACkB;AAClB,QAAM,KAAM,SACV,QACF;AACA,MAAI,CAAC,IAAI;AACP,UAAM,IAAI,MAAM,8CAA8C;AAAA,EAChE;AACA,SAAO;AACT;AAMO,IAAM,WAAW,OAAO,UAAU;","names":[]}
|