@reactra/resource 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +17 -0
- package/dist/cache.d.ts +58 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +167 -0
- package/dist/cache.js.map +1 -0
- package/dist/index.d.ts +350 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +956 -0
- package/dist/index.js.map +1 -0
- package/package.json +36 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Akhil Shastri and the Reactra contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @reactra/resource
|
|
2
|
+
|
|
3
|
+
> Reactra resource runtime — declarative async data (Suspense, swr/cache/retry) for Reactra apps.
|
|
4
|
+
|
|
5
|
+
**Alpha** — published under the npm dist-tag `alpha`. APIs may change before 1.0.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @reactra/resource@alpha
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Part of [Reactra](https://github.com/akhilshastri/reactra) — a compiler-first,
|
|
12
|
+
React-19-compatible framework. See the [documentation](https://reactra-docs.vercel.app) to get
|
|
13
|
+
started.
|
|
14
|
+
|
|
15
|
+
## License
|
|
16
|
+
|
|
17
|
+
MIT
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A settled cache entry. A1 only stores resolved values; `error` is reserved
|
|
3
|
+
* for B/D stages that may cache rejections under explicit retry/SWR semantics.
|
|
4
|
+
* `controller` lets the cache abort an in-flight fetch on supersede / clear;
|
|
5
|
+
* A1 entries are always settled-resolved at write time, so the controller is
|
|
6
|
+
* effectively post-hoc (kept on the entry so future stages can compose).
|
|
7
|
+
*/
|
|
8
|
+
export interface ResourceCacheEntry<T = unknown> {
|
|
9
|
+
readonly value: T | undefined;
|
|
10
|
+
readonly error: unknown;
|
|
11
|
+
readonly promise: Promise<T | undefined>;
|
|
12
|
+
readonly controller: AbortController;
|
|
13
|
+
readonly writtenAt: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The cache API every implementation must satisfy. Pluggable via
|
|
17
|
+
* `configureResources({ cache })` — a custom backend (sessionStorage, IndexedDB,
|
|
18
|
+
* remote KV) can drop in by implementing this shape.
|
|
19
|
+
*/
|
|
20
|
+
export interface ResourceCache {
|
|
21
|
+
get<T = unknown>(resourceName: string, depsKey: string): ResourceCacheEntry<T> | undefined;
|
|
22
|
+
set<T = unknown>(resourceName: string, depsKey: string, entry: ResourceCacheEntry<T>): void;
|
|
23
|
+
/**
|
|
24
|
+
* Drop every entry whose `resourceName` matches. A1: eviction only — mounted
|
|
25
|
+
* consumers re-fetch on their next natural render (subscriber notify fan-out
|
|
26
|
+
* lands with A2/D when useMutation needs immediate refetch).
|
|
27
|
+
*/
|
|
28
|
+
invalidate(resourceName: string | readonly string[]): void;
|
|
29
|
+
/**
|
|
30
|
+
* Remove a single entry by `(resourceName, depsKey)`. Aborts its controller
|
|
31
|
+
* and notifies subscribers. A silent no-op if the entry is not present. Added
|
|
32
|
+
* in Stage A2 so `useResource` can withdraw an in-flight tombstone when `fn`
|
|
33
|
+
* rejects — matching the A1 "do not cache rejections" rule for settled writes.
|
|
34
|
+
*/
|
|
35
|
+
delete(resourceName: string, depsKey: string): void;
|
|
36
|
+
/**
|
|
37
|
+
* Stage D — returns true iff `resourceName` has ever been the target of a
|
|
38
|
+
* successful `set()` on this cache. Used by `useMutation`'s post-success
|
|
39
|
+
* invalidate path to fire **RES014** when `opts.invalidate` lists a name
|
|
40
|
+
* that no consumer has fetched (typo-likely). Names are added on `set` and
|
|
41
|
+
* NEVER removed — `clear` / `invalidate` only evict entries, not name
|
|
42
|
+
* registrations (the heuristic is "ever known", not "currently present").
|
|
43
|
+
*/
|
|
44
|
+
hasName(resourceName: string): boolean;
|
|
45
|
+
subscribe(resourceName: string, depsKey: string, notify: () => void): () => void;
|
|
46
|
+
/** Drop every entry (or every entry for one resourceName). Aborts each controller. */
|
|
47
|
+
clear(resourceName?: string): void;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The default LRU implementation. `Map` preserves insertion order, so a
|
|
51
|
+
* `delete + set` on read bumps the entry to most-recent; oldest entries (front
|
|
52
|
+
* of the iterator) evict past `maxEntries`. Each entry's `AbortController` is
|
|
53
|
+
* aborted on supersede / eviction / clear / invalidate. Subscribers are keyed
|
|
54
|
+
* identically; `notify(key)` fans out to all of them on every mutation that
|
|
55
|
+
* touches that key.
|
|
56
|
+
*/
|
|
57
|
+
export declare const createLRUCache: (maxEntries?: number) => ResourceCache;
|
|
58
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAkBA;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB,CAAC,CAAC,GAAG,OAAO;IAC7C,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,SAAS,CAAA;IAC7B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAA;IACxC,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAA;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,kBAAkB,CAAC,CAAC,CAAC,GAAG,SAAS,CAAA;IAC1F,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAA;IAC3F;;;;OAIG;IACH,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,IAAI,CAAA;IAC1D;;;;;OAKG;IACH,MAAM,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACnD;;;;;;;OAOG;IACH,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAA;IACtC,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;IAChF,sFAAsF;IACtF,KAAK,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACnC;AAQD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,mBAAe,KAAG,aAgIhD,CAAA"}
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// @reactra/resource — ResourceCache (Resource v1.0 §6)
|
|
2
|
+
//
|
|
3
|
+
// Stage A1 surface: process-wide LRU singleton + a typed `ResourceCache`
|
|
4
|
+
// interface that custom implementations conform to. `createLRUCache(maxEntries)`
|
|
5
|
+
// builds the default; `configureResources({ cache })` (in `index.ts`) swaps the
|
|
6
|
+
// active instance at app boot. Entries are stored only on SUCCESSFUL `fn`
|
|
7
|
+
// resolution — A1 doesn't cache rejections (a failed fn naturally retries on
|
|
8
|
+
// next mount, matching v0 behaviour). Subscriber notification fan-out is wired
|
|
9
|
+
// (`subscribe` / notify on `set`/`invalidate`/`clear`) but Stage A1's
|
|
10
|
+
// `useResource` doesn't subscribe yet — mounted consumers detect cache changes
|
|
11
|
+
// on next natural render. A2 (in-flight dedup) and D (`useMutation.invalidate`)
|
|
12
|
+
// will start subscribing.
|
|
13
|
+
//
|
|
14
|
+
// Key shape: `<resourceName>|<depsKey>` (single joined string; the `|`
|
|
15
|
+
// separator is opaque to consumers — only `get(name, depsKey)` / `set(...)` are
|
|
16
|
+
// public). `depsKey` is the v0 `depsToKey` serialization (still computed in the
|
|
17
|
+
// hook); the cache treats it as an opaque opaque string.
|
|
18
|
+
const joinKey = (name, depsKey) => `${name}|${depsKey}`;
|
|
19
|
+
const splitName = (joined) => {
|
|
20
|
+
const sep = joined.indexOf("|");
|
|
21
|
+
return sep < 0 ? joined : joined.slice(0, sep);
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* The default LRU implementation. `Map` preserves insertion order, so a
|
|
25
|
+
* `delete + set` on read bumps the entry to most-recent; oldest entries (front
|
|
26
|
+
* of the iterator) evict past `maxEntries`. Each entry's `AbortController` is
|
|
27
|
+
* aborted on supersede / eviction / clear / invalidate. Subscribers are keyed
|
|
28
|
+
* identically; `notify(key)` fans out to all of them on every mutation that
|
|
29
|
+
* touches that key.
|
|
30
|
+
*/
|
|
31
|
+
export const createLRUCache = (maxEntries = 50) => {
|
|
32
|
+
const entries = new Map();
|
|
33
|
+
const subscribers = new Map();
|
|
34
|
+
// Stage D — names that have ever been the target of a successful `set`.
|
|
35
|
+
// Grows on every set; never shrinks. The "ever known" semantics catches
|
|
36
|
+
// typos in `useMutation.invalidate(["..."])` without false-positiving on
|
|
37
|
+
// legitimate empty-cache invalidates (a mounted consumer has always set
|
|
38
|
+
// its key at least once before the first invalidate is meaningful).
|
|
39
|
+
const knownNames = new Set();
|
|
40
|
+
const notify = (key) => {
|
|
41
|
+
const set = subscribers.get(key);
|
|
42
|
+
if (set === undefined)
|
|
43
|
+
return;
|
|
44
|
+
// Iterate a snapshot — a notify() reentrantly unsubscribing must not
|
|
45
|
+
// disturb this fan-out.
|
|
46
|
+
for (const fn of [...set])
|
|
47
|
+
fn();
|
|
48
|
+
};
|
|
49
|
+
const evictIfOver = () => {
|
|
50
|
+
while (entries.size > maxEntries) {
|
|
51
|
+
const oldestKey = entries.keys().next().value;
|
|
52
|
+
if (oldestKey === undefined)
|
|
53
|
+
break;
|
|
54
|
+
const oldest = entries.get(oldestKey);
|
|
55
|
+
if (oldest !== undefined)
|
|
56
|
+
oldest.controller.abort();
|
|
57
|
+
entries.delete(oldestKey);
|
|
58
|
+
notify(oldestKey);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
get(name, depsKey) {
|
|
63
|
+
const key = joinKey(name, depsKey);
|
|
64
|
+
const e = entries.get(key);
|
|
65
|
+
if (e === undefined)
|
|
66
|
+
return undefined;
|
|
67
|
+
// LRU bump.
|
|
68
|
+
entries.delete(key);
|
|
69
|
+
entries.set(key, e);
|
|
70
|
+
return e;
|
|
71
|
+
},
|
|
72
|
+
set(name, depsKey, entry) {
|
|
73
|
+
knownNames.add(name);
|
|
74
|
+
const key = joinKey(name, depsKey);
|
|
75
|
+
const prior = entries.get(key);
|
|
76
|
+
// Supersede a different in-flight controller (the supersede case in §3 +
|
|
77
|
+
// architect Q9). If `prior.controller === entry.controller`, it's the same
|
|
78
|
+
// fetch landing — don't abort itself.
|
|
79
|
+
if (prior !== undefined && prior.controller !== entry.controller) {
|
|
80
|
+
prior.controller.abort();
|
|
81
|
+
}
|
|
82
|
+
entries.delete(key);
|
|
83
|
+
entries.set(key, entry);
|
|
84
|
+
evictIfOver();
|
|
85
|
+
notify(key);
|
|
86
|
+
},
|
|
87
|
+
delete(name, depsKey) {
|
|
88
|
+
const key = joinKey(name, depsKey);
|
|
89
|
+
const e = entries.get(key);
|
|
90
|
+
if (e === undefined)
|
|
91
|
+
return;
|
|
92
|
+
// Caller chose to delete — abort the controller unconditionally. (The
|
|
93
|
+
// useResource caller is doing this on fn-rejection; the in-flight is
|
|
94
|
+
// already settled-error so the abort is informational.)
|
|
95
|
+
e.controller.abort();
|
|
96
|
+
entries.delete(key);
|
|
97
|
+
notify(key);
|
|
98
|
+
},
|
|
99
|
+
invalidate(resourceName) {
|
|
100
|
+
const names = typeof resourceName === "string" ? [resourceName] : resourceName;
|
|
101
|
+
// RES014 — a name that never wrote to the cache has no entry to evict, so
|
|
102
|
+
// invalidating it is a silent no-op (it will NOT refetch). This catches the
|
|
103
|
+
// common footgun: `command f() => … invalidate [r]` where `r` lacks a
|
|
104
|
+
// `cache`/`swr` modifier, plus useMutation typos. Fires for BOTH paths now
|
|
105
|
+
// that both route through here. knownNames persists across eviction, so a
|
|
106
|
+
// genuinely-cached resource never trips this.
|
|
107
|
+
for (const name of names) {
|
|
108
|
+
if (!knownNames.has(name)) {
|
|
109
|
+
console.warn(`[reactra:resource] RES014: invalidate("${name}") names a resource with no cache entry — ` +
|
|
110
|
+
`it will not refetch. Add a \`cache\` or \`swr\` modifier to \`${name}\`, or check the spelling.`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const nameSet = new Set(names);
|
|
114
|
+
// Snapshot keys — `entries.delete` inside the loop would disturb iteration.
|
|
115
|
+
for (const key of [...entries.keys()]) {
|
|
116
|
+
if (nameSet.has(splitName(key))) {
|
|
117
|
+
const e = entries.get(key);
|
|
118
|
+
if (e !== undefined)
|
|
119
|
+
e.controller.abort();
|
|
120
|
+
entries.delete(key);
|
|
121
|
+
notify(key);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
subscribe(name, depsKey, notifyFn) {
|
|
126
|
+
const key = joinKey(name, depsKey);
|
|
127
|
+
let set = subscribers.get(key);
|
|
128
|
+
if (set === undefined) {
|
|
129
|
+
set = new Set();
|
|
130
|
+
subscribers.set(key, set);
|
|
131
|
+
}
|
|
132
|
+
set.add(notifyFn);
|
|
133
|
+
return () => {
|
|
134
|
+
const s = subscribers.get(key);
|
|
135
|
+
if (s === undefined)
|
|
136
|
+
return;
|
|
137
|
+
s.delete(notifyFn);
|
|
138
|
+
if (s.size === 0)
|
|
139
|
+
subscribers.delete(key);
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
hasName(name) {
|
|
143
|
+
return knownNames.has(name);
|
|
144
|
+
},
|
|
145
|
+
clear(resourceName) {
|
|
146
|
+
if (resourceName === undefined) {
|
|
147
|
+
for (const e of entries.values())
|
|
148
|
+
e.controller.abort();
|
|
149
|
+
const keys = [...entries.keys()];
|
|
150
|
+
entries.clear();
|
|
151
|
+
for (const key of keys)
|
|
152
|
+
notify(key);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
for (const key of [...entries.keys()]) {
|
|
156
|
+
if (splitName(key) === resourceName) {
|
|
157
|
+
const e = entries.get(key);
|
|
158
|
+
if (e !== undefined)
|
|
159
|
+
e.controller.abort();
|
|
160
|
+
entries.delete(key);
|
|
161
|
+
notify(key);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type ResourceCache, type ResourceCacheEntry } from "./cache.ts";
|
|
3
|
+
export { createLRUCache, type ResourceCache, type ResourceCacheEntry } from "./cache.ts";
|
|
4
|
+
/**
|
|
5
|
+
* Hook options. v1 added `resourceName` (compiler-injected from the binding
|
|
6
|
+
* identifier on every DSL `resource` call site) + `cache` + `retry` + `dedup`.
|
|
7
|
+
* v0 signature preserved exactly — every field is optional; omitting them all
|
|
8
|
+
* yields the v0 "every mount fetches" behaviour. See Resource v1 §2.
|
|
9
|
+
*/
|
|
10
|
+
export interface ResourceOptions<T = unknown, S = T> {
|
|
11
|
+
/**
|
|
12
|
+
* Cache key prefix. Compiler-injected on every DSL `resource` declaration
|
|
13
|
+
* (derived from the binding identifier). Raw callers may omit it — doing so
|
|
14
|
+
* disables the cache for that call (and falls back to function-identity
|
|
15
|
+
* in-flight dedup once A2 lands). Required for `useMutation` invalidation
|
|
16
|
+
* to target this resource (§7, Stage D).
|
|
17
|
+
*/
|
|
18
|
+
readonly resourceName?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Cache policy. `false` (default) — every mount triggers a fresh fetch (v0
|
|
21
|
+
* behaviour). `true` — cache the result indefinitely, keyed by
|
|
22
|
+
* `(resourceName, depsKey)`. Object form lets per-resource override defaults.
|
|
23
|
+
* Stage A1 honours `true`/`{}`; `ttlMs`/`staleWhileRevalidate`/`maxEntries`
|
|
24
|
+
* fields are reserved (TTL + SWR ship with Stage B).
|
|
25
|
+
*/
|
|
26
|
+
readonly cache?: boolean | {
|
|
27
|
+
readonly ttlMs?: number;
|
|
28
|
+
readonly staleWhileRevalidate?: boolean;
|
|
29
|
+
readonly maxEntries?: number;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Retry policy on rejection. Default: no retry (v0 behaviour). Stage C ships
|
|
33
|
+
* the implementation; the field is reserved here so v1 callers compile.
|
|
34
|
+
*/
|
|
35
|
+
readonly retry?: number | {
|
|
36
|
+
readonly count: number;
|
|
37
|
+
readonly baseMs?: number;
|
|
38
|
+
readonly jitter?: boolean;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* In-flight dedup — two concurrent calls with the same `(resourceName,
|
|
42
|
+
* depsKey)` share one fetch. Default `true` when `resourceName` is present.
|
|
43
|
+
* Stage A2 ships the implementation; reserved here so v1 callers compile.
|
|
44
|
+
*/
|
|
45
|
+
readonly dedup?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Slice/transform the fetched data with a **re-render bail-out**: the handle's
|
|
48
|
+
* `.data` is `select(rawData)`, and the consumer re-renders ONLY when the
|
|
49
|
+
* selected value changes (`Object.is`) — not when an unselected field of the
|
|
50
|
+
* raw data changes. The DSL `select(u => u.name)` modifier compiles to this.
|
|
51
|
+
* The cache stores the RAW value; each consumer applies its own `select`.
|
|
52
|
+
* v1: return a primitive or a stable reference — an object allocated fresh
|
|
53
|
+
* each call never bails (no comparator yet).
|
|
54
|
+
*/
|
|
55
|
+
readonly select?: (data: T) => S;
|
|
56
|
+
}
|
|
57
|
+
/** Internal accessor — the active singleton. Not part of the public API. */
|
|
58
|
+
export declare const __getResourceCache: () => ResourceCache;
|
|
59
|
+
/** Test-only: reset the singleton + first-mount guard between test cases. */
|
|
60
|
+
export declare const __resetResourceConfigForTest: () => void;
|
|
61
|
+
/**
|
|
62
|
+
* Configure the resource layer at app boot (Resource v1 §9). Pass a custom
|
|
63
|
+
* `cache` implementation (must satisfy `ResourceCache`) or just override the
|
|
64
|
+
* built-in LRU's max-entries with `cacheMaxEntries`. **Must run before the
|
|
65
|
+
* first `useResource` mount** — calling it after throws RES010 (the cache
|
|
66
|
+
* implementation is already in use; the swap is rejected).
|
|
67
|
+
*
|
|
68
|
+
* Calling it more than once before the first mount overwrites silently (last
|
|
69
|
+
* call wins — typically only configures once at app boot). Calling it never
|
|
70
|
+
* is fine — the built-in LRU runs with `maxEntries: 50` and no TTL.
|
|
71
|
+
*/
|
|
72
|
+
export declare const configureResources: (opts?: {
|
|
73
|
+
cache?: ResourceCache;
|
|
74
|
+
cacheMaxEntries?: number;
|
|
75
|
+
}) => void;
|
|
76
|
+
/**
|
|
77
|
+
* Stage B — TTL-based staleness check. Returns `true` iff `cache.ttlMs` is
|
|
78
|
+
* set AND `Date.now() - entry.writtenAt` exceeds it. No TTL = never stale
|
|
79
|
+
* (entries live until eviction). Exported under `__` so the test suite can
|
|
80
|
+
* exercise the predicate without standing up a render.
|
|
81
|
+
*/
|
|
82
|
+
export declare const __isCacheEntryStale: (entry: ResourceCacheEntry, opts: ResourceOptions<any, any>, now?: number) => boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Stage B — staleWhileRevalidate opt-in check. Returns `true` iff
|
|
85
|
+
* `opts.cache` is an object form AND `staleWhileRevalidate: true`. Bare
|
|
86
|
+
* `cache: true` is treated as "cache but no SWR" — explicit opt-in required.
|
|
87
|
+
*/
|
|
88
|
+
export declare const __shouldRevalidate: (opts: ResourceOptions<any, any>) => boolean;
|
|
89
|
+
/** Normalized retry config; null means "no retry wrapper, call fn once". */
|
|
90
|
+
export interface __RetryConfig {
|
|
91
|
+
readonly count: number;
|
|
92
|
+
readonly baseMs: number;
|
|
93
|
+
readonly jitter: boolean;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Stage C — normalize `opts.retry` to a `__RetryConfig` or `null`. `retry: N`
|
|
97
|
+
* (number) becomes `{ count: N, baseMs: 250, jitter: true }`. `retry: { count }`
|
|
98
|
+
* fills in the same defaults for unset fields. `retry: 0` or absent returns
|
|
99
|
+
* null (consumer fires `fn` once with zero retry overhead).
|
|
100
|
+
*/
|
|
101
|
+
export declare const __normalizeRetry: (opts: ResourceOptions<any, any>) => __RetryConfig | null;
|
|
102
|
+
/**
|
|
103
|
+
* Stage C — exponential backoff with optional ±20% jitter. Delay for attempt
|
|
104
|
+
* `n` (0-indexed) is `baseMs * 2^n`, optionally multiplied by a uniform
|
|
105
|
+
* `[0.8, 1.2)` jitter factor. `random` is injectable so tests can pin both
|
|
106
|
+
* extremes (`() => 0` → 0.8x; `() => 1` → 1.2x).
|
|
107
|
+
*/
|
|
108
|
+
export declare const __computeRetryDelay: (attempt: number, config: {
|
|
109
|
+
readonly baseMs: number;
|
|
110
|
+
readonly jitter: boolean;
|
|
111
|
+
}, random?: () => number) => number;
|
|
112
|
+
/**
|
|
113
|
+
* Stage C — abortable `setTimeout` as a Promise. Resolves after `ms`; rejects
|
|
114
|
+
* with a DOMException("Aborted", "AbortError") if `signal` aborts before the
|
|
115
|
+
* timer fires (the timer is cleared). If the signal is already aborted at
|
|
116
|
+
* call time, rejects synchronously on the next microtask.
|
|
117
|
+
*/
|
|
118
|
+
export declare const __delay: (ms: number, signal: AbortSignal) => Promise<void>;
|
|
119
|
+
/**
|
|
120
|
+
* Stage C — fire `fn(deps, { signal })` up to `config.count + 1` times (one
|
|
121
|
+
* initial + `count` retries), with exponential backoff + jitter between
|
|
122
|
+
* attempts. On `AbortError` from `fn` OR signal abort during the backoff
|
|
123
|
+
* delay, the loop short-circuits and the abort propagates. On exhaustion,
|
|
124
|
+
* logs RES012 (dev-gated — prod stays silent since the error already surfaces
|
|
125
|
+
* via `r.error` + ErrorBoundary + the rejected promise) and re-throws the last
|
|
126
|
+
* error UNCONDITIONALLY. The hook's existing `.then` chain handles
|
|
127
|
+
* `setState({ error })` + cache.delete tombstone removal unchanged.
|
|
128
|
+
*/
|
|
129
|
+
export declare const __fireWithRetry: <Deps, T>(deps: NonNullable<Deps>, signal: AbortSignal, config: __RetryConfig, fn: (deps: NonNullable<Deps>, ctx: {
|
|
130
|
+
signal: AbortSignal;
|
|
131
|
+
}) => Promise<T>, random?: () => number) => Promise<T>;
|
|
132
|
+
/**
|
|
133
|
+
* Handle returned by `useResource`. Drives both the `await(r)` Suspense
|
|
134
|
+
* boundary (via `promise`) and the success branch body (via `data`).
|
|
135
|
+
*
|
|
136
|
+
* `S` is the SELECTED type (`= T` when no `select`): `.data` is what the consumer
|
|
137
|
+
* sees (the slice). `.promise` stays `Promise<T | undefined>` (the RAW fetch) — the
|
|
138
|
+
* `await(r){}` lowering discards its resolved value (it only drives Suspense; the
|
|
139
|
+
* body reads `.data`), so there's no need to project it through `select`.
|
|
140
|
+
*/
|
|
141
|
+
export interface ResourceHandle<T, S = T> {
|
|
142
|
+
data: S | undefined;
|
|
143
|
+
error: unknown;
|
|
144
|
+
isPending: boolean;
|
|
145
|
+
refetch: () => void;
|
|
146
|
+
promise: Promise<T | undefined>;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Stable string key for a `deps` value. Arrays are unwrapped element-wise so
|
|
150
|
+
* `[1, 2]` keys identically to another `[1, 2]`; scalar refs are compared by
|
|
151
|
+
* `Object.is`-equivalent identity. Exported for tests.
|
|
152
|
+
*/
|
|
153
|
+
export declare const depsToKey: (deps: unknown) => string;
|
|
154
|
+
/**
|
|
155
|
+
* The Reactra resource hook. Compiler-emitted from `resource r(deps) => fn(deps)`.
|
|
156
|
+
*
|
|
157
|
+
* Behaviour per Resource v0 §2 + §3:
|
|
158
|
+
* - `deps === null` → skip the call. `data/error: undefined`, `isPending: false`,
|
|
159
|
+
* `promise: Promise.resolve(undefined)`.
|
|
160
|
+
* - `deps === undefined` → same as `null` but emits a RES004 warning. The
|
|
161
|
+
* explicit skip sentinel is `null`.
|
|
162
|
+
* - Otherwise `fn(deps, { signal })` runs immediately. `data` updates on
|
|
163
|
+
* resolution; `error` updates on rejection (preserving last good `data`).
|
|
164
|
+
* - Deps change (shallow `Object.is`) aborts the in-flight call and re-fires.
|
|
165
|
+
* - `refetch()` aborts the current call and re-fires with the same deps.
|
|
166
|
+
* - Late resolutions are dropped via a generation counter.
|
|
167
|
+
*
|
|
168
|
+
* The returned `promise` is always non-null and is fed to `React.use()` inside
|
|
169
|
+
* compiled `await(r) { }` blocks so React 19's Suspense can drive the boundary.
|
|
170
|
+
*
|
|
171
|
+
* ## StrictMode-safety (the reason this hook is ref-cached, not useMemo'd)
|
|
172
|
+
*
|
|
173
|
+
* Earlier drafts used `useMemo` for the promise and a `useEffect(() => () =>
|
|
174
|
+
* abort())` cleanup. That breaks under React.StrictMode in dev. StrictMode
|
|
175
|
+
* double-invokes render → mount → cleanup → mount on first mount to surface
|
|
176
|
+
* impurity. The first mount's `useEffect` cleanup aborts the controller while
|
|
177
|
+
* the SAME promise object is still referenced by the rendered tree; on the
|
|
178
|
+
* fake-remount, `React.use(promise)` sees a rejected (AbortError) promise and
|
|
179
|
+
* the surrounding ErrorBoundary fires for what should be a normal load.
|
|
180
|
+
* Removing the cleanup-side abort alone doesn't fix it — the top-of-useMemo
|
|
181
|
+
* abort + StrictMode's deliberate memo-discard still leave a rejected promise
|
|
182
|
+
* visible to React.use either way (useMemo is a cache *hint*, not a guarantee).
|
|
183
|
+
*
|
|
184
|
+
* The fix: cache the entry in a `useRef`. Refs are mutable cells that survive
|
|
185
|
+
* StrictMode's fake-unmount unchanged because they live outside React's
|
|
186
|
+
* render-output reconciliation. On the fake-remount, the same `depsKey` finds
|
|
187
|
+
* the existing entry and reuses its unaborted promise. Abort happens ONLY at
|
|
188
|
+
* the top of the cache-miss branch (genuine deps change or first call), never
|
|
189
|
+
* from an effect cleanup. Late resolutions are still dropped by the
|
|
190
|
+
* `genRef === myGen` guard inside the .then handlers, which covers the cases
|
|
191
|
+
* the cleanup-abort used to: deps-change, refetch, and the rarer "still
|
|
192
|
+
* in-flight at real unmount" case (setState on an unmounted component is a
|
|
193
|
+
* silent no-op in React 18+; no error, no warning, just a dropped state
|
|
194
|
+
* update). The minor cost is that a fetch in flight at real unmount keeps
|
|
195
|
+
* running until the network call completes — but its resolution is dropped,
|
|
196
|
+
* so no setState-on-unmounted and no observable bug. Worth it for StrictMode.
|
|
197
|
+
*
|
|
198
|
+
* Mutating refs during render is the standard React 19 pattern for cache-like
|
|
199
|
+
* state; React docs explicitly bless it when (a) the operation is idempotent
|
|
200
|
+
* for a given input (same depsKey → same entry) and (b) the mutation doesn't
|
|
201
|
+
* affect the rendered output during *this* render. Both hold here.
|
|
202
|
+
*/
|
|
203
|
+
export declare const useResource: <Deps, T, S = T>(deps: Deps, fn: (deps: NonNullable<Deps>, ctx: {
|
|
204
|
+
signal: AbortSignal;
|
|
205
|
+
}) => Promise<T>, opts?: ResourceOptions<T, S>) => ResourceHandle<T, S>;
|
|
206
|
+
/**
|
|
207
|
+
* Props for the ErrorBoundary emitted by `await(r) error(e) { … }` blocks.
|
|
208
|
+
* `fallback` is a render-fn (not an element) so the boundary can pass the
|
|
209
|
+
* caught error to the user's `error(e) { JSX }` body verbatim.
|
|
210
|
+
*/
|
|
211
|
+
/**
|
|
212
|
+
* Resource v1 §8.1 — the runtime helper a compiler-emitted `warm(inputs,
|
|
213
|
+
* signal)` companion calls per-resource. Best-effort cache pre-fill; never
|
|
214
|
+
* throws. The returned promise resolves when the entry settles (success or
|
|
215
|
+
* any failure, including AbortError).
|
|
216
|
+
*
|
|
217
|
+
* Behaviour:
|
|
218
|
+
* - If `(name, depsToKey(deps))` already has a cache entry (settled OR
|
|
219
|
+
* in-flight), resolve immediately. Stage E treats any present entry as
|
|
220
|
+
* "fresh enough" — consumers with SWR drive their own refresh from the
|
|
221
|
+
* render path; warm doesn't aggressively refresh.
|
|
222
|
+
* - Otherwise: write an in-flight tombstone (Stage A2 shape) keyed under
|
|
223
|
+
* a fresh `AbortController` so concurrent warm + consumer mounts dedup.
|
|
224
|
+
* Forward `callerSignal.abort` to the cache controller, propagating
|
|
225
|
+
* cancel-on-real-navigation through to `fn(deps, { signal })`.
|
|
226
|
+
* - On `fn` success: supersede the tombstone with the resolved entry
|
|
227
|
+
* (same controller → idempotent supersede per §6).
|
|
228
|
+
* - On `fn` failure (including AbortError): `cache.delete(name, depsKey)`
|
|
229
|
+
* withdraws the tombstone. Any consumer mid-adopt sees the
|
|
230
|
+
* `cacheBust = cacheCoupled && cached === undefined` check (Stage D)
|
|
231
|
+
* and re-fires fresh on its next render. Failures are silently
|
|
232
|
+
* resolved by the returned promise — RES015 was deliberately not
|
|
233
|
+
* minted (§5; failed warms are debug-only).
|
|
234
|
+
*/
|
|
235
|
+
export declare const warmResource: <Deps, T>(name: string, deps: Deps, fn: (deps: Deps, ctx: {
|
|
236
|
+
signal: AbortSignal;
|
|
237
|
+
}) => Promise<T>, callerSignal: AbortSignal) => Promise<void>;
|
|
238
|
+
/**
|
|
239
|
+
* The name of a resource known to the project, used to type
|
|
240
|
+
* {@link MutationOptions.invalidate}. See Resource v1 §7.
|
|
241
|
+
*
|
|
242
|
+
* **This alias is `string` by default.** When `@reactra/vite-plugin` runs over
|
|
243
|
+
* a project it writes `.reactra/resource-names.d.ts`, which uses TS declaration
|
|
244
|
+
* merging to narrow this alias to the union of every `resource X(...)` name
|
|
245
|
+
* declared across the project (e.g. `"customer" | "orderSummary"`). With that
|
|
246
|
+
* file on the TS `include` path, `invalidate: ["custmer"]` becomes a compile
|
|
247
|
+
* error while `invalidate: ["customer"]` typechecks.
|
|
248
|
+
*
|
|
249
|
+
* The default-to-`string` guarantee is load-bearing: with NO generated file
|
|
250
|
+
* present (a fresh clone, a library consumer, a project that hasn't built yet),
|
|
251
|
+
* `invalidate: string[]` and `invalidate: ["anything"]` keep compiling — the
|
|
252
|
+
* typed union is an additive narrowing, never a hard requirement. RES014 stays
|
|
253
|
+
* the runtime backstop for dynamic / un-generated names.
|
|
254
|
+
*
|
|
255
|
+
* It must be a PROJECT-WIDE union (one merged declaration), not per-file:
|
|
256
|
+
* invalidation is cross-file (a mutation in `save.tsx` invalidates a resource
|
|
257
|
+
* declared in `[id].tsx`), so a per-file union would make correct cross-file
|
|
258
|
+
* invalidation a type error.
|
|
259
|
+
*/
|
|
260
|
+
export type ResourceName = string;
|
|
261
|
+
/** Options for `useMutation`. See Resource v1 §7. */
|
|
262
|
+
export interface MutationOptions {
|
|
263
|
+
/**
|
|
264
|
+
* After a successful mutation, invalidate every resource whose `resourceName`
|
|
265
|
+
* appears in this list. v1 keys invalidation purely by `resourceName`
|
|
266
|
+
* (architect Q6 Path C — the `tags` field is deferred to v1.1 with the DSL
|
|
267
|
+
* grammar extension). Typed as {@link ResourceName}[] so a typo fails at
|
|
268
|
+
* compile time once `.reactra/resource-names.d.ts` is generated; RES014 stays
|
|
269
|
+
* the runtime backstop (warns when a listed name has never been written to
|
|
270
|
+
* the cache, e.g. a dynamic name). The mutation still completes either way.
|
|
271
|
+
*/
|
|
272
|
+
readonly invalidate?: readonly ResourceName[];
|
|
273
|
+
/**
|
|
274
|
+
* If false (default), calling `mutate()` while a previous call is in-flight
|
|
275
|
+
* throws **RES013**. Set true to allow overlapping calls — the most recent
|
|
276
|
+
* resolution wins for the handle's `data`/`error`/`isPending`; older calls
|
|
277
|
+
* still resolve their own returned promise (they're just no-ops for the
|
|
278
|
+
* handle's state).
|
|
279
|
+
*/
|
|
280
|
+
readonly concurrent?: boolean;
|
|
281
|
+
/** Per-mutation retry policy. Same shape as `ResourceOptions.retry`. */
|
|
282
|
+
readonly retry?: ResourceOptions["retry"];
|
|
283
|
+
}
|
|
284
|
+
/** Handle returned by `useMutation`. See Resource v1 §7. */
|
|
285
|
+
export interface MutationHandle<Args, T> {
|
|
286
|
+
/**
|
|
287
|
+
* Fire the mutation. Returns a promise resolving to `T` on success. Throws
|
|
288
|
+
* RES013 if called while `isPending` is true and `concurrent !== true`.
|
|
289
|
+
*/
|
|
290
|
+
mutate: (args: Args) => Promise<T>;
|
|
291
|
+
/** True while the latest `mutate()` call's promise is unresolved. */
|
|
292
|
+
isPending: boolean;
|
|
293
|
+
/** Last successful return value; cleared by `reset()` or overwritten by the next success. */
|
|
294
|
+
data: T | undefined;
|
|
295
|
+
/** Last error; cleared by `reset()` or by the next successful resolution. */
|
|
296
|
+
error: unknown;
|
|
297
|
+
/** Clear `data` + `error` + abort any in-flight call. **Stable reference.** */
|
|
298
|
+
reset: () => void;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Resource v1 §7 — `useMutation(fn, opts?)`. A user-called hook for
|
|
302
|
+
* non-idempotent side effects (POST, PATCH, DELETE, …) that may need to
|
|
303
|
+
* invalidate cached resources on success. Lives in `@reactra/resource`; NOT
|
|
304
|
+
* emitted by the compiler from any DSL keyword (architect Q6 — `mutation` is
|
|
305
|
+
* deliberately not added to the 25-keyword surface in v1).
|
|
306
|
+
*
|
|
307
|
+
* Behaviour:
|
|
308
|
+
* - `mutate(args)` immediately calls `fn(args, { signal })` and returns the
|
|
309
|
+
* promise. `isPending` toggles `true → false` on settle (success OR error).
|
|
310
|
+
* - On success: writes `data`, clears `error`, then runs
|
|
311
|
+
* `__currentCache.invalidate(opts.invalidate)` if `invalidate` is set. The
|
|
312
|
+
* returned promise resolves AFTER invalidation is initiated.
|
|
313
|
+
* - On rejection: writes `error`, retains the previous `data`. Retries (if
|
|
314
|
+
* `opts.retry`) drain before the rejection lands. A final failure does NOT
|
|
315
|
+
* invalidate.
|
|
316
|
+
* - `reset()` aborts the in-flight call, clears `data` + `error`, toggles
|
|
317
|
+
* `isPending` false. Reference is stable across renders.
|
|
318
|
+
* - **RES013** throws if `mutate()` is called while `isPending` is true and
|
|
319
|
+
* `concurrent !== true`. Set `concurrent: true` to opt in to overlap.
|
|
320
|
+
* - **RES014** warns if `opts.invalidate` lists a name not yet known to the
|
|
321
|
+
* cache (likely a typo). The mutation still completes.
|
|
322
|
+
*
|
|
323
|
+
* StrictMode: no `useEffect`. The in-flight controller lives in a `useRef`
|
|
324
|
+
* and is aborted by `reset()` or replaced on the next `mutate()` call.
|
|
325
|
+
*/
|
|
326
|
+
export declare const useMutation: <Args, T>(fn: (args: Args, ctx: {
|
|
327
|
+
signal: AbortSignal;
|
|
328
|
+
}) => Promise<T>, opts?: MutationOptions) => MutationHandle<Args, T>;
|
|
329
|
+
export interface ErrorBoundaryProps {
|
|
330
|
+
children: React.ReactNode;
|
|
331
|
+
fallback: (error: unknown) => React.ReactNode;
|
|
332
|
+
}
|
|
333
|
+
interface ErrorBoundaryState {
|
|
334
|
+
error: unknown;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* React class component used by compiled `await(r) error(e) { … }` blocks.
|
|
338
|
+
* Phase-1 surface: catches render-time errors below the boundary, then
|
|
339
|
+
* renders `fallback(error)`. No reset API yet — a fresh boundary is created
|
|
340
|
+
* on every parent re-render that re-emits a new JSX subtree.
|
|
341
|
+
*
|
|
342
|
+
* Resource v0 §4: errors thrown inside `fn` reject the promise; React.use()
|
|
343
|
+
* re-throws on subsequent render; this boundary catches the re-throw.
|
|
344
|
+
*/
|
|
345
|
+
export declare class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
346
|
+
state: ErrorBoundaryState;
|
|
347
|
+
static getDerivedStateFromError: (error: unknown) => ErrorBoundaryState;
|
|
348
|
+
render(): React.ReactNode;
|
|
349
|
+
}
|
|
350
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|