chizu 0.2.72 → 0.3.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.
@@ -1,24 +1,2 @@
1
- import { Props } from './types';
2
- export { useError } from './utils';
3
1
  export { Reason, AbortError, TimeoutError, DisallowedError } from './types';
4
- export type { Fault, Catcher } from './types';
5
- /**
6
- * Error boundary component for catching and handling errors from actions.
7
- *
8
- * @template E Custom error types to include in the handler's error union.
9
- * @param props.handler - The error handler function to call when an error occurs.
10
- * @param props.children - The children to render within the error boundary.
11
- * @returns The children wrapped in an error context provider.
12
- *
13
- * @example
14
- * ```tsx
15
- * <Error<ApiError | ValidationError>
16
- * handler={({ error }) => {
17
- * if (error instanceof ApiError) handleApiError(error);
18
- * }}
19
- * >
20
- * <App />
21
- * </Error>
22
- * ```
23
- */
24
- export declare function Error<E extends Error = never>({ handler, children, }: Props<E>): import("react/jsx-runtime").JSX.Element;
2
+ export type { Fault } from './types';
@@ -1,4 +1,3 @@
1
- import { ReactNode } from 'react';
2
1
  import { Task } from '../boundary/components/tasks/types.ts';
3
2
  /**
4
3
  * Reasons why an action error occurred.
@@ -57,6 +56,13 @@ export declare class DisallowedError extends Error {
57
56
  }
58
57
  /**
59
58
  * Details about an error that occurred during action execution.
59
+ *
60
+ * Faults are delivered through the global `Lifecycle.Fault` broadcast.
61
+ * Subscribe with `actions.useAction(Lifecycle.Fault, handler)` near the
62
+ * root of your application for app-level concerns (logging, sign-out on
63
+ * auth failure, abort cascades). For component-local recovery, use a
64
+ * `Lifecycle.Error()` factory instead.
65
+ *
60
66
  * @template E Custom error types to include in the union with Error.
61
67
  */
62
68
  export type Fault<E extends Error = never> = {
@@ -74,35 +80,13 @@ export type Fault<E extends Error = never> = {
74
80
  * (e.g., on 403/500 responses to prevent cascading failures).
75
81
  *
76
82
  * @example
77
- * ```tsx
78
- * <Error handler={({ reason, tasks }) => {
79
- * if (reason === Reason.Errored) {
80
- * // Abort all in-flight tasks to prevent cascading errors
81
- * for (const task of tasks) {
82
- * task.controller.abort();
83
- * }
84
- * // Trigger re-authentication flow
83
+ * ```ts
84
+ * actions.useAction(Lifecycle.Fault, (context, fault) => {
85
+ * if (fault.reason === Reason.Errored) {
86
+ * for (const task of fault.tasks) task.controller.abort();
85
87
  * }
86
- * }}>
87
- * {children}
88
- * </Error>
88
+ * });
89
89
  * ```
90
90
  */
91
91
  tasks: ReadonlySet<Task>;
92
92
  };
93
- /**
94
- * Catcher function called when an action error occurs.
95
- * @template E Custom error types to include in the union with Error.
96
- * @param details Information about the error.
97
- */
98
- export type Catcher<E extends Error = never> = (details: Fault<E>) => void;
99
- /**
100
- * Props for the Error boundary component.
101
- * @template E Custom error types to include in the union with Error.
102
- */
103
- export type Props<E extends Error = never> = {
104
- /** Catcher function called when an action error occurs. */
105
- handler: Catcher<E>;
106
- /** Child components to wrap with error handling. */
107
- children?: ReactNode;
108
- };
@@ -1,4 +1,4 @@
1
- import { Catcher, Reason } from './types.ts';
1
+ import { Reason } from './types.ts';
2
2
  /**
3
3
  * Determines the error reason based on what was thrown.
4
4
  *
@@ -13,13 +13,3 @@ export declare function getReason(error: unknown): Reason;
13
13
  * @returns An Error instance (original if already Error, wrapped otherwise).
14
14
  */
15
15
  export declare function getError(error: unknown): Error;
16
- /**
17
- * React context for handling errors that occur within actions.
18
- */
19
- export declare const ErrorContext: import('react').Context<Catcher | undefined>;
20
- /**
21
- * Hook to access the error handler from the nearest Error provider.
22
- *
23
- * @returns The error handler function, or undefined if not within an Error provider.
24
- */
25
- export declare function useError(): Catcher | undefined;
@@ -16,6 +16,14 @@ export declare function withGetters<P extends Props>(a: P, b: RefObject<P>): P;
16
16
  * the generator function's name.
17
17
  */
18
18
  export declare function isGenerator(result: unknown): result is Generator | AsyncGenerator;
19
+ /**
20
+ * Sentinel passed as the dispatch channel during mount replay. Channeled
21
+ * handlers check for this to skip replay &mdash; they require specific
22
+ * channel context and cannot meaningfully process a replay without it.
23
+ *
24
+ * @internal
25
+ */
26
+ export declare const replay: unique symbol;
19
27
  /**
20
28
  * Invokes all listeners for an event and returns a promise that resolves
21
29
  * when every handler has settled. For {@link BroadcastEmitter} instances the
package/dist/index.d.ts CHANGED
@@ -1,15 +1,16 @@
1
1
  export { Action } from './action/index.ts';
2
- export { Entry } from './cache/index.ts';
3
2
  export { Distribution, Lifecycle } from './types/index.ts';
4
- export { Error, Reason, DisallowedError } from './error/index.tsx';
3
+ export { Reason, AbortError, TimeoutError, DisallowedError, } from './error/index.ts';
5
4
  export { Operation, Op, State } from 'immertation';
6
5
  export { annotate } from './annotate/index.ts';
7
6
  export { Boundary } from './boundary/index.tsx';
8
7
  export { Regulators } from './boundary/components/regulators/index.tsx';
9
8
  export { Scope, withScope } from './boundary/components/scope/index.tsx';
10
9
  export { useActions, With } from './hooks/index.ts';
10
+ export { Resource } from './resource/index.ts';
11
+ export type { ResourceHandle, ResourceDispatch, ResourceSuccess, ResourceFailure, } from './resource/index.ts';
11
12
  export * as utils from './utils/index.ts';
12
13
  export type { Box } from 'immertation';
13
- export type { Fault, Catcher } from './error/index.tsx';
14
- export type { Pk, Task, Tasks, Handlers, Meta } from './types/index.ts';
14
+ export type { Fault } from './error/index.ts';
15
+ export type { Pk, Task, Tasks, Handlers, Meta, ScopeCarrier, } from './types/index.ts';
15
16
  export type { Regulator } from './boundary/components/regulators/index.tsx';
@@ -0,0 +1,78 @@
1
+ import { BroadcastPayload, ChanneledAction, Filter, Props } from '../types/index.ts';
2
+ /**
3
+ * Fan-out dispatcher passed to a {@link Resource}'s `onSuccess` and
4
+ * `onError` callbacks. Restricted to broadcast actions (and channeled
5
+ * broadcasts) because resource-level events have no single owning
6
+ * component to scope unicast or multicast to.
7
+ */
8
+ export type ResourceDispatch = {
9
+ <P>(action: BroadcastPayload<P>, payload?: P): Promise<void>;
10
+ <P, C extends Filter>(action: ChanneledAction<P, C>, payload?: P): Promise<void>;
11
+ };
12
+ /**
13
+ * Context passed to a {@link Resource}'s `onSuccess` callback after a
14
+ * successful fetch.
15
+ */
16
+ export type ResourceSuccess<T> = {
17
+ /** The resolved value from the fetcher. */
18
+ readonly response: T;
19
+ /** The reactive `data` proxy of the component that triggered the fetch. */
20
+ readonly data: Props;
21
+ /** Pre-bound dispatcher for the surrounding Boundary's broadcaster. */
22
+ readonly dispatch: ResourceDispatch;
23
+ };
24
+ /**
25
+ * Context passed to a {@link Resource}'s `onError` callback after a
26
+ * failed fetch.
27
+ */
28
+ export type ResourceFailure<E> = {
29
+ /** The thrown error, narrowed to the second generic on `Resource`. */
30
+ readonly error: E;
31
+ /** The reactive `data` proxy of the component that triggered the fetch. */
32
+ readonly data: Props;
33
+ /** Pre-bound dispatcher for the surrounding Boundary's broadcaster. */
34
+ readonly dispatch: ResourceDispatch;
35
+ };
36
+ /**
37
+ * Module-scope handle returned by {@link Resource}. Pass to
38
+ * `actions.useResource(handle)` inside a component to obtain a fetcher
39
+ * thunk bound to the surrounding Boundary's broadcaster and the
40
+ * component's reactive `data`.
41
+ */
42
+ export type ResourceHandle<T, E = Error, Args extends readonly unknown[] = []> = {
43
+ readonly key: string;
44
+ /** @internal */
45
+ readonly fetch: (dispatch: ResourceDispatch, data: Props, ...args: Args) => Promise<T>;
46
+ /** @internal — phantom marker so TS distinguishes by error type */
47
+ readonly _error?: E;
48
+ /** @internal — phantom marker so TS distinguishes by args */
49
+ readonly _args?: Args;
50
+ };
51
+ /**
52
+ * Defines a remote resource &mdash; declare at module scope and consume via
53
+ * `actions.useResource(handle)`. Mirrors the {@link Action} factory pattern:
54
+ * the declaration is a value, not a hook.
55
+ *
56
+ * The fetcher may take arguments. The thunk returned by `actions.useResource`
57
+ * forwards them, and in-flight dedup keys per arg-tuple &ndash; so
58
+ * `fetchPage(null)` and `fetchPage("abc")` run independently, while two
59
+ * concurrent `fetchPage("abc")` calls share one network request.
60
+ *
61
+ * Once a fetch resolves, the next call with the same args fetches anew &ndash;
62
+ * there is no stale cache. Coordination across components happens via the
63
+ * broadcast actions dispatched in `onSuccess` / `onError`.
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * import { Resource } from "chizu";
68
+ *
69
+ * export const feed = Resource<Page<Item>, ApiError, [cursor: string | null]>(
70
+ * "feed",
71
+ * (cursor) =>
72
+ * http.get("feed", { searchParams: { cursor: cursor ?? "" } }).json(),
73
+ * ({ response, dispatch }) =>
74
+ * dispatch(Actions.Broadcast.PageLoaded, response),
75
+ * );
76
+ * ```
77
+ */
78
+ export declare function Resource<T, E = Error, Args extends readonly unknown[] = []>(key: string, fetcher: (...args: Args) => Promise<T>, onSuccess?: (context: ResourceSuccess<T>) => void, onError?: (context: ResourceFailure<E>) => void): ResourceHandle<T, E, Args>;
@@ -1,7 +1,5 @@
1
1
  import { Operation, Process, Inspect, Box } from 'immertation';
2
2
  import { ActionId, Task, Tasks } from '../boundary/components/tasks/types.ts';
3
- import { Option } from '@mobily/ts-belt/Option';
4
- import { Result as TsBeltResult } from '@mobily/ts-belt/Result';
5
3
  import { Regulator } from '../boundary/components/regulators/types.ts';
6
4
  import { Fault } from '../error/types.ts';
7
5
  import * as React from "react";
@@ -58,43 +56,15 @@ export declare class Brand {
58
56
  static readonly Channel: unique symbol;
59
57
  /** Node capture events used by Lifecycle.Node */
60
58
  static readonly Node: unique symbol;
61
- /** Identifies cache entry identifiers created with Entry() */
62
- static readonly Cache: unique symbol;
63
59
  }
64
60
  /**
65
- * Phantom brand symbol for value type tracking on cache entry identifiers.
66
- * Uses a function type `(t: T) => T` to enforce invariance, preventing
67
- * a cache entry declared for one type from being used with a different type.
68
- * @internal
69
- */
70
- declare const CacheValueBrand: unique symbol;
71
- /**
72
- * A branded cache entry identifier.
73
- *
74
- * When the second type parameter `C` is provided, the entry becomes callable
75
- * to produce channeled identifiers for independent cache slots per channel.
61
+ * Internal symbol for the global `Lifecycle.Fault` broadcast. Exposed so the
62
+ * dispatch pipeline can fire faults and short-circuit the regulator policy
63
+ * without depending on the `Lifecycle` class at runtime.
76
64
  *
77
- * @template T - The cached value type.
78
- * @template C - The channel type for channeled entries (defaults to never).
79
- */
80
- export type CacheId<T = unknown, C extends Filter = never> = {
81
- readonly [Brand.Cache]: symbol;
82
- readonly [CacheValueBrand]?: (t: T) => T;
83
- } & ([C] extends [never] ? unknown : {
84
- (channel: C): ChanneledCacheId<T, C>;
85
- });
86
- /**
87
- * Result of calling a channeled cache entry with a channel argument.
88
- * Contains the entry identity and the channel data for scoped cache access.
89
- *
90
- * @template T - The cached value type.
91
- * @template C - The channel type.
65
+ * @internal
92
66
  */
93
- export type ChanneledCacheId<T = unknown, C = unknown> = {
94
- readonly [Brand.Cache]: symbol;
95
- readonly [CacheValueBrand]?: (t: T) => T;
96
- readonly channel: C;
97
- };
67
+ export declare const FaultSymbol: unique symbol;
98
68
  /**
99
69
  * Factory functions for lifecycle actions.
100
70
  *
@@ -116,6 +86,9 @@ export type ChanneledCacheId<T = unknown, C = unknown> = {
116
86
  * // Now regulating Lifecycle.Mount only blocks THIS component's mount:
117
87
  * context.regulator.disallow(Actions.Mount);
118
88
  * ```
89
+ *
90
+ * `Lifecycle.Fault` is a singleton broadcast (not a factory). All components
91
+ * subscribe to the same shared symbol to receive global fault notifications.
119
92
  */
120
93
  export declare class Lifecycle {
121
94
  /** Creates a Mount lifecycle action. Triggered once on component mount (`useLayoutEffect`). */
@@ -151,6 +124,28 @@ export declare class Lifecycle {
151
124
  static Node(): HandlerPayload<unknown, {
152
125
  Name: string;
153
126
  }>;
127
+ /**
128
+ * Global fault broadcast. Receives a `Fault` whenever any action in the
129
+ * `<Boundary>` errors, times out, is supplanted, or is blocked by the
130
+ * regulator. Subscribe via `actions.useAction(Lifecycle.Fault, handler)`.
131
+ *
132
+ * Unlike the per-component `Lifecycle.Error()` factory, `Fault` is a single
133
+ * shared broadcast — every subscriber points at the same symbol. The
134
+ * regulator policy does not apply to `Fault`; faults always reach
135
+ * subscribers so error visibility cannot be silenced.
136
+ *
137
+ * @example
138
+ * ```tsx
139
+ * const actions = useActions<void, typeof Actions>();
140
+ *
141
+ * actions.useAction(Lifecycle.Fault, (context, fault) => {
142
+ * if (fault.reason === Reason.Errored) {
143
+ * console.error(`Action "${fault.action}" failed`, fault.error);
144
+ * }
145
+ * });
146
+ * ```
147
+ */
148
+ static Fault: BroadcastPayload<Fault>;
154
149
  }
155
150
  /**
156
151
  * Distribution modes for actions.
@@ -274,8 +269,12 @@ export type ChanneledAction<P = unknown, C = unknown> = {
274
269
  * Broadcast actions are sent to all mounted components. Values are cached so that
275
270
  * late-mounting components receive the most recent payload.
276
271
  *
272
+ * Late-mounting components receive the most recent cached payload via their
273
+ * `useAction` handler during mount. Use `peek()` in a `Lifecycle.Mount` handler
274
+ * to check whether a cached value exists before performing default fetches.
275
+ *
277
276
  * This type extends `HandlerPayload<P, C>` with an additional brand to enforce at compile-time
278
- * that only broadcast actions can be passed to `context.actions.read()`.
277
+ * that only broadcast actions can be passed to `context.actions.resolution()`.
279
278
  *
280
279
  * @template P - The payload type for the action
281
280
  * @template C - The channel type for channeled dispatches (defaults to never)
@@ -284,8 +283,8 @@ export type ChanneledAction<P = unknown, C = unknown> = {
284
283
  * ```ts
285
284
  * const SignedOut = Action<User>("SignedOut", Distribution.Broadcast);
286
285
  *
287
- * // Read the latest value inside a handler
288
- * const user = await context.actions.read(SignedOut);
286
+ * // Resolve the latest value inside a handler
287
+ * const user = await context.actions.resolution(SignedOut);
289
288
  * ```
290
289
  */
291
290
  export type BroadcastPayload<P = unknown, C extends Filter = never> = HandlerPayload<P, C> & {
@@ -295,20 +294,21 @@ export type BroadcastPayload<P = unknown, C extends Filter = never> = HandlerPay
295
294
  * Branded type for multicast action objects created with `Action()` and `Distribution.Multicast`.
296
295
  * Multicast actions are dispatched to all components within a named scope boundary.
297
296
  *
298
- * When dispatching a multicast action, you MUST provide the scope name as the third argument:
297
+ * When dispatching a multicast action, you MUST provide the scope carrier as the third argument:
299
298
  * ```ts
300
- * actions.dispatch(Actions.Multicast.Update, payload, { scope: "MyScope" });
299
+ * actions.dispatch(Actions.Multicast.Update, payload, { scope: Actions.Multicast });
301
300
  * ```
302
301
  *
303
- * Components receive multicast events only if they are descendants of a `<Scope name="...">`.
302
+ * Components receive multicast events only if they are descendants of a `<Scope of={...}>`.
304
303
  *
305
304
  * @template P - The payload type for the action
306
305
  * @template C - The channel type for channeled dispatches (defaults to never)
307
306
  *
308
307
  * @example
309
308
  * ```tsx
310
- * // Define multicast actions in a shared class
309
+ * // Define multicast actions and their scope together
311
310
  * class MulticastActions {
311
+ * static Scope = "Counter" as const;
312
312
  * static Update = Action<number>("Update", Distribution.Multicast);
313
313
  * }
314
314
  *
@@ -318,26 +318,47 @@ export type BroadcastPayload<P = unknown, C extends Filter = never> = HandlerPay
318
318
  * }
319
319
  *
320
320
  * // In JSX - create a named scope boundary
321
- * <Scope name="Counter">
321
+ * <Scope of={MulticastActions}>
322
322
  * <CounterA />
323
323
  * <CounterB />
324
324
  * </Scope>
325
325
  *
326
- * // Inside CounterA - dispatch to all components in "Counter" scope
327
- * actions.dispatch(Actions.Multicast.Update, 42, { scope: "Counter" });
326
+ * // Inside CounterA - dispatch to all components in the scope
327
+ * actions.dispatch(Actions.Multicast.Update, 42, { scope: Actions.Multicast });
328
328
  * // CounterA and CounterB both receive the event
329
329
  * ```
330
330
  */
331
331
  export type MulticastPayload<P = unknown, C extends Filter = never> = HandlerPayload<P, C> & {
332
332
  readonly [Brand.Multicast]: true;
333
333
  };
334
+ /**
335
+ * A scope carrier — any object exposing a `Scope` string literal.
336
+ *
337
+ * By convention this is the feature's `MulticastActions` class, which owns
338
+ * the scope name alongside its multicast action declarations:
339
+ *
340
+ * ```ts
341
+ * export class MulticastActions {
342
+ * static Scope = "my-feature" as const;
343
+ * static Update = Action<T>("Update", Distribution.Multicast);
344
+ * }
345
+ * ```
346
+ *
347
+ * Requiring a carrier (rather than a bare string) prevents typos and enforces
348
+ * a single source of truth for each scope name.
349
+ *
350
+ * @template S - The scope name literal type.
351
+ */
352
+ export type ScopeCarrier<S extends string = string> = {
353
+ readonly Scope: S;
354
+ };
334
355
  /**
335
356
  * Options for multicast dispatch.
336
357
  * Required when dispatching a multicast action.
337
358
  */
338
359
  export type MulticastOptions = {
339
- /** The name of the scope to multicast to. Must match a `<Scope name="...">` ancestor. */
340
- scope: string;
360
+ /** Carrier object exposing the scope name via `.Scope`. Typically the feature's `MulticastActions` class. */
361
+ scope: ScopeCarrier;
341
362
  };
342
363
  /**
343
364
  * Extracts the payload type `P` from a `HandlerPayload<P>` or `ChanneledAction<P, C>`.
@@ -665,45 +686,6 @@ export type HandlerContext<M extends Model | void, _AC extends Actions | void, D
665
686
  }) => void>(ƒ: F & AssertSync<F>): void;
666
687
  dispatch(action: ActionOrChanneled, payload?: unknown, options?: MulticastOptions): Promise<void>;
667
688
  annotate<T>(operation: Operation, value: T): T;
668
- /**
669
- * Fetches a value from the cache or executes the callback if not cached / expired.
670
- *
671
- * The callback must return an `Option<T>` or `Result<T, E>`. Only `Some` / `Ok`
672
- * values are stored; `None` / `Error` results are skipped and `{ data: null }`
673
- * is returned. Exactly one layer of `Option` / `Result` is unwrapped.
674
- *
675
- * @param entry - The cache entry identifier (from `Entry()`).
676
- * @param ttl - Time-to-live in milliseconds.
677
- * @param fn - Async callback that produces the value to cache.
678
- * @returns An object with `data` set to the cached or freshly-fetched value, or `null`.
679
- *
680
- * @example
681
- * ```ts
682
- * const { data } = await context.actions.cacheable(
683
- * CacheStore.Pairs,
684
- * 30_000,
685
- * async () => Some(await api.fetchPairs()),
686
- * );
687
- * ```
688
- */
689
- cacheable<T>(entry: CacheId<T> | ChanneledCacheId<T>, ttl: number, fn: () => Promise<Option<T>>): Promise<{
690
- data: T | null;
691
- }>;
692
- cacheable<T>(entry: CacheId<T> | ChanneledCacheId<T>, ttl: number, fn: () => Promise<TsBeltResult<T, unknown>>): Promise<{
693
- data: T | null;
694
- }>;
695
- /**
696
- * Removes a cached value from the store.
697
- *
698
- * @param entry - The cache entry identifier to invalidate.
699
- *
700
- * @example
701
- * ```ts
702
- * context.actions.invalidate(CacheStore.Pairs);
703
- * context.actions.invalidate(CacheStore.User({ UserId: 5 }));
704
- * ```
705
- */
706
- invalidate(entry: CacheId<unknown> | ChanneledCacheId<unknown>): void;
707
689
  /**
708
690
  * Feature toggle methods for mutating boolean flags on the model.
709
691
  *
@@ -720,20 +702,21 @@ export type HandlerContext<M extends Model | void, _AC extends Actions | void, D
720
702
  invert<K extends keyof FeatureFlags<M>>(name: K): void;
721
703
  };
722
704
  /**
723
- * Reads the latest broadcast or multicast value, waiting for annotations to settle.
705
+ * Returns the resolved broadcast or multicast value, waiting for any
706
+ * pending annotations to settle before resolving.
724
707
  *
725
708
  * If a value has already been dispatched it resolves immediately.
726
709
  * Otherwise it waits until the next dispatch of the action.
727
710
  * Resolves with `null` if the task is aborted before a value arrives.
728
711
  *
729
- * @param action - The broadcast or multicast action to read.
730
- * @param options - For multicast actions, must include `{ scope: "ScopeName" }`.
712
+ * @param action - The broadcast or multicast action to resolve.
713
+ * @param options - For multicast actions, must include `{ scope: MulticastActions }`.
731
714
  * @returns The dispatched value, or `null` if aborted.
732
715
  *
733
716
  * @example
734
717
  * ```ts
735
718
  * actions.useAction(Actions.FetchPosts, async (context) => {
736
- * const user = await context.actions.read(Actions.Broadcast.User);
719
+ * const user = await context.actions.resolution(Actions.Broadcast.User);
737
720
  * if (!user) return;
738
721
  * const posts = await fetchPosts(user.id, {
739
722
  * signal: context.task.controller.signal,
@@ -742,15 +725,15 @@ export type HandlerContext<M extends Model | void, _AC extends Actions | void, D
742
725
  * });
743
726
  * ```
744
727
  */
745
- read<T>(action: BroadcastPayload<T>): Promise<T | null>;
746
- read<T>(action: MulticastPayload<T>, options: MulticastOptions): Promise<T | null>;
728
+ resolution<T>(action: BroadcastPayload<T>): Promise<T | null>;
729
+ resolution<T>(action: MulticastPayload<T>, options: MulticastOptions): Promise<T | null>;
747
730
  /**
748
731
  * Returns the latest broadcast or multicast value immediately without
749
732
  * waiting for annotations to settle. Use this when you need the current
750
733
  * cached value and do not need to wait for pending operations to complete.
751
734
  *
752
735
  * @param action - The broadcast or multicast action to peek at.
753
- * @param options - For multicast actions, must include `{ scope: "ScopeName" }`.
736
+ * @param options - For multicast actions, must include `{ scope: MulticastActions }`.
754
737
  * @returns The cached value, or `null` if no value has been dispatched.
755
738
  *
756
739
  * @example
@@ -854,14 +837,14 @@ export type UseActions<M extends Model | void, AC extends Actions | void, D exte
854
837
  /**
855
838
  * Dispatches an action with an optional payload.
856
839
  *
857
- * For multicast actions, you MUST provide the scope as the third argument:
840
+ * For multicast actions, you MUST provide the scope carrier as the third argument:
858
841
  * ```ts
859
- * actions.dispatch(Actions.Multicast.Update, payload, { scope: "MyScope" });
842
+ * actions.dispatch(Actions.Multicast.Update, payload, { scope: Actions.Multicast });
860
843
  * ```
861
844
  *
862
845
  * @param action - The action to dispatch
863
846
  * @param payload - The payload to send with the action
864
- * @param options - For multicast actions, must include `{ scope: "ScopeName" }`
847
+ * @param options - For multicast actions, must include `{ scope: MulticastActions }`
865
848
  */
866
849
  dispatch<P>(action: HandlerPayload<P>, payload?: P, options?: MulticastOptions): Promise<void>;
867
850
  dispatch<P>(action: BroadcastPayload<P>, payload?: P, options?: MulticastOptions): Promise<void>;
@@ -975,4 +958,22 @@ export type UseActions<M extends Model | void, AC extends Actions | void, D exte
975
958
  * ```
976
959
  */
977
960
  useAction<A extends ActionId | HandlerPayload | ChanneledAction>(action: A, handler: (context: HandlerContext<M, AC, D>, ...args: [Payload<A>] extends [never] ? [] : [payload: Payload<A>]) => void | Promise<void> | AsyncGenerator | Generator): void;
961
+ /**
962
+ * Connects a {@link Resource} declared at module scope to this component.
963
+ * Returns a thunk that fetches fresh data on every call &ndash; concurrent
964
+ * calls share the in-flight promise. The Resource's `onSuccess` and
965
+ * `onError` callbacks receive `(response, data, dispatch)` where `data`
966
+ * is this component's reactive `data` proxy.
967
+ *
968
+ * @example
969
+ * ```ts
970
+ * const fetchUser = actions.useResource(resources.user);
971
+ *
972
+ * actions.useAction(Actions.Mount, async (context) => {
973
+ * const data = await fetchUser();
974
+ * context.actions.produce(({ model }) => { model.user = data; });
975
+ * });
976
+ * ```
977
+ */
978
+ useResource<T, E, Args extends readonly unknown[]>(resource: import('../resource/index.ts').ResourceHandle<T, E, Args>): (...args: Args) => Promise<T>;
978
979
  };
@@ -1,21 +1,4 @@
1
1
  import { Pk } from '../types/index.ts';
2
- /**
3
- * Configuration constants for Chizu action symbols.
4
- */
5
- export declare const config: {
6
- /** Prefix for all Chizu action symbols. */
7
- actionPrefix: string;
8
- /** Prefix for broadcast action symbols. */
9
- broadcastActionPrefix: string;
10
- /** Prefix for multicast action symbols. */
11
- multicastActionPrefix: string;
12
- /** Prefix for channeled action symbols. */
13
- channelPrefix: string;
14
- /** Prefix for cache operation symbols. */
15
- cachePrefix: string;
16
- /** Prefix for lifecycle action symbols. */
17
- lifecyclePrefix: string;
18
- };
19
2
  /**
20
3
  * Returns a promise that resolves after the specified number of milliseconds.
21
4
  * The sleep will reject with an AbortError when the signal is aborted,
package/dist/utils.d.ts CHANGED
@@ -1,3 +1,29 @@
1
+ /**
2
+ * Internal symbol description factories. Each function returns a namespaced
3
+ * string suitable for `Symbol()` descriptions or `startsWith` checks.
4
+ *
5
+ * @internal
6
+ */
7
+ export declare const describe: {
8
+ /** Unicast action description. `describe.action("Fetch")` &rarr; `"chizu.action/Fetch"` */
9
+ action: (name?: string) => string;
10
+ /** Broadcast action description. `describe.broadcast("User")` &rarr; `"chizu.action/broadcast/User"` */
11
+ broadcast: (name?: string) => string;
12
+ /** Multicast action description. `describe.multicast("Update")` &rarr; `"chizu.action/multicast/Update"` */
13
+ multicast: (name?: string) => string;
14
+ /** Channeled action description. `describe.channel("user")` &rarr; `"chizu.channel/user"` */
15
+ channel: (name?: string) => string;
16
+ /** Cache entry description. `describe.cache("users")` &rarr; `"chizu.cache/users"` */
17
+ cache: (name?: string) => string;
18
+ /** Lifecycle action description. `describe.lifecycle("Mount")` &rarr; `"chizu.action.lifecycle/Mount"` */
19
+ lifecycle: (name?: string) => string;
20
+ /** Mount replay sentinel description. Used to create the {@link replay} symbol. */
21
+ replay: (name?: string) => string;
22
+ };
23
+ /**
24
+ * Flat record used for shallow property comparison in {@link changes}.
25
+ * @internal
26
+ */
1
27
  type Changes = Record<string, unknown>;
2
28
  /**
3
29
  * Get high-level changed paths between two objects.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chizu",
3
- "version": "0.2.72",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "packageManager": "yarn@1.22.22",
@@ -47,6 +47,7 @@
47
47
  "@testing-library/dom": "^10.4.1",
48
48
  "@testing-library/jest-dom": "^6.9.1",
49
49
  "@testing-library/react": "^16.3.2",
50
+ "@types/dom-navigation": "^1.0.7",
50
51
  "@types/lodash": "^4.17.23",
51
52
  "@types/ramda": "^0.31.1",
52
53
  "@types/react": "^19.2.14",
@@ -70,6 +71,7 @@
70
71
  "jest": "^30.2.0",
71
72
  "jest-environment-jsdom": "^30.2.0",
72
73
  "jsdom": "^28.1.0",
74
+ "ky": "^2.0.2",
73
75
  "lodash": "^4.17.23",
74
76
  "lucide-react": "^0.564.0",
75
77
  "madge": "^8.0.0",
@@ -80,6 +82,7 @@
80
82
  "react-flip-numbers": "^3.0.9",
81
83
  "react-router-dom": "^7.13.0",
82
84
  "react-test-renderer": "^19.2.4",
85
+ "react-wayfinder": "^0.1.3",
83
86
  "rollup-plugin-visualizer": "^6.0.5",
84
87
  "terser": "^5.46.0",
85
88
  "traverse": "^0.6.11",
@@ -1,13 +0,0 @@
1
- import { Props } from './types.ts';
2
- import * as React from "react";
3
- export { useCacheStore } from './utils.ts';
4
- export type { CacheContext } from './types.ts';
5
- /**
6
- * Creates a new cache context for storing values from `context.actions.cacheable`.
7
- * Automatically included in `<Boundary>`. Only needed directly if you want to
8
- * isolate a cache context.
9
- *
10
- * @param props.children - The children to render within the cache context.
11
- * @returns The children wrapped in a cache context provider.
12
- */
13
- export declare function CacheProvider({ children }: Props): React.ReactNode;