@flightdev/data 0.0.6
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 +265 -0
- package/dist/cache-DU1v4CKe.d.ts +216 -0
- package/dist/chunk-NOWQL3EM.js +398 -0
- package/dist/index.d.ts +277 -0
- package/dist/index.js +463 -0
- package/dist/react.d.ts +57 -0
- package/dist/react.js +119 -0
- package/dist/solid.d.ts +83 -0
- package/dist/solid.js +142 -0
- package/dist/svelte.d.ts +80 -0
- package/dist/svelte.js +114 -0
- package/dist/vue.d.ts +76 -0
- package/dist/vue.js +136 -0
- package/package.json +84 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
// src/cache.ts
|
|
2
|
+
var LRUCache = class {
|
|
3
|
+
map = /* @__PURE__ */ new Map();
|
|
4
|
+
head = null;
|
|
5
|
+
tail = null;
|
|
6
|
+
size = 0;
|
|
7
|
+
maxSize;
|
|
8
|
+
defaultMaxAge;
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.maxSize = options.maxSize ?? 100;
|
|
11
|
+
this.defaultMaxAge = options.defaultMaxAge ?? 5 * 60 * 1e3;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Get value from cache
|
|
15
|
+
* Returns undefined if not found or expired
|
|
16
|
+
*/
|
|
17
|
+
get(key) {
|
|
18
|
+
const entry = this.map.get(key);
|
|
19
|
+
if (!entry) {
|
|
20
|
+
return void 0;
|
|
21
|
+
}
|
|
22
|
+
if (this.isExpired(entry)) {
|
|
23
|
+
this.delete(key);
|
|
24
|
+
return void 0;
|
|
25
|
+
}
|
|
26
|
+
this.moveToFront(entry);
|
|
27
|
+
return entry.value;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Set value in cache
|
|
31
|
+
*/
|
|
32
|
+
set(key, value, options = {}) {
|
|
33
|
+
const existingEntry = this.map.get(key);
|
|
34
|
+
if (existingEntry) {
|
|
35
|
+
existingEntry.value = value;
|
|
36
|
+
existingEntry.timestamp = Date.now();
|
|
37
|
+
existingEntry.maxAge = options.maxAge ?? this.defaultMaxAge;
|
|
38
|
+
this.moveToFront(existingEntry);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const entry = {
|
|
42
|
+
key,
|
|
43
|
+
value,
|
|
44
|
+
timestamp: Date.now(),
|
|
45
|
+
maxAge: options.maxAge ?? this.defaultMaxAge,
|
|
46
|
+
prev: null,
|
|
47
|
+
next: null
|
|
48
|
+
};
|
|
49
|
+
this.map.set(key, entry);
|
|
50
|
+
this.size++;
|
|
51
|
+
this.addToFront(entry);
|
|
52
|
+
if (this.size > this.maxSize) {
|
|
53
|
+
this.evictLRU();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Delete entry from cache
|
|
58
|
+
*/
|
|
59
|
+
delete(key) {
|
|
60
|
+
const entry = this.map.get(key);
|
|
61
|
+
if (!entry) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
this.removeFromList(entry);
|
|
65
|
+
this.map.delete(key);
|
|
66
|
+
this.size--;
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Check if key exists and is not expired
|
|
71
|
+
*/
|
|
72
|
+
has(key) {
|
|
73
|
+
const entry = this.map.get(key);
|
|
74
|
+
if (!entry) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
if (this.isExpired(entry)) {
|
|
78
|
+
this.delete(key);
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Clear all entries
|
|
85
|
+
*/
|
|
86
|
+
clear() {
|
|
87
|
+
this.map.clear();
|
|
88
|
+
this.head = null;
|
|
89
|
+
this.tail = null;
|
|
90
|
+
this.size = 0;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Get current cache size
|
|
94
|
+
*/
|
|
95
|
+
getSize() {
|
|
96
|
+
return this.size;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get all keys (for invalidation patterns)
|
|
100
|
+
*/
|
|
101
|
+
keys() {
|
|
102
|
+
return Array.from(this.map.keys());
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Invalidate entries matching a pattern
|
|
106
|
+
*/
|
|
107
|
+
invalidate(pattern) {
|
|
108
|
+
let count = 0;
|
|
109
|
+
const keysToDelete = [];
|
|
110
|
+
for (const key of this.map.keys()) {
|
|
111
|
+
let shouldDelete = false;
|
|
112
|
+
if (typeof pattern === "string") {
|
|
113
|
+
shouldDelete = key === pattern || key.startsWith(pattern);
|
|
114
|
+
} else if (pattern instanceof RegExp) {
|
|
115
|
+
shouldDelete = pattern.test(key);
|
|
116
|
+
} else {
|
|
117
|
+
shouldDelete = pattern(key);
|
|
118
|
+
}
|
|
119
|
+
if (shouldDelete) {
|
|
120
|
+
keysToDelete.push(key);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
for (const key of keysToDelete) {
|
|
124
|
+
this.delete(key);
|
|
125
|
+
count++;
|
|
126
|
+
}
|
|
127
|
+
return count;
|
|
128
|
+
}
|
|
129
|
+
// ========================================================================
|
|
130
|
+
// Private Methods
|
|
131
|
+
// ========================================================================
|
|
132
|
+
isExpired(entry) {
|
|
133
|
+
if (!entry.maxAge) return false;
|
|
134
|
+
return Date.now() - entry.timestamp > entry.maxAge;
|
|
135
|
+
}
|
|
136
|
+
addToFront(entry) {
|
|
137
|
+
entry.next = this.head;
|
|
138
|
+
entry.prev = null;
|
|
139
|
+
if (this.head) {
|
|
140
|
+
this.head.prev = entry;
|
|
141
|
+
}
|
|
142
|
+
this.head = entry;
|
|
143
|
+
if (!this.tail) {
|
|
144
|
+
this.tail = entry;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
removeFromList(entry) {
|
|
148
|
+
if (entry.prev) {
|
|
149
|
+
entry.prev.next = entry.next;
|
|
150
|
+
} else {
|
|
151
|
+
this.head = entry.next;
|
|
152
|
+
}
|
|
153
|
+
if (entry.next) {
|
|
154
|
+
entry.next.prev = entry.prev;
|
|
155
|
+
} else {
|
|
156
|
+
this.tail = entry.prev;
|
|
157
|
+
}
|
|
158
|
+
entry.prev = null;
|
|
159
|
+
entry.next = null;
|
|
160
|
+
}
|
|
161
|
+
moveToFront(entry) {
|
|
162
|
+
if (this.head === entry) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
this.removeFromList(entry);
|
|
166
|
+
this.addToFront(entry);
|
|
167
|
+
}
|
|
168
|
+
evictLRU() {
|
|
169
|
+
if (!this.tail) return;
|
|
170
|
+
const lruKey = this.tail.key;
|
|
171
|
+
this.removeFromList(this.tail);
|
|
172
|
+
this.map.delete(lruKey);
|
|
173
|
+
this.size--;
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
var globalCache = null;
|
|
177
|
+
function getCache() {
|
|
178
|
+
if (!globalCache) {
|
|
179
|
+
globalCache = new LRUCache();
|
|
180
|
+
}
|
|
181
|
+
return globalCache;
|
|
182
|
+
}
|
|
183
|
+
function configureCache(options) {
|
|
184
|
+
globalCache = new LRUCache(options);
|
|
185
|
+
}
|
|
186
|
+
function clearCache() {
|
|
187
|
+
if (globalCache) {
|
|
188
|
+
globalCache.clear();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function invalidateCache(pattern) {
|
|
192
|
+
if (!globalCache) return 0;
|
|
193
|
+
return globalCache.invalidate(pattern);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// src/fetcher.ts
|
|
197
|
+
var inFlightRequests = /* @__PURE__ */ new Map();
|
|
198
|
+
async function deduplicatedFetch(key, fetcher, options = {}) {
|
|
199
|
+
const { dedupe = true } = options;
|
|
200
|
+
if (!dedupe) {
|
|
201
|
+
return fetcher();
|
|
202
|
+
}
|
|
203
|
+
const existing = inFlightRequests.get(key);
|
|
204
|
+
if (existing) {
|
|
205
|
+
existing.subscribers++;
|
|
206
|
+
return existing.promise;
|
|
207
|
+
}
|
|
208
|
+
const controller = new AbortController();
|
|
209
|
+
const promise = fetcher();
|
|
210
|
+
const inFlight = {
|
|
211
|
+
promise,
|
|
212
|
+
controller,
|
|
213
|
+
subscribers: 1
|
|
214
|
+
};
|
|
215
|
+
inFlightRequests.set(key, inFlight);
|
|
216
|
+
try {
|
|
217
|
+
const result = await promise;
|
|
218
|
+
return result;
|
|
219
|
+
} finally {
|
|
220
|
+
inFlightRequests.delete(key);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function cancelRequest(key) {
|
|
224
|
+
const inFlight = inFlightRequests.get(key);
|
|
225
|
+
if (inFlight) {
|
|
226
|
+
inFlight.controller.abort();
|
|
227
|
+
inFlightRequests.delete(key);
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
async function fetchData(url, options = {}) {
|
|
233
|
+
if (!url) {
|
|
234
|
+
return {
|
|
235
|
+
data: options.default,
|
|
236
|
+
error: void 0,
|
|
237
|
+
status: "idle",
|
|
238
|
+
isStale: false
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
const {
|
|
242
|
+
key = url,
|
|
243
|
+
cache: useCache = true,
|
|
244
|
+
dedupe = true,
|
|
245
|
+
maxAge,
|
|
246
|
+
transform,
|
|
247
|
+
fetchOptions,
|
|
248
|
+
signal
|
|
249
|
+
} = options;
|
|
250
|
+
const cacheInstance = getCache();
|
|
251
|
+
if (useCache) {
|
|
252
|
+
const cached = cacheInstance.get(key);
|
|
253
|
+
if (cached !== void 0) {
|
|
254
|
+
return {
|
|
255
|
+
data: cached,
|
|
256
|
+
error: void 0,
|
|
257
|
+
status: "success",
|
|
258
|
+
isStale: false
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
const result = await deduplicatedFetch(key, async () => {
|
|
264
|
+
const response = await fetch(url, {
|
|
265
|
+
...fetchOptions,
|
|
266
|
+
signal,
|
|
267
|
+
headers: {
|
|
268
|
+
"Accept": "application/json",
|
|
269
|
+
...fetchOptions?.headers
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
if (!response.ok) {
|
|
273
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
274
|
+
}
|
|
275
|
+
const data = await response.json();
|
|
276
|
+
return transform ? transform(data) : data;
|
|
277
|
+
}, { dedupe });
|
|
278
|
+
if (useCache) {
|
|
279
|
+
cacheInstance.set(key, result, { maxAge });
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
data: result,
|
|
283
|
+
error: void 0,
|
|
284
|
+
status: "success",
|
|
285
|
+
isStale: false
|
|
286
|
+
};
|
|
287
|
+
} catch (error) {
|
|
288
|
+
if (useCache) {
|
|
289
|
+
const stale = cacheInstance.get(key);
|
|
290
|
+
if (stale !== void 0) {
|
|
291
|
+
return {
|
|
292
|
+
data: stale,
|
|
293
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
294
|
+
status: "error",
|
|
295
|
+
isStale: true
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
data: options.default,
|
|
301
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
302
|
+
status: "error",
|
|
303
|
+
isStale: false
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
async function asyncData(key, fetcher, options = {}) {
|
|
308
|
+
const { maxAge, transform } = options;
|
|
309
|
+
const cacheInstance = getCache();
|
|
310
|
+
const cached = cacheInstance.get(key);
|
|
311
|
+
if (cached !== void 0) {
|
|
312
|
+
return {
|
|
313
|
+
data: cached,
|
|
314
|
+
error: void 0,
|
|
315
|
+
status: "success",
|
|
316
|
+
isStale: false
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
try {
|
|
320
|
+
const result = await deduplicatedFetch(key, fetcher);
|
|
321
|
+
const transformed = transform ? transform(result) : result;
|
|
322
|
+
cacheInstance.set(key, transformed, { maxAge });
|
|
323
|
+
return {
|
|
324
|
+
data: transformed,
|
|
325
|
+
error: void 0,
|
|
326
|
+
status: "success",
|
|
327
|
+
isStale: false
|
|
328
|
+
};
|
|
329
|
+
} catch (error) {
|
|
330
|
+
return {
|
|
331
|
+
data: options.default,
|
|
332
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
333
|
+
status: "error",
|
|
334
|
+
isStale: false
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function invalidate(pattern) {
|
|
339
|
+
for (const key of inFlightRequests.keys()) {
|
|
340
|
+
let shouldCancel = false;
|
|
341
|
+
if (typeof pattern === "string") {
|
|
342
|
+
shouldCancel = key === pattern || key.startsWith(pattern);
|
|
343
|
+
} else if (pattern instanceof RegExp) {
|
|
344
|
+
shouldCancel = pattern.test(key);
|
|
345
|
+
} else {
|
|
346
|
+
shouldCancel = pattern(key);
|
|
347
|
+
}
|
|
348
|
+
if (shouldCancel) {
|
|
349
|
+
cancelRequest(key);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return invalidateCache(pattern);
|
|
353
|
+
}
|
|
354
|
+
async function prefetch(url, options) {
|
|
355
|
+
await fetchData(url, { ...options, immediate: true });
|
|
356
|
+
}
|
|
357
|
+
function getCachedData(key) {
|
|
358
|
+
return getCache().get(key);
|
|
359
|
+
}
|
|
360
|
+
function setCachedData(key, data, options) {
|
|
361
|
+
getCache().set(key, data, options);
|
|
362
|
+
}
|
|
363
|
+
var isBrowser = typeof window !== "undefined";
|
|
364
|
+
function createFetchHydrationScript(data) {
|
|
365
|
+
const serialized = JSON.stringify(data).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
|
|
366
|
+
return `<script>window.__FLIGHT_FETCH_DATA__ = ${serialized};</script>`;
|
|
367
|
+
}
|
|
368
|
+
function hydrateFetchData() {
|
|
369
|
+
if (!isBrowser) return;
|
|
370
|
+
const hydrated = window.__FLIGHT_FETCH_DATA__;
|
|
371
|
+
if (!hydrated) return;
|
|
372
|
+
const cache = getCache();
|
|
373
|
+
for (const [key, value] of Object.entries(hydrated)) {
|
|
374
|
+
cache.set(key, value);
|
|
375
|
+
}
|
|
376
|
+
delete window.__FLIGHT_FETCH_DATA__;
|
|
377
|
+
}
|
|
378
|
+
var isServer = !isBrowser;
|
|
379
|
+
var isClient = isBrowser;
|
|
380
|
+
|
|
381
|
+
export {
|
|
382
|
+
LRUCache,
|
|
383
|
+
getCache,
|
|
384
|
+
configureCache,
|
|
385
|
+
clearCache,
|
|
386
|
+
invalidateCache,
|
|
387
|
+
cancelRequest,
|
|
388
|
+
fetchData,
|
|
389
|
+
asyncData,
|
|
390
|
+
invalidate,
|
|
391
|
+
prefetch,
|
|
392
|
+
getCachedData,
|
|
393
|
+
setCachedData,
|
|
394
|
+
createFetchHydrationScript,
|
|
395
|
+
hydrateFetchData,
|
|
396
|
+
isServer,
|
|
397
|
+
isClient
|
|
398
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
export { C as CacheOptions, j as FetchState, F as FetchStatus, L as LRUCache, S as SetOptions, l as UseAsyncDataOptions, m as UseAsyncDataReturn, U as UseFetchOptions, k as UseFetchReturn, a as asyncData, c as cancelRequest, q as clearCache, o as configureCache, b as createFetchHydrationScript, f as fetchData, n as getCache, g as getCachedData, h as hydrateFetchData, i as invalidate, r as invalidateCache, e as isClient, d as isServer, p as prefetch, s as setCachedData } from './cache-DU1v4CKe.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Type definitions for @flightdev/data
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Context passed to loader functions
|
|
8
|
+
*/
|
|
9
|
+
interface LoaderContext {
|
|
10
|
+
/** Route parameters from dynamic segments */
|
|
11
|
+
params: Record<string, string>;
|
|
12
|
+
/** Incoming request object (server-side only) */
|
|
13
|
+
request: Request;
|
|
14
|
+
/** URL search params */
|
|
15
|
+
searchParams: URLSearchParams;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Loader function signature
|
|
19
|
+
* Runs on the server before rendering the page
|
|
20
|
+
*/
|
|
21
|
+
type LoaderFunction<T = unknown> = (context: LoaderContext) => Promise<T> | T;
|
|
22
|
+
/**
|
|
23
|
+
* Context passed to action functions
|
|
24
|
+
*/
|
|
25
|
+
interface ActionContext {
|
|
26
|
+
/** Route parameters from dynamic segments */
|
|
27
|
+
params: Record<string, string>;
|
|
28
|
+
/** Incoming request with form data */
|
|
29
|
+
request: Request;
|
|
30
|
+
/** URL search params */
|
|
31
|
+
searchParams: URLSearchParams;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Action function signature
|
|
35
|
+
* Handles form submissions and mutations
|
|
36
|
+
*/
|
|
37
|
+
type ActionFunction<T = unknown> = (context: ActionContext) => Promise<T | Response> | T | Response;
|
|
38
|
+
/**
|
|
39
|
+
* Utility type to extract the return type of a loader
|
|
40
|
+
* Removes Promise wrapper if present
|
|
41
|
+
*/
|
|
42
|
+
type SerializeFrom<T> = T extends (...args: unknown[]) => infer R ? R extends Promise<infer U> ? U : R : never;
|
|
43
|
+
/**
|
|
44
|
+
* Fetcher state for client-side data fetching
|
|
45
|
+
*/
|
|
46
|
+
type FetcherState<T = unknown> = {
|
|
47
|
+
state: 'idle';
|
|
48
|
+
data: undefined;
|
|
49
|
+
error: undefined;
|
|
50
|
+
} | {
|
|
51
|
+
state: 'loading';
|
|
52
|
+
data: undefined;
|
|
53
|
+
error: undefined;
|
|
54
|
+
} | {
|
|
55
|
+
state: 'success';
|
|
56
|
+
data: T;
|
|
57
|
+
error: undefined;
|
|
58
|
+
} | {
|
|
59
|
+
state: 'error';
|
|
60
|
+
data: undefined;
|
|
61
|
+
error: Error;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @flightdev/data - Data Hooks
|
|
66
|
+
*
|
|
67
|
+
* Framework-agnostic data hooks with React 19 support.
|
|
68
|
+
*
|
|
69
|
+
* Philosophy:
|
|
70
|
+
* - Zero external dependencies
|
|
71
|
+
* - Framework agnostic (React, Vue, Solid, etc.)
|
|
72
|
+
* - User decides how to provide/consume data
|
|
73
|
+
* - SSR/Hydration support built-in
|
|
74
|
+
*
|
|
75
|
+
* @module @flightdev/data/hooks
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Set loader data in store (used during SSR hydration)
|
|
80
|
+
*/
|
|
81
|
+
declare function setLoaderData(key: string, data: unknown): void;
|
|
82
|
+
/**
|
|
83
|
+
* Get loader data from store
|
|
84
|
+
*/
|
|
85
|
+
declare function getLoaderData<T>(key: string): T | undefined;
|
|
86
|
+
/**
|
|
87
|
+
* Set action data in store
|
|
88
|
+
*/
|
|
89
|
+
declare function setActionData(key: string, data: unknown): void;
|
|
90
|
+
/**
|
|
91
|
+
* Get action data from store
|
|
92
|
+
*/
|
|
93
|
+
declare function getActionData<T>(key: string): T | undefined;
|
|
94
|
+
/**
|
|
95
|
+
* Clear all data stores (for testing/cleanup)
|
|
96
|
+
*/
|
|
97
|
+
declare function clearDataStores(): void;
|
|
98
|
+
/**
|
|
99
|
+
* Universal `use()` hook - Suspense-aware promise unwrapping
|
|
100
|
+
*
|
|
101
|
+
* Works like React 19's use() hook but framework-agnostic.
|
|
102
|
+
* For React: integrates with Suspense
|
|
103
|
+
* For others: provides consistent API
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```typescript
|
|
107
|
+
* const data = use(fetchData());
|
|
108
|
+
* return <div>{data.name}</div>;
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
declare function use<T>(promise: Promise<T>): T;
|
|
112
|
+
/**
|
|
113
|
+
* useLoaderData - Access loader data
|
|
114
|
+
*
|
|
115
|
+
* Framework-aware: Uses React hooks when available,
|
|
116
|
+
* falls back to store-based implementation otherwise.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```typescript
|
|
120
|
+
* export async function loader({ params }: LoaderContext) {
|
|
121
|
+
* const user = await db.users.find(params.id);
|
|
122
|
+
* return { user };
|
|
123
|
+
* }
|
|
124
|
+
*
|
|
125
|
+
* export default function UserPage() {
|
|
126
|
+
* const { user } = useLoaderData<typeof loader>();
|
|
127
|
+
* return <h1>{user.name}</h1>;
|
|
128
|
+
* }
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
declare function useLoaderData<T extends (...args: never[]) => unknown>(): SerializeFrom<T>;
|
|
132
|
+
/**
|
|
133
|
+
* useActionData - Access action result
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```typescript
|
|
137
|
+
* export async function action({ request }: ActionContext) {
|
|
138
|
+
* const formData = await request.formData();
|
|
139
|
+
* const result = await processForm(formData);
|
|
140
|
+
* return result;
|
|
141
|
+
* }
|
|
142
|
+
*
|
|
143
|
+
* export default function FormPage() {
|
|
144
|
+
* const actionData = useActionData<typeof action>();
|
|
145
|
+
* return actionData?.error ? <p>{actionData.error}</p> : null;
|
|
146
|
+
* }
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
declare function useActionData<T>(): T | undefined;
|
|
150
|
+
/**
|
|
151
|
+
* useFetcher - Client-side data fetching
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```typescript
|
|
155
|
+
* function SearchBox() {
|
|
156
|
+
* const fetcher = useFetcher<SearchResult[]>();
|
|
157
|
+
*
|
|
158
|
+
* return (
|
|
159
|
+
* <div>
|
|
160
|
+
* <input
|
|
161
|
+
* onChange={e => fetcher.load(`/api/search?q=${e.target.value}`)}
|
|
162
|
+
* />
|
|
163
|
+
* {fetcher.state === 'loading' && <Spinner />}
|
|
164
|
+
* {fetcher.data?.map(result => (
|
|
165
|
+
* <SearchResult key={result.id} {...result} />
|
|
166
|
+
* ))}
|
|
167
|
+
* {fetcher.error && <ErrorMessage error={fetcher.error} />}
|
|
168
|
+
* </div>
|
|
169
|
+
* );
|
|
170
|
+
* }
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
declare function useFetcher<T>(): {
|
|
174
|
+
state: FetcherState<T>['state'];
|
|
175
|
+
data: T | undefined;
|
|
176
|
+
error: Error | undefined;
|
|
177
|
+
submit: (data: FormData | Record<string, string>, options?: {
|
|
178
|
+
method?: string;
|
|
179
|
+
action?: string;
|
|
180
|
+
}) => void;
|
|
181
|
+
load: (href: string) => void;
|
|
182
|
+
reset: () => void;
|
|
183
|
+
};
|
|
184
|
+
/**
|
|
185
|
+
* Create hydration script for SSR
|
|
186
|
+
* Injects loader data into the page for client hydration
|
|
187
|
+
*/
|
|
188
|
+
declare function createHydrationScript(pathname: string, loaderData: unknown, actionData?: unknown): string;
|
|
189
|
+
/**
|
|
190
|
+
* Hydrate data from SSR script (call on client initialization)
|
|
191
|
+
*/
|
|
192
|
+
declare function hydrateFromWindow(): void;
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Form Component
|
|
196
|
+
*
|
|
197
|
+
* Enhanced form that integrates with Flight actions.
|
|
198
|
+
* Handles submission, loading states, and redirects.
|
|
199
|
+
*/
|
|
200
|
+
declare let Form: unknown;
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Response Utilities
|
|
204
|
+
*
|
|
205
|
+
* Helper functions for creating standard responses in loaders and actions.
|
|
206
|
+
*/
|
|
207
|
+
/**
|
|
208
|
+
* Create a redirect response
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```ts
|
|
212
|
+
* export async function action({ request }: ActionContext) {
|
|
213
|
+
* await saveData(request);
|
|
214
|
+
* return redirect('/success');
|
|
215
|
+
* }
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
declare function redirect(url: string, init?: ResponseInit): Response;
|
|
219
|
+
/**
|
|
220
|
+
* Create a JSON response
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* ```ts
|
|
224
|
+
* export async function loader() {
|
|
225
|
+
* const data = await fetchData();
|
|
226
|
+
* return json({ data });
|
|
227
|
+
* }
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
230
|
+
declare function json<T>(data: T, init?: ResponseInit): Response;
|
|
231
|
+
/**
|
|
232
|
+
* Create a deferred response for streaming SSR
|
|
233
|
+
* Allows returning promises that resolve during streaming
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* ```ts
|
|
237
|
+
* export async function loader() {
|
|
238
|
+
* return defer({
|
|
239
|
+
* // Critical data - blocks render
|
|
240
|
+
* user: await getUser(),
|
|
241
|
+
* // Deferred data - streams in later
|
|
242
|
+
* posts: getPosts(), // No await!
|
|
243
|
+
* });
|
|
244
|
+
* }
|
|
245
|
+
* ```
|
|
246
|
+
*/
|
|
247
|
+
declare function defer<T extends Record<string, unknown>>(data: T): T;
|
|
248
|
+
/**
|
|
249
|
+
* Check if a response is a redirect
|
|
250
|
+
*/
|
|
251
|
+
declare function isRedirectResponse(response: Response): boolean;
|
|
252
|
+
/**
|
|
253
|
+
* Check if a response is JSON
|
|
254
|
+
*/
|
|
255
|
+
declare function isJsonResponse(response: Response): boolean;
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Server Utilities
|
|
259
|
+
*
|
|
260
|
+
* Functions for running loaders and actions on the server.
|
|
261
|
+
* These are used by the Flight SSR runtime.
|
|
262
|
+
*/
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Run a loader function and return the result
|
|
266
|
+
*/
|
|
267
|
+
declare function runLoader<T>(loader: LoaderFunction<T>, context: LoaderContext): Promise<T | Response>;
|
|
268
|
+
/**
|
|
269
|
+
* Run an action function and return the result
|
|
270
|
+
*/
|
|
271
|
+
declare function runAction<T>(action: ActionFunction<T>, context: ActionContext): Promise<T | Response>;
|
|
272
|
+
/**
|
|
273
|
+
* Generate script tag for hydrating loader data
|
|
274
|
+
*/
|
|
275
|
+
declare function hydrateLoaderData(pathname: string, data: unknown): string;
|
|
276
|
+
|
|
277
|
+
export { type ActionContext, type ActionFunction, Form, type LoaderContext, type LoaderFunction, type SerializeFrom, clearDataStores, createHydrationScript, defer, getActionData, getLoaderData, hydrateFromWindow, hydrateLoaderData, isJsonResponse, isRedirectResponse, json, redirect, runAction, runLoader, setActionData, setLoaderData, use, useActionData, useFetcher, useLoaderData };
|