@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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2026 Flight 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,265 @@
1
+ # @flight-framework/data
2
+
3
+ Data fetching primitives for Flight Framework.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @flight-framework/data
9
+ ```
10
+
11
+ ## Loaders
12
+
13
+ Loaders run on the server before rendering a page. They fetch data that the page needs.
14
+
15
+ ```tsx
16
+ // src/routes/posts/[slug].page.tsx
17
+ import { useLoaderData } from '@flight-framework/data';
18
+
19
+ export async function loader({ params }: LoaderContext) {
20
+ const post = await db.posts.findUnique({
21
+ where: { slug: params.slug }
22
+ });
23
+
24
+ if (!post) {
25
+ throw new Response('Not Found', { status: 404 });
26
+ }
27
+
28
+ return { post };
29
+ }
30
+
31
+ export default function PostPage() {
32
+ const { post } = useLoaderData<typeof loader>();
33
+
34
+ return (
35
+ <article>
36
+ <h1>{post.title}</h1>
37
+ <div>{post.content}</div>
38
+ </article>
39
+ );
40
+ }
41
+ ```
42
+
43
+ ## Actions
44
+
45
+ Actions handle form submissions and mutations. They run on the server when a form is submitted.
46
+
47
+ ```tsx
48
+ import { redirect, Form } from '@flight-framework/data';
49
+
50
+ export async function action({ request }: ActionContext) {
51
+ const formData = await request.formData();
52
+
53
+ await db.posts.create({
54
+ data: {
55
+ title: formData.get('title') as string,
56
+ content: formData.get('content') as string,
57
+ }
58
+ });
59
+
60
+ return redirect('/posts');
61
+ }
62
+
63
+ export default function NewPostPage() {
64
+ return (
65
+ <Form method="post">
66
+ <input name="title" placeholder="Title" required />
67
+ <textarea name="content" placeholder="Content" />
68
+ <button type="submit">Create Post</button>
69
+ </Form>
70
+ );
71
+ }
72
+ ```
73
+
74
+ ## useFetcher
75
+
76
+ For data fetching without navigation:
77
+
78
+ ```tsx
79
+ import { useFetcher } from '@flight-framework/data';
80
+
81
+ function SearchBox() {
82
+ const fetcher = useFetcher<SearchResult[]>();
83
+
84
+ return (
85
+ <div>
86
+ <input
87
+ type="search"
88
+ onChange={(e) => fetcher.load(`/api/search?q=${e.target.value}`)}
89
+ />
90
+
91
+ {fetcher.state === 'loading' && <Spinner />}
92
+
93
+ {fetcher.data?.map(result => (
94
+ <SearchResultItem key={result.id} result={result} />
95
+ ))}
96
+ </div>
97
+ );
98
+ }
99
+ ```
100
+
101
+ ## Response Utilities
102
+
103
+ ```ts
104
+ import { redirect, json, notFound, badRequest } from '@flight-framework/data';
105
+
106
+ // Redirect
107
+ return redirect('/dashboard');
108
+
109
+ // JSON response
110
+ return json({ success: true });
111
+
112
+ // 404 Not Found
113
+ return notFound();
114
+
115
+ // 400 Bad Request
116
+ return badRequest({ error: 'Invalid input' });
117
+ ```
118
+
119
+ ## Type Safety
120
+
121
+ The `SerializeFrom` utility type extracts the return type of a loader:
122
+
123
+ ```tsx
124
+ import type { SerializeFrom } from '@flight-framework/data';
125
+
126
+ export async function loader() {
127
+ return { posts: await getPosts() };
128
+ }
129
+
130
+ // Type is: { posts: Post[] }
131
+ type LoaderData = SerializeFrom<typeof loader>;
132
+ ```
133
+
134
+ ## SSR Integration
135
+
136
+ Loader data is automatically hydrated from SSR to client:
137
+
138
+ ```tsx
139
+ // entry-server.tsx
140
+ import { hydrateLoaderData } from '@flight-framework/data';
141
+
142
+ const html = `
143
+ <html>
144
+ <body>
145
+ <div id="root">${content}</div>
146
+ ${hydrateLoaderData(url, loaderData)}
147
+ </body>
148
+ </html>
149
+ `;
150
+ ```
151
+
152
+ ---
153
+
154
+ ## Client-Side Data Fetching
155
+
156
+ For client-side data fetching with caching and deduplication, Flight provides `useFetch` and `useAsyncData`.
157
+
158
+ ### useFetch
159
+
160
+ Simple, cached data fetching:
161
+
162
+ ```tsx
163
+ // React
164
+ import { useFetch } from '@flight-framework/data/react';
165
+
166
+ function Users() {
167
+ const { data, pending, error, refresh } = useFetch<User[]>('/api/users');
168
+
169
+ if (pending) return <p>Loading...</p>;
170
+ if (error) return <p>Error: {error.message}</p>;
171
+
172
+ return (
173
+ <div>
174
+ <button onClick={refresh}>Refresh</button>
175
+ <ul>{data.map(u => <li key={u.id}>{u.name}</li>)}</ul>
176
+ </div>
177
+ );
178
+ }
179
+ ```
180
+
181
+ ### Framework Adapters
182
+
183
+ Import from the adapter for your framework:
184
+
185
+ ```typescript
186
+ // React
187
+ import { useFetch, useAsyncData } from '@flight-framework/data/react';
188
+
189
+ // Vue
190
+ import { useFetch, useAsyncData } from '@flight-framework/data/vue';
191
+
192
+ // Svelte
193
+ import { useFetch, useAsyncData } from '@flight-framework/data/svelte';
194
+
195
+ // Solid
196
+ import { useFetch, useAsyncData } from '@flight-framework/data/solid';
197
+ ```
198
+
199
+ ### useAsyncData
200
+
201
+ For custom fetching logic:
202
+
203
+ ```tsx
204
+ import { useAsyncData } from '@flight-framework/data/react';
205
+
206
+ function Analytics() {
207
+ const { data, pending, execute } = useAsyncData(
208
+ 'analytics-weekly',
209
+ async () => await analyticsAPI.getWeeklyReport()
210
+ );
211
+
212
+ return (
213
+ <div>
214
+ <button onClick={execute}>Refresh</button>
215
+ {pending ? <Spinner /> : <Chart data={data} />}
216
+ </div>
217
+ );
218
+ }
219
+ ```
220
+
221
+ ### Options
222
+
223
+ ```typescript
224
+ useFetch('/api/data', {
225
+ key: 'custom-key', // Cache key (default: url)
226
+ immediate: true, // Fetch on mount (default: true)
227
+ default: [], // Default value while loading
228
+ cache: true, // Enable caching (default: true)
229
+ dedupe: true, // Deduplicate requests (default: true)
230
+ maxAge: 60000, // Cache TTL in ms
231
+ revalidateOnFocus: true // Re-fetch on window focus
232
+ });
233
+ ```
234
+
235
+ ### Cache Utilities
236
+
237
+ ```typescript
238
+ import { invalidate, prefetch, clearCache } from '@flight-framework/data';
239
+
240
+ // Invalidate specific key
241
+ invalidate('/api/users');
242
+
243
+ // Invalidate by pattern
244
+ invalidate(key => key.startsWith('/api/'));
245
+
246
+ // Prefetch data
247
+ await prefetch('/api/users');
248
+
249
+ // Clear all cache
250
+ clearCache();
251
+ ```
252
+
253
+ ### Features
254
+
255
+ | Feature | Description |
256
+ |---------|-------------|
257
+ | LRU Cache | O(1) operations, memory-bounded |
258
+ | Deduplication | Multiple calls share one request |
259
+ | Stale-While-Revalidate | Instant stale data, background refresh |
260
+ | SSR Hydration | Seamless server-to-client transfer |
261
+ | Framework Agnostic | React, Vue, Svelte, Solid |
262
+
263
+ ## License
264
+
265
+ MIT
@@ -0,0 +1,216 @@
1
+ /**
2
+ * @flightdev/data - Fetcher
3
+ *
4
+ * High-performance data fetching with:
5
+ * - Request deduplication
6
+ * - Stale-while-revalidate
7
+ * - SSR hydration
8
+ * - Framework-agnostic core
9
+ *
10
+ * Zero external dependencies.
11
+ *
12
+ * @module @flightdev/data/fetcher
13
+ */
14
+ type FetchStatus = 'idle' | 'pending' | 'success' | 'error';
15
+ interface FetchState<T> {
16
+ data: T | undefined;
17
+ error: Error | undefined;
18
+ status: FetchStatus;
19
+ isStale: boolean;
20
+ }
21
+ interface UseFetchOptions<T> {
22
+ /** Cache key (default: url) */
23
+ key?: string;
24
+ /** Fetch immediately on mount (default: true) */
25
+ immediate?: boolean;
26
+ /** Default value while loading */
27
+ default?: T;
28
+ /** Transform response data */
29
+ transform?: (data: unknown) => T;
30
+ /** Enable caching (default: true) */
31
+ cache?: boolean;
32
+ /** Enable request deduplication (default: true) */
33
+ dedupe?: boolean;
34
+ /** Cache TTL in milliseconds */
35
+ maxAge?: number;
36
+ /** Custom fetch options */
37
+ fetchOptions?: RequestInit;
38
+ /** Revalidate on window focus */
39
+ revalidateOnFocus?: boolean;
40
+ /** Revalidate on reconnect */
41
+ revalidateOnReconnect?: boolean;
42
+ }
43
+ interface UseFetchReturn<T> {
44
+ data: T | undefined;
45
+ pending: boolean;
46
+ error: Error | undefined;
47
+ status: FetchStatus;
48
+ isStale: boolean;
49
+ refresh: () => Promise<void>;
50
+ execute: () => Promise<void>;
51
+ }
52
+ interface UseAsyncDataOptions<T> {
53
+ /** Default value while loading */
54
+ default?: T;
55
+ /** Fetch immediately (default: true) */
56
+ immediate?: boolean;
57
+ /** Transform response data */
58
+ transform?: (data: unknown) => T;
59
+ /** Cache TTL in milliseconds */
60
+ maxAge?: number;
61
+ }
62
+ interface UseAsyncDataReturn<T> {
63
+ data: T | undefined;
64
+ pending: boolean;
65
+ error: Error | undefined;
66
+ status: FetchStatus;
67
+ execute: () => Promise<void>;
68
+ refresh: () => Promise<void>;
69
+ }
70
+ /**
71
+ * Cancel an in-flight request
72
+ */
73
+ declare function cancelRequest(key: string): boolean;
74
+ interface FetchDataOptions<T> extends UseFetchOptions<T> {
75
+ signal?: AbortSignal;
76
+ }
77
+ /**
78
+ * Core fetch function - framework agnostic
79
+ */
80
+ declare function fetchData<T>(url: string | null, options?: FetchDataOptions<T>): Promise<FetchState<T>>;
81
+ /**
82
+ * Core async data function - framework agnostic
83
+ */
84
+ declare function asyncData<T>(key: string, fetcher: () => Promise<T>, options?: UseAsyncDataOptions<T>): Promise<FetchState<T>>;
85
+ /**
86
+ * Invalidate cache entries
87
+ */
88
+ declare function invalidate(pattern: string | RegExp | ((key: string) => boolean)): number;
89
+ /**
90
+ * Prefetch data and store in cache
91
+ */
92
+ declare function prefetch<T>(url: string, options?: UseFetchOptions<T>): Promise<void>;
93
+ /**
94
+ * Get data from cache without fetching
95
+ */
96
+ declare function getCachedData<T>(key: string): T | undefined;
97
+ /**
98
+ * Manually set cache data
99
+ */
100
+ declare function setCachedData<T>(key: string, data: T, options?: {
101
+ maxAge?: number;
102
+ }): void;
103
+ interface HydrationData {
104
+ [key: string]: unknown;
105
+ }
106
+ /**
107
+ * Create hydration script for SSR
108
+ */
109
+ declare function createFetchHydrationScript(data: HydrationData): string;
110
+ /**
111
+ * Hydrate cache from SSR data
112
+ */
113
+ declare function hydrateFetchData(): void;
114
+ declare const isServer: boolean;
115
+ declare const isClient: boolean;
116
+
117
+ /**
118
+ * @flightdev/data - LRU Cache
119
+ *
120
+ * High-performance LRU (Least Recently Used) cache with O(1) operations.
121
+ * Uses doubly-linked list + Map for optimal performance.
122
+ *
123
+ * Zero external dependencies.
124
+ *
125
+ * @module @flightdev/data/cache
126
+ */
127
+ interface CacheOptions {
128
+ /** Maximum number of entries (default: 100) */
129
+ maxSize?: number;
130
+ /** Default TTL in milliseconds (default: 5 minutes) */
131
+ defaultMaxAge?: number;
132
+ }
133
+ interface SetOptions {
134
+ /** TTL in milliseconds for this entry */
135
+ maxAge?: number;
136
+ }
137
+ /**
138
+ * High-performance LRU Cache
139
+ *
140
+ * - O(1) get, set, delete operations
141
+ * - Memory-bounded with auto-eviction
142
+ * - TTL support per entry
143
+ * - Zero dependencies
144
+ *
145
+ * @example
146
+ * ```typescript
147
+ * const cache = new LRUCache<User>({ maxSize: 100 });
148
+ * cache.set('user:1', userData, { maxAge: 60000 });
149
+ * const user = cache.get('user:1');
150
+ * ```
151
+ */
152
+ declare class LRUCache<T = unknown> {
153
+ private map;
154
+ private head;
155
+ private tail;
156
+ private size;
157
+ private readonly maxSize;
158
+ private readonly defaultMaxAge;
159
+ constructor(options?: CacheOptions);
160
+ /**
161
+ * Get value from cache
162
+ * Returns undefined if not found or expired
163
+ */
164
+ get(key: string): T | undefined;
165
+ /**
166
+ * Set value in cache
167
+ */
168
+ set(key: string, value: T, options?: SetOptions): void;
169
+ /**
170
+ * Delete entry from cache
171
+ */
172
+ delete(key: string): boolean;
173
+ /**
174
+ * Check if key exists and is not expired
175
+ */
176
+ has(key: string): boolean;
177
+ /**
178
+ * Clear all entries
179
+ */
180
+ clear(): void;
181
+ /**
182
+ * Get current cache size
183
+ */
184
+ getSize(): number;
185
+ /**
186
+ * Get all keys (for invalidation patterns)
187
+ */
188
+ keys(): string[];
189
+ /**
190
+ * Invalidate entries matching a pattern
191
+ */
192
+ invalidate(pattern: string | RegExp | ((key: string) => boolean)): number;
193
+ private isExpired;
194
+ private addToFront;
195
+ private removeFromList;
196
+ private moveToFront;
197
+ private evictLRU;
198
+ }
199
+ /**
200
+ * Get the global cache instance
201
+ */
202
+ declare function getCache<T = unknown>(): LRUCache<T>;
203
+ /**
204
+ * Configure the global cache
205
+ */
206
+ declare function configureCache(options: CacheOptions): void;
207
+ /**
208
+ * Clear the global cache
209
+ */
210
+ declare function clearCache(): void;
211
+ /**
212
+ * Invalidate cache entries matching pattern
213
+ */
214
+ declare function invalidateCache(pattern: string | RegExp | ((key: string) => boolean)): number;
215
+
216
+ export { type CacheOptions as C, type FetchStatus as F, LRUCache as L, type SetOptions as S, type UseFetchOptions as U, asyncData as a, createFetchHydrationScript as b, cancelRequest as c, isServer as d, isClient as e, fetchData as f, getCachedData as g, hydrateFetchData as h, invalidate as i, type FetchState as j, type UseFetchReturn as k, type UseAsyncDataOptions as l, type UseAsyncDataReturn as m, getCache as n, configureCache as o, prefetch as p, clearCache as q, invalidateCache as r, setCachedData as s };