march-hare 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,24 @@
1
1
  import { Env } from '../boundary/components/env/types.js';
2
2
  import { Cache } from '../cache/index.js';
3
- import { BroadcastPayload, MulticastPayload, Filter } from '../types/index.js';
3
+ import { BroadcastPayload, MulticastPayload, ChanneledAction, Filter } from '../types/index.js';
4
+ /**
5
+ * Helper that constrains `Partial<P>` to a {@link Filter}-compatible
6
+ * shape. Resource params hold primitive leaves by convention, so the
7
+ * auto-broadcast {@link ResourceHandle.action} can use them directly as
8
+ * a channel filter.
9
+ */
10
+ export type ActionChannel<P extends object> = Partial<P> & Filter;
4
11
  /**
5
12
  * Dispatch surface exposed on a Resource fetcher's `context`. Restricted
6
13
  * to broadcast and multicast actions &mdash; unicast targets the calling
7
- * component, which a Resource fetcher does not have.
14
+ * component, which a Resource fetcher does not have. Channeled
15
+ * dispatches (e.g. `dispatch(action({ UserId: 5 }), payload)`) are
16
+ * accepted on the same overloads &mdash; the routing layer reads the
17
+ * action's underlying distribution off its symbol.
8
18
  */
9
19
  export type Dispatch = {
10
- <C extends Filter = never>(action: BroadcastPayload<never, C> | MulticastPayload<never, C>): Promise<void>;
11
- <P, C extends Filter = never>(action: BroadcastPayload<P, C> | MulticastPayload<P, C>, payload: P): Promise<void>;
20
+ <C extends Filter = never>(action: BroadcastPayload<never, C> | MulticastPayload<never, C> | ChanneledAction<never, C>): Promise<void>;
21
+ <P, C extends Filter = never>(action: BroadcastPayload<P, C> | MulticastPayload<P, C> | ChanneledAction<P, C>, payload: P): Promise<void>;
12
22
  };
13
23
  /**
14
24
  * `context` object passed to every {@link Fetcher}.
@@ -107,6 +117,31 @@ export type ResourceHandle<T, P extends object = Record<never, never>> = ([
107
117
  keyof P
108
118
  ] extends [never] ? (params?: P) => Invocation<T, P> : (params: P) => Invocation<T, P>) & {
109
119
  readonly get: [keyof P] extends [never] ? (params?: P) => T | null : (params: P) => T | null;
120
+ /**
121
+ * Broadcast channeled action fired automatically after every successful
122
+ * fetch of this Resource. Always invoke before subscribing: pass no
123
+ * arguments to match every fetch, or a partial-params object to narrow.
124
+ * Matching follows the subscriber-as-filter rule &mdash; every key on the
125
+ * subscriber's channel must equal the same key on the dispatch channel,
126
+ * and adding keys progressively narrows the filter.
127
+ *
128
+ * Late-mounting subscribers replay every cached entry whose channel
129
+ * satisfies their filter &mdash; the broadcast cache is sharded by
130
+ * `(action, channel)`, so a partial subscriber that mounts after
131
+ * several distinct fetches catches up with all of them rather than
132
+ * just the most recent.
133
+ *
134
+ * Failures do not broadcast &mdash; the cache is only written on
135
+ * success (see resource/utils.ts), and the broadcast follows the same
136
+ * gate.
137
+ *
138
+ * ```ts
139
+ * actions.useAction(user.action(), (context, value) => { ... });
140
+ * actions.useAction(user.action({ id: 5 }), (context, value) => { ... });
141
+ * actions.stream(user.action({ id: 5 }), (value) => <span>{value.name}</span>);
142
+ * ```
143
+ */
144
+ readonly action: (channel?: ActionChannel<P>) => ChanneledAction<T, ActionChannel<P>>;
110
145
  };
111
146
  /**
112
147
  * Drops cache slots whose stored params match the supplied `where`
@@ -1,5 +1,4 @@
1
1
  import { Cache } from '../cache/index.js';
2
- import { unset } from '../utils/utils.js';
3
2
  import { Env } from '../boundary/components/env/types.js';
4
3
  import { Fetcher, ResourceEvictor, ResourceHandle } from './types.js';
5
4
  export { Cache } from '../cache/index.js';
@@ -29,14 +28,6 @@ export declare function defaultCache(fetcher: object): Cache;
29
28
  * @internal
30
29
  */
31
30
  export declare function key(params: object): string;
32
- /**
33
- * Re-export of the shared `unset` sentinel from {@link "../utils/index.ts"}.
34
- *
35
- * @internal
36
- */
37
- export declare const config: {
38
- readonly unset: typeof unset;
39
- };
40
31
  /**
41
32
  * Per-Resource eviction callbacks. Each `Resource` declaration registers
42
33
  * one entry on construction; the public `nuke(...)` (defined in
@@ -550,20 +550,22 @@ export type Payload<A> = A extends {
550
550
  * By convention, use uppercase keys (e.g., `{UserId: 4}` not `{userId: 4}`)
551
551
  * to distinguish filter keys from payload properties.
552
552
  *
553
- * When dispatching, handlers are invoked if ALL properties in the dispatch filter
554
- * match the corresponding properties in the registered filter.
553
+ * **Matching direction:** the subscriber's filter is the constraint;
554
+ * every key the subscriber supplies must be present and equal on the
555
+ * dispatch channel. Extra keys on the dispatch channel are ignored, so a
556
+ * dispatcher is free to be more specific than any single subscriber
557
+ * needs. A subscriber that supplies no keys (uncalled action or empty
558
+ * filter) matches every dispatch.
555
559
  *
556
560
  * @example
557
561
  * ```ts
558
- * // Register a handler for a specific user
559
- * actions.useAction([Actions.User, { UserId: 1 }], handler);
560
- *
561
- * // Dispatch matches if all dispatch properties match registered properties
562
- * dispatch([Actions.User, { UserId: 1 }], payload); // Matches
563
- * dispatch([Actions.User, { UserId: 2 }], payload); // No match
564
- * dispatch([Actions.User, { UserId: 1, Role: "admin" }], payload); // Matches
565
- * dispatch([Actions.User, {}], payload); // Matches all
566
- * dispatch(Actions.User, payload); // Matches ALL handlers
562
+ * actions.useAction(Actions.User({ UserId: 1 }), handler);
563
+ *
564
+ * actions.dispatch(Actions.User({ UserId: 1 }), payload); // Matches (exact)
565
+ * actions.dispatch(Actions.User({ UserId: 2 }), payload); // No match (UserId mismatch)
566
+ * actions.dispatch(Actions.User({ UserId: 1, Role: "admin" }), payload); // Matches (extra dispatch keys ignored)
567
+ * actions.dispatch(Actions.User({}), payload); // No match (subscriber asked for UserId)
568
+ * actions.dispatch(Actions.User, payload); // Matches ALL handlers (uncalled bypasses channel filtering)
567
569
  * ```
568
570
  */
569
571
  export type Filter = Record<string, string | number | bigint | boolean | symbol>;
@@ -5,6 +5,23 @@ import { Stored } from './types.js';
5
5
  * recorded" from "a legitimately stored null".
6
6
  */
7
7
  export declare const unset: unique symbol;
8
+ /**
9
+ * Internal configuration shared across the Resource and Cache layers.
10
+ *
11
+ * - `unset` &mdash; re-export of the shared sentinel so consumers can pull
12
+ * both fields from a single object.
13
+ * - `storageNamespace` &mdash; global prefix the Cache layer prepends to
14
+ * every key it persists. Lets `Cache.clear()` and the partial-match
15
+ * evictors scope themselves to the library's own entries on shared
16
+ * backends (`localStorage`, `chrome.storage.local`, MMKV) without
17
+ * nuking third-party state on the same origin.
18
+ *
19
+ * @internal
20
+ */
21
+ export declare const config: {
22
+ readonly unset: typeof unset;
23
+ readonly storageNamespace: "mh:";
24
+ };
8
25
  /**
9
26
  * Returns a function to force a component re-render. Useful when state is
10
27
  * managed externally (e.g., refs) but the UI needs updating.
package/dist/utils.d.ts CHANGED
@@ -17,8 +17,6 @@ export declare const describe: {
17
17
  cache: (name?: string) => string;
18
18
  /** Lifecycle action description. `describe.lifecycle("Mount")` &rarr; `"march-hare.action.lifecycle/Mount"` */
19
19
  lifecycle: (name?: string) => string;
20
- /** Mount replay sentinel description. Used to create the {@link replay} symbol. */
21
- replay: (name?: string) => string;
22
20
  };
23
21
  /**
24
22
  * Flat record used for shallow property comparison in {@link changes}.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "march-hare",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "packageManager": "yarn@1.22.22",