march-hare 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +480 -209
  2. package/dist/{hooks → actions}/index.d.ts +2 -39
  3. package/dist/{hooks → actions}/utils.d.ts +0 -39
  4. package/dist/app/index.d.ts +112 -0
  5. package/dist/app/types.d.ts +49 -0
  6. package/dist/boundary/components/broadcast/utils.d.ts +1 -1
  7. package/dist/boundary/components/env/index.d.ts +26 -0
  8. package/dist/boundary/components/env/types.d.ts +11 -0
  9. package/dist/boundary/components/env/utils.d.ts +36 -0
  10. package/dist/boundary/components/scope/index.d.ts +1 -39
  11. package/dist/boundary/components/scope/types.d.ts +17 -13
  12. package/dist/boundary/components/scope/utils.d.ts +12 -8
  13. package/dist/boundary/components/sharing/index.d.ts +43 -0
  14. package/dist/boundary/index.d.ts +10 -10
  15. package/dist/boundary/types.d.ts +6 -16
  16. package/dist/cache/index.d.ts +4 -4
  17. package/dist/coalesce/index.d.ts +57 -0
  18. package/dist/context/index.d.ts +39 -0
  19. package/dist/context/types.d.ts +14 -0
  20. package/dist/error/index.d.ts +1 -1
  21. package/dist/error/types.d.ts +8 -19
  22. package/dist/index.d.ts +8 -13
  23. package/dist/march-hare.js +7 -5
  24. package/dist/march-hare.umd.cjs +1 -1
  25. package/dist/resource/index.d.ts +52 -78
  26. package/dist/resource/types.d.ts +83 -10
  27. package/dist/scope/index.d.ts +63 -0
  28. package/dist/scope/types.d.ts +55 -0
  29. package/dist/types/index.d.ts +77 -39
  30. package/dist/utils/index.d.ts +6 -5
  31. package/dist/with/index.d.ts +40 -0
  32. package/package.json +1 -1
  33. package/dist/boundary/components/store/index.d.ts +0 -41
  34. package/dist/boundary/components/store/types.d.ts +0 -11
  35. package/dist/boundary/components/store/utils.d.ts +0 -64
  36. /package/dist/{hooks → actions}/types.d.ts +0 -0
@@ -1,6 +1,5 @@
1
1
  import { Data } from './types';
2
- import { Model, Props, Actions, UseActions, Context as ContextHandle } from '../types/index';
3
- export { With } from './utils';
2
+ import { Model, Props, Actions, UseActions } from '../types/index';
4
3
  /**
5
4
  * A hook for managing state with actions.
6
5
  *
@@ -43,41 +42,5 @@ export { With } from './utils';
43
42
  * >(initialModel, () => ({ query: props.query }));
44
43
  * ```
45
44
  */
46
- export declare function useActions<_M extends void = void, A extends Actions | void = void, D extends Props = Props>(getData?: Data<D>): UseActions<void, A, D>;
45
+ export declare function useActions<M extends void = void, A extends Actions | void = void, D extends Props = Props>(getData?: Data<D>): UseActions<M, A, D>;
47
46
  export declare function useActions<M extends Model, A extends Actions | void = void, D extends Props = Props>(initialModel: M, getData?: Data<D>): UseActions<M, A, D>;
48
- /**
49
- * Returns a stable, typed controller handle up-front &mdash; before a
50
- * model is declared via `context.useActions(...)`. Use this when an
51
- * external imperative library (form, animation, third-party SDK) needs a
52
- * dispatch callback at construction time, while the value that library
53
- * returns must flow back into the controller's data callback.
54
- *
55
- * The handle exposes `dispatch(action, payload?)` and a `useView(...)`
56
- * method that materialises the component-local model and reactive data
57
- * &mdash; the M and D pair of `useContext<M, AC, D>` &mdash; and
58
- * returns the `[model, actions, data]` tuple with `useAction`, `dispatch`,
59
- * `inspect`, and `stream` attached. The first invocation of
60
- * `context.actions.dispatch(...)` must come from an event handler &mdash; not
61
- * synchronously during render &mdash; because the underlying dispatch
62
- * target is wired up when `context.useActions(...)` runs in the same
63
- * render pass.
64
- *
65
- * @template M The model type representing the component's state.
66
- * @template AC The actions class containing action definitions.
67
- * @template D The data type for reactive external values.
68
- *
69
- * @example
70
- * ```ts
71
- * const context = useContext<Model, typeof Actions, Data>();
72
- *
73
- * const form = useForm({
74
- * onSubmit: () => void context.actions.dispatch(Actions.Submit),
75
- * });
76
- *
77
- * const actions = context.useActions(
78
- * { user: user() },
79
- * () => ({ form }),
80
- * );
81
- * ```
82
- */
83
- export declare function useContext<M extends Model | void = void, AC extends Actions | void = void, D extends Props = Props>(): ContextHandle<M, AC, D>;
@@ -63,45 +63,6 @@ export declare function useLifecycles({ unicast, broadcast, dispatchers, scope,
63
63
  * @returns A memoized data proxy of the object.
64
64
  */
65
65
  export declare function useData<P extends Props>(props: P): P;
66
- /**
67
- * Handler factories that wire an action directly to a model field.
68
- *
69
- * - {@link With.Update} assigns the dispatched payload to `model[key]`.
70
- * - {@link With.Invert} flips a boolean field on `model[key]`.
71
- *
72
- * Both are typed so the call site fails to compile when `key` is missing or
73
- * has an incompatible type.
74
- *
75
- * @example
76
- * ```ts
77
- * import { With } from "march-hare";
78
- *
79
- * type Model = { name: string; sidebar: boolean };
80
- *
81
- * class Actions {
82
- * static SetName = Action<string>("SetName");
83
- * static ToggleSidebar = Action("ToggleSidebar");
84
- * }
85
- *
86
- * actions.useAction(Actions.SetName, With.Update("name"));
87
- * actions.useAction(Actions.ToggleSidebar, With.Invert("sidebar"));
88
- * ```
89
- */
90
- export declare const With: {
91
- /**
92
- * Returns a handler that assigns the action payload to `model[key]`.
93
- *
94
- * Type-checks at the call site: the payload type must be assignable to
95
- * the model property's type, and the key must exist on the model.
96
- */
97
- Update<K extends string>(key: K): <M extends Model, A extends Actions | void, D extends Props, P extends K extends keyof M ? M[K] : never>(context: HandlerContext<M, A, D>, payload: P) => void;
98
- /**
99
- * Returns a handler that inverts a boolean field on the model.
100
- *
101
- * Type-checks at the call site: `model[key]` must be a boolean.
102
- */
103
- Invert<K extends string>(key: K): <M extends Model & Record<K, boolean>, A extends Actions | void, D extends Props>(context: HandlerContext<M, A, D>) => void;
104
- };
105
66
  /**
106
67
  * Scans a handler registry for a lifecycle action of the given type.
107
68
  *
@@ -0,0 +1,112 @@
1
+ import { Env } from '../boundary/components/env/index';
2
+ import { Actions, Model, Props } from '../types/index';
3
+ import { Scope } from '../scope/index';
4
+ import { AppContextHandle, AppResource } from './types';
5
+ import * as React from "react";
6
+ export type { AppArgs, AppContextHandle, AppFetcher, AppResource, } from './types';
7
+ /**
8
+ * Returned from {@link App}. Bundles the Boundary, hooks, and Resource
9
+ * factory bound to a single typed Env shape `S`.
10
+ */
11
+ export type App<S extends object> = {
12
+ /**
13
+ * Boundary component for this App. Wraps the subtree with the initial
14
+ * `env` value passed to {@link App}.
15
+ */
16
+ readonly Boundary: React.FC<{
17
+ children: React.ReactNode;
18
+ }>;
19
+ /**
20
+ * Hook returning a stable `Context` handle. The handle's
21
+ * `context.useActions(initialModel?, getData?)` materialises the
22
+ * component's `[model, actions, data]` tuple. Every handler's
23
+ * `context.env` is typed as `S`.
24
+ */
25
+ readonly useContext: <M extends Model | void = void, AC extends Actions | void = void, D extends Props = Props>() => AppContextHandle<M, AC, D, S>;
26
+ /**
27
+ * Read-only Proxy over the per-Boundary Env, typed as `S`. Reads use
28
+ * dot notation (`env.session`) and always reflect the latest value
29
+ * across `await` boundaries. Writes flow through
30
+ * `context.actions.produce(({ env }) => { ... })`.
31
+ */
32
+ readonly useEnv: () => Readonly<S>;
33
+ /**
34
+ * `Resource` factory bound to this App's Env. Same shape as the
35
+ * top-level `Resource`: call directly for an in-memory cache, or use
36
+ * `app.Resource.Cachable(cache, fetcher)` for persistence.
37
+ */
38
+ readonly Resource: AppResource<S>;
39
+ /**
40
+ * Opens a typed multicast scope. The generic `MulticastActions` declares
41
+ * the `Distribution.Multicast` action class (or union of classes)
42
+ * whose dispatches are routed through this scope &mdash; the
43
+ * returned handle mirrors the App surface but widens
44
+ * `useContext().actions.dispatch` to accept actions from `MulticastActions`
45
+ * on top of the local `AC` class.
46
+ *
47
+ * Render `<scope.Boundary>` to open the scope at runtime; nesting
48
+ * multiple boundaries from different `app.Scope()` calls is fine,
49
+ * each runs as an independent emitter shadowed for its subtree.
50
+ *
51
+ * The Scope handle deliberately does NOT expose a further `Scope`
52
+ * method &mdash; the multicast surface must be declared at the
53
+ * `app.Scope<MulticastActions>()` call site so the type union is explicit.
54
+ */
55
+ readonly Scope: <MulticastActions>() => Scope<S, MulticastActions>;
56
+ };
57
+ /**
58
+ * Creates an `App` &mdash; the entrypoint for a typed Env shape `S`,
59
+ * inferred from `config.env`. `App<S>` exposes `Boundary`, hooks, and
60
+ * a `Resource` factory all wired against the same shape.
61
+ *
62
+ * Each `<app.Boundary>` instance owns its own Env, so different `App`s
63
+ * can coexist in the same tree with completely independent shapes.
64
+ *
65
+ * @example
66
+ * ```tsx
67
+ * import { App } from "march-hare";
68
+ *
69
+ * type Session = { accessToken: string };
70
+ *
71
+ * export const app = App({
72
+ * env: {
73
+ * session: null as Session | null,
74
+ * operating: "idle" as "idle" | "signing-out",
75
+ * },
76
+ * });
77
+ *
78
+ * // Root render.
79
+ * <app.Boundary>
80
+ * <Root />
81
+ * </app.Boundary>;
82
+ *
83
+ * // In a feature's actions.ts:
84
+ * export function useAuthActions() {
85
+ * const context = app.useContext<void, typeof Actions>();
86
+ * const actions = context.useActions();
87
+ *
88
+ * actions.useAction(Actions.SignOut, async (context) => {
89
+ * context.actions.produce(({ env }) => {
90
+ * env.session = null;
91
+ * });
92
+ * });
93
+ *
94
+ * return actions;
95
+ * }
96
+ *
97
+ * // In resources.ts:
98
+ * export const user = app.Resource<User>((context) =>
99
+ * ky
100
+ * .get("/api/user", {
101
+ * headers: context.env.session
102
+ * ? { Authorization: `Bearer ${context.env.session.accessToken}` }
103
+ * : {},
104
+ * signal: context.controller.signal,
105
+ * })
106
+ * .json<User>(),
107
+ * );
108
+ * ```
109
+ */
110
+ export declare function App<S extends object = Env>(config?: {
111
+ env: S;
112
+ }): App<S>;
@@ -0,0 +1,49 @@
1
+ import { Args, ResourceHandle } from '../resource/types';
2
+ import { Cache } from '../cache/index';
3
+ import { Actions, Context, Model, Props, UseActions } from '../types/index';
4
+ import { Data } from '../actions/types';
5
+ /**
6
+ * Args object passed to an `app.Resource` fetcher. Same shape as the
7
+ * base `Resource` fetcher's args but with `env` typed as `S`.
8
+ */
9
+ export type AppArgs<S, P extends object = Record<never, never>> = Omit<Args<P>, "env"> & {
10
+ readonly env: Readonly<S>;
11
+ };
12
+ /**
13
+ * Fetcher signature for an `app.Resource` declaration. The fetcher's
14
+ * `context.env` is typed against the App's inferred Env shape `S`.
15
+ */
16
+ export type AppFetcher<S, T, P extends object = Record<never, never>> = (context: AppArgs<S, P>) => Promise<T>;
17
+ /**
18
+ * `app.Resource(fetcher)` &mdash; in-memory cache, no persistence.
19
+ * `app.Resource.Cachable(cache, fetcher)` &mdash; persistent cache wired
20
+ * to the supplied `Cache` adapter. Both forms type `context.env` as
21
+ * the App's Env shape.
22
+ */
23
+ export type AppResource<S> = {
24
+ <T, P extends object = Record<never, never>>(fetcher: AppFetcher<S, T, P>): ResourceHandle<T, P>;
25
+ readonly Cachable: <T, P extends object = Record<never, never>>(cache: Cache, fetcher: AppFetcher<S, T, P>) => ResourceHandle<T, P>;
26
+ };
27
+ /**
28
+ * Phantom marker so consumers of the App's hooks see `env: S` typing
29
+ * at the type-system level; at runtime the value is the same proxy as
30
+ * the loose `Env` type.
31
+ *
32
+ * @internal
33
+ */
34
+ type EnvView<S> = {
35
+ readonly __appEnv?: S;
36
+ };
37
+ type AppActionsResult<M, AC, D, S> = UseActions<M extends Model | void ? M : void, AC extends Actions | void ? AC : void, D extends Props ? D : Props> & EnvView<S>;
38
+ type AppUseActions<M, AC, D, S> = M extends void ? (getData?: Data<D & Props>) => AppActionsResult<M, AC, D, S> : (initialModel: M, getData?: Data<D & Props>) => AppActionsResult<M, AC, D, S>;
39
+ /**
40
+ * `Context` handle returned by `app.useContext()`. Mirrors the base
41
+ * {@link Context} but with the Env-typed slots overridden to `S`.
42
+ */
43
+ export type AppContextHandle<M, AC, D, S> = {
44
+ readonly actions: {
45
+ dispatch: Context<M extends Model | void ? M : void, AC extends Actions | void ? AC : void, D extends Props ? D : Props>["actions"]["dispatch"];
46
+ };
47
+ readonly useActions: AppUseActions<M, AC, D, S>;
48
+ };
49
+ export {};
@@ -6,7 +6,7 @@ import * as React from "react";
6
6
  * When a broadcast or multicast action is dispatched, the payload is
7
7
  * stored so that late-mounting components can replay it via
8
8
  * {@link useLifecycles} and handlers can read it via
9
- * `context.actions.resolution()`.
9
+ * `context.actions.final()`.
10
10
  */
11
11
  export declare class BroadcastEmitter extends EventEmitter {
12
12
  private cache;
@@ -0,0 +1,26 @@
1
+ import { Props } from './types';
2
+ import * as React from "react";
3
+ export { useEnv } from './utils';
4
+ /**
5
+ * Loose runtime shape for the per-`<Boundary>` Env. Each {@link App}
6
+ * narrows this to its own typed env via `App<S>({ env })`; the
7
+ * loose type exists so the framework's internal plumbing
8
+ * (`<Boundary>`, `useEnv`, handler `context.env`, Resource
9
+ * fetcher `context.env`) does not need to be parametric over S.
10
+ *
11
+ * Consumers should declare their Env shape inline via `App({ env })`
12
+ * &mdash; the inferred `S` is what flows through `app.useContext`,
13
+ * `app.useEnv`, and `app.Resource`. Module augmentation of `Env`
14
+ * is no longer required.
15
+ */
16
+ export type Env = Record<string, unknown>;
17
+ /**
18
+ * Provides a per-Boundary {@link Env} value to every component inside
19
+ * the boundary. Usually wired in via the `<Boundary env={initial}>`
20
+ * prop rather than used directly.
21
+ *
22
+ * The Env is **not** reactive. Mutating it does not trigger a
23
+ * re-render. Drive view state through the model; use the Env for
24
+ * cross-handler coordination.
25
+ */
26
+ export declare function Env({ initial, children }: Props): React.ReactNode;
@@ -0,0 +1,11 @@
1
+ import { ReactNode } from 'react';
2
+ import { Env } from './index';
3
+ export type { Env } from './index';
4
+ /**
5
+ * Props for the Env provider component. Accepts the initial Env
6
+ * value that satisfies the augmented {@link Env} interface.
7
+ */
8
+ export type Props = {
9
+ initial: Env;
10
+ children: ReactNode;
11
+ };
@@ -0,0 +1,36 @@
1
+ import { RefObject } from 'react';
2
+ import { Env } from './index';
3
+ import * as React from "react";
4
+ /**
5
+ * React context exposing the per-Boundary Env 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<Env>>;
11
+ /**
12
+ * Hook that returns a read-only handle to the per-Boundary {@link Env}.
13
+ * Reads use plain dot notation (`env.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 Env inside an action handler
18
+ * via `context.actions.produce(({ model, env }) => { env.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
+ * Prefer `app.useEnv()` from an {@link App} instance &mdash; the App
23
+ * factory infers the Env's shape from `app.env` and types every
24
+ * read/write against it. The bare `useEnv()` exists for non-App
25
+ * call sites and returns the loose {@link Env} record type.
26
+ */
27
+ export declare function useEnv(): Env;
28
+ /**
29
+ * Internal accessor for the per-Boundary Env ref &mdash; used by the
30
+ * Resource layer to pass a fresh snapshot to each fetcher invocation
31
+ * and by the action layer to write through `context.actions.produce`.
32
+ * Not exported from the library.
33
+ *
34
+ * @internal
35
+ */
36
+ export declare function useEnvRef(): RefObject<Env>;
@@ -1,40 +1,2 @@
1
- import { MulticastPayload } from '../../../types/index';
2
- import { ComponentType, ReactNode } from 'react';
3
- export { useScope, getScope } from './utils';
1
+ export { Context, useScope, getScope } from './utils';
4
2
  export type { ScopeEntry, ScopeContext } from './types';
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 &mdash; 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;
@@ -1,20 +1,24 @@
1
1
  import { BroadcastEmitter } from '../broadcast/utils';
2
- import { ActionId } from '../tasks/types';
3
2
  /**
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.
3
+ * Runtime entry for a single multicast scope opened by an
4
+ * `<app.Scope().Boundary>`. The `id` uniquely identifies this scope
5
+ * instance; the `emitter` carries every multicast event dispatched
6
+ * inside the boundary, keyed internally by the action's symbol.
7
+ *
8
+ * Nested boundaries shadow outer ones via React context.
9
+ *
10
+ * @internal
7
11
  */
8
12
  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
+ readonly id: symbol;
14
+ readonly emitter: BroadcastEmitter;
13
15
  };
14
16
  /**
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.
17
+ * The scope context. `null` when no `<app.Scope().Boundary>` exists in
18
+ * the ancestor chain; otherwise the nearest entry &mdash; React context
19
+ * shadowing makes nested boundaries override outer ones for the
20
+ * subtree.
21
+ *
22
+ * @internal
19
23
  */
20
- export type ScopeContext = Map<ActionId, ScopeEntry> | null;
24
+ export type ScopeContext = ScopeEntry | null;
@@ -1,19 +1,23 @@
1
1
  import { ScopeContext, ScopeEntry } from './types';
2
- import { ActionId } from '../tasks/types';
3
2
  import * as React from "react";
4
3
  /**
5
- * React context for the scope chain.
6
- * Starts as null (no scopes).
4
+ * React context for the nearest multicast scope. `null` at the root.
5
+ *
6
+ * @internal
7
7
  */
8
8
  export declare const Context: React.Context<ScopeContext>;
9
9
  /**
10
- * Hook to access the scope context from the nearest ancestor.
10
+ * Hook that returns the nearest multicast scope entry. `null` when
11
+ * the caller is not rendered inside any `<app.Scope().Boundary>`.
11
12
  *
12
- * @returns The scope context chain, or null if not inside any scope.
13
+ * @internal
13
14
  */
14
15
  export declare function useScope(): ScopeContext;
15
16
  /**
16
- * Looks up the scope opened by the given multicast action.
17
- * O(1) lookup from the flattened scope map.
17
+ * Pass-through accessor. Kept for the dispatch/subscribe sites that
18
+ * previously needed an action-keyed lookup; now the scope is a single
19
+ * entry (or `null`), so this returns it as-is.
20
+ *
21
+ * @internal
18
22
  */
19
- export declare function getScope(context: ScopeContext, action: ActionId): ScopeEntry | null;
23
+ export declare function getScope(context: ScopeContext): ScopeEntry | null;
@@ -0,0 +1,43 @@
1
+ import { PendingCall } from '../../../resource/index';
2
+ import * as React from "react";
3
+ /**
4
+ * Per-`<Boundary>` registry for `.coalesce(token)` sharing. Outer map
5
+ * keys on the `PendingCall.run` function identity (stable per Resource
6
+ * via the `build()` closure); inner map keys on
7
+ * `${paramsKey}|${coalesceKey(token)}`. While an entry exists every
8
+ * caller awaiting `.coalesce(token)` for the same Resource + params +
9
+ * token receives the same promise.
10
+ *
11
+ * Lifted into React context so each `<app.Boundary>` owns its own
12
+ * registry &mdash; two `App` instances in the same tree cannot collide
13
+ * on a shared token like `.coalesce("k")`.
14
+ *
15
+ * @internal
16
+ */
17
+ export type Sharing = WeakMap<PendingCall["run"], Map<string, Promise<unknown>>>;
18
+ /**
19
+ * React context exposing the per-Boundary sharing registry. The
20
+ * fallback is a fresh `WeakMap` used when `useSharing()` is read
21
+ * outside any `<Boundary>` &mdash; calls work but never share with any
22
+ * other component.
23
+ *
24
+ * @internal
25
+ */
26
+ export declare const Context: React.Context<Sharing>;
27
+ /**
28
+ * Wraps children with a Boundary-scoped sharing registry for
29
+ * `.coalesce(token)`. Rendered as part of {@link Boundary}; not
30
+ * exposed standalone.
31
+ *
32
+ * @internal
33
+ */
34
+ export declare function SharingProvider({ children, }: {
35
+ children: React.ReactNode;
36
+ }): React.ReactElement;
37
+ /**
38
+ * Hook returning the per-Boundary sharing registry. Used by the
39
+ * `.coalesce(token)` chainable inside `useActions`.
40
+ *
41
+ * @internal
42
+ */
43
+ export declare function useSharing(): Sharing;
@@ -1,21 +1,21 @@
1
1
  import { Props } from './types';
2
2
  import * as React from "react";
3
3
  /**
4
- * Creates a unified context boundary for all March Hare features.
5
- * Wraps children with Broadcaster, Store, and Tasks providers.
4
+ * Low-level boundary primitive. Wraps children with the Broadcaster,
5
+ * Env, and Tasks providers required by every March Hare hook.
6
6
  *
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
- *
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.
7
+ * Most applications should reach for {@link App} instead &mdash;
8
+ * `App<S>({ env })` returns a typed `app.Boundary` along with
9
+ * matching `useContext` / `useEnv` / `Resource` factories that all
10
+ * close over the App's inferred env shape `S`. The bare `Boundary`
11
+ * is exposed for advanced or library-internal use where the loose
12
+ * Env record type is sufficient.
13
13
  *
14
14
  * @example
15
15
  * ```tsx
16
- * <Boundary store={{ session: null, locale: "en-GB" }}>
16
+ * <Boundary env={{ session: null, locale: "en-GB" }}>
17
17
  * <App />
18
18
  * </Boundary>
19
19
  * ```
20
20
  */
21
- export declare function Boundary({ store, children }: Props): React.ReactNode;
21
+ export declare function Boundary({ env, children }: Props): React.ReactNode;
@@ -1,22 +1,12 @@
1
- import { Store } from './components/store/types';
1
+ import { Env } from './components/env/types';
2
2
  import type * as React from "react";
3
3
  export type Props = {
4
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.
5
+ * Initial value of the per-Boundary {@link Env}. Prefer `App({ env })`
6
+ * &mdash; it infers the Env shape and threads it through `app.useContext`,
7
+ * `app.useEnv`, and `app.Resource`. Pass `env` directly here only for
8
+ * advanced cases where the loose {@link Env} record type is sufficient.
19
9
  */
20
- store?: Store;
10
+ env?: Env;
21
11
  children: React.ReactNode;
22
12
  };
@@ -28,11 +28,11 @@ export type { Adapter, Encoded } from './types';
28
28
  * clear: () => localStorage.clear(),
29
29
  * });
30
30
  *
31
- * // Wired into a Resource — successful runs write through automatically.
32
- * export const cat = Resource(
33
- * async ({ controller }) => fetchCat(controller.signal),
31
+ * // Wire it into a Resource — successful runs write through automatically.
32
+ * export const cat = Resource({
34
33
  * cache,
35
- * );
34
+ * fetch: (context) => fetchCat(context.controller.signal),
35
+ * });
36
36
  * ```
37
37
  */
38
38
  export type Cache = {
@@ -0,0 +1,57 @@
1
+ import { Coalesce } from '../resource/types';
2
+ /**
3
+ * Sentinel token used when `.coalesce()` is called with no explicit
4
+ * argument. Every untokened caller for the same `(Resource, params)`
5
+ * slot collapses onto this single key, so multiple callers chaining
6
+ * `.coalesce()` share one in-flight fetch.
7
+ *
8
+ * The symbol description follows the `march-hare.{category}/{name}`
9
+ * convention used by the rest of the library's internal symbols.
10
+ *
11
+ * @internal
12
+ */
13
+ export declare const token: unique symbol;
14
+ /**
15
+ * Builds the per-call dedupe key for `.coalesce(token)`.
16
+ *
17
+ * The full registry key (constructed at the call site) is
18
+ * `${JSON.stringify(params)}|${coalesceKey(token)}`; this function is
19
+ * responsible only for the trailing token segment. Every supported
20
+ * `Coalesce` primitive (`string`, `number`, `bigint`, `boolean`,
21
+ * `symbol`) is prefixed with a single-character type tag so that
22
+ * structurally identical values from different types stay distinct
23
+ * &mdash; e.g. the string `"5"` does not collide with the number `5`,
24
+ * and `Symbol("X")` does not collide with the string `"X"`.
25
+ *
26
+ * Symbols are keyed by their `description` (falling back to
27
+ * `String(value)` for description-less symbols) so two `Symbol("X")`
28
+ * instances declared in separate modules still hash to the same key.
29
+ * That is intentional: the public contract is "same description &rarr;
30
+ * same coalesce group", which keeps the API friendly for the common
31
+ * enum-token pattern at call sites.
32
+ *
33
+ * Any unrecognised value falls through to the object branch and is
34
+ * keyed by its JSON shape; `Coalesce` is constrained to primitives at
35
+ * the type level, so this branch is reachable only from `unknown`
36
+ * coercion in tests.
37
+ *
38
+ * @internal
39
+ */
40
+ export declare function coalesceKey(value: Coalesce): string;
41
+ /**
42
+ * Wraps `promise` so that aborting `signal` rejects the returned
43
+ * promise with `signal.reason`, even when `promise` itself never
44
+ * settles. The original promise is left alone &mdash; the underlying
45
+ * work continues (other awaiters keep their grip) and only this
46
+ * caller's view of it is severed.
47
+ *
48
+ * Used by the `.coalesce(token)` chainable: the shared in-flight fetch
49
+ * runs on a detached `AbortController` so one caller's abort does not
50
+ * cancel work the others are still waiting on, while each caller's own
51
+ * `context.task.controller` still aborts its personal await via this
52
+ * wrapper. The `{ once: true }` listener and the cleanup on settle keep
53
+ * the wrapper free of leaks across long-lived signals.
54
+ *
55
+ * @internal
56
+ */
57
+ export declare function withAbort<T>(promise: Promise<T>, signal: AbortSignal): Promise<T>;