@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,446 @@
1
+ /**
2
+ * React SSR Renderer
3
+ *
4
+ * Provides server-side rendering for React components using:
5
+ * - renderToPipeableStream / renderToReadableStream for streaming
6
+ * - React Helmet for head management
7
+ * - Suspense boundary support
8
+ */
9
+
10
+ import type {
11
+ SSRContext,
12
+ SSRElement,
13
+ SSRPage,
14
+ FrameworkSSRRenderer,
15
+ SSRHydrationData,
16
+ } from "../types.js";
17
+
18
+ // React types (dynamically imported)
19
+ interface ReactElement {
20
+ type: unknown;
21
+ props: Record<string, unknown>;
22
+ key: string | null;
23
+ }
24
+
25
+ interface ReactComponent {
26
+ (props: Record<string, unknown>): ReactElement | null;
27
+ }
28
+
29
+ // Head element storage (similar to React Helmet)
30
+ let headElements: SSRElement[] = [];
31
+
32
+ /**
33
+ * Reset head elements for a new render
34
+ */
35
+ export function resetHead(): void {
36
+ headElements = [];
37
+ }
38
+
39
+ /**
40
+ * Get collected head elements
41
+ */
42
+ export function getHeadElements(): SSRElement[] {
43
+ return [...headElements];
44
+ }
45
+
46
+ /**
47
+ * Add a head element
48
+ */
49
+ export function addHeadElement(element: SSRElement): void {
50
+ headElements.push(element);
51
+ }
52
+
53
+ /**
54
+ * Create a title element for head
55
+ */
56
+ export function title(text: string): SSRElement {
57
+ return { tag: "title", attrs: {}, children: [{ tag: "#text", attrs: {}, innerHTML: text }] };
58
+ }
59
+
60
+ /**
61
+ * Create a meta element for head
62
+ */
63
+ export function meta(attrs: Record<string, string>): SSRElement {
64
+ return { tag: "meta", attrs };
65
+ }
66
+
67
+ /**
68
+ * Create a link element for head
69
+ */
70
+ export function link(attrs: Record<string, string>): SSRElement {
71
+ return { tag: "link", attrs };
72
+ }
73
+
74
+ /**
75
+ * Create a script element for head
76
+ */
77
+ export function script(attrs: Record<string, string>, innerHTML?: string): SSRElement {
78
+ return { tag: "script", attrs, innerHTML };
79
+ }
80
+
81
+ /**
82
+ * Create a style element for head
83
+ */
84
+ export function style(innerHTML: string, attrs?: Record<string, string>): SSRElement {
85
+ return { tag: "style", attrs: attrs || {}, innerHTML };
86
+ }
87
+
88
+ /**
89
+ * React SSR Renderer implementation
90
+ */
91
+ export class ReactSSRRenderer implements FrameworkSSRRenderer {
92
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
93
+ private react: any = null;
94
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
95
+ private reactDomServer: any = null;
96
+ private initialized = false;
97
+
98
+ /**
99
+ * Initialize React modules
100
+ */
101
+ async init(): Promise<void> {
102
+ if (this.initialized) return;
103
+
104
+ try {
105
+ this.react = await import("react");
106
+ this.reactDomServer = await import("react-dom/server");
107
+ this.initialized = true;
108
+ } catch (error) {
109
+ throw new Error(
110
+ "React is not installed. Install it with: bun add react react-dom"
111
+ );
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Render a component to HTML string
117
+ */
118
+ async renderToString(component: unknown, context: SSRContext): Promise<string> {
119
+ await this.init();
120
+
121
+ if (!this.reactDomServer) {
122
+ throw new Error("React DOM Server not initialized");
123
+ }
124
+
125
+ resetHead();
126
+
127
+ try {
128
+ // Use renderToString for non-streaming
129
+ const renderToStringFn = (this.reactDomServer as unknown as { renderToString: (el: unknown) => string }).renderToString;
130
+ const html = renderToStringFn(component as ReactElement);
131
+ return html;
132
+ } catch (error) {
133
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
134
+ throw new Error(`React renderToString failed: ${errorMessage}`);
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Render a component to a stream
140
+ */
141
+ renderToStream(component: unknown, context: SSRContext): ReadableStream<Uint8Array> {
142
+ // Create a promise-based initialization
143
+ const encoder = new TextEncoder();
144
+
145
+ return new ReadableStream<Uint8Array>({
146
+ start: async (controller) => {
147
+ try {
148
+ await this.init();
149
+
150
+ if (!this.reactDomServer) {
151
+ controller.error(new Error("React DOM Server not initialized"));
152
+ return;
153
+ }
154
+
155
+ resetHead();
156
+
157
+ // Check if renderToReadableStream is available (modern API)
158
+ if ("renderToReadableStream" in this.reactDomServer) {
159
+ const stream = await this.reactDomServer.renderToReadableStream(
160
+ component as ReactElement,
161
+ {
162
+ bootstrapScripts: [],
163
+ onError: (error: Error) => {
164
+ console.error("React streaming error:", error);
165
+ },
166
+ }
167
+ );
168
+
169
+ const reader = stream.getReader();
170
+
171
+ while (true) {
172
+ const { done, value } = await reader.read();
173
+ if (done) {
174
+ controller.close();
175
+ break;
176
+ }
177
+ controller.enqueue(value);
178
+ }
179
+ } else {
180
+ // Fallback to renderToString for older React versions
181
+ const html = this.reactDomServer.renderToString(component as ReactElement);
182
+ controller.enqueue(encoder.encode(html));
183
+ controller.close();
184
+ }
185
+ } catch (error) {
186
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
187
+ controller.error(new Error(`React renderToStream failed: ${errorMessage}`));
188
+ }
189
+ },
190
+ });
191
+ }
192
+
193
+ /**
194
+ * Get head elements from component
195
+ */
196
+ getHeadElements(context: SSRContext): SSRElement[] {
197
+ return getHeadElements();
198
+ }
199
+
200
+ /**
201
+ * Create the framework-specific component
202
+ */
203
+ createComponent(page: SSRPage, context: SSRContext): unknown {
204
+ // Return the page render function wrapped in React context
205
+ return {
206
+ page,
207
+ context,
208
+ };
209
+ }
210
+
211
+ /**
212
+ * Render with Suspense support
213
+ */
214
+ async renderWithSuspense(
215
+ component: unknown,
216
+ context: SSRContext,
217
+ fallback: string = "<div>Loading...</div>"
218
+ ): Promise<string> {
219
+ await this.init();
220
+
221
+ if (!this.react || !this.reactDomServer) {
222
+ throw new Error("React not initialized");
223
+ }
224
+
225
+ resetHead();
226
+
227
+ try {
228
+ // Wrap with Suspense if available
229
+ const suspenseWrapper = this.react.createElement(
230
+ this.react.Suspense,
231
+ { fallback },
232
+ component as ReactElement
233
+ );
234
+
235
+ const html = this.reactDomServer.renderToString(suspenseWrapper);
236
+ return html;
237
+ } catch (error) {
238
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
239
+ throw new Error(`React renderWithSuspense failed: ${errorMessage}`);
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Render to stream with all callbacks
245
+ */
246
+ async renderToStreamWithCallbacks(
247
+ component: unknown,
248
+ context: SSRContext,
249
+ options: {
250
+ onAllReady?: () => void;
251
+ onShellReady?: () => void;
252
+ onShellError?: (error: Error) => void;
253
+ onError?: (error: Error) => void;
254
+ } = {}
255
+ ): Promise<ReadableStream<Uint8Array>> {
256
+ await this.init();
257
+
258
+ if (!this.reactDomServer) {
259
+ throw new Error("React DOM Server not initialized");
260
+ }
261
+
262
+ resetHead();
263
+
264
+ const encoder = new TextEncoder();
265
+
266
+ return new ReadableStream<Uint8Array>({
267
+ start: async (controller) => {
268
+ try {
269
+ if ("renderToReadableStream" in this.reactDomServer!) {
270
+ const stream = await this.reactDomServer.renderToReadableStream(
271
+ component as ReactElement,
272
+ {
273
+ bootstrapScripts: [],
274
+ onError: (error: Error) => {
275
+ options.onError?.(error);
276
+ console.error("React streaming error:", error);
277
+ },
278
+ }
279
+ );
280
+
281
+ // Wait for shell to be ready
282
+ await stream.allReady;
283
+ options.onAllReady?.();
284
+
285
+ const reader = stream.getReader();
286
+
287
+ while (true) {
288
+ const { done, value } = await reader.read();
289
+ if (done) {
290
+ controller.close();
291
+ break;
292
+ }
293
+ controller.enqueue(value);
294
+ }
295
+ } else {
296
+ // Fallback
297
+ const html = this.reactDomServer!.renderToString(component as ReactElement);
298
+ options.onAllReady?.();
299
+ controller.enqueue(encoder.encode(html));
300
+ controller.close();
301
+ }
302
+ } catch (error) {
303
+ const err = error instanceof Error ? error : new Error("Unknown error");
304
+ options.onShellError?.(err);
305
+ controller.error(err);
306
+ }
307
+ },
308
+ });
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Create a React SSR renderer
314
+ */
315
+ export function createReactSSRRenderer(): ReactSSRRenderer {
316
+ return new ReactSSRRenderer();
317
+ }
318
+
319
+ /**
320
+ * Convert SSRElement to HTML string
321
+ */
322
+ export function ssrElementToString(element: SSRElement): string {
323
+ if (element.tag === "#text") {
324
+ return escapeHtml(element.innerHTML || "");
325
+ }
326
+
327
+ const attrs = Object.entries(element.attrs)
328
+ .map(([key, value]) => `${key}="${escapeHtml(value)}"`)
329
+ .join(" ");
330
+
331
+ const openTag = attrs ? `<${element.tag} ${attrs}>` : `<${element.tag}>`;
332
+
333
+ if (element.innerHTML) {
334
+ return `${openTag}${element.innerHTML}</${element.tag}>`;
335
+ }
336
+
337
+ if (element.children && element.children.length > 0) {
338
+ const children = element.children.map(ssrElementToString).join("");
339
+ return `${openTag}${children}</${element.tag}>`;
340
+ }
341
+
342
+ // Self-closing tags
343
+ const voidElements = ["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"];
344
+ if (voidElements.includes(element.tag)) {
345
+ return attrs ? `<${element.tag} ${attrs}>` : `<${element.tag}>`;
346
+ }
347
+
348
+ return `${openTag}</${element.tag}>`;
349
+ }
350
+
351
+ /**
352
+ * Escape HTML special characters
353
+ */
354
+ function escapeHtml(str: string): string {
355
+ return str
356
+ .replace(/&/g, "\x26amp;")
357
+ .replace(/</g, "\x26lt;")
358
+ .replace(/>/g, "\x26gt;")
359
+ .replace(/"/g, "\x26quot;")
360
+ .replace(/'/g, "&#39;");
361
+ }
362
+
363
+ /**
364
+ * React Helmet-like head management
365
+ */
366
+ export class ReactHelmet {
367
+ private static instance: ReactHelmet;
368
+ private title = "";
369
+ private metaTags: SSRElement[] = [];
370
+ private linkTags: SSRElement[] = [];
371
+ private scriptTags: SSRElement[] = [];
372
+ private styleTags: SSRElement[] = [];
373
+
374
+ static getInstance(): ReactHelmet {
375
+ if (!ReactHelmet.instance) {
376
+ ReactHelmet.instance = new ReactHelmet();
377
+ }
378
+ return ReactHelmet.instance;
379
+ }
380
+
381
+ setTitle(title: string): this {
382
+ this.title = title;
383
+ addHeadElement({ tag: "title", attrs: {}, children: [{ tag: "#text", attrs: {}, innerHTML: title }] });
384
+ return this;
385
+ }
386
+
387
+ addMeta(attrs: Record<string, string>): this {
388
+ this.metaTags.push({ tag: "meta", attrs });
389
+ addHeadElement({ tag: "meta", attrs });
390
+ return this;
391
+ }
392
+
393
+ addLink(attrs: Record<string, string>): this {
394
+ this.linkTags.push({ tag: "link", attrs });
395
+ addHeadElement({ tag: "link", attrs });
396
+ return this;
397
+ }
398
+
399
+ addScript(attrs: Record<string, string>, innerHTML?: string): this {
400
+ this.scriptTags.push({ tag: "script", attrs, innerHTML });
401
+ addHeadElement({ tag: "script", attrs, innerHTML });
402
+ return this;
403
+ }
404
+
405
+ addStyle(innerHTML: string, attrs?: Record<string, string>): this {
406
+ this.styleTags.push({ tag: "style", attrs: attrs || {}, innerHTML });
407
+ addHeadElement({ tag: "style", attrs: attrs || {}, innerHTML });
408
+ return this;
409
+ }
410
+
411
+ reset(): void {
412
+ this.title = "";
413
+ this.metaTags = [];
414
+ this.linkTags = [];
415
+ this.scriptTags = [];
416
+ this.styleTags = [];
417
+ resetHead();
418
+ }
419
+
420
+ getTitle(): string {
421
+ return this.title;
422
+ }
423
+
424
+ getMetaTags(): SSRElement[] {
425
+ return [...this.metaTags];
426
+ }
427
+
428
+ getLinkTags(): SSRElement[] {
429
+ return [...this.linkTags];
430
+ }
431
+
432
+ getScriptTags(): SSRElement[] {
433
+ return [...this.scriptTags];
434
+ }
435
+
436
+ getStyleTags(): SSRElement[] {
437
+ return [...this.styleTags];
438
+ }
439
+ }
440
+
441
+ /**
442
+ * Get React Helmet instance
443
+ */
444
+ export function helmet(): ReactHelmet {
445
+ return ReactHelmet.getInstance();
446
+ }