@buenojs/bueno 0.8.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/.env.example +109 -0
- package/.github/workflows/ci.yml +31 -0
- package/LICENSE +21 -0
- package/README.md +892 -0
- package/architecture.md +652 -0
- package/bun.lock +70 -0
- package/dist/cli/index.js +3233 -0
- package/dist/index.js +9014 -0
- package/package.json +77 -0
- package/src/cache/index.ts +795 -0
- package/src/cli/ARCHITECTURE.md +837 -0
- package/src/cli/bin.ts +10 -0
- package/src/cli/commands/build.ts +425 -0
- package/src/cli/commands/dev.ts +248 -0
- package/src/cli/commands/generate.ts +541 -0
- package/src/cli/commands/help.ts +55 -0
- package/src/cli/commands/index.ts +112 -0
- package/src/cli/commands/migration.ts +355 -0
- package/src/cli/commands/new.ts +804 -0
- package/src/cli/commands/start.ts +208 -0
- package/src/cli/core/args.ts +283 -0
- package/src/cli/core/console.ts +349 -0
- package/src/cli/core/index.ts +60 -0
- package/src/cli/core/prompt.ts +424 -0
- package/src/cli/core/spinner.ts +265 -0
- package/src/cli/index.ts +135 -0
- package/src/cli/templates/deploy.ts +295 -0
- package/src/cli/templates/docker.ts +307 -0
- package/src/cli/templates/index.ts +24 -0
- package/src/cli/utils/fs.ts +428 -0
- package/src/cli/utils/index.ts +8 -0
- package/src/cli/utils/strings.ts +197 -0
- package/src/config/env.ts +408 -0
- package/src/config/index.ts +506 -0
- package/src/config/loader.ts +329 -0
- package/src/config/merge.ts +285 -0
- package/src/config/types.ts +320 -0
- package/src/config/validation.ts +441 -0
- package/src/container/forward-ref.ts +143 -0
- package/src/container/index.ts +386 -0
- package/src/context/index.ts +360 -0
- package/src/database/index.ts +1142 -0
- package/src/database/migrations/index.ts +371 -0
- package/src/database/schema/index.ts +619 -0
- package/src/frontend/api-routes.ts +640 -0
- package/src/frontend/bundler.ts +643 -0
- package/src/frontend/console-client.ts +419 -0
- package/src/frontend/console-stream.ts +587 -0
- package/src/frontend/dev-server.ts +846 -0
- package/src/frontend/file-router.ts +611 -0
- package/src/frontend/frameworks/index.ts +106 -0
- package/src/frontend/frameworks/react.ts +85 -0
- package/src/frontend/frameworks/solid.ts +104 -0
- package/src/frontend/frameworks/svelte.ts +110 -0
- package/src/frontend/frameworks/vue.ts +92 -0
- package/src/frontend/hmr-client.ts +663 -0
- package/src/frontend/hmr.ts +728 -0
- package/src/frontend/index.ts +342 -0
- package/src/frontend/islands.ts +552 -0
- package/src/frontend/isr.ts +555 -0
- package/src/frontend/layout.ts +475 -0
- package/src/frontend/ssr/react.ts +446 -0
- package/src/frontend/ssr/solid.ts +523 -0
- package/src/frontend/ssr/svelte.ts +546 -0
- package/src/frontend/ssr/vue.ts +504 -0
- package/src/frontend/ssr.ts +699 -0
- package/src/frontend/types.ts +2274 -0
- package/src/health/index.ts +604 -0
- package/src/index.ts +410 -0
- package/src/lock/index.ts +587 -0
- package/src/logger/index.ts +444 -0
- package/src/logger/transports/index.ts +969 -0
- package/src/metrics/index.ts +494 -0
- package/src/middleware/built-in.ts +360 -0
- package/src/middleware/index.ts +94 -0
- package/src/modules/filters.ts +458 -0
- package/src/modules/guards.ts +405 -0
- package/src/modules/index.ts +1256 -0
- package/src/modules/interceptors.ts +574 -0
- package/src/modules/lazy.ts +418 -0
- package/src/modules/lifecycle.ts +478 -0
- package/src/modules/metadata.ts +90 -0
- package/src/modules/pipes.ts +626 -0
- package/src/router/index.ts +339 -0
- package/src/router/linear.ts +371 -0
- package/src/router/regex.ts +292 -0
- package/src/router/tree.ts +562 -0
- package/src/rpc/index.ts +1263 -0
- package/src/security/index.ts +436 -0
- package/src/ssg/index.ts +631 -0
- package/src/storage/index.ts +456 -0
- package/src/telemetry/index.ts +1097 -0
- package/src/testing/index.ts +1586 -0
- package/src/types/index.ts +236 -0
- package/src/types/optional-deps.d.ts +219 -0
- package/src/validation/index.ts +276 -0
- package/src/websocket/index.ts +1004 -0
- package/tests/integration/cli.test.ts +1016 -0
- package/tests/integration/fullstack.test.ts +234 -0
- package/tests/unit/cache.test.ts +174 -0
- package/tests/unit/cli-commands.test.ts +892 -0
- package/tests/unit/cli.test.ts +1258 -0
- package/tests/unit/container.test.ts +279 -0
- package/tests/unit/context.test.ts +221 -0
- package/tests/unit/database.test.ts +183 -0
- package/tests/unit/linear-router.test.ts +280 -0
- package/tests/unit/lock.test.ts +336 -0
- package/tests/unit/middleware.test.ts +184 -0
- package/tests/unit/modules.test.ts +142 -0
- package/tests/unit/pubsub.test.ts +257 -0
- package/tests/unit/regex-router.test.ts +265 -0
- package/tests/unit/router.test.ts +373 -0
- package/tests/unit/rpc.test.ts +1248 -0
- package/tests/unit/security.test.ts +174 -0
- package/tests/unit/telemetry.test.ts +371 -0
- package/tests/unit/test-cache.test.ts +110 -0
- package/tests/unit/test-database.test.ts +282 -0
- package/tests/unit/tree-router.test.ts +325 -0
- package/tests/unit/validation.test.ts +794 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context System
|
|
3
|
+
*
|
|
4
|
+
* Provides a rich request/response context for route handlers
|
|
5
|
+
* with convenient methods for accessing request data and building responses.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { PathParams, StatusCode } from "../types";
|
|
9
|
+
|
|
10
|
+
// ============= Context Types =============
|
|
11
|
+
|
|
12
|
+
interface ContextVariables {
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface ResponseState {
|
|
17
|
+
status: StatusCode;
|
|
18
|
+
headers: Headers;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ============= Cookie Parser =============
|
|
22
|
+
|
|
23
|
+
function parseCookies(cookieHeader: string): Record<string, string> {
|
|
24
|
+
const cookies: Record<string, string> = {};
|
|
25
|
+
|
|
26
|
+
for (const part of cookieHeader.split(";")) {
|
|
27
|
+
const trimmed = part.trim();
|
|
28
|
+
if (trimmed) {
|
|
29
|
+
const [name, ...rest] = trimmed.split("=");
|
|
30
|
+
if (name && rest.length > 0) {
|
|
31
|
+
cookies[name.trim()] = rest.join("=").trim();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return cookies;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ============= Context Class =============
|
|
40
|
+
|
|
41
|
+
export class Context<V extends ContextVariables = ContextVariables> {
|
|
42
|
+
/** Raw Request object */
|
|
43
|
+
readonly req: Request;
|
|
44
|
+
|
|
45
|
+
/** Path parameters extracted from route */
|
|
46
|
+
readonly params: PathParams;
|
|
47
|
+
|
|
48
|
+
/** Query parameters parsed from URL */
|
|
49
|
+
readonly query: Record<string, string>;
|
|
50
|
+
|
|
51
|
+
/** Cached cookies */
|
|
52
|
+
private _cookies?: Record<string, string>;
|
|
53
|
+
|
|
54
|
+
/** Cached body */
|
|
55
|
+
private _bodyCache?: unknown;
|
|
56
|
+
|
|
57
|
+
/** Response state for building responses */
|
|
58
|
+
private _response: ResponseState;
|
|
59
|
+
|
|
60
|
+
/** Context variables */
|
|
61
|
+
private _variables: V = {} as V;
|
|
62
|
+
|
|
63
|
+
constructor(request: Request, params: PathParams = {}) {
|
|
64
|
+
this.req = request;
|
|
65
|
+
this.params = params;
|
|
66
|
+
this.query = this.parseQuery(request.url);
|
|
67
|
+
this._response = {
|
|
68
|
+
status: 200,
|
|
69
|
+
headers: new Headers(),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ============= Request Properties =============
|
|
74
|
+
|
|
75
|
+
/** HTTP method */
|
|
76
|
+
get method(): string {
|
|
77
|
+
return this.req.method;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Parsed URL */
|
|
81
|
+
get url(): URL {
|
|
82
|
+
return new URL(this.req.url);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Path name */
|
|
86
|
+
get path(): string {
|
|
87
|
+
return this.url.pathname;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ============= Request Helpers =============
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get request header (case-insensitive)
|
|
94
|
+
*/
|
|
95
|
+
getHeader(name: string): string | undefined {
|
|
96
|
+
return this.req.headers.get(name) ?? undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get cookie value
|
|
101
|
+
*/
|
|
102
|
+
getCookie(name: string): string | undefined {
|
|
103
|
+
if (!this._cookies) {
|
|
104
|
+
const cookieHeader = this.req.headers.get("Cookie");
|
|
105
|
+
this._cookies = cookieHeader ? parseCookies(cookieHeader) : {};
|
|
106
|
+
}
|
|
107
|
+
return this._cookies[name];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get all cookies
|
|
112
|
+
*/
|
|
113
|
+
get cookies(): Record<string, string> {
|
|
114
|
+
if (!this._cookies) {
|
|
115
|
+
const cookieHeader = this.req.headers.get("Cookie");
|
|
116
|
+
this._cookies = cookieHeader ? parseCookies(cookieHeader) : {};
|
|
117
|
+
}
|
|
118
|
+
return this._cookies;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ============= Body Parsing =============
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Parse body as JSON
|
|
125
|
+
*/
|
|
126
|
+
async body<T = unknown>(): Promise<T> {
|
|
127
|
+
if (this._bodyCache !== undefined) {
|
|
128
|
+
return this._bodyCache as T;
|
|
129
|
+
}
|
|
130
|
+
const text = await this.req.text();
|
|
131
|
+
this._bodyCache = text ? JSON.parse(text) : null;
|
|
132
|
+
return this._bodyCache as T;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Parse body as JSON (alias for body)
|
|
137
|
+
*/
|
|
138
|
+
async parseBody<T = unknown>(): Promise<T> {
|
|
139
|
+
return this.body<T>();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get body as text
|
|
144
|
+
*/
|
|
145
|
+
async bodyText(): Promise<string> {
|
|
146
|
+
return this.req.text();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Parse body as FormData
|
|
151
|
+
*/
|
|
152
|
+
async bodyFormData(): Promise<FormData> {
|
|
153
|
+
return this.req.formData() as Promise<FormData>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get body as ArrayBuffer
|
|
158
|
+
*/
|
|
159
|
+
async bodyArrayBuffer(): Promise<ArrayBuffer> {
|
|
160
|
+
return this.req.arrayBuffer();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get body as Blob
|
|
165
|
+
*/
|
|
166
|
+
async bodyBlob(): Promise<Blob> {
|
|
167
|
+
return this.req.blob();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ============= Variable Storage =============
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Set a context variable
|
|
174
|
+
*/
|
|
175
|
+
set<K extends keyof V>(key: K, value: V[K]): this {
|
|
176
|
+
this._variables[key] = value;
|
|
177
|
+
return this;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get a context variable
|
|
182
|
+
*/
|
|
183
|
+
get<K extends keyof V>(key: K): V[K] | undefined {
|
|
184
|
+
return this._variables[key];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Check if variable exists
|
|
189
|
+
*/
|
|
190
|
+
has(key: keyof V): boolean {
|
|
191
|
+
return key in this._variables;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ============= Response Building =============
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Set response status
|
|
198
|
+
*/
|
|
199
|
+
status(code: StatusCode): this {
|
|
200
|
+
this._response.status = code;
|
|
201
|
+
return this;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Set response header
|
|
206
|
+
*/
|
|
207
|
+
setHeader(name: string, value: string): this {
|
|
208
|
+
this._response.headers.set(name, value);
|
|
209
|
+
return this;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Append response header
|
|
214
|
+
*/
|
|
215
|
+
appendHeader(name: string, value: string): this {
|
|
216
|
+
this._response.headers.append(name, value);
|
|
217
|
+
return this;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Create JSON response
|
|
222
|
+
*/
|
|
223
|
+
json<T>(data: T): Response {
|
|
224
|
+
this._response.headers.set("Content-Type", "application/json");
|
|
225
|
+
return new Response(JSON.stringify(data), {
|
|
226
|
+
status: this._response.status,
|
|
227
|
+
headers: this._response.headers,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Create text response
|
|
233
|
+
*/
|
|
234
|
+
text(data: string): Response {
|
|
235
|
+
if (!this._response.headers.has("Content-Type")) {
|
|
236
|
+
this._response.headers.set("Content-Type", "text/plain");
|
|
237
|
+
}
|
|
238
|
+
return new Response(data, {
|
|
239
|
+
status: this._response.status,
|
|
240
|
+
headers: this._response.headers,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Create HTML response
|
|
246
|
+
*/
|
|
247
|
+
html(data: string): Response {
|
|
248
|
+
this._response.headers.set("Content-Type", "text/html; charset=utf-8");
|
|
249
|
+
return new Response(data, {
|
|
250
|
+
status: this._response.status,
|
|
251
|
+
headers: this._response.headers,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Create redirect response
|
|
257
|
+
*/
|
|
258
|
+
redirect(url: string, status: StatusCode = 302): Response {
|
|
259
|
+
return new Response(null, {
|
|
260
|
+
status,
|
|
261
|
+
headers: {
|
|
262
|
+
Location: url,
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Create 404 Not Found response
|
|
269
|
+
*/
|
|
270
|
+
notFound(message = "Not Found"): Response {
|
|
271
|
+
return new Response(JSON.stringify({ error: message }), {
|
|
272
|
+
status: 404,
|
|
273
|
+
headers: { "Content-Type": "application/json" },
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Create error response
|
|
279
|
+
*/
|
|
280
|
+
error(message: string, status: StatusCode = 500): Response {
|
|
281
|
+
return new Response(JSON.stringify({ error: message }), {
|
|
282
|
+
status,
|
|
283
|
+
headers: { "Content-Type": "application/json" },
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Create a new Response with current state
|
|
289
|
+
*/
|
|
290
|
+
newResponse(body: Blob | ArrayBuffer | FormData | URLSearchParams | ReadableStream<unknown> | string | null, options?: ResponseInit): Response {
|
|
291
|
+
return new Response(body, {
|
|
292
|
+
status: this._response.status,
|
|
293
|
+
headers: this._response.headers,
|
|
294
|
+
...options,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ============= Utility Methods =============
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Parse query parameters from URL
|
|
302
|
+
*/
|
|
303
|
+
private parseQuery(url: string): Record<string, string> {
|
|
304
|
+
const query: Record<string, string> = {};
|
|
305
|
+
const searchParams = new URL(url).searchParams;
|
|
306
|
+
|
|
307
|
+
for (const [key, value] of searchParams.entries()) {
|
|
308
|
+
query[key] = value;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return query;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Check if request accepts a content type
|
|
316
|
+
*/
|
|
317
|
+
accepts(...types: string[]): string | undefined {
|
|
318
|
+
const acceptHeader = this.req.headers.get("Accept");
|
|
319
|
+
if (!acceptHeader) return types[0];
|
|
320
|
+
|
|
321
|
+
// Simple implementation - just check if type is in accept header
|
|
322
|
+
for (const type of types) {
|
|
323
|
+
if (acceptHeader.includes(type) || acceptHeader.includes("*/*")) {
|
|
324
|
+
return type;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return undefined;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Check if request is AJAX
|
|
333
|
+
*/
|
|
334
|
+
get isXHR(): boolean {
|
|
335
|
+
return this.req.headers.get("X-Requested-With") === "XMLHttpRequest";
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Get client IP address
|
|
340
|
+
*/
|
|
341
|
+
get ip(): string | undefined {
|
|
342
|
+
return (
|
|
343
|
+
this.req.headers.get("x-forwarded-for")?.split(",")[0].trim() ??
|
|
344
|
+
this.req.headers.get("x-real-ip") ??
|
|
345
|
+
undefined
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// ============= Context Factory =============
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Create a new context
|
|
354
|
+
*/
|
|
355
|
+
export function createContext(
|
|
356
|
+
request: Request,
|
|
357
|
+
params: PathParams = {},
|
|
358
|
+
): Context {
|
|
359
|
+
return new Context(request, params);
|
|
360
|
+
}
|