@flight-framework/core 0.0.1
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/dist/actions/index.d.ts +108 -0
- package/dist/actions/index.js +3 -0
- package/dist/actions/index.js.map +1 -0
- package/dist/adapters/index.d.ts +243 -0
- package/dist/adapters/index.js +3 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/cache/index.d.ts +76 -0
- package/dist/cache/index.js +3 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/chunk-3AIQVGTM.js +120 -0
- package/dist/chunk-3AIQVGTM.js.map +1 -0
- package/dist/chunk-AFSKXC6V.js +188 -0
- package/dist/chunk-AFSKXC6V.js.map +1 -0
- package/dist/chunk-AJ3IBYXT.js +47 -0
- package/dist/chunk-AJ3IBYXT.js.map +1 -0
- package/dist/chunk-GCQZ4FHI.js +245 -0
- package/dist/chunk-GCQZ4FHI.js.map +1 -0
- package/dist/chunk-I5RHYGX6.js +167 -0
- package/dist/chunk-I5RHYGX6.js.map +1 -0
- package/dist/chunk-KWFX6WHG.js +311 -0
- package/dist/chunk-KWFX6WHG.js.map +1 -0
- package/dist/chunk-Q4C5CCHK.js +13 -0
- package/dist/chunk-Q4C5CCHK.js.map +1 -0
- package/dist/chunk-QEFGUHYD.js +221 -0
- package/dist/chunk-QEFGUHYD.js.map +1 -0
- package/dist/chunk-TKXN7KGE.js +145 -0
- package/dist/chunk-TKXN7KGE.js.map +1 -0
- package/dist/chunk-WAGCTWGY.js +93 -0
- package/dist/chunk-WAGCTWGY.js.map +1 -0
- package/dist/chunk-Y22KEW2F.js +223 -0
- package/dist/chunk-Y22KEW2F.js.map +1 -0
- package/dist/chunk-ZVC3ZWLM.js +52 -0
- package/dist/chunk-ZVC3ZWLM.js.map +1 -0
- package/dist/config/index.d.ts +146 -0
- package/dist/config/index.js +3 -0
- package/dist/config/index.js.map +1 -0
- package/dist/file-router/index.d.ts +71 -0
- package/dist/file-router/index.js +3 -0
- package/dist/file-router/index.js.map +1 -0
- package/dist/handlers/index.d.ts +59 -0
- package/dist/handlers/index.js +3 -0
- package/dist/handlers/index.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/index.d.ts +152 -0
- package/dist/middleware/index.js +3 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/render/index.d.ts +131 -0
- package/dist/render/index.js +3 -0
- package/dist/render/index.js.map +1 -0
- package/dist/router/index.d.ts +65 -0
- package/dist/router/index.js +3 -0
- package/dist/router/index.js.map +1 -0
- package/dist/rsc/index.d.ts +131 -0
- package/dist/rsc/index.js +3 -0
- package/dist/rsc/index.js.map +1 -0
- package/dist/server/index.d.ts +135 -0
- package/dist/server/index.js +6 -0
- package/dist/server/index.js.map +1 -0
- package/dist/streaming/index.d.ts +169 -0
- package/dist/streaming/index.js +3 -0
- package/dist/streaming/index.js.map +1 -0
- package/package.json +100 -0
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.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @flight/core - Server Actions
|
|
3
|
+
*
|
|
4
|
+
* Implementation of React 19 style Server Actions.
|
|
5
|
+
* Functions marked with 'use server' run on the server.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Server action metadata
|
|
9
|
+
*/
|
|
10
|
+
interface ServerAction {
|
|
11
|
+
/** Unique action ID */
|
|
12
|
+
id: string;
|
|
13
|
+
/** Original function name */
|
|
14
|
+
name: string;
|
|
15
|
+
/** File path where action is defined */
|
|
16
|
+
filePath: string;
|
|
17
|
+
/** The actual action function */
|
|
18
|
+
fn: (...args: unknown[]) => Promise<unknown>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Server action result
|
|
22
|
+
*/
|
|
23
|
+
interface ActionResult<T = unknown> {
|
|
24
|
+
success: boolean;
|
|
25
|
+
data?: T;
|
|
26
|
+
error?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Form action data
|
|
30
|
+
*/
|
|
31
|
+
interface FormActionData {
|
|
32
|
+
actionId: string;
|
|
33
|
+
formData: FormData;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Register a server action
|
|
37
|
+
*/
|
|
38
|
+
declare function registerAction(action: ServerAction): void;
|
|
39
|
+
/**
|
|
40
|
+
* Get a registered action by ID
|
|
41
|
+
*/
|
|
42
|
+
declare function getAction(id: string): ServerAction | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Get all registered actions
|
|
45
|
+
*/
|
|
46
|
+
declare function getAllActions(): ServerAction[];
|
|
47
|
+
/**
|
|
48
|
+
* Clear all registered actions (useful for testing)
|
|
49
|
+
*/
|
|
50
|
+
declare function clearActions(): void;
|
|
51
|
+
/**
|
|
52
|
+
* Execute a server action
|
|
53
|
+
*/
|
|
54
|
+
declare function executeAction<T = unknown>(actionId: string, args: unknown[]): Promise<ActionResult<T>>;
|
|
55
|
+
/**
|
|
56
|
+
* Execute a form action
|
|
57
|
+
*/
|
|
58
|
+
declare function executeFormAction<T = unknown>(actionId: string, formData: FormData): Promise<ActionResult<T>>;
|
|
59
|
+
/**
|
|
60
|
+
* Generate a unique action ID
|
|
61
|
+
*/
|
|
62
|
+
declare function generateActionId(name: string, filePath: string): string;
|
|
63
|
+
/**
|
|
64
|
+
* Get cookies in server action context
|
|
65
|
+
*/
|
|
66
|
+
declare function cookies(): {
|
|
67
|
+
get: (name: string) => string | undefined;
|
|
68
|
+
set: (name: string, value: string, options?: CookieOptions) => void;
|
|
69
|
+
delete: (name: string) => void;
|
|
70
|
+
};
|
|
71
|
+
interface CookieOptions {
|
|
72
|
+
maxAge?: number;
|
|
73
|
+
expires?: Date;
|
|
74
|
+
path?: string;
|
|
75
|
+
domain?: string;
|
|
76
|
+
secure?: boolean;
|
|
77
|
+
httpOnly?: boolean;
|
|
78
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Redirect in server action context
|
|
82
|
+
*/
|
|
83
|
+
declare function redirect(url: string): never;
|
|
84
|
+
/**
|
|
85
|
+
* Special error for redirects
|
|
86
|
+
*/
|
|
87
|
+
declare class RedirectError extends Error {
|
|
88
|
+
readonly url: string;
|
|
89
|
+
constructor(url: string);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Check if error is a redirect
|
|
93
|
+
*/
|
|
94
|
+
declare function isRedirectError(error: unknown): error is RedirectError;
|
|
95
|
+
/**
|
|
96
|
+
* Parse form data to typed object
|
|
97
|
+
*/
|
|
98
|
+
declare function parseFormData<T extends Record<string, string>>(formData: FormData): T;
|
|
99
|
+
/**
|
|
100
|
+
* Create action reference for form action attribute
|
|
101
|
+
*/
|
|
102
|
+
declare function createActionReference(actionId: string): string;
|
|
103
|
+
/**
|
|
104
|
+
* Handle action request from client
|
|
105
|
+
*/
|
|
106
|
+
declare function handleActionRequest(request: Request): Promise<Response>;
|
|
107
|
+
|
|
108
|
+
export { type ActionResult, type CookieOptions, type FormActionData, RedirectError, type ServerAction, clearActions, cookies, createActionReference, executeAction, executeFormAction, generateActionId, getAction, getAllActions, handleActionRequest, isRedirectError, parseFormData, redirect, registerAction };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { RedirectError, clearActions, cookies, createActionReference, executeAction, executeFormAction, generateActionId, getAction, getAllActions, handleActionRequest, isRedirectError, parseFormData, redirect, registerAction } from '../chunk-TKXN7KGE.js';
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
3
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flight Adapters - Universal deployment adapters
|
|
3
|
+
*
|
|
4
|
+
* Adapters transform Flight's output for different deployment targets.
|
|
5
|
+
* The user chooses where to deploy - Flight provides the adapters.
|
|
6
|
+
*/
|
|
7
|
+
/** Build manifest containing all generated assets */
|
|
8
|
+
interface BuildManifest {
|
|
9
|
+
/** Entry points by name */
|
|
10
|
+
entries: Record<string, string>;
|
|
11
|
+
/** All generated files */
|
|
12
|
+
files: string[];
|
|
13
|
+
/** Route information */
|
|
14
|
+
routes: RouteManifestEntry[];
|
|
15
|
+
/** Server entry point (if applicable) */
|
|
16
|
+
serverEntry?: string;
|
|
17
|
+
/** Client entry point */
|
|
18
|
+
clientEntry?: string;
|
|
19
|
+
}
|
|
20
|
+
/** Route information in manifest */
|
|
21
|
+
interface RouteManifestEntry {
|
|
22
|
+
/** Route path pattern */
|
|
23
|
+
path: string;
|
|
24
|
+
/** Render mode for this route */
|
|
25
|
+
mode: 'ssr' | 'ssg' | 'csr' | 'isr';
|
|
26
|
+
/** Component/handler file path */
|
|
27
|
+
component: string;
|
|
28
|
+
/** Pre-rendered paths (for SSG/ISR) */
|
|
29
|
+
prerendered?: string[];
|
|
30
|
+
}
|
|
31
|
+
/** Builder utilities passed to adapters */
|
|
32
|
+
interface AdapterBuilder {
|
|
33
|
+
/** Build manifest */
|
|
34
|
+
manifest: BuildManifest;
|
|
35
|
+
/** Project root directory */
|
|
36
|
+
root: string;
|
|
37
|
+
/** Build output directory */
|
|
38
|
+
outDir: string;
|
|
39
|
+
/** Read a file from the build output */
|
|
40
|
+
readFile(path: string): Promise<string>;
|
|
41
|
+
/** Write a file to the adapter output */
|
|
42
|
+
writeFile(path: string, content: string): Promise<void>;
|
|
43
|
+
/** Copy files from build to adapter output */
|
|
44
|
+
copy(from: string, to: string): Promise<void>;
|
|
45
|
+
/** Get all files matching a pattern */
|
|
46
|
+
glob(pattern: string): Promise<string[]>;
|
|
47
|
+
/** Compress files for production */
|
|
48
|
+
compress?(files: string[]): Promise<void>;
|
|
49
|
+
/** Log info message */
|
|
50
|
+
log: {
|
|
51
|
+
info(message: string): void;
|
|
52
|
+
warn(message: string): void;
|
|
53
|
+
error(message: string): void;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/** Flight adapter interface */
|
|
57
|
+
interface FlightAdapter {
|
|
58
|
+
/** Adapter name */
|
|
59
|
+
name: string;
|
|
60
|
+
/**
|
|
61
|
+
* Transform the build output for the target platform
|
|
62
|
+
* This is called after Flight's build process completes
|
|
63
|
+
*/
|
|
64
|
+
adapt(builder: AdapterBuilder): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Optional: Start listening for requests (for Node.js/Bun adapters)
|
|
67
|
+
*/
|
|
68
|
+
listen?(server: unknown, port: number): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Optional: Emulate the platform during development
|
|
71
|
+
* Useful for testing platform-specific behavior locally
|
|
72
|
+
*/
|
|
73
|
+
emulate?(): {
|
|
74
|
+
/** Platform-specific env vars */
|
|
75
|
+
env?: Record<string, string>;
|
|
76
|
+
/** Custom middleware for dev server */
|
|
77
|
+
middleware?: unknown[];
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Optional: Declare what this adapter supports
|
|
81
|
+
* Flight uses this to warn about incompatible features
|
|
82
|
+
*/
|
|
83
|
+
supports?: {
|
|
84
|
+
/** Can read files at runtime (for dynamic content) */
|
|
85
|
+
read?: () => boolean;
|
|
86
|
+
/** Supports streaming responses */
|
|
87
|
+
streaming?: () => boolean;
|
|
88
|
+
/** Supports WebSockets */
|
|
89
|
+
websockets?: () => boolean;
|
|
90
|
+
/** Supports edge runtime */
|
|
91
|
+
edge?: () => boolean;
|
|
92
|
+
/** Supports Node.js runtime */
|
|
93
|
+
node?: () => boolean;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
interface AdapterOptions {
|
|
97
|
+
name: string;
|
|
98
|
+
adapt: (builder: AdapterBuilder) => Promise<void>;
|
|
99
|
+
emulate?: FlightAdapter['emulate'];
|
|
100
|
+
supports?: FlightAdapter['supports'];
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Create a Flight adapter
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```typescript
|
|
107
|
+
* import { createAdapter } from '@flight/core/adapters';
|
|
108
|
+
*
|
|
109
|
+
* export default function myAdapter(options = {}) {
|
|
110
|
+
* return createAdapter({
|
|
111
|
+
* name: 'my-adapter',
|
|
112
|
+
* async adapt(builder) {
|
|
113
|
+
* // Transform build output for your platform
|
|
114
|
+
* },
|
|
115
|
+
* });
|
|
116
|
+
* }
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
declare function createAdapter(options: AdapterOptions): FlightAdapter;
|
|
120
|
+
/**
|
|
121
|
+
* Storage Adapter Interface
|
|
122
|
+
*
|
|
123
|
+
* Implement this to use any storage provider:
|
|
124
|
+
* S3, Supabase Storage, Cloudflare R2, local filesystem, etc.
|
|
125
|
+
*/
|
|
126
|
+
interface StorageAdapter {
|
|
127
|
+
name: string;
|
|
128
|
+
/** Upload a file */
|
|
129
|
+
upload(path: string, data: Buffer | Uint8Array | string, options?: {
|
|
130
|
+
contentType?: string;
|
|
131
|
+
metadata?: Record<string, string>;
|
|
132
|
+
}): Promise<{
|
|
133
|
+
url: string;
|
|
134
|
+
path: string;
|
|
135
|
+
}>;
|
|
136
|
+
/** Download a file */
|
|
137
|
+
download(path: string): Promise<Buffer>;
|
|
138
|
+
/** Delete a file */
|
|
139
|
+
delete(path: string): Promise<void>;
|
|
140
|
+
/** List files with optional prefix */
|
|
141
|
+
list(prefix?: string): Promise<string[]>;
|
|
142
|
+
/** Get a signed URL for direct access */
|
|
143
|
+
getSignedUrl?(path: string, options?: {
|
|
144
|
+
expiresIn?: number;
|
|
145
|
+
operation?: 'read' | 'write';
|
|
146
|
+
}): Promise<string>;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Auth Adapter Interface
|
|
150
|
+
*
|
|
151
|
+
* Implement this to use any auth provider:
|
|
152
|
+
* Better Auth, Supabase Auth, Lucia, Auth.js, etc.
|
|
153
|
+
*/
|
|
154
|
+
interface AuthAdapter {
|
|
155
|
+
name: string;
|
|
156
|
+
/** Get the current user from a request */
|
|
157
|
+
getUser(request: Request): Promise<AuthUser | null>;
|
|
158
|
+
/** Verify a session token */
|
|
159
|
+
verifySession(token: string): Promise<AuthSession | null>;
|
|
160
|
+
/** Optional middleware for handling auth-specific routes (e.g., /api/auth/*) */
|
|
161
|
+
middleware?: (req: Request) => Promise<Response | null>;
|
|
162
|
+
}
|
|
163
|
+
interface AuthUser {
|
|
164
|
+
id: string;
|
|
165
|
+
email?: string;
|
|
166
|
+
name?: string;
|
|
167
|
+
avatar?: string;
|
|
168
|
+
metadata?: Record<string, unknown>;
|
|
169
|
+
}
|
|
170
|
+
interface AuthSession {
|
|
171
|
+
user: AuthUser;
|
|
172
|
+
expiresAt: Date;
|
|
173
|
+
token: string;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Email Adapter Interface
|
|
177
|
+
*
|
|
178
|
+
* Implement this to use any email provider:
|
|
179
|
+
* Resend, SendGrid, Postmark, SMTP, etc.
|
|
180
|
+
*/
|
|
181
|
+
interface EmailAdapter {
|
|
182
|
+
name: string;
|
|
183
|
+
/** Send an email */
|
|
184
|
+
send(options: {
|
|
185
|
+
to: string | string[];
|
|
186
|
+
subject: string;
|
|
187
|
+
html?: string;
|
|
188
|
+
text?: string;
|
|
189
|
+
from?: string;
|
|
190
|
+
replyTo?: string;
|
|
191
|
+
attachments?: Array<{
|
|
192
|
+
filename: string;
|
|
193
|
+
content: Buffer | string;
|
|
194
|
+
contentType?: string;
|
|
195
|
+
}>;
|
|
196
|
+
}): Promise<{
|
|
197
|
+
id: string;
|
|
198
|
+
success: boolean;
|
|
199
|
+
}>;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Queue/Jobs Adapter Interface
|
|
203
|
+
*
|
|
204
|
+
* Implement this to use any job queue:
|
|
205
|
+
* BullMQ, Quirrel, Inngest, custom, etc.
|
|
206
|
+
*/
|
|
207
|
+
interface JobsAdapter {
|
|
208
|
+
name: string;
|
|
209
|
+
/** Add a job to the queue */
|
|
210
|
+
enqueue<T>(jobName: string, data: T, options?: {
|
|
211
|
+
delay?: number;
|
|
212
|
+
priority?: number;
|
|
213
|
+
retries?: number;
|
|
214
|
+
}): Promise<{
|
|
215
|
+
id: string;
|
|
216
|
+
}>;
|
|
217
|
+
/** Schedule a recurring job */
|
|
218
|
+
schedule?(jobName: string, cron: string, data?: unknown): Promise<{
|
|
219
|
+
id: string;
|
|
220
|
+
}>;
|
|
221
|
+
/** Cancel a job */
|
|
222
|
+
cancel?(jobId: string): Promise<void>;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Database Adapter Interface
|
|
226
|
+
*
|
|
227
|
+
* Note: This is intentionally minimal.
|
|
228
|
+
* For databases, use your preferred ORM/query builder directly.
|
|
229
|
+
* This interface is for Flight's internal use (sessions, cache, etc.)
|
|
230
|
+
*/
|
|
231
|
+
interface DatabaseAdapter {
|
|
232
|
+
name: string;
|
|
233
|
+
/** Raw query execution */
|
|
234
|
+
query<T = unknown>(sql: string, params?: unknown[]): Promise<T[]>;
|
|
235
|
+
/** Execute a mutation */
|
|
236
|
+
execute(sql: string, params?: unknown[]): Promise<{
|
|
237
|
+
rowsAffected: number;
|
|
238
|
+
}>;
|
|
239
|
+
/** Transaction support */
|
|
240
|
+
transaction?<T>(fn: (tx: DatabaseAdapter) => Promise<T>): Promise<T>;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export { type AdapterBuilder, type AdapterOptions, type AuthAdapter, type AuthSession, type AuthUser, type BuildManifest, type DatabaseAdapter, type EmailAdapter, type FlightAdapter, type JobsAdapter, type RouteManifestEntry, type StorageAdapter, createAdapter };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flight Cache - Agnostic caching primitives
|
|
3
|
+
*
|
|
4
|
+
* Flight provides the interface, you choose the implementation.
|
|
5
|
+
* Use in-memory, Redis, Cloudflare KV, or anything else.
|
|
6
|
+
*/
|
|
7
|
+
interface CacheOptions {
|
|
8
|
+
/** Time-to-live in seconds */
|
|
9
|
+
ttl?: number;
|
|
10
|
+
/** Cache tags for invalidation */
|
|
11
|
+
tags?: string[];
|
|
12
|
+
/** Stale-while-revalidate time in seconds */
|
|
13
|
+
swr?: number;
|
|
14
|
+
}
|
|
15
|
+
interface CacheEntry<T> {
|
|
16
|
+
/** Cached value */
|
|
17
|
+
value: T;
|
|
18
|
+
/** When this entry expires (timestamp) */
|
|
19
|
+
expiresAt?: number;
|
|
20
|
+
/** When this entry becomes stale (timestamp) */
|
|
21
|
+
staleAt?: number;
|
|
22
|
+
/** Cache tags for invalidation */
|
|
23
|
+
tags?: string[];
|
|
24
|
+
/** When this entry was created */
|
|
25
|
+
createdAt: number;
|
|
26
|
+
}
|
|
27
|
+
interface Cache {
|
|
28
|
+
/** Get a value from cache */
|
|
29
|
+
get<T>(key: string): Promise<T | undefined>;
|
|
30
|
+
/** Set a value in cache */
|
|
31
|
+
set<T>(key: string, value: T, options?: CacheOptions): Promise<void>;
|
|
32
|
+
/** Delete a value from cache */
|
|
33
|
+
delete(key: string): Promise<boolean>;
|
|
34
|
+
/** Check if a key exists */
|
|
35
|
+
has(key: string): Promise<boolean>;
|
|
36
|
+
/** Clear all cache entries */
|
|
37
|
+
clear(): Promise<void>;
|
|
38
|
+
/** Invalidate entries by tag */
|
|
39
|
+
invalidateTag(tag: string): Promise<void>;
|
|
40
|
+
/** Get or set with a factory function */
|
|
41
|
+
getOrSet<T>(key: string, factory: () => Promise<T>, options?: CacheOptions): Promise<T>;
|
|
42
|
+
}
|
|
43
|
+
/** Adapter interface for external cache providers */
|
|
44
|
+
interface CacheAdapter {
|
|
45
|
+
name: string;
|
|
46
|
+
get<T>(key: string): Promise<CacheEntry<T> | undefined>;
|
|
47
|
+
set<T>(key: string, entry: CacheEntry<T>): Promise<void>;
|
|
48
|
+
delete(key: string): Promise<boolean>;
|
|
49
|
+
has(key: string): Promise<boolean>;
|
|
50
|
+
clear(): Promise<void>;
|
|
51
|
+
keys?(pattern?: string): Promise<string[]>;
|
|
52
|
+
}
|
|
53
|
+
interface CreateCacheOptions {
|
|
54
|
+
/** Custom cache adapter (defaults to in-memory) */
|
|
55
|
+
adapter?: CacheAdapter;
|
|
56
|
+
/** Default TTL for all entries */
|
|
57
|
+
defaultTTL?: number;
|
|
58
|
+
/** Key prefix for namespacing */
|
|
59
|
+
prefix?: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Create a new cache instance
|
|
63
|
+
*/
|
|
64
|
+
declare function createCache(options?: CreateCacheOptions): Cache;
|
|
65
|
+
/**
|
|
66
|
+
* Create a cache key from multiple parts
|
|
67
|
+
*/
|
|
68
|
+
declare function cacheKey(...parts: (string | number | boolean | undefined)[]): string;
|
|
69
|
+
/**
|
|
70
|
+
* Wrap a function with caching
|
|
71
|
+
*/
|
|
72
|
+
declare function cached<T extends (...args: unknown[]) => Promise<unknown>>(cache: Cache, fn: T, options?: CacheOptions & {
|
|
73
|
+
keyFn?: (...args: Parameters<T>) => string;
|
|
74
|
+
}): T;
|
|
75
|
+
|
|
76
|
+
export { type Cache, type CacheAdapter, type CacheEntry, type CacheOptions, type CreateCacheOptions, cacheKey, cached, createCache };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// src/cache/index.ts
|
|
2
|
+
var MemoryCacheAdapter = class {
|
|
3
|
+
name = "memory";
|
|
4
|
+
store = /* @__PURE__ */ new Map();
|
|
5
|
+
async get(key) {
|
|
6
|
+
const entry = this.store.get(key);
|
|
7
|
+
if (!entry) return void 0;
|
|
8
|
+
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
|
9
|
+
this.store.delete(key);
|
|
10
|
+
return void 0;
|
|
11
|
+
}
|
|
12
|
+
return entry;
|
|
13
|
+
}
|
|
14
|
+
async set(key, entry) {
|
|
15
|
+
this.store.set(key, entry);
|
|
16
|
+
}
|
|
17
|
+
async delete(key) {
|
|
18
|
+
return this.store.delete(key);
|
|
19
|
+
}
|
|
20
|
+
async has(key) {
|
|
21
|
+
const entry = await this.get(key);
|
|
22
|
+
return entry !== void 0;
|
|
23
|
+
}
|
|
24
|
+
async clear() {
|
|
25
|
+
this.store.clear();
|
|
26
|
+
}
|
|
27
|
+
async keys(pattern) {
|
|
28
|
+
const allKeys = Array.from(this.store.keys());
|
|
29
|
+
if (!pattern) return allKeys;
|
|
30
|
+
const regex = new RegExp(pattern.replace(/\*/g, ".*"));
|
|
31
|
+
return allKeys.filter((key) => regex.test(key));
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
function createCache(options = {}) {
|
|
35
|
+
const {
|
|
36
|
+
adapter = new MemoryCacheAdapter(),
|
|
37
|
+
defaultTTL,
|
|
38
|
+
prefix = ""
|
|
39
|
+
} = options;
|
|
40
|
+
const tagIndex = /* @__PURE__ */ new Map();
|
|
41
|
+
function prefixKey(key) {
|
|
42
|
+
return prefix ? `${prefix}:${key}` : key;
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
async get(key) {
|
|
46
|
+
const entry = await adapter.get(prefixKey(key));
|
|
47
|
+
if (!entry) return void 0;
|
|
48
|
+
if (entry.staleAt && Date.now() > entry.staleAt) {
|
|
49
|
+
return entry.value;
|
|
50
|
+
}
|
|
51
|
+
return entry.value;
|
|
52
|
+
},
|
|
53
|
+
async set(key, value, opts) {
|
|
54
|
+
const ttl = opts?.ttl ?? defaultTTL;
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
const entry = {
|
|
57
|
+
value,
|
|
58
|
+
createdAt: now,
|
|
59
|
+
tags: opts?.tags
|
|
60
|
+
};
|
|
61
|
+
if (ttl) {
|
|
62
|
+
entry.expiresAt = now + ttl * 1e3;
|
|
63
|
+
if (opts?.swr) {
|
|
64
|
+
entry.staleAt = now + (ttl - opts.swr) * 1e3;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const fullKey = prefixKey(key);
|
|
68
|
+
await adapter.set(fullKey, entry);
|
|
69
|
+
if (opts?.tags) {
|
|
70
|
+
for (const tag of opts.tags) {
|
|
71
|
+
if (!tagIndex.has(tag)) {
|
|
72
|
+
tagIndex.set(tag, /* @__PURE__ */ new Set());
|
|
73
|
+
}
|
|
74
|
+
tagIndex.get(tag).add(fullKey);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
async delete(key) {
|
|
79
|
+
return adapter.delete(prefixKey(key));
|
|
80
|
+
},
|
|
81
|
+
async has(key) {
|
|
82
|
+
return adapter.has(prefixKey(key));
|
|
83
|
+
},
|
|
84
|
+
async clear() {
|
|
85
|
+
await adapter.clear();
|
|
86
|
+
tagIndex.clear();
|
|
87
|
+
},
|
|
88
|
+
async invalidateTag(tag) {
|
|
89
|
+
const keys = tagIndex.get(tag);
|
|
90
|
+
if (!keys) return;
|
|
91
|
+
for (const key of keys) {
|
|
92
|
+
await adapter.delete(key);
|
|
93
|
+
}
|
|
94
|
+
tagIndex.delete(tag);
|
|
95
|
+
},
|
|
96
|
+
async getOrSet(key, factory, opts) {
|
|
97
|
+
const cached2 = await this.get(key);
|
|
98
|
+
if (cached2 !== void 0) {
|
|
99
|
+
return cached2;
|
|
100
|
+
}
|
|
101
|
+
const value = await factory();
|
|
102
|
+
await this.set(key, value, opts);
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function cacheKey(...parts) {
|
|
108
|
+
return parts.filter((p) => p !== void 0).map((p) => String(p)).join(":");
|
|
109
|
+
}
|
|
110
|
+
function cached(cache, fn, options) {
|
|
111
|
+
const { keyFn = (...args) => JSON.stringify(args), ...cacheOpts } = options ?? {};
|
|
112
|
+
return (async (...args) => {
|
|
113
|
+
const key = keyFn(...args);
|
|
114
|
+
return cache.getOrSet(key, () => fn(...args), cacheOpts);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export { cacheKey, cached, createCache };
|
|
119
|
+
//# sourceMappingURL=chunk-3AIQVGTM.js.map
|
|
120
|
+
//# sourceMappingURL=chunk-3AIQVGTM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cache/index.ts"],"names":["cached"],"mappings":"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","file":"chunk-3AIQVGTM.js","sourcesContent":["/**\r\n * Flight Cache - Agnostic caching primitives\r\n * \r\n * Flight provides the interface, you choose the implementation.\r\n * Use in-memory, Redis, Cloudflare KV, or anything else.\r\n */\r\n\r\n// ============================================================================\r\n// Types\r\n// ============================================================================\r\n\r\nexport interface CacheOptions {\r\n /** Time-to-live in seconds */\r\n ttl?: number;\r\n /** Cache tags for invalidation */\r\n tags?: string[];\r\n /** Stale-while-revalidate time in seconds */\r\n swr?: number;\r\n}\r\n\r\nexport interface CacheEntry<T> {\r\n /** Cached value */\r\n value: T;\r\n /** When this entry expires (timestamp) */\r\n expiresAt?: number;\r\n /** When this entry becomes stale (timestamp) */\r\n staleAt?: number;\r\n /** Cache tags for invalidation */\r\n tags?: string[];\r\n /** When this entry was created */\r\n createdAt: number;\r\n}\r\n\r\nexport interface Cache {\r\n /** Get a value from cache */\r\n get<T>(key: string): Promise<T | undefined>;\r\n\r\n /** Set a value in cache */\r\n set<T>(key: string, value: T, options?: CacheOptions): Promise<void>;\r\n\r\n /** Delete a value from cache */\r\n delete(key: string): Promise<boolean>;\r\n\r\n /** Check if a key exists */\r\n has(key: string): Promise<boolean>;\r\n\r\n /** Clear all cache entries */\r\n clear(): Promise<void>;\r\n\r\n /** Invalidate entries by tag */\r\n invalidateTag(tag: string): Promise<void>;\r\n\r\n /** Get or set with a factory function */\r\n getOrSet<T>(\r\n key: string,\r\n factory: () => Promise<T>,\r\n options?: CacheOptions\r\n ): Promise<T>;\r\n}\r\n\r\n/** Adapter interface for external cache providers */\r\nexport interface CacheAdapter {\r\n name: string;\r\n get<T>(key: string): Promise<CacheEntry<T> | undefined>;\r\n set<T>(key: string, entry: CacheEntry<T>): Promise<void>;\r\n delete(key: string): Promise<boolean>;\r\n has(key: string): Promise<boolean>;\r\n clear(): Promise<void>;\r\n keys?(pattern?: string): Promise<string[]>;\r\n}\r\n\r\n// ============================================================================\r\n// In-Memory Cache Implementation (Default)\r\n// ============================================================================\r\n\r\nclass MemoryCacheAdapter implements CacheAdapter {\r\n name = 'memory';\r\n private store = new Map<string, CacheEntry<unknown>>();\r\n\r\n async get<T>(key: string): Promise<CacheEntry<T> | undefined> {\r\n const entry = this.store.get(key) as CacheEntry<T> | undefined;\r\n if (!entry) return undefined;\r\n\r\n // Check if expired\r\n if (entry.expiresAt && Date.now() > entry.expiresAt) {\r\n this.store.delete(key);\r\n return undefined;\r\n }\r\n\r\n return entry;\r\n }\r\n\r\n async set<T>(key: string, entry: CacheEntry<T>): Promise<void> {\r\n this.store.set(key, entry as CacheEntry<unknown>);\r\n }\r\n\r\n async delete(key: string): Promise<boolean> {\r\n return this.store.delete(key);\r\n }\r\n\r\n async has(key: string): Promise<boolean> {\r\n const entry = await this.get(key);\r\n return entry !== undefined;\r\n }\r\n\r\n async clear(): Promise<void> {\r\n this.store.clear();\r\n }\r\n\r\n async keys(pattern?: string): Promise<string[]> {\r\n const allKeys = Array.from(this.store.keys());\r\n if (!pattern) return allKeys;\r\n\r\n const regex = new RegExp(pattern.replace(/\\*/g, '.*'));\r\n return allKeys.filter(key => regex.test(key));\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Cache Factory\r\n// ============================================================================\r\n\r\nexport interface CreateCacheOptions {\r\n /** Custom cache adapter (defaults to in-memory) */\r\n adapter?: CacheAdapter;\r\n /** Default TTL for all entries */\r\n defaultTTL?: number;\r\n /** Key prefix for namespacing */\r\n prefix?: string;\r\n}\r\n\r\n/**\r\n * Create a new cache instance\r\n */\r\nexport function createCache(options: CreateCacheOptions = {}): Cache {\r\n const {\r\n adapter = new MemoryCacheAdapter(),\r\n defaultTTL,\r\n prefix = ''\r\n } = options;\r\n\r\n const tagIndex = new Map<string, Set<string>>();\r\n\r\n function prefixKey(key: string): string {\r\n return prefix ? `${prefix}:${key}` : key;\r\n }\r\n\r\n return {\r\n async get<T>(key: string): Promise<T | undefined> {\r\n const entry = await adapter.get<T>(prefixKey(key));\r\n if (!entry) return undefined;\r\n\r\n // Check stale-while-revalidate\r\n if (entry.staleAt && Date.now() > entry.staleAt) {\r\n // Return stale value (caller should revalidate)\r\n return entry.value;\r\n }\r\n\r\n return entry.value;\r\n },\r\n\r\n async set<T>(key: string, value: T, opts?: CacheOptions): Promise<void> {\r\n const ttl = opts?.ttl ?? defaultTTL;\r\n const now = Date.now();\r\n\r\n const entry: CacheEntry<T> = {\r\n value,\r\n createdAt: now,\r\n tags: opts?.tags,\r\n };\r\n\r\n if (ttl) {\r\n entry.expiresAt = now + (ttl * 1000);\r\n if (opts?.swr) {\r\n entry.staleAt = now + ((ttl - opts.swr) * 1000);\r\n }\r\n }\r\n\r\n const fullKey = prefixKey(key);\r\n await adapter.set(fullKey, entry);\r\n\r\n // Update tag index\r\n if (opts?.tags) {\r\n for (const tag of opts.tags) {\r\n if (!tagIndex.has(tag)) {\r\n tagIndex.set(tag, new Set());\r\n }\r\n tagIndex.get(tag)!.add(fullKey);\r\n }\r\n }\r\n },\r\n\r\n async delete(key: string): Promise<boolean> {\r\n return adapter.delete(prefixKey(key));\r\n },\r\n\r\n async has(key: string): Promise<boolean> {\r\n return adapter.has(prefixKey(key));\r\n },\r\n\r\n async clear(): Promise<void> {\r\n await adapter.clear();\r\n tagIndex.clear();\r\n },\r\n\r\n async invalidateTag(tag: string): Promise<void> {\r\n const keys = tagIndex.get(tag);\r\n if (!keys) return;\r\n\r\n for (const key of keys) {\r\n await adapter.delete(key);\r\n }\r\n tagIndex.delete(tag);\r\n },\r\n\r\n async getOrSet<T>(\r\n key: string,\r\n factory: () => Promise<T>,\r\n opts?: CacheOptions\r\n ): Promise<T> {\r\n const cached = await this.get<T>(key);\r\n if (cached !== undefined) {\r\n return cached;\r\n }\r\n\r\n const value = await factory();\r\n await this.set(key, value, opts);\r\n return value;\r\n },\r\n };\r\n}\r\n\r\n// ============================================================================\r\n// Cache Utilities\r\n// ============================================================================\r\n\r\n/**\r\n * Create a cache key from multiple parts\r\n */\r\nexport function cacheKey(...parts: (string | number | boolean | undefined)[]): string {\r\n return parts\r\n .filter(p => p !== undefined)\r\n .map(p => String(p))\r\n .join(':');\r\n}\r\n\r\n/**\r\n * Wrap a function with caching\r\n */\r\nexport function cached<T extends (...args: unknown[]) => Promise<unknown>>(\r\n cache: Cache,\r\n fn: T,\r\n options?: CacheOptions & { keyFn?: (...args: Parameters<T>) => string }\r\n): T {\r\n const { keyFn = (...args: unknown[]) => JSON.stringify(args), ...cacheOpts } = options ?? {};\r\n\r\n return (async (...args: Parameters<T>) => {\r\n const key = keyFn(...args);\r\n return cache.getOrSet(key, () => fn(...args) as Promise<unknown>, cacheOpts);\r\n }) as T;\r\n}\r\n"]}
|