march-hare 0.6.0 → 0.7.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.
@@ -6,24 +6,28 @@ export { getActionSymbol, getLifecycleType, isBroadcastAction, isMulticastAction
6
6
  type ActionFactory = {
7
7
  /**
8
8
  * Creates a new unicast action with the given name.
9
+ *
10
+ * `K` is the literal type of the action name and is captured as a phantom
11
+ * brand so `Action("A")` and `Action("B")` produce structurally-distinct
12
+ * types. **Note:** when the caller supplies any explicit generic
13
+ * (`Action<P>("Name")`), TypeScript fills `K` from its default and the
14
+ * literal is lost. The Name brand still helps for `Action("Name")` calls
15
+ * (e.g. lifecycle / no-payload actions) which is the most common source of
16
+ * foreign-class collisions.
17
+ *
9
18
  * @template P The payload type for the action.
10
- * @template C The channel type for channeled dispatches (defaults to never).
11
- * @param name The action name, used for debugging purposes.
12
- * @returns A typed action object.
19
+ * @template C The channel type for channeled dispatches.
20
+ * @template K The literal type of the action name (inferred when no other
21
+ * generics are supplied; defaults to `string` otherwise).
13
22
  */
14
- <P = never, C extends Filter = never>(name: string): HandlerPayload<P, C>;
23
+ <P = never, C extends Filter = never, K extends string = string>(name: K): HandlerPayload<P, C, K>;
15
24
  /**
16
25
  * Creates a new action with the specified distribution mode.
17
- * @template P The payload type for the action.
18
- * @template C The channel type for channeled dispatches (defaults to never).
19
- * @param name The action name, used for debugging purposes.
20
- * @param distribution The distribution mode (Unicast, Broadcast, or Multicast).
21
- * @returns A typed action object (BroadcastPayload if Broadcast, MulticastPayload if Multicast).
22
26
  */
23
- <P = never, C extends Filter = never>(name: string, distribution: Distribution.Broadcast): BroadcastPayload<P, C>;
24
- <P = never, C extends Filter = never>(name: string, distribution: Distribution.Multicast): MulticastPayload<P, C>;
25
- <P = never, C extends Filter = never>(name: string, distribution: Distribution.Unicast): HandlerPayload<P, C>;
26
- <P = never, C extends Filter = never>(name: string, distribution: Distribution): HandlerPayload<P, C> | BroadcastPayload<P, C> | MulticastPayload<P, C>;
27
+ <P = never, C extends Filter = never, K extends string = string>(name: K, distribution: Distribution.Broadcast): BroadcastPayload<P, C, K>;
28
+ <P = never, C extends Filter = never, K extends string = string>(name: K, distribution: Distribution.Multicast): MulticastPayload<P, C, K>;
29
+ <P = never, C extends Filter = never, K extends string = string>(name: K, distribution: Distribution.Unicast): HandlerPayload<P, C, K>;
30
+ <P = never, C extends Filter = never, K extends string = string>(name: K, distribution: Distribution): HandlerPayload<P, C, K> | BroadcastPayload<P, C, K> | MulticastPayload<P, C, K>;
27
31
  };
28
32
  /**
29
33
  * Creates a new action with a given payload type, optional channel type, and optional distribution mode.
@@ -0,0 +1,41 @@
1
+ import { Props } from './types.ts';
2
+ import * as React from "react";
3
+ export { useStore } from './utils.ts';
4
+ /**
5
+ * App-wide store of cross-cutting, mutable state. The interface is
6
+ * declared empty here and **augmented** by consumer code via module
7
+ * augmentation:
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * declare module "march-hare" {
12
+ * interface Store {
13
+ * session: Session | null;
14
+ * locale: string;
15
+ * featureFlags: Record<string, boolean>;
16
+ * }
17
+ * }
18
+ * ```
19
+ *
20
+ * Every key declared here flows into:
21
+ *
22
+ * - `useStore()` &mdash; the per-`<Boundary>` handle for reads and writes.
23
+ * - `context.store` inside `useActions` handlers.
24
+ * - The `store` field on every `Resource` fetcher's args object.
25
+ *
26
+ * The Store is **not** reactive. Mutating it does not re-render. Drive
27
+ * view state through the model; use the Store for cross-handler
28
+ * coordination, session tokens, locale, feature flags, etc.
29
+ */
30
+ export interface Store {
31
+ }
32
+ /**
33
+ * Provides a per-Boundary {@link Store} value to every component inside
34
+ * the boundary. Usually wired in via the `<Boundary store={initial}>`
35
+ * prop rather than used directly.
36
+ *
37
+ * The Store is **not** reactive. Mutating it does not trigger a
38
+ * re-render. Drive view state through the model; use the Store for
39
+ * cross-handler coordination.
40
+ */
41
+ export declare function Store({ initial, children }: Props): React.ReactNode;
@@ -0,0 +1,11 @@
1
+ import { ReactNode } from 'react';
2
+ import { Store } from './index.tsx';
3
+ export type { Store } from './index.tsx';
4
+ /**
5
+ * Props for the Store provider component. Accepts the initial Store
6
+ * value that satisfies the augmented {@link Store} interface.
7
+ */
8
+ export type Props = {
9
+ initial: Store;
10
+ children: ReactNode;
11
+ };
@@ -0,0 +1,64 @@
1
+ import { RefObject } from 'react';
2
+ import { Store } from './index.tsx';
3
+ import * as React from "react";
4
+ /**
5
+ * React context exposing the per-Boundary Store ref. The ref itself is
6
+ * stable across renders &mdash; readers grab `.current` at call time.
7
+ *
8
+ * @internal
9
+ */
10
+ export declare const Context: React.Context<React.RefObject<Store>>;
11
+ /**
12
+ * Hook that returns a read-only handle to the per-Boundary {@link Store}.
13
+ * Reads use plain dot notation (`store.session`) and always reflect the
14
+ * latest value, even after `await` boundaries &mdash; the handle is a
15
+ * `Proxy` that delegates property access to the live ref.
16
+ *
17
+ * Writes are not exposed here. Mutate the Store inside an action handler
18
+ * via `context.actions.produce(({ model, store }) => { store.x = ... })`
19
+ * &mdash; the same Immer-style recipe used for the model. Mutations do
20
+ * **not** trigger a re-render; drive view state through the model.
21
+ *
22
+ * The Store's shape is declared via module augmentation on the library's
23
+ * {@link Store} interface, so dot reads are fully typed at every call
24
+ * site.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * declare module "march-hare" {
29
+ * interface Store {
30
+ * session: Session | null;
31
+ * locale: string;
32
+ * }
33
+ * }
34
+ *
35
+ * function useAuthActions() {
36
+ * const store = useStore();
37
+ * const actions = useActions<void, typeof Actions>();
38
+ *
39
+ * actions.useAction(Actions.SignIn, async (context, credentials) => {
40
+ * const result = await context.actions.resource(signIn(credentials));
41
+ * context.actions.produce(({ store }) => {
42
+ * store.session = result;
43
+ * });
44
+ * });
45
+ *
46
+ * actions.useAction(Actions.Refresh, async (context) => {
47
+ * if (store.session === null) return;
48
+ * // ...
49
+ * });
50
+ *
51
+ * return actions;
52
+ * }
53
+ * ```
54
+ */
55
+ export declare function useStore(): Store;
56
+ /**
57
+ * Internal accessor for the per-Boundary Store ref &mdash; used by the
58
+ * Resource layer to pass a fresh snapshot to each fetcher invocation
59
+ * and by the action layer to write through `context.actions.produce`.
60
+ * Not exported from the library.
61
+ *
62
+ * @internal
63
+ */
64
+ export declare function useStoreRef(): RefObject<Store>;
@@ -27,9 +27,9 @@ export type ActionId = symbol | string;
27
27
  * ```
28
28
  */
29
29
  export type Task<P = unknown> = {
30
- controller: AbortController;
31
- action: ActionId;
32
- payload: P;
30
+ readonly controller: AbortController;
31
+ readonly action: ActionId;
32
+ readonly payload: P;
33
33
  };
34
34
  /**
35
35
  * A set of running tasks ordered by creation time (oldest first).
@@ -2,19 +2,20 @@ import { Props } from './types.ts';
2
2
  import * as React from "react";
3
3
  /**
4
4
  * Creates a unified context boundary for all March Hare features.
5
- * Wraps children with Broadcaster, Mode, and Tasks providers.
5
+ * Wraps children with Broadcaster, Store, and Tasks providers.
6
6
  *
7
- * Use this at the root of your application or to create isolated context boundaries
8
- * for libraries that need their own March Hare context.
7
+ * Use this at the root of your application or to create isolated context
8
+ * boundaries for libraries that need their own March Hare context.
9
9
  *
10
- * @param props.children - The children to render within the boundary.
11
- * @returns The children wrapped in all required context providers.
10
+ * Pass the `store` prop with the initial Store value (session, locale,
11
+ * feature flags, etc.) &mdash; the shape is determined by module
12
+ * augmentation on the library's `Store` interface.
12
13
  *
13
14
  * @example
14
15
  * ```tsx
15
- * <Boundary>
16
+ * <Boundary store={{ session: null, locale: "en-GB" }}>
16
17
  * <App />
17
18
  * </Boundary>
18
19
  * ```
19
20
  */
20
- export declare function Boundary({ children }: Props): React.ReactNode;
21
+ export declare function Boundary({ store, children }: Props): React.ReactNode;
@@ -1,4 +1,22 @@
1
+ import { Store } from './components/store/types.ts';
1
2
  import type * as React from "react";
2
3
  export type Props = {
4
+ /**
5
+ * Initial value of the per-Boundary {@link Store}. The shape is
6
+ * derived from module augmentation &mdash; declare the keys your
7
+ * application needs once via:
8
+ *
9
+ * ```ts
10
+ * declare module "march-hare" {
11
+ * interface Store {
12
+ * session: Session | null;
13
+ * locale: string;
14
+ * }
15
+ * }
16
+ * ```
17
+ *
18
+ * Optional only when the augmented Store has no required keys.
19
+ */
20
+ store?: Store;
3
21
  children: React.ReactNode;
4
22
  };
@@ -0,0 +1,44 @@
1
+ import { Adapter, Stored } from './types.ts';
2
+ export type { Adapter, Encoded } from './types.ts';
3
+ /**
4
+ * Persistence-aware cache for a single {@link Resource}. Wraps a
5
+ * synchronous {@link Adapter} (localStorage, MMKV, chrome.storage with a
6
+ * sync facade, etc.) and traffics in {@link Stored} envelopes &mdash;
7
+ * storage entries serialise as {@link Encoded}`<T>` so the
8
+ * `Temporal.Instant` timestamp survives the string round-trip and
9
+ * `.exceeds({...})` can short-circuit on the persisted timestamp after
10
+ * a reload.
11
+ *
12
+ * Call with no arguments for an in-memory cache scoped to this
13
+ * instance &mdash; useful for tests, ephemeral state, or when you want a
14
+ * first-class cache object to share between Resources without
15
+ * persistence. Pass an {@link Adapter} to back the cache with a
16
+ * persistent store.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * // In-memory, scoped to this instance.
21
+ * const cache = Cache();
22
+ *
23
+ * // Persisted via localStorage.
24
+ * const cache = Cache({
25
+ * get: (key) => localStorage.getItem(key),
26
+ * set: (key, value) => localStorage.setItem(key, value),
27
+ * remove: (key) => localStorage.removeItem(key),
28
+ * clear: () => localStorage.clear(),
29
+ * });
30
+ *
31
+ * // Wired into a Resource — successful runs write through automatically.
32
+ * export const cat = Resource(
33
+ * async ({ controller }) => fetchCat(controller.signal),
34
+ * cache,
35
+ * );
36
+ * ```
37
+ */
38
+ export type Cache = {
39
+ get<T>(key: string): Stored<T>;
40
+ set<T>(key: string, value: Stored<T>): boolean;
41
+ remove(key: string): void;
42
+ clear(): void;
43
+ };
44
+ export declare function Cache(adapter?: Adapter): Cache;
@@ -0,0 +1,54 @@
1
+ export type { Stored } from '../utils/types.ts';
2
+ /**
3
+ * On-disk JSON shape of a `Stored` envelope. The Cache wrapper
4
+ * encodes a populated Stored as `{ data, at: at.toString() }` so the
5
+ * `Temporal.Instant` survives the string round-trip, and decodes via
6
+ * `Temporal.Instant.from(...)` on read. Adapters never see this shape
7
+ * directly &mdash; they shuttle the already-stringified JSON.
8
+ *
9
+ * @template T The payload type carried by the matching {@link Stored}.
10
+ */
11
+ export type Encoded<T> = {
12
+ readonly data: T;
13
+ readonly at: string;
14
+ };
15
+ /**
16
+ * Adapter contract for synchronous key/value storage. Implement once per
17
+ * backend (localStorage, MMKV on React Native, chrome.storage with a sync
18
+ * facade, etc.) and pass to {@link Cache}. The adapter shuttles raw
19
+ * strings; JSON encoding and `Temporal.Instant` round-tripping happen
20
+ * inside the Cache wrapper, so adapters stay trivial.
21
+ */
22
+ export type Adapter = {
23
+ /**
24
+ * Return the raw string stored under `key`, or `null` when no entry
25
+ * exists. The Cache wrapper handles JSON parsing and `Temporal.Instant`
26
+ * round-tripping, so this stays a plain string getter. Treat any
27
+ * read-time error (decryption, IPC, etc.) as "not found" and return
28
+ * `null` &mdash; the Cache falls through to its next fallback rather
29
+ * than crashing the render.
30
+ */
31
+ readonly get: (key: string) => string | null;
32
+ /**
33
+ * Persist the raw string `value` under `key`. The Cache guarantees
34
+ * `value` is a JSON-encoded `{ data, at }` envelope produced by a
35
+ * resolved snapshot &mdash; never a placeholder. Throwing is fine on
36
+ * quota, private mode, sandboxed iframes, etc.; the Cache catches and
37
+ * swallows so a write failure can't poison an already-resolved fetch.
38
+ */
39
+ readonly set: (key: string, value: string) => void;
40
+ /**
41
+ * Drop the entry at `key`. Idempotent &mdash; calling `remove` for a
42
+ * key that isn't present must not throw.
43
+ */
44
+ readonly remove: (key: string) => void;
45
+ /**
46
+ * Wipe every entry this adapter can see. On a shared backend such as
47
+ * `localStorage` this means the whole origin &mdash; third-party SDK
48
+ * state, dismissed banners, route hints, etc. all go with it. Adapter
49
+ * authors should either delegate to the backend's native clear
50
+ * (accepting that scope) or namespace by key prefix and remove only
51
+ * their own.
52
+ */
53
+ readonly clear: () => void;
54
+ };
@@ -5,12 +5,15 @@ export { Operation, Op, State } from 'immertation';
5
5
  export { annotate } from './annotate/index.ts';
6
6
  export { Boundary } from './boundary/index.tsx';
7
7
  export { withScope } from './boundary/components/scope/index.tsx';
8
- export { useMode } from './boundary/components/mode/index.tsx';
9
- export type { ModeHandle } from './boundary/components/mode/index.tsx';
8
+ export { useStore } from './boundary/components/store/index.tsx';
9
+ export type { Store } from './boundary/components/store/index.tsx';
10
10
  export { useActions, With } from './hooks/index.ts';
11
11
  export { Resource } from './resource/index.ts';
12
- export type { ResourceHandle, ResourceFetcher, BoundRun, IfOptions, } from './resource/index.ts';
12
+ export type { Fetcher } from './resource/index.ts';
13
+ export { Cache } from './cache/index.ts';
14
+ export type { Adapter, Encoded } from './cache/index.ts';
13
15
  export * as utils from './utils/index.ts';
16
+ export type { Stored, Unset } from './utils/index.ts';
14
17
  export type { Box } from 'immertation';
15
18
  export type { Fault } from './error/index.ts';
16
- export type { Pk, Task, Tasks, Handlers } from './types/index.ts';
19
+ export type { Pk, Task, Tasks, Handlers, Handler, LeafActions, Dispatchable, Subscribable, } from './types/index.ts';
@@ -1,99 +1,102 @@
1
+ import { Fetcher } from './types.ts';
2
+ import { Cache } from './utils.ts';
3
+ import { Store } from '../boundary/components/store/index.tsx';
4
+ export type { Fetcher } from './types.ts';
1
5
  /**
2
- * Options accepted by `run.if(...)`.
6
+ * Snapshot of the most recent resource invocation. `cat(params)` writes
7
+ * one of these into a module-scope slot; the next
8
+ * `context.actions.resource(...)` / `.set(...)` call consumes it via
9
+ * {@link consumePending}.
3
10
  *
4
- * - `over` &ndash; a `Temporal.Duration`, a `DurationLike` object
5
- * (e.g. `{ minutes: 5 }`), or an ISO 8601 duration string (`"PT5M"`).
6
- * If the most recent successful run resolved longer ago than this
7
- * window, `run(...)` is called. Otherwise the cached data is returned
8
- * without hitting the network.
9
- *
10
- * @example
11
- * ```ts
12
- * await user.run.if({ over: { minutes: 5 } });
13
- * await user.run.if({ over: "PT5M" });
14
- * await user.run.if({ over: Temporal.Duration.from({ minutes: 5 }) });
15
- * ```
11
+ * @internal
16
12
  */
17
- export type IfOptions = {
18
- readonly over: Temporal.DurationLike;
13
+ export type PendingCall = {
14
+ readonly run: (store: Store, controller: AbortController, params: object) => Promise<unknown>;
15
+ readonly read: (params: object) => {
16
+ data: unknown;
17
+ at: Temporal.Instant | null;
18
+ };
19
+ readonly seed: (params: object, data: unknown, at: Temporal.Instant) => void;
20
+ readonly params: object;
19
21
  };
20
22
  /**
21
- * Fetcher signature accepted by {@link Resource}. Receives the
22
- * call-site `params` object and returns a `Promise` of the response.
23
- * Side-effects (dispatching broadcasts, analytics, etc.) belong in
24
- * the calling `useAction` handler, not inside the fetcher.
25
- */
26
- export type ResourceFetcher<T, P extends object = Record<never, never>> = (params: P) => Promise<T>;
27
- /**
28
- * Component-bound `run` callable returned by `actions.useResource`. It
29
- * is invokable like the underlying fetcher (`run(params)`) and also
30
- * carries an `if` method that triggers the network call only when the
31
- * cached data is older than the supplied freshness window.
23
+ * Reads and clears the slot populated by the most recent resource
24
+ * invocation. Throws when the slot is empty &mdash; the public
25
+ * `.resource(...)` shape requires a fresh `cat(params)` call as its
26
+ * argument.
32
27
  *
33
- * The conditional specialisation collapses the call signature when
34
- * `P` is empty &mdash; `run()` instead of `run({})`.
28
+ * @internal
35
29
  */
36
- export type BoundRun<T, P extends object> = [keyof P] extends [never] ? {
37
- (): Promise<T>;
38
- /**
39
- * Calls `run()` if the most recent successful run resolved longer
40
- * ago than `options.over`. Otherwise returns the cached data.
41
- */
42
- readonly if: (options: IfOptions) => Promise<T>;
43
- } : {
44
- (params: P): Promise<T>;
45
- /**
46
- * Calls `run(params)` if the most recent successful run resolved
47
- * longer ago than `options.over`. Otherwise returns the cached data.
48
- */
49
- readonly if: (options: IfOptions, params: P) => Promise<T>;
50
- };
30
+ export declare function consumePending(): PendingCall;
51
31
  /**
52
- * Module-scope handle returned by {@link Resource}. Pass to
53
- * `actions.useResource(handle)` inside a component to obtain a
54
- * `{ run, data, at }` object.
32
+ * Resource handle returned by `Resource(...)`. Call it with `params` to
33
+ * read the per-params cache slot synchronously and prime the slot
34
+ * consumed by `context.actions.resource(...)` for a follow-up fetch or
35
+ * `context.actions.resource.set(...)` for an out-of-band write.
36
+ *
37
+ * ```ts
38
+ * // Sync cache read in a model literal.
39
+ * { cat: cat({ id: 5 }) }
40
+ *
41
+ * // Fetch with `.exceeds(...)` for cache-aware refresh.
42
+ * await context.actions.resource(cat({ id: 5 })).exceeds({ minutes: 5 });
43
+ *
44
+ * // Write through to the per-params cache slot.
45
+ * context.actions.resource.set(cat({ id: 5 }), data);
46
+ * ```
55
47
  */
56
- export type ResourceHandle<T, P extends object = Record<never, never>> = {
57
- readonly key: string;
58
- /** @internal */
59
- readonly run: (params: P) => Promise<T>;
60
- /** Most recent successful data across all param-sets, or `null`. */
61
- readonly data: T | null;
62
- /** Instant of the most recent successful run, or `null`. */
63
- readonly at: Temporal.Instant | null;
64
- };
48
+ export type Resource<T, P extends object = Record<never, never>> = [
49
+ keyof P
50
+ ] extends [never] ? (params?: P) => T | null : (params: P) => T | null;
65
51
  /**
66
- * Defines a remote resource &mdash; declare at module scope and consume
67
- * via `actions.useResource(handle)`. Mirrors the {@link Action} factory
68
- * pattern: the declaration is a value, not a hook.
52
+ * Defines a remote resource &mdash; declared at module scope and used
53
+ * directly. Calling the returned handle with `params` returns the sync
54
+ * cache value (`T | null`) and primes the slot consumed by
55
+ * `context.actions.resource(...)` / `.set(...)` for fetch and write
56
+ * paths.
69
57
  *
70
- * The fetcher takes a single `params` argument (defaults to `{}`) and
71
- * returns a `Promise<T>`. Resources do **not** carry any callbacks
72
- * &ndash; any side-effects the caller wants on success or failure
73
- * (broadcasting, logging, model updates) belong in the `useAction`
74
- * handler that called `await user.run(...)`.
58
+ * The fetcher receives a single args object `{ store, controller, params }`:
75
59
  *
76
- * `params` are typed via the second generic and forwarded to every
77
- * `run(params)` call site. In-flight dedup keys per params shape, so
78
- * `feed.run({ cursor: null })` and `feed.run({ cursor: "abc" })` execute
79
- * independently while two concurrent `feed.run({ cursor: "abc" })` calls
80
- * share one network request.
60
+ * - `store` &ndash; snapshot of the per-`<Boundary>` Store (session,
61
+ * locale, feature flags, etc.). Reads only; writes go through
62
+ * `context.actions.produce(({ store }) => ...)` in handlers.
63
+ * - `controller` &ndash; the `AbortController` auto-threaded from the
64
+ * calling handler's `context.task.controller`. Pass `controller.signal`
65
+ * to `fetch`/`ky`, or call `controller.abort()` to fail fast.
66
+ * - `params` &ndash; the call-site params object (defaults to `{}`).
81
67
  *
82
- * Each call to `run()` always hits the network; `data` and `at`
83
- * are read-only snapshots of the most recent successful payload and
84
- * the instant it resolved &ndash; not a memoised result.
68
+ * Resources do **not** carry any callbacks &ndash; side-effects
69
+ * (broadcasting, logging, model updates) belong in the `useAction`
70
+ * handler that awaited `context.actions.resource(...)`.
71
+ *
72
+ * Every successful fetch writes through to the per-fetcher {@link Cache}
73
+ * (in-memory by default, persistent when an adapter is supplied via the
74
+ * second argument).
85
75
  *
86
76
  * @example
87
77
  * ```ts
88
- * import { Resource } from "march-hare";
78
+ * import { Resource, Cache } from "march-hare";
89
79
  *
90
- * export const feed = Resource<Page<Item>, { cursor: string | null }>(
91
- * "feed",
92
- * ({ cursor }) =>
93
- * http
94
- * .get("feed", { searchParams: { cursor: cursor ?? "" } })
95
- * .json<Page<Item>>(),
80
+ * export const user = Resource<User, { id: number }>(
81
+ * ({ store, controller, params }) =>
82
+ * ky.get(`users/${params.id}`, {
83
+ * headers: store.session
84
+ * ? { Authorization: `Bearer ${store.session.accessToken}` }
85
+ * : {},
86
+ * signal: controller.signal,
87
+ * }).json<User>(),
96
88
  * );
89
+ *
90
+ * // Sync cache read at module scope or in the model literal.
91
+ * const cached: User | null = user({ id: 5 });
92
+ *
93
+ * // Fetch inside a handler — controller and Store auto-threaded.
94
+ * actions.useAction(Actions.Mount, async (context) => {
95
+ * const data = await context.actions
96
+ * .resource(user({ id: 5 }))
97
+ * .exceeds({ minutes: 5 });
98
+ * context.actions.produce(({ model }) => void (model.user = data));
99
+ * });
97
100
  * ```
98
101
  */
99
- export declare function Resource<T, P extends object = Record<never, never>>(key: string, fetcher: ResourceFetcher<T, P>): ResourceHandle<T, P>;
102
+ export declare function Resource<T, P extends object = Record<never, never>>(fetcher: Fetcher<T, P>, cache?: Cache): Resource<T, P>;
@@ -0,0 +1,27 @@
1
+ import { Store } from '../boundary/components/store/index.tsx';
2
+ /**
3
+ * Args object passed to every {@link Fetcher}. The fetcher destructures
4
+ * whatever it needs; unused fields can be omitted.
5
+ *
6
+ * - `store` &mdash; snapshot of the per-`<Boundary>` Store at the
7
+ * moment the fetcher is invoked.
8
+ * - `controller` &mdash; the `AbortController` auto-threaded from the
9
+ * calling handler's `context.task.controller`. Pass
10
+ * `controller.signal` to `fetch`/`ky`/`EventSource`, or call
11
+ * `controller.abort()` to fail fast.
12
+ * - `params` &mdash; the call-site params object. Defaults to `{}`.
13
+ *
14
+ * @internal
15
+ */
16
+ export type Args<P extends object = Record<never, never>> = {
17
+ readonly store: Store;
18
+ readonly controller: AbortController;
19
+ readonly params: P;
20
+ };
21
+ /**
22
+ * Fetcher signature accepted by `Resource`. Receives the args object
23
+ * `{ store, controller, params }`. Side-effects (dispatching broadcasts,
24
+ * analytics, etc.) belong in the calling `useAction` handler, not
25
+ * inside the fetcher.
26
+ */
27
+ export type Fetcher<T, P extends object = Record<never, never>> = (args: Args<P>) => Promise<T>;
@@ -0,0 +1,37 @@
1
+ import { Cache } from '../cache/index.ts';
2
+ import { unset } from '../utils/index.ts';
3
+ export { Cache } from '../cache/index.ts';
4
+ /**
5
+ * Default in-memory `Cache` used when {@link Resource} is constructed
6
+ * without an explicit one. Each fetcher gets its own slot via the
7
+ * outer `WeakMap` so unrelated Resources don't share a string-key
8
+ * namespace.
9
+ *
10
+ * @internal
11
+ */
12
+ export declare const defaults: WeakMap<object, Cache>;
13
+ /**
14
+ * Returns the {@link Cache} bound to `fetcher`, allocating a fresh
15
+ * in-memory Cache on first access.
16
+ *
17
+ * @internal
18
+ */
19
+ export declare function defaultCache(fetcher: object): Cache;
20
+ /**
21
+ * Stable string key derived from the call-site `params`. Two calls with
22
+ * the same logical params (same key order, same primitive values) hit
23
+ * the same slot. JSON.stringify is sufficient for the Chizu params
24
+ * convention (primitive-leaf objects); callers who need order-stable
25
+ * keying should normalise the object before passing it in.
26
+ *
27
+ * @internal
28
+ */
29
+ export declare function key(params: object): string;
30
+ /**
31
+ * Re-export of the shared `unset` sentinel from {@link "../utils/index.ts"}.
32
+ *
33
+ * @internal
34
+ */
35
+ export declare const config: {
36
+ readonly unset: typeof unset;
37
+ };