@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,504 @@
1
+ /**
2
+ * Vue SSR Renderer
3
+ *
4
+ * Provides server-side rendering for Vue components using:
5
+ * - renderToString / renderToStream from vue/server-renderer
6
+ * - Vue Router integration
7
+ * - Vue Meta for head management
8
+ */
9
+
10
+ import type {
11
+ SSRContext,
12
+ SSRElement,
13
+ SSRPage,
14
+ FrameworkSSRRenderer,
15
+ } from "../types.js";
16
+
17
+ // Vue types (dynamically imported)
18
+ interface VueApp {
19
+ component(name: string, component: unknown): VueApp;
20
+ use(plugin: unknown, ...options: unknown[]): VueApp;
21
+ provide(key: string | symbol, value: unknown): VueApp;
22
+ config: {
23
+ globalProperties: Record<string, unknown>;
24
+ errorHandler: ((err: unknown, instance: unknown, info: string) => void) | null;
25
+ };
26
+ mount(selector: string): unknown;
27
+ unmount(): void;
28
+ ssrContext: SSRContext | null;
29
+ }
30
+
31
+ interface VueRouter {
32
+ push(location: string): Promise<unknown>;
33
+ replace(location: string): Promise<unknown>;
34
+ currentRoute: {
35
+ value: {
36
+ path: string;
37
+ params: Record<string, string>;
38
+ query: Record<string, string>;
39
+ meta: Record<string, unknown>;
40
+ };
41
+ };
42
+ isReady(): Promise<void>;
43
+ }
44
+
45
+ interface CreateSSRAppOptions {
46
+ /**
47
+ * Component to render
48
+ */
49
+ component: unknown;
50
+
51
+ /**
52
+ * Props to pass to component
53
+ */
54
+ props?: Record<string, unknown>;
55
+
56
+ /**
57
+ * Initial state for hydration
58
+ */
59
+ state?: Record<string, unknown>;
60
+ }
61
+
62
+ // Head element storage
63
+ let headElements: SSRElement[] = [];
64
+
65
+ /**
66
+ * Reset head elements for a new render
67
+ */
68
+ export function resetHead(): void {
69
+ headElements = [];
70
+ }
71
+
72
+ /**
73
+ * Get collected head elements
74
+ */
75
+ export function getHeadElements(): SSRElement[] {
76
+ return [...headElements];
77
+ }
78
+
79
+ /**
80
+ * Add a head element
81
+ */
82
+ export function addHeadElement(element: SSRElement): void {
83
+ headElements.push(element);
84
+ }
85
+
86
+ /**
87
+ * Vue SSR Renderer implementation
88
+ */
89
+ export class VueSSRRenderer implements FrameworkSSRRenderer {
90
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
91
+ private vue: any = null;
92
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
93
+ private vueServerRenderer: any = null;
94
+ private initialized = false;
95
+
96
+ /**
97
+ * Initialize Vue modules
98
+ */
99
+ async init(): Promise<void> {
100
+ if (this.initialized) return;
101
+
102
+ try {
103
+ this.vue = await import("vue");
104
+ this.vueServerRenderer = await import("vue/server-renderer");
105
+ this.initialized = true;
106
+ } catch (error) {
107
+ throw new Error(
108
+ "Vue is not installed. Install it with: bun add vue"
109
+ );
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Create a Vue SSR app instance
115
+ */
116
+ async createSSRApp(options: CreateSSRAppOptions): Promise<VueApp> {
117
+ await this.init();
118
+
119
+ if (!this.vue) {
120
+ throw new Error("Vue not initialized");
121
+ }
122
+
123
+ const { createSSRApp } = this.vue;
124
+ const app = createSSRApp(options.component, options.props || {});
125
+
126
+ // Provide initial state for hydration
127
+ if (options.state) {
128
+ app.provide("__INITIAL_STATE__", options.state);
129
+ }
130
+
131
+ return app as VueApp;
132
+ }
133
+
134
+ /**
135
+ * Render a component to HTML string
136
+ */
137
+ async renderToString(component: unknown, context: SSRContext): Promise<string> {
138
+ await this.init();
139
+
140
+ if (!this.vueServerRenderer) {
141
+ throw new Error("Vue Server Renderer not initialized");
142
+ }
143
+
144
+ resetHead();
145
+
146
+ try {
147
+ // If component is already a Vue app, render it directly
148
+ if (this.isVueApp(component)) {
149
+ component.ssrContext = context;
150
+ const html = await this.vueServerRenderer.renderToString(component);
151
+ return html;
152
+ }
153
+
154
+ // Otherwise create an app and render
155
+ const app = await this.createSSRApp({
156
+ component,
157
+ props: { context },
158
+ });
159
+ app.ssrContext = context;
160
+
161
+ const html = await this.vueServerRenderer.renderToString(app);
162
+ return html;
163
+ } catch (error) {
164
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
165
+ throw new Error(`Vue renderToString failed: ${errorMessage}`);
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Render a component to a stream
171
+ */
172
+ renderToStream(component: unknown, context: SSRContext): ReadableStream<Uint8Array> {
173
+ const encoder = new TextEncoder();
174
+
175
+ return new ReadableStream<Uint8Array>({
176
+ start: async (controller) => {
177
+ try {
178
+ await this.init();
179
+
180
+ if (!this.vueServerRenderer) {
181
+ controller.error(new Error("Vue Server Renderer not initialized"));
182
+ return;
183
+ }
184
+
185
+ resetHead();
186
+
187
+ // Set SSR context
188
+ if (this.isVueApp(component)) {
189
+ component.ssrContext = context;
190
+ }
191
+
192
+ // Use renderToNodeStream equivalent via Web Streams
193
+ const { renderToWebStream } = this.vueServerRenderer;
194
+
195
+ if (renderToWebStream) {
196
+ const stream = renderToWebStream(component);
197
+
198
+ const reader = stream.getReader();
199
+
200
+ while (true) {
201
+ const { done, value } = await reader.read();
202
+ if (done) {
203
+ controller.close();
204
+ break;
205
+ }
206
+ controller.enqueue(value);
207
+ }
208
+ } else {
209
+ // Fallback to renderToString
210
+ const html = await this.vueServerRenderer.renderToString(component);
211
+ controller.enqueue(encoder.encode(html));
212
+ controller.close();
213
+ }
214
+ } catch (error) {
215
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
216
+ controller.error(new Error(`Vue renderToStream failed: ${errorMessage}`));
217
+ }
218
+ },
219
+ });
220
+ }
221
+
222
+ /**
223
+ * Get head elements from component
224
+ */
225
+ getHeadElements(context: SSRContext): SSRElement[] {
226
+ return getHeadElements();
227
+ }
228
+
229
+ /**
230
+ * Create the framework-specific component
231
+ */
232
+ createComponent(page: SSRPage, context: SSRContext): unknown {
233
+ return {
234
+ page,
235
+ context,
236
+ };
237
+ }
238
+
239
+ /**
240
+ * Check if object is a Vue app instance
241
+ */
242
+ private isVueApp(obj: unknown): obj is VueApp {
243
+ return (
244
+ typeof obj === "object" &&
245
+ obj !== null &&
246
+ "component" in obj &&
247
+ "use" in obj &&
248
+ "provide" in obj &&
249
+ "config" in obj
250
+ );
251
+ }
252
+
253
+ /**
254
+ * Create Vue Router for SSR
255
+ */
256
+ async createRouter(routes: unknown[]): Promise<{
257
+ router: VueRouter;
258
+ app: VueApp;
259
+ }> {
260
+ await this.init();
261
+
262
+ if (!this.vue) {
263
+ throw new Error("Vue not initialized");
264
+ }
265
+
266
+ try {
267
+ const vueRouter = await import("vue-router");
268
+ const { createMemoryHistory, createRouter } = vueRouter;
269
+
270
+ const router = createRouter({
271
+ history: createMemoryHistory(),
272
+ routes: routes as [],
273
+ });
274
+
275
+ const app = await this.createSSRApp({
276
+ component: {},
277
+ });
278
+
279
+ app.use(router);
280
+
281
+ return { router: router as unknown as VueRouter, app };
282
+ } catch (error) {
283
+ throw new Error(
284
+ "Vue Router is not installed. Install it with: bun add vue-router"
285
+ );
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Render with Vue Router
291
+ */
292
+ async renderWithRouter(
293
+ app: VueApp,
294
+ router: VueRouter,
295
+ url: string,
296
+ context: SSRContext
297
+ ): Promise<string> {
298
+ await this.init();
299
+
300
+ if (!this.vueServerRenderer) {
301
+ throw new Error("Vue Server Renderer not initialized");
302
+ }
303
+
304
+ resetHead();
305
+
306
+ try {
307
+ // Push to router and wait for ready
308
+ await router.push(url);
309
+ await router.isReady();
310
+
311
+ // Set SSR context
312
+ app.ssrContext = context;
313
+
314
+ // Render
315
+ const html = await this.vueServerRenderer.renderToString(app);
316
+ return html;
317
+ } catch (error) {
318
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
319
+ throw new Error(`Vue renderWithRouter failed: ${errorMessage}`);
320
+ }
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Create a Vue SSR renderer
326
+ */
327
+ export function createVueSSRRenderer(): VueSSRRenderer {
328
+ return new VueSSRRenderer();
329
+ }
330
+
331
+ /**
332
+ * Vue Meta-like head management
333
+ */
334
+ export class VueMeta {
335
+ private static instance: VueMeta;
336
+ private title = "";
337
+ private metaTags: SSRElement[] = [];
338
+ private linkTags: SSRElement[] = [];
339
+ private scriptTags: SSRElement[] = [];
340
+ private styleTags: SSRElement[] = [];
341
+
342
+ static getInstance(): VueMeta {
343
+ if (!VueMeta.instance) {
344
+ VueMeta.instance = new VueMeta();
345
+ }
346
+ return VueMeta.instance;
347
+ }
348
+
349
+ setTitle(title: string): this {
350
+ this.title = title;
351
+ addHeadElement({ tag: "title", attrs: {}, children: [{ tag: "#text", attrs: {}, innerHTML: title }] });
352
+ return this;
353
+ }
354
+
355
+ addMeta(attrs: Record<string, string>): this {
356
+ this.metaTags.push({ tag: "meta", attrs });
357
+ addHeadElement({ tag: "meta", attrs });
358
+ return this;
359
+ }
360
+
361
+ addLink(attrs: Record<string, string>): this {
362
+ this.linkTags.push({ tag: "link", attrs });
363
+ addHeadElement({ tag: "link", attrs });
364
+ return this;
365
+ }
366
+
367
+ addScript(attrs: Record<string, string>, innerHTML?: string): this {
368
+ this.scriptTags.push({ tag: "script", attrs, innerHTML });
369
+ addHeadElement({ tag: "script", attrs, innerHTML });
370
+ return this;
371
+ }
372
+
373
+ addStyle(innerHTML: string, attrs?: Record<string, string>): this {
374
+ this.styleTags.push({ tag: "style", attrs: attrs || {}, innerHTML });
375
+ addHeadElement({ tag: "style", attrs: attrs || {}, innerHTML });
376
+ return this;
377
+ }
378
+
379
+ reset(): void {
380
+ this.title = "";
381
+ this.metaTags = [];
382
+ this.linkTags = [];
383
+ this.scriptTags = [];
384
+ this.styleTags = [];
385
+ resetHead();
386
+ }
387
+
388
+ getTitle(): string {
389
+ return this.title;
390
+ }
391
+
392
+ getMetaTags(): SSRElement[] {
393
+ return [...this.metaTags];
394
+ }
395
+
396
+ getLinkTags(): SSRElement[] {
397
+ return [...this.linkTags];
398
+ }
399
+
400
+ getScriptTags(): SSRElement[] {
401
+ return [...this.scriptTags];
402
+ }
403
+
404
+ getStyleTags(): SSRElement[] {
405
+ return [...this.styleTags];
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Get Vue Meta instance
411
+ */
412
+ export function vueMeta(): VueMeta {
413
+ return VueMeta.getInstance();
414
+ }
415
+
416
+ /**
417
+ * Convert SSRElement to HTML string
418
+ */
419
+ export function ssrElementToString(element: SSRElement): string {
420
+ if (element.tag === "#text") {
421
+ return escapeHtml(element.innerHTML || "");
422
+ }
423
+
424
+ const attrs = Object.entries(element.attrs)
425
+ .map(([key, value]) => `${key}="${escapeHtml(value)}"`)
426
+ .join(" ");
427
+
428
+ const openTag = attrs ? `<${element.tag} ${attrs}>` : `<${element.tag}>`;
429
+
430
+ if (element.innerHTML) {
431
+ return `${openTag}${element.innerHTML}</${element.tag}>`;
432
+ }
433
+
434
+ if (element.children && element.children.length > 0) {
435
+ const children = element.children.map(ssrElementToString).join("");
436
+ return `${openTag}${children}</${element.tag}>`;
437
+ }
438
+
439
+ // Self-closing tags
440
+ const voidElements = ["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"];
441
+ if (voidElements.includes(element.tag)) {
442
+ return attrs ? `<${element.tag} ${attrs}>` : `<${element.tag}>`;
443
+ }
444
+
445
+ return `${openTag}</${element.tag}>`;
446
+ }
447
+
448
+ /**
449
+ * Escape HTML special characters
450
+ */
451
+ function escapeHtml(str: string): string {
452
+ return str
453
+ .replace(/&/g, "\x26amp;")
454
+ .replace(/</g, "\x26lt;")
455
+ .replace(/>/g, "\x26gt;")
456
+ .replace(/"/g, "\x26quot;")
457
+ .replace(/'/g, "&#39;");
458
+ }
459
+
460
+ /**
461
+ * Vue useHead composable-like function
462
+ */
463
+ export function useHead(head: {
464
+ title?: string;
465
+ meta?: Record<string, string>[];
466
+ link?: Record<string, string>[];
467
+ script?: Record<string, string>[];
468
+ style?: { innerHTML: string; attrs?: Record<string, string> }[];
469
+ }): void {
470
+ const meta = vueMeta();
471
+
472
+ if (head.title) {
473
+ meta.setTitle(head.title);
474
+ }
475
+
476
+ head.meta?.forEach((attrs) => meta.addMeta(attrs));
477
+ head.link?.forEach((attrs) => meta.addLink(attrs));
478
+ head.script?.forEach((attrs) => meta.addScript(attrs));
479
+ head.style?.forEach(({ innerHTML, attrs }) => meta.addStyle(innerHTML, attrs));
480
+ }
481
+
482
+ /**
483
+ * Create Vue SSR context with initial state
484
+ */
485
+ export function createVueSSRContext(
486
+ request: Request,
487
+ initialState: Record<string, unknown> = {}
488
+ ): SSRContext {
489
+ const url = new URL(request.url);
490
+
491
+ return {
492
+ url: request.url,
493
+ request,
494
+ headers: new Headers(),
495
+ status: 200,
496
+ head: [],
497
+ body: [],
498
+ data: initialState,
499
+ modules: new Set(),
500
+ pathname: url.pathname,
501
+ query: url.searchParams,
502
+ params: {},
503
+ };
504
+ }