@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.
Files changed (120) hide show
  1. package/.env.example +109 -0
  2. package/.github/workflows/ci.yml +31 -0
  3. package/LICENSE +21 -0
  4. package/README.md +892 -0
  5. package/architecture.md +652 -0
  6. package/bun.lock +70 -0
  7. package/dist/cli/index.js +3233 -0
  8. package/dist/index.js +9014 -0
  9. package/package.json +77 -0
  10. package/src/cache/index.ts +795 -0
  11. package/src/cli/ARCHITECTURE.md +837 -0
  12. package/src/cli/bin.ts +10 -0
  13. package/src/cli/commands/build.ts +425 -0
  14. package/src/cli/commands/dev.ts +248 -0
  15. package/src/cli/commands/generate.ts +541 -0
  16. package/src/cli/commands/help.ts +55 -0
  17. package/src/cli/commands/index.ts +112 -0
  18. package/src/cli/commands/migration.ts +355 -0
  19. package/src/cli/commands/new.ts +804 -0
  20. package/src/cli/commands/start.ts +208 -0
  21. package/src/cli/core/args.ts +283 -0
  22. package/src/cli/core/console.ts +349 -0
  23. package/src/cli/core/index.ts +60 -0
  24. package/src/cli/core/prompt.ts +424 -0
  25. package/src/cli/core/spinner.ts +265 -0
  26. package/src/cli/index.ts +135 -0
  27. package/src/cli/templates/deploy.ts +295 -0
  28. package/src/cli/templates/docker.ts +307 -0
  29. package/src/cli/templates/index.ts +24 -0
  30. package/src/cli/utils/fs.ts +428 -0
  31. package/src/cli/utils/index.ts +8 -0
  32. package/src/cli/utils/strings.ts +197 -0
  33. package/src/config/env.ts +408 -0
  34. package/src/config/index.ts +506 -0
  35. package/src/config/loader.ts +329 -0
  36. package/src/config/merge.ts +285 -0
  37. package/src/config/types.ts +320 -0
  38. package/src/config/validation.ts +441 -0
  39. package/src/container/forward-ref.ts +143 -0
  40. package/src/container/index.ts +386 -0
  41. package/src/context/index.ts +360 -0
  42. package/src/database/index.ts +1142 -0
  43. package/src/database/migrations/index.ts +371 -0
  44. package/src/database/schema/index.ts +619 -0
  45. package/src/frontend/api-routes.ts +640 -0
  46. package/src/frontend/bundler.ts +643 -0
  47. package/src/frontend/console-client.ts +419 -0
  48. package/src/frontend/console-stream.ts +587 -0
  49. package/src/frontend/dev-server.ts +846 -0
  50. package/src/frontend/file-router.ts +611 -0
  51. package/src/frontend/frameworks/index.ts +106 -0
  52. package/src/frontend/frameworks/react.ts +85 -0
  53. package/src/frontend/frameworks/solid.ts +104 -0
  54. package/src/frontend/frameworks/svelte.ts +110 -0
  55. package/src/frontend/frameworks/vue.ts +92 -0
  56. package/src/frontend/hmr-client.ts +663 -0
  57. package/src/frontend/hmr.ts +728 -0
  58. package/src/frontend/index.ts +342 -0
  59. package/src/frontend/islands.ts +552 -0
  60. package/src/frontend/isr.ts +555 -0
  61. package/src/frontend/layout.ts +475 -0
  62. package/src/frontend/ssr/react.ts +446 -0
  63. package/src/frontend/ssr/solid.ts +523 -0
  64. package/src/frontend/ssr/svelte.ts +546 -0
  65. package/src/frontend/ssr/vue.ts +504 -0
  66. package/src/frontend/ssr.ts +699 -0
  67. package/src/frontend/types.ts +2274 -0
  68. package/src/health/index.ts +604 -0
  69. package/src/index.ts +410 -0
  70. package/src/lock/index.ts +587 -0
  71. package/src/logger/index.ts +444 -0
  72. package/src/logger/transports/index.ts +969 -0
  73. package/src/metrics/index.ts +494 -0
  74. package/src/middleware/built-in.ts +360 -0
  75. package/src/middleware/index.ts +94 -0
  76. package/src/modules/filters.ts +458 -0
  77. package/src/modules/guards.ts +405 -0
  78. package/src/modules/index.ts +1256 -0
  79. package/src/modules/interceptors.ts +574 -0
  80. package/src/modules/lazy.ts +418 -0
  81. package/src/modules/lifecycle.ts +478 -0
  82. package/src/modules/metadata.ts +90 -0
  83. package/src/modules/pipes.ts +626 -0
  84. package/src/router/index.ts +339 -0
  85. package/src/router/linear.ts +371 -0
  86. package/src/router/regex.ts +292 -0
  87. package/src/router/tree.ts +562 -0
  88. package/src/rpc/index.ts +1263 -0
  89. package/src/security/index.ts +436 -0
  90. package/src/ssg/index.ts +631 -0
  91. package/src/storage/index.ts +456 -0
  92. package/src/telemetry/index.ts +1097 -0
  93. package/src/testing/index.ts +1586 -0
  94. package/src/types/index.ts +236 -0
  95. package/src/types/optional-deps.d.ts +219 -0
  96. package/src/validation/index.ts +276 -0
  97. package/src/websocket/index.ts +1004 -0
  98. package/tests/integration/cli.test.ts +1016 -0
  99. package/tests/integration/fullstack.test.ts +234 -0
  100. package/tests/unit/cache.test.ts +174 -0
  101. package/tests/unit/cli-commands.test.ts +892 -0
  102. package/tests/unit/cli.test.ts +1258 -0
  103. package/tests/unit/container.test.ts +279 -0
  104. package/tests/unit/context.test.ts +221 -0
  105. package/tests/unit/database.test.ts +183 -0
  106. package/tests/unit/linear-router.test.ts +280 -0
  107. package/tests/unit/lock.test.ts +336 -0
  108. package/tests/unit/middleware.test.ts +184 -0
  109. package/tests/unit/modules.test.ts +142 -0
  110. package/tests/unit/pubsub.test.ts +257 -0
  111. package/tests/unit/regex-router.test.ts +265 -0
  112. package/tests/unit/router.test.ts +373 -0
  113. package/tests/unit/rpc.test.ts +1248 -0
  114. package/tests/unit/security.test.ts +174 -0
  115. package/tests/unit/telemetry.test.ts +371 -0
  116. package/tests/unit/test-cache.test.ts +110 -0
  117. package/tests/unit/test-database.test.ts +282 -0
  118. package/tests/unit/tree-router.test.ts +325 -0
  119. package/tests/unit/validation.test.ts +794 -0
  120. 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
+ }