march-hare 0.8.0 → 0.10.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 +491 -211
- package/dist/actions/index.d.ts +46 -0
- package/dist/{hooks → actions}/utils.d.ts +0 -39
- package/dist/app/index.d.ts +132 -0
- package/dist/app/types.d.ts +82 -0
- package/dist/boundary/components/broadcast/utils.d.ts +1 -1
- package/dist/boundary/components/env/index.d.ts +26 -0
- package/dist/boundary/components/env/types.d.ts +11 -0
- package/dist/boundary/components/env/utils.d.ts +36 -0
- package/dist/boundary/components/scope/index.d.ts +1 -39
- package/dist/boundary/components/scope/types.d.ts +17 -13
- package/dist/boundary/components/scope/utils.d.ts +12 -8
- package/dist/boundary/components/sharing/index.d.ts +43 -0
- package/dist/boundary/components/tap/index.d.ts +36 -0
- package/dist/boundary/components/tap/types.d.ts +150 -0
- package/dist/boundary/components/tap/utils.d.ts +14 -0
- package/dist/boundary/index.d.ts +10 -10
- package/dist/boundary/types.d.ts +46 -14
- package/dist/cache/index.d.ts +4 -4
- package/dist/coalesce/index.d.ts +57 -0
- package/dist/context/index.d.ts +41 -0
- package/dist/context/types.d.ts +14 -0
- package/dist/error/index.d.ts +1 -1
- package/dist/error/types.d.ts +8 -19
- package/dist/index.d.ts +9 -13
- package/dist/march-hare.js +8 -5
- package/dist/march-hare.umd.cjs +1 -1
- package/dist/resource/index.d.ts +55 -78
- package/dist/resource/types.d.ts +87 -11
- package/dist/resource/utils.d.ts +1 -1
- package/dist/scope/index.d.ts +63 -0
- package/dist/scope/types.d.ts +55 -0
- package/dist/types/index.d.ts +108 -58
- package/dist/utils/index.d.ts +6 -5
- package/dist/with/index.d.ts +111 -0
- package/package.json +1 -1
- package/dist/boundary/components/store/index.d.ts +0 -41
- package/dist/boundary/components/store/types.d.ts +0 -11
- package/dist/boundary/components/store/utils.d.ts +0 -64
- package/dist/hooks/index.d.ts +0 -83
- /package/dist/{hooks → actions}/types.d.ts +0 -0
package/dist/types/index.d.ts
CHANGED
|
@@ -1,8 +1,41 @@
|
|
|
1
1
|
import { Operation, Process, Inspect, Box } from 'immertation';
|
|
2
2
|
import { ActionId, Task, Tasks } from '../boundary/components/tasks/types';
|
|
3
3
|
import { Fault } from '../error/types';
|
|
4
|
-
import {
|
|
4
|
+
import { Env } from '../boundary/components/env/index';
|
|
5
|
+
import { Coalesce } from '../resource/types';
|
|
6
|
+
import { WithHandle } from '../with/index';
|
|
5
7
|
import * as React from "react";
|
|
8
|
+
/**
|
|
9
|
+
* Chainable handle returned from `context.actions.resource(invocation)`.
|
|
10
|
+
*
|
|
11
|
+
* - `.exceeds(duration)` short-circuits the fetch when the per-params
|
|
12
|
+
* cache age is within the supplied freshness window.
|
|
13
|
+
* - `.coalesce(token)` opts the call into in-flight sharing: any other
|
|
14
|
+
* caller with the same Resource, same structural params, and equal
|
|
15
|
+
* `token` joins the same promise.
|
|
16
|
+
*
|
|
17
|
+
* Awaiting the handle (`await context.actions.resource(...)`) triggers
|
|
18
|
+
* the fetch with whichever options have been set on the chain.
|
|
19
|
+
*/
|
|
20
|
+
export type ResourceCall<T> = PromiseLike<T> & {
|
|
21
|
+
/**
|
|
22
|
+
* Skip the fetch when the cached payload is within `duration`.
|
|
23
|
+
* Accepts a `Temporal.Duration`, a `DurationLike` object
|
|
24
|
+
* (`{ minutes: 5 }`), or an ISO 8601 string (`"PT5M"`).
|
|
25
|
+
*/
|
|
26
|
+
readonly exceeds: (duration: Temporal.DurationLike) => ResourceCall<T>;
|
|
27
|
+
/**
|
|
28
|
+
* Join an in-flight fetch for the same `(resource, params, token)`
|
|
29
|
+
* tuple. The shared fetch runs against a detached `AbortController`
|
|
30
|
+
* so a single caller's abort never cancels work other callers are
|
|
31
|
+
* waiting on; each caller still sees its own `context.task.controller`
|
|
32
|
+
* abort as a rejection of its personal await.
|
|
33
|
+
*
|
|
34
|
+
* `token` is optional — omit it to share with every other
|
|
35
|
+
* untokened caller for the same `(resource, params)` slot.
|
|
36
|
+
*/
|
|
37
|
+
readonly coalesce: (token?: Coalesce) => ResourceCall<T>;
|
|
38
|
+
};
|
|
6
39
|
export type { ActionId, Box, Task, Tasks };
|
|
7
40
|
/**
|
|
8
41
|
* Type for objects with a Brand.Action symbol property.
|
|
@@ -34,7 +67,7 @@ export type BrandedObject = {
|
|
|
34
67
|
};
|
|
35
68
|
/**
|
|
36
69
|
* Recursive readonly. Locks every nested property so that read-only
|
|
37
|
-
* projections on `context` (model, data,
|
|
70
|
+
* projections on `context` (model, data, env) reject direct assignment
|
|
38
71
|
* — mutation must go through `context.actions.produce(...)`.
|
|
39
72
|
*
|
|
40
73
|
* Function types pass through untouched so method calls (e.g.
|
|
@@ -85,13 +118,13 @@ export declare class Brand {
|
|
|
85
118
|
*/
|
|
86
119
|
export declare const FaultSymbol: unique symbol;
|
|
87
120
|
/**
|
|
88
|
-
* Internal symbol for the global `Lifecycle.
|
|
121
|
+
* Internal symbol for the global `Lifecycle.Env` broadcast. The env
|
|
89
122
|
* mutation path in `useActions` fires this symbol whenever a
|
|
90
|
-
* `produce({
|
|
123
|
+
* `produce({ env })` call changes the slot reference.
|
|
91
124
|
*
|
|
92
125
|
* @internal
|
|
93
126
|
*/
|
|
94
|
-
export declare const
|
|
127
|
+
export declare const EnvSymbol: unique symbol;
|
|
95
128
|
/**
|
|
96
129
|
* Factory functions for lifecycle actions.
|
|
97
130
|
*
|
|
@@ -111,10 +144,10 @@ export declare const StoreSymbol: unique symbol;
|
|
|
111
144
|
* }
|
|
112
145
|
* ```
|
|
113
146
|
*
|
|
114
|
-
* `Lifecycle.Fault` and `Lifecycle.
|
|
147
|
+
* `Lifecycle.Fault` and `Lifecycle.Env` are singleton broadcasts (not
|
|
115
148
|
* factories). All components subscribe to the same shared symbol —
|
|
116
|
-
* `Fault` delivers global fault notifications, `
|
|
117
|
-
*
|
|
149
|
+
* `Fault` delivers global fault notifications, `Env` delivers per-`Boundary`
|
|
150
|
+
* env-change notifications.
|
|
118
151
|
*/
|
|
119
152
|
export declare class Lifecycle {
|
|
120
153
|
/** Creates a Mount lifecycle action. Triggered once on component mount (`useLayoutEffect`). */
|
|
@@ -146,30 +179,30 @@ export declare class Lifecycle {
|
|
|
146
179
|
*/
|
|
147
180
|
static Fault: BroadcastPayload<Fault, never, "Fault">;
|
|
148
181
|
/**
|
|
149
|
-
* Global
|
|
150
|
-
* snapshot whenever a `context.actions.produce(({
|
|
182
|
+
* Global env-change broadcast. Receives the latest {@link Env}
|
|
183
|
+
* snapshot whenever a `context.actions.produce(({ env }) => ...)` call
|
|
151
184
|
* mutates the slot. Subscribe via
|
|
152
|
-
* `actions.useAction(Lifecycle.
|
|
153
|
-
* it directly with `actions.stream(Lifecycle.
|
|
185
|
+
* `actions.useAction(Lifecycle.Env, handler)` — or render against
|
|
186
|
+
* it directly with `actions.stream(Lifecycle.Env, (env) => ...)`.
|
|
154
187
|
*
|
|
155
188
|
* Like `Lifecycle.Fault`, this is a singleton broadcast (not a factory):
|
|
156
189
|
* every subscriber points at the same shared symbol. The latest value is
|
|
157
190
|
* cached on the broadcast emitter so that late-mounting handlers and
|
|
158
|
-
* streams receive the current
|
|
191
|
+
* streams receive the current env on mount.
|
|
159
192
|
*
|
|
160
193
|
* @example
|
|
161
194
|
* ```tsx
|
|
162
|
-
* actions.useAction(Lifecycle.
|
|
163
|
-
* console.log("
|
|
195
|
+
* actions.useAction(Lifecycle.Env, (context, env) => {
|
|
196
|
+
* console.log("env changed", env);
|
|
164
197
|
* });
|
|
165
198
|
*
|
|
166
199
|
* // In JSX:
|
|
167
|
-
* {actions.stream(Lifecycle.
|
|
168
|
-
* <span>{
|
|
200
|
+
* {actions.stream(Lifecycle.Env, (env) => (
|
|
201
|
+
* <span>{env.locale}</span>
|
|
169
202
|
* ))}
|
|
170
203
|
* ```
|
|
171
204
|
*/
|
|
172
|
-
static
|
|
205
|
+
static Env: BroadcastPayload<Env, never, "Env">;
|
|
173
206
|
}
|
|
174
207
|
/**
|
|
175
208
|
* Distribution modes for actions.
|
|
@@ -179,21 +212,29 @@ export declare class Lifecycle {
|
|
|
179
212
|
* - **Broadcast** – Action is distributed to all mounted components that have
|
|
180
213
|
* defined a handler for it. Values are cached for late-mounting components.
|
|
181
214
|
* - **Multicast** – Action defines its own scope. Components reach it by
|
|
182
|
-
*
|
|
215
|
+
* rendering inside a `<scope.Boundary>` produced by `app.Scope<MulticastActions>()`.
|
|
183
216
|
*
|
|
184
217
|
* @example
|
|
185
218
|
* ```ts
|
|
186
|
-
* export class
|
|
187
|
-
* // The action itself acts as the scope identifier.
|
|
219
|
+
* export class MulticastActions {
|
|
188
220
|
* static Mood = Action<Mood>("Mood", Distribution.Multicast);
|
|
189
221
|
* }
|
|
190
222
|
*
|
|
223
|
+
* export const scope = app.Scope<typeof MulticastActions>();
|
|
224
|
+
*
|
|
191
225
|
* // Wrap the subtree where the scope applies.
|
|
192
|
-
* export default
|
|
226
|
+
* export default function Mood() {
|
|
227
|
+
* return (
|
|
228
|
+
* <scope.Boundary>
|
|
229
|
+
* <Happy />
|
|
230
|
+
* <Sad />
|
|
231
|
+
* </scope.Boundary>
|
|
232
|
+
* );
|
|
233
|
+
* }
|
|
193
234
|
*
|
|
194
235
|
* // Dispatch / subscribe — no extra options.
|
|
195
|
-
* actions.dispatch(
|
|
196
|
-
* actions.useAction(
|
|
236
|
+
* actions.dispatch(MulticastActions.Mood, mood);
|
|
237
|
+
* actions.useAction(MulticastActions.Mood, (context, mood) => { ... });
|
|
197
238
|
* ```
|
|
198
239
|
*/
|
|
199
240
|
export declare enum Distribution {
|
|
@@ -201,7 +242,7 @@ export declare enum Distribution {
|
|
|
201
242
|
Unicast = "unicast",
|
|
202
243
|
/** Action is broadcast to all mounted components and can be consumed. */
|
|
203
244
|
Broadcast = "broadcast",
|
|
204
|
-
/** Action is multicast to every component inside its
|
|
245
|
+
/** Action is multicast to every component inside its `<scope.Boundary>`. */
|
|
205
246
|
Multicast = "multicast"
|
|
206
247
|
}
|
|
207
248
|
/**
|
|
@@ -238,14 +279,14 @@ export declare enum Phase {
|
|
|
238
279
|
*/
|
|
239
280
|
export type Pk<T> = undefined | symbol | T;
|
|
240
281
|
/**
|
|
241
|
-
*
|
|
242
|
-
* `null` / `undefined` while loading, awaiting a fetch, or before
|
|
282
|
+
* Maybe-present field type — a value that may be a concrete `T`,
|
|
283
|
+
* or `null` / `undefined` while loading, awaiting a fetch, or before
|
|
243
284
|
* upstream data has arrived. Use this for model fields whose presence
|
|
244
285
|
* is determined by async or external state.
|
|
245
286
|
*
|
|
246
287
|
* @template T - The concrete value type
|
|
247
288
|
*/
|
|
248
|
-
export type
|
|
289
|
+
export type Maybe<T> = T | null | undefined;
|
|
249
290
|
/**
|
|
250
291
|
* Base constraint type for model state objects.
|
|
251
292
|
* Models must be plain objects with string keys.
|
|
@@ -315,7 +356,7 @@ export type ChanneledAction<P = unknown, C = unknown, Name extends string = stri
|
|
|
315
356
|
* to check whether a cached value exists before performing default fetches.
|
|
316
357
|
*
|
|
317
358
|
* This type extends `HandlerPayload<P, C>` with an additional brand to enforce at compile-time
|
|
318
|
-
* that only broadcast actions can be passed to `context.actions.
|
|
359
|
+
* that only broadcast actions can be passed to `context.actions.final()`.
|
|
319
360
|
*
|
|
320
361
|
* @template P - The payload type for the action
|
|
321
362
|
* @template C - The channel type for channeled dispatches (defaults to never)
|
|
@@ -325,7 +366,7 @@ export type ChanneledAction<P = unknown, C = unknown, Name extends string = stri
|
|
|
325
366
|
* const SignedOut = Action<User>("SignedOut", Distribution.Broadcast);
|
|
326
367
|
*
|
|
327
368
|
* // Resolve the latest value inside a handler
|
|
328
|
-
* const user = await context.actions.
|
|
369
|
+
* const user = await context.actions.final(SignedOut);
|
|
329
370
|
* ```
|
|
330
371
|
*/
|
|
331
372
|
export type BroadcastPayload<P = unknown, C extends Filter = never, Name extends string = string> = HandlerPayload<P, C, Name> & {
|
|
@@ -442,16 +483,19 @@ export type Filter = Record<string, string | number | bigint | boolean | symbol>
|
|
|
442
483
|
*/
|
|
443
484
|
export type ActionOrChanneled<A extends HandlerPayload = HandlerPayload> = A | ChanneledAction;
|
|
444
485
|
/**
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
*
|
|
451
|
-
*
|
|
486
|
+
* Type guard that produces a compile-time error if an async function is
|
|
487
|
+
* passed. Used to enforce synchronous callbacks in `produce()`.
|
|
488
|
+
*
|
|
489
|
+
* The `[F]` tuple wrapping prevents distribution over function unions, and
|
|
490
|
+
* checking the actual signature (`(...args: never[]) => Promise<unknown>`)
|
|
491
|
+
* sidesteps TypeScript's lenient `Promise<void>`→`void` assignability that
|
|
492
|
+
* would otherwise let an async recipe satisfy a `(draft) => void`
|
|
493
|
+
* constraint. Async F collapses the argument type to `never`, which no
|
|
494
|
+
* function value can satisfy.
|
|
495
|
+
*
|
|
452
496
|
* @internal
|
|
453
497
|
*/
|
|
454
|
-
type AssertSync<F> =
|
|
498
|
+
type AssertSync<F> = [F] extends [(...args: never[]) => Promise<unknown>] ? never : F;
|
|
455
499
|
/**
|
|
456
500
|
* Base type for data props passed to useActions.
|
|
457
501
|
* Represents any object that can be captured as reactive data.
|
|
@@ -469,29 +513,27 @@ export type Actions = object;
|
|
|
469
513
|
export type Result = {
|
|
470
514
|
processes: Set<Process>;
|
|
471
515
|
};
|
|
472
|
-
export type HandlerContext<M extends Model | void, AC extends Actions | void, D extends Props = Props> = {
|
|
516
|
+
export type HandlerContext<M extends Model | void, AC extends Actions | void, D extends Props = Props, S extends Env = Env> = {
|
|
473
517
|
readonly model: DeepReadonly<M>;
|
|
474
518
|
readonly phase: Phase;
|
|
475
519
|
readonly task: Task;
|
|
476
520
|
readonly data: DeepReadonly<D>;
|
|
477
521
|
readonly tasks: ReadonlySet<Task>;
|
|
478
|
-
readonly
|
|
522
|
+
readonly env: Readonly<S>;
|
|
479
523
|
readonly actions: {
|
|
480
524
|
produce<F extends (draft: {
|
|
481
525
|
model: M;
|
|
482
|
-
|
|
526
|
+
env: S;
|
|
483
527
|
readonly inspect: Readonly<Inspect<M>>;
|
|
484
528
|
}) => void>(ƒ: F & AssertSync<F>): void;
|
|
485
529
|
dispatch(action: NoPayloadActions<Dispatchable<AC>>): Promise<void>;
|
|
486
530
|
dispatch<A extends WithPayloadActions<Dispatchable<AC>>>(action: A, payload: Payload<A>): Promise<void>;
|
|
487
531
|
annotate<T>(value: T, operation?: Operation): T;
|
|
488
532
|
readonly inspect: Readonly<Inspect<M>>;
|
|
489
|
-
resource: (<T>(invocation: T | null) =>
|
|
490
|
-
readonly exceeds: (duration: Temporal.DurationLike) => Promise<T>;
|
|
491
|
-
}) & {
|
|
533
|
+
resource: (<T>(invocation: T | null) => ResourceCall<T>) & {
|
|
492
534
|
set<T>(invocation: T | null, data: T): void;
|
|
493
535
|
};
|
|
494
|
-
|
|
536
|
+
final<T>(action: BroadcastPayload<T> | MulticastPayload<T>): Promise<T | null>;
|
|
495
537
|
peek<T>(action: BroadcastPayload<T> | MulticastPayload<T>): T | null;
|
|
496
538
|
};
|
|
497
539
|
};
|
|
@@ -512,7 +554,7 @@ export type HandlerContext<M extends Model | void, AC extends Actions | void, D
|
|
|
512
554
|
* @example
|
|
513
555
|
* ```tsx
|
|
514
556
|
* const [model, actions, data] = useActions<Model, typeof Actions, Data>(
|
|
515
|
-
*
|
|
557
|
+
* model,
|
|
516
558
|
* () => ({ user, theme }),
|
|
517
559
|
* );
|
|
518
560
|
*
|
|
@@ -540,7 +582,7 @@ export type HandlerContext<M extends Model | void, AC extends Actions | void, D
|
|
|
540
582
|
*
|
|
541
583
|
* @see {@link Handlers} for the recommended HKT pattern
|
|
542
584
|
*/
|
|
543
|
-
export type Handler<M extends Model | void, AC extends Actions | void, K extends keyof AC & string, D extends Props = Props> = (context: HandlerContext<M, AC, D>, ...args: [Payload<AC[K] & HandlerPayload<unknown>>] extends [never] ? [] : [payload: Payload<AC[K] & HandlerPayload<unknown>>]) => void | Promise<void> | AsyncGenerator | Generator;
|
|
585
|
+
export type Handler<M extends Model | void, AC extends Actions | void, K extends keyof AC & string, D extends Props = Props, S extends Env = Env> = (context: HandlerContext<M, AC, D, S>, ...args: [Payload<AC[K] & HandlerPayload<unknown>>] extends [never] ? [] : [payload: Payload<AC[K] & HandlerPayload<unknown>>]) => void | Promise<void> | AsyncGenerator | Generator;
|
|
544
586
|
/**
|
|
545
587
|
* String keys of `AC` excluding inherited `prototype` from class constructors.
|
|
546
588
|
* When action containers are classes (`typeof MyActions`), TypeScript includes
|
|
@@ -557,7 +599,7 @@ type OwnKeys<AC> = Exclude<keyof AC & string, "prototype">;
|
|
|
557
599
|
*
|
|
558
600
|
* Used to constrain `dispatch` and `useAction` so that only actions owned by
|
|
559
601
|
* the component's `AC` (plus the global `Lifecycle.Fault` /
|
|
560
|
-
* `Lifecycle.
|
|
602
|
+
* `Lifecycle.Env`) can be referenced.
|
|
561
603
|
*/
|
|
562
604
|
export type LeafActions<AC> = AC extends void ? never : {
|
|
563
605
|
[K in OwnKeys<AC>]: OwnKeys<AC[K]> extends never ? AC[K] : LeafActions<AC[K]>;
|
|
@@ -576,10 +618,10 @@ export type ChanneledOf<A> = A extends HandlerPayload<infer P, infer C> ? [C] ex
|
|
|
576
618
|
export type Dispatchable<AC> = LeafActions<AC> | ChanneledOf<LeafActions<AC>>;
|
|
577
619
|
/**
|
|
578
620
|
* Everything `useAction` will subscribe to for a given `AC`: same as
|
|
579
|
-
* `Dispatchable<AC>` plus the shared `Lifecycle.Fault` and `Lifecycle.
|
|
621
|
+
* `Dispatchable<AC>` plus the shared `Lifecycle.Fault` and `Lifecycle.Env`
|
|
580
622
|
* broadcasts which live outside `AC` but are subscribable by any component.
|
|
581
623
|
*/
|
|
582
|
-
export type Subscribable<AC> = Dispatchable<AC> | typeof Lifecycle.Fault | typeof Lifecycle.
|
|
624
|
+
export type Subscribable<AC> = Dispatchable<AC> | typeof Lifecycle.Fault | typeof Lifecycle.Env;
|
|
583
625
|
/**
|
|
584
626
|
* Subset of a union of actions whose payload type is `never`. Used to split
|
|
585
627
|
* `dispatch`/`useAction` into a no-payload and a with-payload overload so
|
|
@@ -629,10 +671,10 @@ export type WithPayloadActions<U> = Exclude<U, {
|
|
|
629
671
|
* export const handlePaymentSent: H["Broadcast"]["PaymentSent"] = (context) => { ... };
|
|
630
672
|
* ```
|
|
631
673
|
*/
|
|
632
|
-
export type Handlers<M extends Model | void, AC extends Actions | void, D extends Props = Props, RootAC extends Actions | void = AC> = {
|
|
633
|
-
[K in OwnKeys<AC>]: OwnKeys<AC[K]> extends never ? (context: HandlerContext<M, RootAC, D>, ...args: [Payload<AC[K] & HandlerPayload<unknown>>] extends [never] ? [] : [payload: Payload<AC[K] & HandlerPayload<unknown>>]) => void | Promise<void> | AsyncGenerator | Generator : Handlers<M, AC[K] & Actions, D, RootAC>;
|
|
674
|
+
export type Handlers<M extends Model | void, AC extends Actions | void, D extends Props = Props, RootAC extends Actions | void = AC, S extends Env = Env> = {
|
|
675
|
+
[K in OwnKeys<AC>]: OwnKeys<AC[K]> extends never ? (context: HandlerContext<M, RootAC, D, S>, ...args: [Payload<AC[K] & HandlerPayload<unknown>>] extends [never] ? [] : [payload: Payload<AC[K] & HandlerPayload<unknown>>]) => void | Promise<void> | AsyncGenerator | Generator : Handlers<M, AC[K] & Actions, D, RootAC, S>;
|
|
634
676
|
};
|
|
635
|
-
export type UseActions<M extends Model | void, AC extends Actions | void, D extends Props = Props> = [
|
|
677
|
+
export type UseActions<M extends Model | void, AC extends Actions | void, D extends Props = Props, S extends Env = Env> = [
|
|
636
678
|
Readonly<M>,
|
|
637
679
|
{
|
|
638
680
|
/**
|
|
@@ -665,6 +707,7 @@ export type UseActions<M extends Model | void, AC extends Actions | void, D exte
|
|
|
665
707
|
* );
|
|
666
708
|
* ```
|
|
667
709
|
*/
|
|
710
|
+
stream(action: typeof Lifecycle.Env, renderer: (value: Readonly<S>, inspect: Inspect<S>) => React.ReactNode): React.ReactNode;
|
|
668
711
|
stream<T extends object>(action: BroadcastPayload<T>, renderer: (value: T, inspect: Inspect<T>) => React.ReactNode): React.ReactNode;
|
|
669
712
|
},
|
|
670
713
|
DeepReadonly<D>
|
|
@@ -705,8 +748,9 @@ export type UseActions<M extends Model | void, AC extends Actions | void, D exte
|
|
|
705
748
|
* });
|
|
706
749
|
* ```
|
|
707
750
|
*/
|
|
708
|
-
useAction(action: NoPayloadActions<Subscribable<AC>>, handler: (context: HandlerContext<M, AC, D>) => void | Promise<void> | AsyncGenerator | Generator): void;
|
|
709
|
-
useAction
|
|
751
|
+
useAction(action: NoPayloadActions<Subscribable<AC>>, handler: (context: HandlerContext<M, AC, D, S>) => void | Promise<void> | AsyncGenerator | Generator): void;
|
|
752
|
+
useAction(action: typeof Lifecycle.Env, handler: (context: HandlerContext<M, AC, D, S>, env: Readonly<S>) => void | Promise<void> | AsyncGenerator | Generator): void;
|
|
753
|
+
useAction<A extends WithPayloadActions<Subscribable<AC>>>(action: A, handler: (context: HandlerContext<M, AC, D, S>, payload: Payload<A>) => void | Promise<void> | AsyncGenerator | Generator): void;
|
|
710
754
|
};
|
|
711
755
|
/**
|
|
712
756
|
* Stable, typed dispatch function for the actions class `AC`. Same call
|
|
@@ -729,10 +773,16 @@ export type Dispatch<AC extends Actions | void> = {
|
|
|
729
773
|
* `React.Context` — it's the March Hare action surface returned by
|
|
730
774
|
* the `useContext` hook of this library.
|
|
731
775
|
*/
|
|
732
|
-
export type Context<M extends Model | void, AC extends Actions | void, D extends Props = Props> = {
|
|
776
|
+
export type Context<M extends Model | void, AC extends Actions | void, D extends Props = Props, S extends Env = Env> = {
|
|
733
777
|
readonly actions: {
|
|
734
778
|
dispatch: Dispatch<AC>;
|
|
735
779
|
};
|
|
736
|
-
|
|
737
|
-
|
|
780
|
+
/**
|
|
781
|
+
* Typed bag of handler factories bound to `M`. Methods accept lodash-style
|
|
782
|
+
* dotted paths with array indices (e.g. `"a.b.c"`, `"items.0.id"`) and
|
|
783
|
+
* autocomplete from the model. See {@link WithHandle}.
|
|
784
|
+
*/
|
|
785
|
+
readonly with: WithHandle<M>;
|
|
786
|
+
useActions(getData?: () => D): UseActions<M, AC, D, S>;
|
|
787
|
+
useActions(model: M extends void ? never : M, getData?: () => D): UseActions<M, AC, D, S>;
|
|
738
788
|
};
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -3,15 +3,15 @@ export { unset } from './utils';
|
|
|
3
3
|
export type { Stored, Unset } from './types';
|
|
4
4
|
/**
|
|
5
5
|
* Returns a promise that resolves after the specified number of
|
|
6
|
-
* milliseconds, or rejects with an {@link
|
|
7
|
-
*
|
|
6
|
+
* milliseconds, or rejects with an {@link Aborted} when the signal is aborted. Use to inject a cancellable
|
|
7
|
+
* delay into an action handler.
|
|
8
8
|
*
|
|
9
9
|
* @param ms How long to wait before resolving.
|
|
10
10
|
* @param signal Optional {@link AbortSignal} that cancels the sleep early.
|
|
11
11
|
* Pass `context.task.controller.signal` to tie the wait to
|
|
12
12
|
* the lifetime of the current action.
|
|
13
13
|
* @returns A promise that resolves after `ms` milliseconds or rejects with
|
|
14
|
-
* an {@link
|
|
14
|
+
* an {@link Aborted} if `signal` aborts first.
|
|
15
15
|
*/
|
|
16
16
|
export declare function sleep(ms: number, signal: AbortSignal | undefined): Promise<void>;
|
|
17
17
|
/**
|
|
@@ -21,12 +21,13 @@ export declare function sleep(ms: number, signal: AbortSignal | undefined): Prom
|
|
|
21
21
|
*
|
|
22
22
|
* @param ms Interval in milliseconds between invocations of `fn`.
|
|
23
23
|
* @param signal Optional {@link AbortSignal} that cancels polling early.
|
|
24
|
-
* Aborts propagate as an {@link
|
|
24
|
+
* Aborts propagate as an {@link Aborted} rejection.
|
|
25
25
|
* @param fn Predicate invoked each iteration. Return `true` to stop
|
|
26
26
|
* polling, `false` to schedule another invocation after `ms`.
|
|
27
27
|
* May be sync or async.
|
|
28
28
|
* @returns A promise that resolves when `fn` returns `true`, or rejects
|
|
29
|
-
* with
|
|
29
|
+
* with a `DOMException("aborted", "Aborted")` if `signal`
|
|
30
|
+
* aborts first.
|
|
30
31
|
*/
|
|
31
32
|
export declare function poll(ms: number, signal: AbortSignal | undefined, fn: () => boolean | Promise<boolean>): Promise<void>;
|
|
32
33
|
/**
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Actions, HandlerContext, Model, Props } from '../types/index';
|
|
2
|
+
import { Env } from '../boundary/components/env/index';
|
|
3
|
+
type Primitive = string | number | bigint | boolean | symbol | null | undefined;
|
|
4
|
+
type Depth = [never, 0, 1, 2, 3, 4, 5];
|
|
5
|
+
/**
|
|
6
|
+
* Lodash-style dotted paths reachable from `T`. Yields `"a"`, `"a.b"`,
|
|
7
|
+
* `"items.0"`, `"items.0.name"`, etc. Recursion is capped at depth 5 to
|
|
8
|
+
* keep the type-checker tractable on deeply nested models.
|
|
9
|
+
*
|
|
10
|
+
* @template T The object type to enumerate paths from.
|
|
11
|
+
* @template D Recursion budget (internal).
|
|
12
|
+
*/
|
|
13
|
+
export type Paths<T, D extends number = 5> = [D] extends [never] ? never : T extends Primitive ? never : T extends ReadonlyArray<infer U> ? `${number}` | (U extends Primitive ? never : `${number}.${Paths<U, Depth[D]>}`) : T extends object ? {
|
|
14
|
+
[K in Extract<keyof T, string>]: T[K] extends Primitive ? K : K | `${K}.${Paths<T[K], Depth[D]>}`;
|
|
15
|
+
}[Extract<keyof T, string>] : never;
|
|
16
|
+
/**
|
|
17
|
+
* Subset of {@link Paths} whose leaf type is `boolean`. Used by
|
|
18
|
+
* `context.with.invert` (and the legacy {@link With.Invert}) to restrict the
|
|
19
|
+
* key to togglable fields only.
|
|
20
|
+
*
|
|
21
|
+
* @template T The object type to enumerate boolean leaves from.
|
|
22
|
+
* @template D Recursion budget (internal).
|
|
23
|
+
*/
|
|
24
|
+
export type BooleanPaths<T, D extends number = 5> = [D] extends [never] ? never : T extends Primitive ? never : T extends ReadonlyArray<infer U> ? (U extends boolean ? `${number}` : never) | (U extends Primitive ? never : `${number}.${BooleanPaths<U, Depth[D]>}`) : T extends object ? {
|
|
25
|
+
[K in Extract<keyof T, string>]: T[K] extends boolean ? K : T[K] extends Primitive ? never : `${K}.${BooleanPaths<T[K], Depth[D]>}`;
|
|
26
|
+
}[Extract<keyof T, string>] : never;
|
|
27
|
+
/**
|
|
28
|
+
* Resolves the leaf type at a dotted path on `T`. `Get<{a:{b:number}},"a.b">`
|
|
29
|
+
* is `number`; `Get<{items: string[]},"items.0">` is `string`.
|
|
30
|
+
*
|
|
31
|
+
* @template T The object type to walk.
|
|
32
|
+
* @template P The dotted path string.
|
|
33
|
+
*/
|
|
34
|
+
export type Get<T, P extends string> = P extends `${infer Head}.${infer Tail}` ? T extends ReadonlyArray<infer U> ? Head extends `${number}` ? Get<U, Tail> : never : Head extends keyof T ? Get<T[Head], Tail> : never : T extends ReadonlyArray<infer U> ? P extends `${number}` ? U : never : P extends keyof T ? T[P] : never;
|
|
35
|
+
/**
|
|
36
|
+
* Returned by `context.with` — a typed bag of handler factories
|
|
37
|
+
* bound to the model `M` declared in `useContext<M, …>()`. Methods accept
|
|
38
|
+
* lodash-style dotted paths (`"a.b.c"`) with array indices (`"items.0.id"`).
|
|
39
|
+
*
|
|
40
|
+
* - `update(key)` — assigns the dispatched payload to `model[key]`.
|
|
41
|
+
* - `invert(key)` — flips a boolean leaf at `model[key]`.
|
|
42
|
+
* - `always(key, value)` — assigns a fixed `value` to `model[key]`,
|
|
43
|
+
* ignoring any dispatched payload.
|
|
44
|
+
*
|
|
45
|
+
* @template M The model type to bind keys against.
|
|
46
|
+
*/
|
|
47
|
+
export type WithHandle<M> = M extends Model ? {
|
|
48
|
+
update<K extends Paths<M>>(key: K): <A extends Actions | void, D extends Props, S extends Env = Env>(context: HandlerContext<M, A, D, S>, payload: Get<M, K>) => void;
|
|
49
|
+
invert<K extends BooleanPaths<M>>(key: K): <A extends Actions | void, D extends Props, S extends Env = Env>(context: HandlerContext<M, A, D, S>) => void;
|
|
50
|
+
always<K extends Paths<M>>(key: K, value: Get<M, K>): <A extends Actions | void, D extends Props, S extends Env = Env>(context: HandlerContext<M, A, D, S>) => void;
|
|
51
|
+
} : Record<string, never>;
|
|
52
|
+
/**
|
|
53
|
+
* Builds the {@link WithHandle} object returned via `context.with`. The
|
|
54
|
+
* runtime is identical for any model — only the call-site types differ.
|
|
55
|
+
*
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
export declare function bindWith<M extends Model | void>(): WithHandle<M>;
|
|
59
|
+
/**
|
|
60
|
+
* Handler factories that wire an action directly to a model field. Prefer
|
|
61
|
+
* `context.with` from `useContext<Model>()` for first-class autocompletion
|
|
62
|
+
* over dotted paths; this top-level form is kept for callers that don't have
|
|
63
|
+
* a typed `context` in scope.
|
|
64
|
+
*
|
|
65
|
+
* - {@link With.Update} assigns the dispatched payload to a model path.
|
|
66
|
+
* - {@link With.Invert} flips a boolean leaf at a model path.
|
|
67
|
+
* - {@link With.Always} assigns a fixed value to a model path, ignoring any
|
|
68
|
+
* dispatched payload.
|
|
69
|
+
*
|
|
70
|
+
* Keys may be lodash-style dotted paths (`"a.b.c"`) and support array
|
|
71
|
+
* indices (`"items.0.name"`). The model type is inferred at handler-bind
|
|
72
|
+
* time; an invalid path or mismatched payload fails to compile when the
|
|
73
|
+
* handler is registered with `useAction`.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* import { With } from "march-hare";
|
|
78
|
+
*
|
|
79
|
+
* type Model = {
|
|
80
|
+
* name: string;
|
|
81
|
+
* sidebar: boolean;
|
|
82
|
+
* nested: { open: boolean };
|
|
83
|
+
* items: { id: number }[];
|
|
84
|
+
* };
|
|
85
|
+
*
|
|
86
|
+
* actions.useAction(Actions.SetName, With.Update("name"));
|
|
87
|
+
* actions.useAction(Actions.SetFirstId, With.Update("items.0.id"));
|
|
88
|
+
* actions.useAction(Actions.ToggleSidebar, With.Invert("sidebar"));
|
|
89
|
+
* actions.useAction(Actions.ToggleNested, With.Invert("nested.open"));
|
|
90
|
+
* actions.useAction(Actions.Open, With.Always("nested.open", true));
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export declare const With: {
|
|
94
|
+
/**
|
|
95
|
+
* Returns a handler that assigns the action payload to the model leaf at
|
|
96
|
+
* the given lodash-style path. The payload type must match `Get<M, K>`,
|
|
97
|
+
* and the path must exist on the model.
|
|
98
|
+
*/
|
|
99
|
+
Update<K extends string>(key: K): <M extends Model, A extends Actions | void, D extends Props, P extends K extends Paths<M> ? Get<M, K> : never, S extends Env = Env>(context: HandlerContext<M, A, D, S>, payload: P) => void;
|
|
100
|
+
/**
|
|
101
|
+
* Returns a handler that inverts a boolean leaf at the given lodash-style
|
|
102
|
+
* path. The leaf must be a `boolean` on the model.
|
|
103
|
+
*/
|
|
104
|
+
Invert<K extends string>(key: K): <M extends Model, A extends Actions | void, D extends Props, S extends Env = Env>(context: K extends BooleanPaths<M> ? HandlerContext<M, A, D, S> : never) => void;
|
|
105
|
+
/**
|
|
106
|
+
* Returns a handler that assigns a fixed `value` to the model leaf at the
|
|
107
|
+
* given lodash-style path. The dispatched payload (if any) is ignored.
|
|
108
|
+
*/
|
|
109
|
+
Always<K extends string, V>(key: K, value: V): <M extends Model, A extends Actions | void, D extends Props, S extends Env = Env>(context: K extends Paths<M> ? V extends Get<M, K> ? HandlerContext<M, A, D, S> : never : never) => void;
|
|
110
|
+
};
|
|
111
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { Props } from './types';
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
export { useStore } from './utils';
|
|
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()` — 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;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { ReactNode } from 'react';
|
|
2
|
-
import { Store } from './index';
|
|
3
|
-
export type { Store } from './index';
|
|
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
|
-
};
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { RefObject } from 'react';
|
|
2
|
-
import { Store } from './index';
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
/**
|
|
5
|
-
* React context exposing the per-Boundary Store ref. The ref itself is
|
|
6
|
-
* stable across renders — 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 — 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
|
-
* — 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 — 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>;
|