@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/dist/index.js ADDED
@@ -0,0 +1,463 @@
1
+ import {
2
+ LRUCache,
3
+ asyncData,
4
+ cancelRequest,
5
+ clearCache,
6
+ configureCache,
7
+ createFetchHydrationScript,
8
+ fetchData,
9
+ getCache,
10
+ getCachedData,
11
+ hydrateFetchData,
12
+ invalidate,
13
+ invalidateCache,
14
+ isClient,
15
+ isServer,
16
+ prefetch,
17
+ setCachedData
18
+ } from "./chunk-NOWQL3EM.js";
19
+
20
+ // src/hooks.ts
21
+ var isBrowser = typeof window !== "undefined";
22
+ var loaderDataStore = /* @__PURE__ */ new Map();
23
+ var actionDataStore = /* @__PURE__ */ new Map();
24
+ function setLoaderData(key, data) {
25
+ loaderDataStore.set(key, data);
26
+ }
27
+ function getLoaderData(key) {
28
+ return loaderDataStore.get(key);
29
+ }
30
+ function setActionData(key, data) {
31
+ actionDataStore.set(key, data);
32
+ }
33
+ function getActionData(key) {
34
+ return actionDataStore.get(key);
35
+ }
36
+ function clearDataStores() {
37
+ loaderDataStore.clear();
38
+ actionDataStore.clear();
39
+ }
40
+ function getCurrentPathname() {
41
+ if (isBrowser) {
42
+ return window.location.pathname;
43
+ }
44
+ return "/";
45
+ }
46
+ function getHydratedLoaderData(pathname) {
47
+ if (!isBrowser) return void 0;
48
+ const hydrated = window.__FLIGHT_LOADER_DATA__;
49
+ if (!hydrated) return void 0;
50
+ const key = pathname ?? getCurrentPathname();
51
+ return hydrated[key];
52
+ }
53
+ function getHydratedActionData() {
54
+ if (!isBrowser) return void 0;
55
+ return window.__FLIGHT_ACTION_DATA__;
56
+ }
57
+ function use(promise) {
58
+ const thenable = promise;
59
+ if (thenable.status === "fulfilled") {
60
+ return thenable.value;
61
+ }
62
+ if (thenable.status === "rejected") {
63
+ throw thenable.reason;
64
+ }
65
+ if (thenable.status !== "pending") {
66
+ thenable.status = "pending";
67
+ thenable.then(
68
+ (value) => {
69
+ thenable.status = "fulfilled";
70
+ thenable.value = value;
71
+ },
72
+ (error) => {
73
+ thenable.status = "rejected";
74
+ thenable.reason = error instanceof Error ? error : new Error(String(error));
75
+ }
76
+ );
77
+ }
78
+ throw thenable;
79
+ }
80
+ var reactHooks = null;
81
+ function getReactHooks() {
82
+ if (reactHooks) return reactHooks;
83
+ let React;
84
+ if (typeof globalThis !== "undefined") {
85
+ const maybeReact = globalThis.React;
86
+ if (maybeReact && typeof maybeReact.useState === "function" && typeof maybeReact.useCallback === "function") {
87
+ React = maybeReact;
88
+ }
89
+ }
90
+ if (!React && isBrowser) {
91
+ const win = window;
92
+ if (win.React) {
93
+ const maybeReact = win.React;
94
+ if (maybeReact && typeof maybeReact.useState === "function" && typeof maybeReact.useCallback === "function") {
95
+ React = maybeReact;
96
+ }
97
+ }
98
+ }
99
+ if (!React) {
100
+ return null;
101
+ }
102
+ const { useState, useCallback } = React;
103
+ reactHooks = {
104
+ /**
105
+ * useLoaderData - Access loader data from SSR or context
106
+ */
107
+ useLoaderData: function useFlightLoaderData() {
108
+ const [data] = useState(() => {
109
+ const hydrated = getHydratedLoaderData();
110
+ if (hydrated !== void 0) return hydrated;
111
+ const stored = getLoaderData(getCurrentPathname());
112
+ if (stored !== void 0) return stored;
113
+ return void 0;
114
+ });
115
+ return data;
116
+ },
117
+ /**
118
+ * useActionData - Access action result from form submission
119
+ */
120
+ useActionData: function useFlightActionData() {
121
+ const [data] = useState(() => {
122
+ const hydrated = getHydratedActionData();
123
+ if (hydrated !== void 0) return hydrated;
124
+ const stored = getActionData(getCurrentPathname());
125
+ if (stored !== void 0) return stored;
126
+ return void 0;
127
+ });
128
+ return data;
129
+ },
130
+ /**
131
+ * useFetcher - Client-side data fetching without navigation
132
+ */
133
+ useFetcher: function useFlightFetcher() {
134
+ const [state, setState] = useState({
135
+ state: "idle",
136
+ data: void 0,
137
+ error: void 0
138
+ });
139
+ const reset = useCallback(() => {
140
+ setState({ state: "idle", data: void 0, error: void 0 });
141
+ }, []);
142
+ const submit = useCallback(async (formData, options = {}) => {
143
+ const { method = "POST", action = getCurrentPathname() } = options;
144
+ setState({ state: "loading", data: void 0, error: void 0 });
145
+ try {
146
+ const body = formData instanceof FormData ? formData : new URLSearchParams(formData);
147
+ const headers = {};
148
+ if (!(formData instanceof FormData)) {
149
+ headers["Content-Type"] = "application/x-www-form-urlencoded";
150
+ }
151
+ const response = await fetch(action, {
152
+ method,
153
+ body,
154
+ headers: Object.keys(headers).length > 0 ? headers : void 0
155
+ });
156
+ if (!response.ok) {
157
+ throw new Error(`Request failed: ${response.status} ${response.statusText}`);
158
+ }
159
+ const contentType = response.headers.get("Content-Type") ?? "";
160
+ let result;
161
+ if (contentType.includes("application/json")) {
162
+ result = await response.json();
163
+ } else {
164
+ result = await response.text();
165
+ }
166
+ setState({ state: "success", data: result, error: void 0 });
167
+ } catch (error) {
168
+ setState({
169
+ state: "error",
170
+ data: void 0,
171
+ error: error instanceof Error ? error : new Error(String(error))
172
+ });
173
+ }
174
+ }, []);
175
+ const load = useCallback(async (href) => {
176
+ setState({ state: "loading", data: void 0, error: void 0 });
177
+ try {
178
+ const response = await fetch(href, {
179
+ headers: { "Accept": "application/json" }
180
+ });
181
+ if (!response.ok) {
182
+ throw new Error(`Request failed: ${response.status} ${response.statusText}`);
183
+ }
184
+ const contentType = response.headers.get("Content-Type") ?? "";
185
+ let result;
186
+ if (contentType.includes("application/json")) {
187
+ result = await response.json();
188
+ } else {
189
+ result = await response.text();
190
+ }
191
+ setState({ state: "success", data: result, error: void 0 });
192
+ } catch (error) {
193
+ setState({
194
+ state: "error",
195
+ data: void 0,
196
+ error: error instanceof Error ? error : new Error(String(error))
197
+ });
198
+ }
199
+ }, []);
200
+ return {
201
+ state: state.state,
202
+ data: state.state === "success" ? state.data : void 0,
203
+ error: state.state === "error" ? state.error : void 0,
204
+ submit,
205
+ load,
206
+ reset
207
+ };
208
+ }
209
+ };
210
+ return reactHooks;
211
+ }
212
+ function useLoaderData() {
213
+ const hooks = getReactHooks();
214
+ if (hooks) {
215
+ return hooks.useLoaderData();
216
+ }
217
+ const hydrated = getHydratedLoaderData();
218
+ if (hydrated !== void 0) return hydrated;
219
+ return getLoaderData(getCurrentPathname());
220
+ }
221
+ function useActionData() {
222
+ const hooks = getReactHooks();
223
+ if (hooks) {
224
+ return hooks.useActionData();
225
+ }
226
+ const hydrated = getHydratedActionData();
227
+ if (hydrated !== void 0) return hydrated;
228
+ return getActionData(getCurrentPathname());
229
+ }
230
+ function useFetcher() {
231
+ const hooks = getReactHooks();
232
+ if (hooks) {
233
+ return hooks.useFetcher();
234
+ }
235
+ console.warn("[Flight] useFetcher called outside React context. Returning static implementation.");
236
+ return {
237
+ state: "idle",
238
+ data: void 0,
239
+ error: void 0,
240
+ submit: () => {
241
+ console.warn("[Flight] useFetcher.submit() called outside React context");
242
+ },
243
+ load: () => {
244
+ console.warn("[Flight] useFetcher.load() called outside React context");
245
+ },
246
+ reset: () => {
247
+ }
248
+ };
249
+ }
250
+ function createHydrationScript(pathname, loaderData, actionData) {
251
+ const script = ["<script>"];
252
+ if (loaderData !== void 0) {
253
+ script.push(
254
+ `window.__FLIGHT_LOADER_DATA__ = window.__FLIGHT_LOADER_DATA__ || {};`,
255
+ `window.__FLIGHT_LOADER_DATA__[${JSON.stringify(pathname)}] = ${JSON.stringify(loaderData)};`
256
+ );
257
+ }
258
+ if (actionData !== void 0) {
259
+ script.push(
260
+ `window.__FLIGHT_ACTION_DATA__ = ${JSON.stringify(actionData)};`
261
+ );
262
+ }
263
+ script.push("</script>");
264
+ return script.join("\n");
265
+ }
266
+ function hydrateFromWindow() {
267
+ if (!isBrowser) return;
268
+ const loaderData = window.__FLIGHT_LOADER_DATA__;
269
+ if (loaderData) {
270
+ for (const [key, value] of Object.entries(loaderData)) {
271
+ setLoaderData(key, value);
272
+ }
273
+ }
274
+ const actionData = window.__FLIGHT_ACTION_DATA__;
275
+ if (actionData !== void 0) {
276
+ setActionData(getCurrentPathname(), actionData);
277
+ }
278
+ }
279
+
280
+ // src/form.ts
281
+ var isBrowser2 = typeof window !== "undefined";
282
+ var Form = null;
283
+ if (typeof globalThis !== "undefined") {
284
+ try {
285
+ const React = globalThis.React;
286
+ if (React?.createElement) {
287
+ const { useState, useCallback } = React;
288
+ Form = function FlightForm({
289
+ action,
290
+ method = "post",
291
+ children,
292
+ className,
293
+ onSuccess,
294
+ onError,
295
+ replace = false,
296
+ encType = "application/x-www-form-urlencoded"
297
+ }) {
298
+ const [isSubmitting, setIsSubmitting] = useState(false);
299
+ const handleSubmit = useCallback(async (e) => {
300
+ e.preventDefault();
301
+ if (!isBrowser2) return;
302
+ const form = e.target;
303
+ const formData = new FormData(form);
304
+ const targetAction = action || form.action || window.location.pathname;
305
+ const targetMethod = method.toUpperCase();
306
+ setIsSubmitting(true);
307
+ try {
308
+ const response = await fetch(targetAction, {
309
+ method: targetMethod,
310
+ body: encType === "multipart/form-data" ? formData : new URLSearchParams(formData),
311
+ headers: encType === "multipart/form-data" ? void 0 : { "Content-Type": "application/x-www-form-urlencoded" }
312
+ });
313
+ if (response.redirected) {
314
+ const redirectUrl = response.url;
315
+ if (replace) {
316
+ window.history.replaceState(null, "", redirectUrl);
317
+ } else {
318
+ window.history.pushState(null, "", redirectUrl);
319
+ }
320
+ window.dispatchEvent(new PopStateEvent("popstate"));
321
+ window.scrollTo({ top: 0, left: 0, behavior: "instant" });
322
+ return;
323
+ }
324
+ const location = response.headers.get("Location");
325
+ if (location) {
326
+ if (replace) {
327
+ window.history.replaceState(null, "", location);
328
+ } else {
329
+ window.history.pushState(null, "", location);
330
+ }
331
+ window.dispatchEvent(new PopStateEvent("popstate"));
332
+ window.scrollTo({ top: 0, left: 0, behavior: "instant" });
333
+ return;
334
+ }
335
+ if (!response.ok) {
336
+ throw new Error(`Form submission failed: ${response.status}`);
337
+ }
338
+ const contentType = response.headers.get("Content-Type") || "";
339
+ let data;
340
+ if (contentType.includes("application/json")) {
341
+ data = await response.json();
342
+ } else {
343
+ data = await response.text();
344
+ }
345
+ window.__FLIGHT_ACTION_DATA__ = data;
346
+ onSuccess?.(data);
347
+ } catch (error) {
348
+ const err = error instanceof Error ? error : new Error(String(error));
349
+ onError?.(err);
350
+ } finally {
351
+ setIsSubmitting(false);
352
+ }
353
+ }, [action, method, encType, replace, onSuccess, onError]);
354
+ return React.createElement("form", {
355
+ action,
356
+ method,
357
+ className,
358
+ encType,
359
+ onSubmit: handleSubmit,
360
+ "data-submitting": isSubmitting || void 0
361
+ }, children);
362
+ };
363
+ }
364
+ } catch {
365
+ }
366
+ }
367
+
368
+ // src/response.ts
369
+ function redirect(url, init) {
370
+ const headers = new Headers(init?.headers);
371
+ headers.set("Location", url);
372
+ return new Response(null, {
373
+ ...init,
374
+ status: init?.status || 302,
375
+ headers
376
+ });
377
+ }
378
+ function json(data, init) {
379
+ const headers = new Headers(init?.headers);
380
+ headers.set("Content-Type", "application/json; charset=utf-8");
381
+ return new Response(JSON.stringify(data), {
382
+ ...init,
383
+ headers
384
+ });
385
+ }
386
+ function defer(data) {
387
+ return Object.assign(data, { __deferred: true });
388
+ }
389
+ function isRedirectResponse(response) {
390
+ return response.status >= 300 && response.status < 400 && response.headers.has("Location");
391
+ }
392
+ function isJsonResponse(response) {
393
+ const contentType = response.headers.get("Content-Type") || "";
394
+ return contentType.includes("application/json");
395
+ }
396
+
397
+ // src/server.ts
398
+ async function runLoader(loader, context) {
399
+ try {
400
+ const result = await loader(context);
401
+ return result;
402
+ } catch (error) {
403
+ console.error("Loader error:", error);
404
+ throw error;
405
+ }
406
+ }
407
+ async function runAction(action, context) {
408
+ try {
409
+ const result = await action(context);
410
+ if (result instanceof Response) {
411
+ return result;
412
+ }
413
+ return result;
414
+ } catch (error) {
415
+ console.error("Action error:", error);
416
+ throw error;
417
+ }
418
+ }
419
+ function hydrateLoaderData(pathname, data) {
420
+ const serialized = JSON.stringify(data).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
421
+ return `<script>
422
+ window.__FLIGHT_LOADER_DATA__ = window.__FLIGHT_LOADER_DATA__ || {};
423
+ window.__FLIGHT_LOADER_DATA__["${pathname}"] = ${serialized};
424
+ </script>`;
425
+ }
426
+ export {
427
+ Form,
428
+ LRUCache,
429
+ asyncData,
430
+ cancelRequest,
431
+ clearCache,
432
+ clearDataStores,
433
+ configureCache,
434
+ createFetchHydrationScript,
435
+ createHydrationScript,
436
+ defer,
437
+ fetchData,
438
+ getActionData,
439
+ getCache,
440
+ getCachedData,
441
+ getLoaderData,
442
+ hydrateFetchData,
443
+ hydrateFromWindow,
444
+ hydrateLoaderData,
445
+ invalidate,
446
+ invalidateCache,
447
+ isClient,
448
+ isJsonResponse,
449
+ isRedirectResponse,
450
+ isServer,
451
+ json,
452
+ prefetch,
453
+ redirect,
454
+ runAction,
455
+ runLoader,
456
+ setActionData,
457
+ setCachedData,
458
+ setLoaderData,
459
+ use,
460
+ useActionData,
461
+ useFetcher,
462
+ useLoaderData
463
+ };
@@ -0,0 +1,57 @@
1
+ import { U as UseFetchOptions, k as UseFetchReturn, l as UseAsyncDataOptions, m as UseAsyncDataReturn } from './cache-DU1v4CKe.js';
2
+ export { q as clearCache, o as configureCache, g as getCachedData, i as invalidate, r as invalidateCache, p as prefetch, s as setCachedData } from './cache-DU1v4CKe.js';
3
+
4
+ /**
5
+ * @flightdev/data - React Adapter
6
+ *
7
+ * React hooks for data fetching using Flight's core.
8
+ * Uses React 18+ hooks: useState, useEffect, useCallback, useRef.
9
+ *
10
+ * @module @flightdev/data/react
11
+ */
12
+
13
+ /**
14
+ * React hook for data fetching with caching and deduplication
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * function Users() {
19
+ * const { data, pending, error, refresh } = useFetch<User[]>('/api/users');
20
+ *
21
+ * if (pending) return <p>Loading...</p>;
22
+ * if (error) return <p>Error: {error.message}</p>;
23
+ *
24
+ * return <ul>{data.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
25
+ * }
26
+ * ```
27
+ */
28
+ declare function useFetch<T = unknown>(url: string | null, options?: UseFetchOptions<T>): UseFetchReturn<T>;
29
+ /**
30
+ * React hook for custom async data fetching
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * function Analytics() {
35
+ * const { data, pending, execute } = useAsyncData(
36
+ * 'analytics-weekly',
37
+ * () => analyticsAPI.getWeeklyReport()
38
+ * );
39
+ *
40
+ * return (
41
+ * <div>
42
+ * <button onClick={execute}>Refresh</button>
43
+ * {pending ? <Spinner /> : <Chart data={data} />}
44
+ * </div>
45
+ * );
46
+ * }
47
+ * ```
48
+ */
49
+ declare function useAsyncData<T = unknown>(key: string, fetcher: () => Promise<T>, options?: UseAsyncDataOptions<T>): UseAsyncDataReturn<T>;
50
+
51
+ /**
52
+ * Hydrate data on client startup
53
+ * Call this before React hydration
54
+ */
55
+ declare function hydrateFlightData(): void;
56
+
57
+ export { hydrateFlightData, useAsyncData, useFetch };
package/dist/react.js ADDED
@@ -0,0 +1,119 @@
1
+ import {
2
+ asyncData,
3
+ clearCache,
4
+ configureCache,
5
+ fetchData,
6
+ getCachedData,
7
+ hydrateFetchData,
8
+ invalidate,
9
+ invalidateCache,
10
+ prefetch,
11
+ setCachedData
12
+ } from "./chunk-NOWQL3EM.js";
13
+
14
+ // src/react.ts
15
+ import { useState, useEffect, useCallback, useRef } from "react";
16
+ var isBrowser = typeof window !== "undefined";
17
+ function useFetch(url, options = {}) {
18
+ const { immediate = true } = options;
19
+ const optionsRef = useRef(options);
20
+ optionsRef.current = options;
21
+ const [state, setState] = useState({
22
+ data: options.default,
23
+ error: void 0,
24
+ status: "idle",
25
+ isStale: false
26
+ });
27
+ const [pending, setPending] = useState(immediate && !!url);
28
+ const execute = useCallback(async () => {
29
+ if (!url) return;
30
+ setPending(true);
31
+ const result = await fetchData(url, optionsRef.current);
32
+ setState(result);
33
+ setPending(false);
34
+ }, [url]);
35
+ const refresh = useCallback(async () => {
36
+ if (!url) return;
37
+ const key = optionsRef.current.key ?? url;
38
+ invalidate(key);
39
+ await execute();
40
+ }, [url, execute]);
41
+ useEffect(() => {
42
+ if (immediate && url) {
43
+ execute();
44
+ }
45
+ }, [immediate, url, execute]);
46
+ useEffect(() => {
47
+ if (!optionsRef.current.revalidateOnFocus || !isBrowser) return;
48
+ const onFocus = () => execute();
49
+ window.addEventListener("focus", onFocus);
50
+ return () => window.removeEventListener("focus", onFocus);
51
+ }, [execute]);
52
+ useEffect(() => {
53
+ if (!optionsRef.current.revalidateOnReconnect || !isBrowser) return;
54
+ const onOnline = () => execute();
55
+ window.addEventListener("online", onOnline);
56
+ return () => window.removeEventListener("online", onOnline);
57
+ }, [execute]);
58
+ return {
59
+ data: state.data,
60
+ pending,
61
+ error: state.error,
62
+ status: state.status,
63
+ isStale: state.isStale,
64
+ refresh,
65
+ execute
66
+ };
67
+ }
68
+ function useAsyncData(key, fetcher, options = {}) {
69
+ const { immediate = true } = options;
70
+ const optionsRef = useRef(options);
71
+ const fetcherRef = useRef(fetcher);
72
+ optionsRef.current = options;
73
+ fetcherRef.current = fetcher;
74
+ const [state, setState] = useState({
75
+ data: options.default,
76
+ error: void 0,
77
+ status: "idle",
78
+ isStale: false
79
+ });
80
+ const [pending, setPending] = useState(immediate);
81
+ const execute = useCallback(async () => {
82
+ setPending(true);
83
+ const result = await asyncData(key, fetcherRef.current, optionsRef.current);
84
+ setState(result);
85
+ setPending(false);
86
+ }, [key]);
87
+ const refresh = useCallback(async () => {
88
+ invalidate(key);
89
+ await execute();
90
+ }, [key, execute]);
91
+ useEffect(() => {
92
+ if (immediate) {
93
+ execute();
94
+ }
95
+ }, [immediate, execute]);
96
+ return {
97
+ data: state.data,
98
+ pending,
99
+ error: state.error,
100
+ status: state.status,
101
+ execute,
102
+ refresh
103
+ };
104
+ }
105
+ function hydrateFlightData() {
106
+ hydrateFetchData();
107
+ }
108
+ export {
109
+ clearCache,
110
+ configureCache,
111
+ getCachedData,
112
+ hydrateFlightData,
113
+ invalidate,
114
+ invalidateCache,
115
+ prefetch,
116
+ setCachedData,
117
+ useAsyncData,
118
+ useFetch
119
+ };