march-hare 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +453 -0
- package/dist/march-hare.js +6 -0
- package/dist/march-hare.umd.cjs +1 -0
- package/dist/src/library/action/index.d.ts +66 -0
- package/dist/src/library/action/utils.d.ts +89 -0
- package/dist/src/library/annotate/index.d.ts +25 -0
- package/dist/src/library/boundary/components/broadcast/index.d.ts +12 -0
- package/dist/src/library/boundary/components/broadcast/types.d.ts +19 -0
- package/dist/src/library/boundary/components/broadcast/utils.d.ts +39 -0
- package/dist/src/library/boundary/components/consumer/components/partition/index.d.ts +27 -0
- package/dist/src/library/boundary/components/consumer/components/partition/types.d.ts +9 -0
- package/dist/src/library/boundary/components/consumer/index.d.ts +19 -0
- package/dist/src/library/boundary/components/consumer/types.d.ts +37 -0
- package/dist/src/library/boundary/components/consumer/utils.d.ts +13 -0
- package/dist/src/library/boundary/components/mode/index.d.ts +15 -0
- package/dist/src/library/boundary/components/mode/types.d.ts +7 -0
- package/dist/src/library/boundary/components/mode/utils.d.ts +55 -0
- package/dist/src/library/boundary/components/scope/index.d.ts +40 -0
- package/dist/src/library/boundary/components/scope/types.d.ts +20 -0
- package/dist/src/library/boundary/components/scope/utils.d.ts +19 -0
- package/dist/src/library/boundary/components/tasks/index.d.ts +14 -0
- package/dist/src/library/boundary/components/tasks/types.d.ts +43 -0
- package/dist/src/library/boundary/components/tasks/utils.d.ts +26 -0
- package/dist/src/library/boundary/index.d.ts +20 -0
- package/dist/src/library/boundary/types.d.ts +4 -0
- package/dist/src/library/error/index.d.ts +2 -0
- package/dist/src/library/error/types.d.ts +75 -0
- package/dist/src/library/error/utils.d.ts +15 -0
- package/dist/src/library/hooks/index.d.ts +43 -0
- package/dist/src/library/hooks/types.d.ts +72 -0
- package/dist/src/library/hooks/utils.d.ts +198 -0
- package/dist/src/library/index.d.ts +16 -0
- package/dist/src/library/resource/index.d.ts +99 -0
- package/dist/src/library/types/index.d.ts +718 -0
- package/dist/src/library/utils/index.d.ts +42 -0
- package/dist/src/library/utils/utils.d.ts +5 -0
- package/dist/src/library/utils.d.ts +37 -0
- package/package.json +104 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { ChanneledAction, AnyAction } from '../types/index.ts';
|
|
2
|
+
import { ActionId } from '../boundary/components/tasks/types.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Extracts the underlying symbol from an action or channeled action.
|
|
5
|
+
* This symbol is used as the event emitter key for dispatching.
|
|
6
|
+
*
|
|
7
|
+
* @param action The action or channeled action to extract the symbol from.
|
|
8
|
+
* @returns The underlying symbol, or the action itself if it's already a symbol/string.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* const Increment = Action<number>("Increment");
|
|
13
|
+
* getActionSymbol(Increment); // Symbol(march-hare.action/Increment)
|
|
14
|
+
* getActionSymbol(Increment({ UserId: 5 })); // Symbol(march-hare.action/Increment)
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function getActionSymbol(action: AnyAction): ActionId;
|
|
18
|
+
/**
|
|
19
|
+
* Checks whether an action is a broadcast action.
|
|
20
|
+
* Broadcast actions are sent to all mounted components that have defined a handler for them.
|
|
21
|
+
*
|
|
22
|
+
* @param action The action to check.
|
|
23
|
+
* @returns True if the action is a broadcast action, false otherwise.
|
|
24
|
+
*/
|
|
25
|
+
export declare function isBroadcastAction(action: AnyAction): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Extracts the action name from an action.
|
|
28
|
+
*
|
|
29
|
+
* Parses both regular actions (`march-hare.action/Name`) and
|
|
30
|
+
* distributed actions (`march-hare.action/distributed/Name`)
|
|
31
|
+
* to extract just the name portion.
|
|
32
|
+
*
|
|
33
|
+
* @param action The action to extract the name from.
|
|
34
|
+
* @returns The extracted action name, or "unknown" if parsing fails.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* const Increment = Action("Increment");
|
|
39
|
+
* getName(Increment); // "Increment"
|
|
40
|
+
*
|
|
41
|
+
* const SignedOut = Action("SignedOut", Distribution.Broadcast);
|
|
42
|
+
* getName(SignedOut); // "SignedOut"
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare function getName(action: AnyAction): string;
|
|
46
|
+
/**
|
|
47
|
+
* Checks if the given action is a channeled action (result of calling `Action(channel)`).
|
|
48
|
+
*
|
|
49
|
+
* @param action - The action to check
|
|
50
|
+
* @returns `true` if the action is a channeled action with a channel property, `false` otherwise
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* const UserUpdated = Action<User, { UserId: number }>("UserUpdated");
|
|
55
|
+
*
|
|
56
|
+
* isChanneledAction(UserUpdated); // false
|
|
57
|
+
* isChanneledAction(UserUpdated({ UserId: 1 })); // true
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export declare function isChanneledAction(action: AnyAction): action is ChanneledAction;
|
|
61
|
+
/**
|
|
62
|
+
* Extracts the lifecycle type from an action's symbol description.
|
|
63
|
+
*
|
|
64
|
+
* Returns the lifecycle name (`"Mount"`, `"Unmount"`, `"Error"`, `"Update"`)
|
|
65
|
+
* when the action symbol's description starts with the lifecycle prefix, or
|
|
66
|
+
* `null` for non-lifecycle actions.
|
|
67
|
+
*
|
|
68
|
+
* @param action The action to inspect.
|
|
69
|
+
* @returns The lifecycle name, or `null` if not a lifecycle action.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* class Actions {
|
|
74
|
+
* static Mount = Lifecycle.Mount();
|
|
75
|
+
* }
|
|
76
|
+
*
|
|
77
|
+
* getLifecycleType(Actions.Mount); // "Mount"
|
|
78
|
+
* getLifecycleType(Action("Increment")); // null
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function getLifecycleType(action: AnyAction): string | null;
|
|
82
|
+
/**
|
|
83
|
+
* Checks whether an action is a multicast action.
|
|
84
|
+
* Multicast actions are dispatched to all components within a named scope boundary.
|
|
85
|
+
*
|
|
86
|
+
* @param action The action to check.
|
|
87
|
+
* @returns True if the action is a multicast action, false otherwise.
|
|
88
|
+
*/
|
|
89
|
+
export declare function isMulticastAction(action: AnyAction): boolean;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Operation } from 'immertation';
|
|
2
|
+
/**
|
|
3
|
+
* Wraps a value with an operation annotation for use in initial model definitions.
|
|
4
|
+
* When passed as part of the initial model to `useActions`, the annotation is
|
|
5
|
+
* registered during hydration so that `actions.inspect` reports the field as pending
|
|
6
|
+
* from the very first render.
|
|
7
|
+
*
|
|
8
|
+
* @param value - The value to annotate.
|
|
9
|
+
* @param operation - The operation type (defaults to {@link Operation.Update}).
|
|
10
|
+
* @returns The annotated value (typed as T for assignment compatibility).
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { annotate } from "march-hare";
|
|
15
|
+
*
|
|
16
|
+
* type Model = { user: User | null };
|
|
17
|
+
*
|
|
18
|
+
* const model: Model = {
|
|
19
|
+
* user: annotate(null),
|
|
20
|
+
* };
|
|
21
|
+
*
|
|
22
|
+
* // actions.inspect.user.pending() === true from the first render
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function annotate<T>(value: T, operation?: Operation): T;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Props } from './types.ts';
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
export { useBroadcast, BroadcastEmitter } from './utils.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Creates a new broadcast context for distributed actions. Only needed if you
|
|
6
|
+
* want to isolate a broadcast context, useful for libraries that want to provide
|
|
7
|
+
* their own broadcast context without interfering with the app's broadcast context.
|
|
8
|
+
*
|
|
9
|
+
* @param props.children - The children to render within the broadcast context.
|
|
10
|
+
* @returns The children wrapped in a broadcast context provider.
|
|
11
|
+
*/
|
|
12
|
+
export declare function Broadcaster({ children }: Props): React.ReactNode;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BroadcastEmitter } from './utils.ts';
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
/**
|
|
4
|
+
* The broadcast context is a BroadcastEmitter used for distributed actions across components.
|
|
5
|
+
* Extends EventEmitter with a per-action value cache so late-mounting components can replay
|
|
6
|
+
* broadcast values even without a Partition (from `stream()`).
|
|
7
|
+
*/
|
|
8
|
+
export type BroadcastContext = BroadcastEmitter;
|
|
9
|
+
/**
|
|
10
|
+
* Return type for the useBroadcast hook.
|
|
11
|
+
* Provides access to the shared BroadcastEmitter for emitting and subscribing to distributed actions.
|
|
12
|
+
*/
|
|
13
|
+
export type UseBroadcast = BroadcastContext;
|
|
14
|
+
/**
|
|
15
|
+
* Props for the Broadcaster provider component.
|
|
16
|
+
*/
|
|
17
|
+
export type Props = {
|
|
18
|
+
children: React.ReactNode;
|
|
19
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { default as EventEmitter } from 'eventemitter3';
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
/**
|
|
4
|
+
* EventEmitter subclass that caches the latest payload per event.
|
|
5
|
+
*
|
|
6
|
+
* When a broadcast or multicast action is dispatched, the payload is
|
|
7
|
+
* stored so that late-mounting components can replay it via
|
|
8
|
+
* {@link useLifecycles} and handlers can read it via
|
|
9
|
+
* `context.actions.resolution()`.
|
|
10
|
+
*/
|
|
11
|
+
export declare class BroadcastEmitter extends EventEmitter {
|
|
12
|
+
private cache;
|
|
13
|
+
emit(event: string | symbol, ...args: unknown[]): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Cache a value for a given event without emitting to listeners.
|
|
16
|
+
* Used by {@link emitAsync} to preserve caching when bypassing `emit()`.
|
|
17
|
+
*/
|
|
18
|
+
setCache(event: string | symbol, value: unknown): void;
|
|
19
|
+
/**
|
|
20
|
+
* Retrieve the last emitted payload for a given event.
|
|
21
|
+
*/
|
|
22
|
+
getCached(event: string | symbol): unknown;
|
|
23
|
+
/**
|
|
24
|
+
* Emit without caching the payload. Used by the framework to publish
|
|
25
|
+
* fire-and-forget events (such as `Lifecycle.Fault`) where late-mounting
|
|
26
|
+
* subscribers must not replay a stale value.
|
|
27
|
+
*/
|
|
28
|
+
fire(event: string | symbol, ...args: unknown[]): boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* React context for broadcasting distributed actions across components.
|
|
32
|
+
*/
|
|
33
|
+
export declare const Context: React.Context<BroadcastEmitter>;
|
|
34
|
+
/**
|
|
35
|
+
* Hook to access the broadcast EventEmitter for emitting and listening to distributed actions.
|
|
36
|
+
*
|
|
37
|
+
* @returns The BroadcastEmitter instance for distributed actions.
|
|
38
|
+
*/
|
|
39
|
+
export declare function useBroadcast(): BroadcastEmitter;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Props } from './types.ts';
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
/**
|
|
4
|
+
* Renders output for the `stream()` method by subscribing to distributed action events.
|
|
5
|
+
*
|
|
6
|
+
* This component manages a subscription to the broadcast emitter for distributed actions,
|
|
7
|
+
* storing the latest dispatched value in the Consumer context. When a new value arrives,
|
|
8
|
+
* all mounted Partition instances for that action re-render with the updated value.
|
|
9
|
+
*
|
|
10
|
+
* On mount, if a value was previously dispatched for this action, it renders immediately
|
|
11
|
+
* with that cached value. If no value exists yet, it renders `null` until the first dispatch.
|
|
12
|
+
*
|
|
13
|
+
* Uses Immertation's State class internally to manage consumed values, providing real
|
|
14
|
+
* annotation tracking through the `inspect` proxy. When a payload containing annotated
|
|
15
|
+
* values is dispatched, the annotations are preserved and accessible via `inspect`.
|
|
16
|
+
*
|
|
17
|
+
* The renderer callback receives two arguments:
|
|
18
|
+
* - `value`: The latest dispatched payload
|
|
19
|
+
* - `inspect`: A proxy for checking annotation status (pending, is, draft, settled, etc.)
|
|
20
|
+
*
|
|
21
|
+
* @template T - The payload type for the action (must be an object type)
|
|
22
|
+
* @param props.action - The distributed action symbol to subscribe to
|
|
23
|
+
* @param props.renderer - Callback that receives value and inspect, returns React nodes
|
|
24
|
+
* @returns The result of calling renderer, or null if no value exists
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export declare function Partition<T extends object>({ action, renderer, }: Props<T>): React.ReactNode;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Props } from './types.ts';
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
export { useConsumer } from './utils.ts';
|
|
4
|
+
export { Partition } from './components/partition/index.tsx';
|
|
5
|
+
export type { Props as PartitionProps } from './components/partition/types.ts';
|
|
6
|
+
export type { ConsumerRenderer, Entry, ConsumerContext } from './types.ts';
|
|
7
|
+
/**
|
|
8
|
+
* Creates a new consumer context for storing distributed action values. Only needed if you
|
|
9
|
+
* want to isolate a consumer context, useful for libraries that want to provide
|
|
10
|
+
* their own consumer context without interfering with the app's consumer context.
|
|
11
|
+
*
|
|
12
|
+
* The Consumer stores the latest value for each distributed action, allowing components
|
|
13
|
+
* that mount later to access values that were dispatched before they mounted. This enables
|
|
14
|
+
* the `stream()` method to render the most recent value immediately.
|
|
15
|
+
*
|
|
16
|
+
* @param props.children - The children to render within the consumer context.
|
|
17
|
+
* @returns The children wrapped in a consumer context provider.
|
|
18
|
+
*/
|
|
19
|
+
export declare function Consumer({ children }: Props): React.ReactNode;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Inspect, State } from 'immertation';
|
|
2
|
+
import { ActionId } from '../tasks/types.ts';
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
/**
|
|
5
|
+
* Callback function for the stream() method.
|
|
6
|
+
* Receives the dispatched value and an inspect proxy for annotation tracking.
|
|
7
|
+
*
|
|
8
|
+
* @template T - The payload type
|
|
9
|
+
*/
|
|
10
|
+
export type ConsumerRenderer<T> = (value: T, inspect: Inspect<T>) => React.ReactNode;
|
|
11
|
+
/**
|
|
12
|
+
* Model for consumed values.
|
|
13
|
+
* Allows Immertation's State to manage the value with real inspect capabilities.
|
|
14
|
+
* @template T - The payload type
|
|
15
|
+
*/
|
|
16
|
+
export type Model<T> = {
|
|
17
|
+
value: T;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Entry storing the latest value and listeners for an action.
|
|
21
|
+
* Uses Immertation's State to provide real inspect functionality for consumed values.
|
|
22
|
+
* @template T - The payload type
|
|
23
|
+
*/
|
|
24
|
+
export type Entry<T = unknown> = {
|
|
25
|
+
state: State<Model<T>>;
|
|
26
|
+
listeners: Set<() => void>;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* The consumer context is a Map storing entries keyed by action.
|
|
30
|
+
*/
|
|
31
|
+
export type ConsumerContext = Map<ActionId, Entry>;
|
|
32
|
+
/**
|
|
33
|
+
* Props for the Consumer provider component.
|
|
34
|
+
*/
|
|
35
|
+
export type Props = {
|
|
36
|
+
children: React.ReactNode;
|
|
37
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ConsumerContext } from './types.ts';
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
/**
|
|
4
|
+
* React context for the consumer store.
|
|
5
|
+
* Stores the latest value for each distributed action with full annotation support.
|
|
6
|
+
*/
|
|
7
|
+
export declare const Context: React.Context<ConsumerContext>;
|
|
8
|
+
/**
|
|
9
|
+
* Hook to access the consumer store from context.
|
|
10
|
+
*
|
|
11
|
+
* @returns The Map of action symbols to their State entries.
|
|
12
|
+
*/
|
|
13
|
+
export declare function useConsumer(): ConsumerContext;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Props } from './types.ts';
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
export { useMode } from './utils.ts';
|
|
4
|
+
export type { ModeHandle } from './utils.ts';
|
|
5
|
+
/**
|
|
6
|
+
* Provides a single mutable mode handle to every component inside the
|
|
7
|
+
* boundary. Components opt in by calling {@link useMode} and threading the
|
|
8
|
+
* returned handle through {@link useActions}'s `data` callback.
|
|
9
|
+
*
|
|
10
|
+
* Mode is **not** reactive — mutating it does not trigger a re-render.
|
|
11
|
+
* Use it for cross-handler coordination (e.g. flagging an in-progress
|
|
12
|
+
* sign-out) when you do not want the value showing up as render-time UI
|
|
13
|
+
* state.
|
|
14
|
+
*/
|
|
15
|
+
export declare function Mode({ children }: Props): React.ReactNode;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* React context exposing the per-Boundary mode handle. The handle itself
|
|
4
|
+
* is stable across renders — readers grab `.current` at call time.
|
|
5
|
+
*
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export declare const Context: React.Context<React.RefObject<unknown>>;
|
|
9
|
+
/**
|
|
10
|
+
* Handle returned by {@link useMode}. Reads always reflect the latest
|
|
11
|
+
* write, even after `await` boundaries.
|
|
12
|
+
*/
|
|
13
|
+
export type ModeHandle<T> = {
|
|
14
|
+
read(): T | null;
|
|
15
|
+
update(value: T | null): void;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Hook that returns a `{ read, update }` handle to the per-Boundary mode
|
|
19
|
+
* value.
|
|
20
|
+
*
|
|
21
|
+
* Mode is a single mutable value shared across every component inside the
|
|
22
|
+
* surrounding `<Boundary>`. It is **not** reactive — mutating it does
|
|
23
|
+
* not trigger a re-render. Use it for coordinating between async action
|
|
24
|
+
* handlers (e.g. short-circuiting refreshes during sign-out).
|
|
25
|
+
*
|
|
26
|
+
* Pass the handle through the {@link useActions} `data` callback so it
|
|
27
|
+
* shows up under `context.data` inside handlers, fully typed.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* enum Mode {
|
|
32
|
+
* Idle,
|
|
33
|
+
* SigningOut,
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* function useSignOutActions() {
|
|
37
|
+
* const mode = useMode<Mode>();
|
|
38
|
+
* const actions = useActions<Model, typeof Actions>(model, () => ({ mode }));
|
|
39
|
+
*
|
|
40
|
+
* actions.useAction(Actions.SignOut, async (context) => {
|
|
41
|
+
* context.data.mode.update(Mode.SigningOut);
|
|
42
|
+
* await api.signOut();
|
|
43
|
+
* context.data.mode.update(Mode.Idle);
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* actions.useAction(Actions.Refresh, async (context) => {
|
|
47
|
+
* if (context.data.mode.read() === Mode.SigningOut) return;
|
|
48
|
+
* // ...
|
|
49
|
+
* });
|
|
50
|
+
*
|
|
51
|
+
* return actions;
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare function useMode<T>(): ModeHandle<T>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { MulticastPayload } from '../../../types/index.ts';
|
|
2
|
+
import { ComponentType, ReactNode } from 'react';
|
|
3
|
+
export { useScope, getScope } from './utils.ts';
|
|
4
|
+
export type { ScopeEntry, ScopeContext } from './types.ts';
|
|
5
|
+
/**
|
|
6
|
+
* Higher-order component that opens a multicast scope keyed by the supplied
|
|
7
|
+
* multicast action. Components rendered inside the wrapped tree (and any
|
|
8
|
+
* descendants) participate in the scope: every dispatch of `action` reaches
|
|
9
|
+
* every subscriber inside this boundary.
|
|
10
|
+
*
|
|
11
|
+
* Each multicast action defines its own scope — pass the same action you
|
|
12
|
+
* declared with `Distribution.Multicast` to both `withScope` and
|
|
13
|
+
* `actions.dispatch`.
|
|
14
|
+
*
|
|
15
|
+
* Multicast caches the most recent dispatched value per scope so late-mounted
|
|
16
|
+
* components can read it via `context.actions.resolution()`.
|
|
17
|
+
*
|
|
18
|
+
* @param action - The multicast action that opens this scope.
|
|
19
|
+
* @param Component - The component to wrap.
|
|
20
|
+
* @returns A component that renders the original inside a fresh scope boundary.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* export class Scope {
|
|
25
|
+
* static Mood = Action<Mood>("Mood", Distribution.Multicast);
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* function Mood() {
|
|
29
|
+
* return (
|
|
30
|
+
* <>
|
|
31
|
+
* <Happy />
|
|
32
|
+
* <Sad />
|
|
33
|
+
* </>
|
|
34
|
+
* );
|
|
35
|
+
* }
|
|
36
|
+
*
|
|
37
|
+
* export default withScope(Scope.Mood, Mood);
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function withScope<P extends object, T = unknown>(action: MulticastPayload<T>, Component: ComponentType<P>): (props: P) => ReactNode;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { BroadcastEmitter } from '../broadcast/utils.ts';
|
|
2
|
+
import { ActionId } from '../tasks/types.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Represents a single scope in the ancestor chain. The scope key is the
|
|
5
|
+
* action id of the multicast action that opens the scope; each multicast
|
|
6
|
+
* action defines its own scope.
|
|
7
|
+
*/
|
|
8
|
+
export type ScopeEntry = {
|
|
9
|
+
/** The action id that opened this scope */
|
|
10
|
+
action: ActionId;
|
|
11
|
+
/** BroadcastEmitter for multicast events within this scope (caches last payload per event) */
|
|
12
|
+
emitter: BroadcastEmitter;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* The scope context is a flattened map of ancestor scopes keyed by the
|
|
16
|
+
* multicast action that opened each scope. Each `withScope` merges its entry
|
|
17
|
+
* with the parent's map, building up a complete lookup table for O(1)
|
|
18
|
+
* retrieval. null indicates no scope ancestor.
|
|
19
|
+
*/
|
|
20
|
+
export type ScopeContext = Map<ActionId, ScopeEntry> | null;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ScopeContext, ScopeEntry } from './types.ts';
|
|
2
|
+
import { ActionId } from '../tasks/types.ts';
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
/**
|
|
5
|
+
* React context for the scope chain.
|
|
6
|
+
* Starts as null (no scopes).
|
|
7
|
+
*/
|
|
8
|
+
export declare const Context: React.Context<ScopeContext>;
|
|
9
|
+
/**
|
|
10
|
+
* Hook to access the scope context from the nearest ancestor.
|
|
11
|
+
*
|
|
12
|
+
* @returns The scope context chain, or null if not inside any scope.
|
|
13
|
+
*/
|
|
14
|
+
export declare function useScope(): ScopeContext;
|
|
15
|
+
/**
|
|
16
|
+
* Looks up the scope opened by the given multicast action.
|
|
17
|
+
* O(1) lookup from the flattened scope map.
|
|
18
|
+
*/
|
|
19
|
+
export declare function getScope(context: ScopeContext, action: ActionId): ScopeEntry | null;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Props } from './types.ts';
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
export type { Task } from './types.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Creates a new tasks context for action control. Only needed if you
|
|
6
|
+
* want to isolate a tasks context, useful for libraries that want to provide
|
|
7
|
+
* their own tasks context without interfering with the app's tasks context.
|
|
8
|
+
*
|
|
9
|
+
* Tasks added within this context are isolated from tasks in other contexts.
|
|
10
|
+
*
|
|
11
|
+
* @param props.children - The children to render within the tasks context.
|
|
12
|
+
* @returns The children wrapped in a tasks context provider.
|
|
13
|
+
*/
|
|
14
|
+
export declare function Tasks({ children }: Props): React.ReactNode;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* An action identifier - either a symbol (including branded HandlerPayload) or string.
|
|
4
|
+
*/
|
|
5
|
+
export type ActionId = symbol | string;
|
|
6
|
+
/**
|
|
7
|
+
* Represents a running task with its associated metadata.
|
|
8
|
+
* Tasks are stored in a Set ordered by creation time (oldest first).
|
|
9
|
+
*
|
|
10
|
+
* @template P - The payload type for this task
|
|
11
|
+
* @property controller - The AbortController to cancel this task
|
|
12
|
+
* @property action - The action identifier that triggered this task
|
|
13
|
+
* @property payload - The payload passed when the action was dispatched
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* // Abort all tasks for a specific action
|
|
18
|
+
* for (const runningTask of context.tasks) {
|
|
19
|
+
* if (runningTask.action === Actions.Fetch) {
|
|
20
|
+
* runningTask.controller.abort();
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* // Abort the oldest task
|
|
25
|
+
* const oldest = context.tasks.values().next().value;
|
|
26
|
+
* oldest?.controller.abort();
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export type Task<P = unknown> = {
|
|
30
|
+
controller: AbortController;
|
|
31
|
+
action: ActionId;
|
|
32
|
+
payload: P;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* A set of running tasks ordered by creation time (oldest first).
|
|
36
|
+
*/
|
|
37
|
+
export type Tasks = Set<Task>;
|
|
38
|
+
/**
|
|
39
|
+
* Props for the Tasks provider component.
|
|
40
|
+
*/
|
|
41
|
+
export type Props = {
|
|
42
|
+
children: React.ReactNode;
|
|
43
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Tasks } from './types.ts';
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
/**
|
|
4
|
+
* React context for the shared tasks Set.
|
|
5
|
+
* Tasks are ordered by creation time (oldest first) since Sets maintain insertion order.
|
|
6
|
+
*/
|
|
7
|
+
export declare const Context: React.Context<Tasks>;
|
|
8
|
+
/**
|
|
9
|
+
* Hook to access the shared tasks Set from context.
|
|
10
|
+
* Returns the Set of all currently running tasks across all components in the context.
|
|
11
|
+
*
|
|
12
|
+
* @returns The Set of Task instances in the current context.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const tasks = useTasks();
|
|
17
|
+
*
|
|
18
|
+
* // Abort all tasks for a specific action
|
|
19
|
+
* for (const task of tasks) {
|
|
20
|
+
* if (task.action === Actions.Fetch) {
|
|
21
|
+
* task.controller.abort();
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function useTasks(): Tasks;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Props } from './types.ts';
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
/**
|
|
4
|
+
* Creates a unified context boundary for all March Hare features.
|
|
5
|
+
* Wraps children with Broadcaster, Mode, and Tasks providers.
|
|
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.
|
|
9
|
+
*
|
|
10
|
+
* @param props.children - The children to render within the boundary.
|
|
11
|
+
* @returns The children wrapped in all required context providers.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* <Boundary>
|
|
16
|
+
* <App />
|
|
17
|
+
* </Boundary>
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function Boundary({ children }: Props): React.ReactNode;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Task } from '../boundary/components/tasks/types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Reasons why an action error occurred.
|
|
4
|
+
*/
|
|
5
|
+
export declare enum Reason {
|
|
6
|
+
/** Action exceeded its timeout limit. */
|
|
7
|
+
Timedout = 0,
|
|
8
|
+
/** Action was cancelled by a newer dispatch. */
|
|
9
|
+
Supplanted = 1,
|
|
10
|
+
/** A generic error thrown in the user's action handler. */
|
|
11
|
+
Errored = 2
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Error thrown when an action is aborted, e.g., when a component unmounts
|
|
15
|
+
* or when a newer dispatch cancels a previous run. Works across all platforms
|
|
16
|
+
* including React Native where `DOMException` is unavailable.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* throw new AbortError("User cancelled the request");
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare class AbortError extends Error {
|
|
24
|
+
name: string;
|
|
25
|
+
constructor(message?: string);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Error thrown when an action exceeds its timeout limit.
|
|
29
|
+
* Works across all platforms including React Native where `DOMException` is unavailable.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* throw new TimeoutError("Request took too long");
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare class TimeoutError extends Error {
|
|
37
|
+
name: string;
|
|
38
|
+
constructor(message?: string);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Details about an error that occurred during action execution.
|
|
42
|
+
*
|
|
43
|
+
* Faults are delivered through the global `Lifecycle.Fault` broadcast.
|
|
44
|
+
* Subscribe with `actions.useAction(Lifecycle.Fault, handler)` near the
|
|
45
|
+
* root of your application for app-level concerns (logging, sign-out on
|
|
46
|
+
* auth failure, abort cascades). For component-local recovery, use a
|
|
47
|
+
* `Lifecycle.Error()` factory instead.
|
|
48
|
+
*
|
|
49
|
+
* @template E Custom error types to include in the union with Error.
|
|
50
|
+
*/
|
|
51
|
+
export type Fault<E extends Error = never> = {
|
|
52
|
+
/** The reason for the error. */
|
|
53
|
+
reason: Reason;
|
|
54
|
+
/** The Error object that was thrown. */
|
|
55
|
+
error: Error | E;
|
|
56
|
+
/** The name of the action that caused the error (e.g., "Increment"). */
|
|
57
|
+
action: string;
|
|
58
|
+
/** Whether the component has a `Lifecycle.Error()` handler registered. */
|
|
59
|
+
handled: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* All currently running tasks across the application.
|
|
62
|
+
* Use this to programmatically abort in-flight actions during error recovery
|
|
63
|
+
* (e.g., on 403/500 responses to prevent cascading failures).
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* actions.useAction(Lifecycle.Fault, (context, fault) => {
|
|
68
|
+
* if (fault.reason === Reason.Errored) {
|
|
69
|
+
* for (const task of fault.tasks) task.controller.abort();
|
|
70
|
+
* }
|
|
71
|
+
* });
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
tasks: ReadonlySet<Task>;
|
|
75
|
+
};
|