march-hare 0.13.11 → 0.14.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.
@@ -2,7 +2,7 @@ import { ResourceHandle } from './types.js';
2
2
  import { Cache } from './utils.js';
3
3
  import { AppFetcher } from '../app/types.js';
4
4
  import { Env } from '../boundary/components/env/types.js';
5
- export type { Coalesce, Fetcher, Invocation, ResourceHandle } from './types.js';
5
+ export type { Fetcher, Invocation, ResourceHandle } from './types.js';
6
6
  /**
7
7
  * Evicts cache entries across every Resource constructed in the
8
8
  * current process. Resources register themselves on declaration, so
@@ -44,9 +44,13 @@ export declare function nuke(where?: object): void;
44
44
  * Standalone `shared.Resource` declarations always use an in-memory
45
45
  * cache — reach for `app.Resource` when persistence is required.
46
46
  *
47
- * Concurrent calls fire fresh requests by default. Opt in to in-flight
48
- * sharing per call via `.coalesce(key)` on the thenable returned from
49
- * `context.actions.resource(...)`.
47
+ * Concurrent calls with the same `(Resource, params)` share a single
48
+ * in-flight fetch by default — one network request, every caller
49
+ * resolves with the same payload. The underlying work is refcounted: if
50
+ * every caller aborts, the shared `AbortController` is aborted too.
51
+ * Chain `.isolated()` on the thenable returned from
52
+ * `context.actions.resource(...)` to opt out (own controller, own
53
+ * request) for the rare cases that need it.
50
54
  *
51
55
  * @template E The Env shape (or union) the fetcher's `context.env` is
52
56
  * typed against.
@@ -40,14 +40,6 @@ export type Args<P extends object = Record<never, never>> = {
40
40
  * and a broadcast/multicast-only `dispatch`.
41
41
  */
42
42
  export type Fetcher<T, P extends object = Record<never, never>> = (context: Args<P>) => Promise<T>;
43
- /**
44
- * Per-call coalescing token. Two callers with the same Resource, same
45
- * structural params, and equal `Coalesce` value share a single in-flight
46
- * promise; different tokens (or different params) fire independent
47
- * fetches. Primitives compose naturally via stringification; objects
48
- * are serialised with `JSON.stringify`.
49
- */
50
- export type Coalesce = string | number | bigint | boolean | symbol | object;
51
43
  /**
52
44
  * Config form accepted by `Resource`. The fetcher shorthand
53
45
  * `Resource(fetcher)` is equivalent to `Resource({ fetch: fetcher })`.
@@ -2,7 +2,7 @@ import { Operation, Process, Inspect as ImmInspect, Box } from 'immertation';
2
2
  import { ActionId, Task, Tasks } from '../boundary/components/tasks/types.js';
3
3
  import { Fault } from '../error/types.js';
4
4
  import { Env } from '../boundary/components/env/types.js';
5
- import { Coalesce, Invocation } from '../resource/types.js';
5
+ import { Invocation } from '../resource/types.js';
6
6
  import { WithHandle } from '../with/types.js';
7
7
  import * as React from "react";
8
8
  /**
@@ -26,25 +26,22 @@ type ValueAt<T, K extends PropertyKey> = T extends unknown ? K extends keyof T ?
26
26
  export type Inspect<T, D extends number = 8> = ImmInspect<T> & ([D] extends [0] ? object : {
27
27
  [K in UnionKeys<T> as ValueAt<T, K> extends (...args: unknown[]) => unknown ? never : K]: Inspect<ValueAt<T, K>, DepthLimiter[D]>;
28
28
  });
29
- /**
30
- * Chainable handle returned from `context.actions.resource(invocation)`.
31
- *
32
- * - `.exceeds(duration)` short-circuits the fetch when the per-params
33
- * cache age is within the supplied freshness window.
34
- * - `.coalesce(token)` opts the call into in-flight sharing: any other
35
- * caller with the same Resource, same structural params, and equal
36
- * `token` joins the same promise.
37
- *
38
- * Awaiting the handle (`await context.actions.resource(...)`) triggers
39
- * the fetch with whichever options have been set on the chain.
40
- */
41
29
  /**
42
30
  * Fetch-configured chain returned from `.exceeds(...)` and
43
- * `.coalesce(...)`. Awaiting the chain runs the fetch with whichever
31
+ * `.isolated()`. Awaiting the chain runs the fetch with whichever
44
32
  * options are set; `.evict()` is intentionally absent because the
45
33
  * "configured a fetch then evicted instead" sequence has no coherent
46
34
  * meaning &mdash; eviction is always available off the bare
47
35
  * `context.actions.resource(...)` call.
36
+ *
37
+ * Concurrent callers with the same `(Resource, params)` automatically
38
+ * share a single in-flight fetch &mdash; one network request, every
39
+ * caller resolves with the same payload. The shared fetch runs on a
40
+ * detached `AbortController` so one caller's abort never cancels work
41
+ * other callers are still waiting on; when every caller has released
42
+ * (their `context.task.controller` aborted) the shared controller is
43
+ * aborted too. Chain `.isolated()` to opt out for the rare case that
44
+ * needs an independent request.
48
45
  */
49
46
  export type ResourceFetch<T> = PromiseLike<T> & {
50
47
  /**
@@ -54,22 +51,25 @@ export type ResourceFetch<T> = PromiseLike<T> & {
54
51
  */
55
52
  readonly exceeds: (duration: Temporal.DurationLike) => ResourceFetch<T>;
56
53
  /**
57
- * Join an in-flight fetch for the same `(resource, params, token)`
58
- * tuple. The shared fetch runs against a detached `AbortController`
59
- * so a single caller's abort never cancels work other callers are
60
- * waiting on; each caller still sees its own `context.task.controller`
61
- * abort as a rejection of its personal await.
54
+ * Opt this call out of the default `(Resource, params)` coalesce
55
+ * path. The fetch fires as an independent network request against
56
+ * the caller's own `context.task.controller` &mdash; no joining of
57
+ * any in-flight fetch, no refcounted detached controller. Aborting
58
+ * the caller's task cancels the network exactly as a regular fetch
59
+ * would.
62
60
  *
63
- * `token` is optional &mdash; omit it to share with every other
64
- * untokened caller for the same `(resource, params)` slot.
61
+ * Reach for this only when two callers need parallel fetches with
62
+ * byte-identical params and the difference in intent genuinely can't
63
+ * be modelled by differing params. The default is almost always
64
+ * what you want.
65
65
  */
66
- readonly coalesce: (token?: Coalesce) => ResourceFetch<T>;
66
+ readonly isolated: () => ResourceFetch<T>;
67
67
  };
68
68
  /**
69
69
  * Chainable handle returned from `context.actions.resource(invocation)`.
70
- * Either resolve to the fetched value (`.exceeds`/`.coalesce` + await)
70
+ * Either resolve to the fetched value (`.exceeds`/`.isolated` + await)
71
71
  * or drop the cache slot (`.evict`) &mdash; the two paths are mutually
72
- * exclusive, so once `.exceeds` or `.coalesce` runs the chain narrows
72
+ * exclusive, so once `.exceeds` or `.isolated` runs the chain narrows
73
73
  * to {@link ResourceFetch} and `.evict` is no longer available.
74
74
  */
75
75
  export type ResourceCall<T> = ResourceFetch<T> & {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "march-hare",
3
- "version": "0.13.11",
3
+ "version": "0.14.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "packageManager": "yarn@1.22.22",