vatts 1.0.2-alpha.3 → 1.0.2-alpha.5

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/dist/index.js CHANGED
@@ -59,7 +59,7 @@ const fs_1 = __importDefault(require("fs"));
59
59
  const express_1 = require("./adapters/express");
60
60
  const builder_1 = require("./builder");
61
61
  const router_1 = require("./router");
62
- const renderer_1 = require("./renderer");
62
+ const renderer_1 = require("./renderer"); // Usando a nova função de streaming
63
63
  const http_1 = require("./api/http");
64
64
  Object.defineProperty(exports, "VattsRequest", { enumerable: true, get: function () { return http_1.VattsRequest; } });
65
65
  Object.defineProperty(exports, "VattsResponse", { enumerable: true, get: function () { return http_1.VattsResponse; } });
@@ -121,13 +121,9 @@ function isLargeProject(projectDir) {
121
121
  }
122
122
  }
123
123
  scanDirectory(srcDir);
124
- // Considera projeto grande se:
125
- // - Mais de 20 arquivos de frontend/style
126
- // - Ou tamanho total > 500KB
127
124
  return totalFiles > 20 || totalSize > 500 * 1024;
128
125
  }
129
126
  catch (error) {
130
- // Em caso de erro, assume que não é um projeto grande
131
127
  return false;
132
128
  }
133
129
  }
@@ -137,44 +133,32 @@ function createEntryFile(projectDir, routes) {
137
133
  const tempDir = path_1.default.join(projectDir, '.vatts', 'temp');
138
134
  fs_1.default.mkdirSync(tempDir, { recursive: true });
139
135
  const entryFilePath = path_1.default.join(tempDir, 'entry.client.js');
140
- // Verifica se há layout
141
136
  const layout = (0, router_1.getLayout)();
142
- // Verifica se há notFound personalizado
143
137
  const notFound = (0, router_1.getNotFound)();
144
- // Gera imports dinâmicos para cada componente
145
138
  const imports = routes
146
139
  .map((route, index) => {
147
140
  const relativePath = path_1.default.relative(tempDir, route.componentPath).replace(/\\/g, '/');
148
141
  return `import route${index} from '${relativePath}';`;
149
142
  })
150
143
  .join('\n');
151
- // Import do layout se existir
152
144
  const layoutImport = layout
153
145
  ? `import LayoutComponent from '${path_1.default.relative(tempDir, layout.componentPath).replace(/\\/g, '/')}';`
154
146
  : '';
155
- // Import do notFound se existir
156
147
  const notFoundImport = notFound
157
148
  ? `import NotFoundComponent from '${path_1.default.relative(tempDir, notFound.componentPath).replace(/\\/g, '/')}';`
158
149
  : '';
159
- // Registra os componentes no window para o cliente acessar
160
150
  const componentRegistration = routes
161
151
  .map((route, index) => ` '${route.componentPath}': route${index}.component || route${index}.default?.component,`)
162
152
  .join('\n');
163
- // Registra o layout se existir
164
153
  const layoutRegistration = layout
165
154
  ? `window.__VATTS_LAYOUT__ = LayoutComponent.default || LayoutComponent;`
166
155
  : `window.__VATTS_LAYOUT__ = null;`;
167
- // Registra o notFound se existir
168
156
  const notFoundRegistration = notFound
169
157
  ? `window.__VATTS_NOT_FOUND__ = NotFoundComponent.default || NotFoundComponent;`
170
158
  : `window.__VATTS_NOT_FOUND__ = null;`;
171
- // Caminho correto para o entry.client
172
- // IMPORTANT: quando o pacote é instalado via npm, bundlers (Rollup/Vite/etc.) não transpilam TSX em node_modules.
173
- // Por isso, aqui a gente sempre aponta para os artefatos compilados em dist/.
174
- const sdkDir = path_1.default.dirname(__dirname); // pasta do pacote (pai de dist/ e src/)
159
+ const sdkDir = path_1.default.dirname(__dirname);
175
160
  const entryClientPath = path_1.default.join(sdkDir, 'dist', 'client', 'entry.client.js');
176
161
  const relativeEntryPath = path_1.default.relative(tempDir, entryClientPath).replace(/\\/g, '/');
177
- // Import do DefaultNotFound do SDK (compilado)
178
162
  const defaultNotFoundPath = path_1.default.join(sdkDir, 'dist', 'client', 'DefaultNotFound.js');
179
163
  const relativeDefaultNotFoundPath = path_1.default.relative(tempDir, defaultNotFoundPath).replace(/\\/g, '/');
180
164
  const entryContent = `// Arquivo gerado automaticamente pelo vatts
@@ -183,28 +167,22 @@ ${layoutImport}
183
167
  ${notFoundImport}
184
168
  import DefaultNotFound from '${relativeDefaultNotFoundPath}';
185
169
 
186
- // Registra os componentes para o cliente
187
170
  window.__VATTS_COMPONENTS__ = {
188
171
  ${componentRegistration}
189
172
  };
190
173
 
191
- // Registra o layout se existir
192
174
  ${layoutRegistration}
193
-
194
- // Registra o notFound se existir
195
175
  ${notFoundRegistration}
196
176
 
197
-
198
177
  window.__VATTS_DEFAULT_NOT_FOUND__ = DefaultNotFound;
199
178
 
200
- // Importa e executa o entry.client.tsx
201
179
  import '${relativeEntryPath}';
202
180
  `;
203
181
  try {
204
182
  fs_1.default.writeFileSync(entryFilePath, entryContent);
205
183
  }
206
184
  catch (e) {
207
- console.error("sdfijnsdfnijfsdijnfsdnijsdfnijfsdnijfsdnijfsdn", e);
185
+ console.error("Error writing entry file", e);
208
186
  }
209
187
  return entryFilePath;
210
188
  }
@@ -218,74 +196,54 @@ function vatts(options) {
218
196
  (0, env_1.loadEnv)({ dir, dev, envFiles });
219
197
  // @ts-ignore
220
198
  process.vatts = options;
199
+ // @ts-ignore
200
+ process.env.PORT = options.port;
221
201
  const userWebDir = path_1.default.join(dir, 'src', 'web');
222
202
  const userWebRoutesDir = path_1.default.join(userWebDir, 'routes');
223
203
  const userBackendRoutesDir = path_1.default.join(dir, 'src', 'backend', 'routes');
224
- /**
225
- * Executa middlewares sequencialmente e depois o handler final
226
- * @param middlewares Array de middlewares para executar
227
- * @param finalHandler Handler final da rota
228
- * @param request Requisição do Vatts.js
229
- * @param params Parâmetros da rota
230
- * @returns Resposta do middleware ou handler final
231
- */
232
204
  async function executeMiddlewareChain(middlewares, finalHandler, request, params) {
233
205
  if (!middlewares || middlewares.length === 0) {
234
- // Não há middlewares, executa diretamente o handler final
235
206
  return await finalHandler(request, params);
236
207
  }
237
208
  let currentIndex = 0;
238
- // Função next que será chamada pelos middlewares
239
209
  const next = async () => {
240
210
  if (currentIndex < middlewares.length) {
241
- // Ainda há middlewares para executar
242
211
  const currentMiddleware = middlewares[currentIndex];
243
212
  currentIndex++;
244
213
  return await currentMiddleware(request, params, next);
245
214
  }
246
215
  else {
247
- // Todos os middlewares foram executados, chama o handler final
248
216
  return await finalHandler(request, params);
249
217
  }
250
218
  };
251
- // Inicia a cadeia de execução
252
219
  return await next();
253
220
  }
254
221
  let frontendRoutes = [];
255
222
  let hotReloadManager = null;
256
223
  let entryPoint;
257
- let outfile;
258
- // Função para regenerar o entry file
259
224
  const regenerateEntryFile = () => {
260
- // Recarrega todas as rotas e componentes
261
225
  const newFrontendRoutes = (0, router_1.loadRoutes)(userWebRoutesDir);
262
226
  const newLayout = (0, router_1.loadLayout)(userWebDir);
263
227
  const newNotFound = (0, router_1.loadNotFound)(userWebDir);
264
- // Se nada mudou, não reescreve o entry file (evita disparar rebuild extra)
265
228
  const oldKey = frontendRoutes.map(r => `${r.pattern ?? ''}:${r.componentPath}`).join('|');
266
229
  const newKey = newFrontendRoutes.map(r => `${r.pattern ?? ''}:${r.componentPath}`).join('|');
267
230
  if (oldKey === newKey) {
268
- // Ainda atualiza refs internas de layout/notFound, mas evita re-gerar o entry.
269
231
  frontendRoutes = newFrontendRoutes;
270
232
  return;
271
233
  }
272
234
  frontendRoutes = newFrontendRoutes;
273
- // Regenera o entry file
274
235
  entryPoint = createEntryFile(dir, frontendRoutes);
275
236
  };
276
237
  return {
277
238
  prepare: async () => {
278
239
  const isProduction = !dev;
279
240
  if (!isProduction) {
280
- // Inicia hot reload apenas em desenvolvimento (com suporte ao main)
281
241
  hotReloadManager = new hotReload_1.HotReloadManager(dir);
282
242
  await hotReloadManager.start();
283
- // Adiciona callback para recarregar TUDO quando qualquer arquivo mudar
284
243
  hotReloadManager.onBackendApiChange(() => {
285
244
  (0, router_1.loadBackendRoutes)(userBackendRoutesDir);
286
- (0, router_1.processWebSocketRoutes)(); // Processa rotas WS após recarregar backend
245
+ (0, router_1.processWebSocketRoutes)();
287
246
  });
288
- // Adiciona callback para regenerar entry file quando frontend mudar
289
247
  hotReloadManager.onFrontendChange(() => {
290
248
  regenerateEntryFile();
291
249
  });
@@ -297,13 +255,10 @@ function vatts(options) {
297
255
  const spinner1 = setInterval(() => {
298
256
  timee.update(` ${console_1.Colors.FgYellow}${spinnerFrames1[frameIndex1]}${console_1.Colors.Reset} Loading routes and components...`);
299
257
  frameIndex1 = (frameIndex1 + 1) % spinnerFrames1.length;
300
- }, 100); // muda a cada 100ms
301
- // ORDEM IMPORTANTE: Carrega TUDO antes de criar o arquivo de entrada
258
+ }, 100);
302
259
  frontendRoutes = (0, router_1.loadRoutes)(userWebRoutesDir);
303
260
  (0, router_1.loadBackendRoutes)(userBackendRoutesDir);
304
- // Processa rotas WebSocket após carregar backend
305
261
  (0, router_1.processWebSocketRoutes)();
306
- // Carrega layout.tsx ANTES de criar o entry file
307
262
  const layout = (0, router_1.loadLayout)(userWebDir);
308
263
  const notFound = (0, router_1.loadNotFound)(userWebDir);
309
264
  const outDir = path_1.default.join(dir, '.vatts');
@@ -313,20 +268,18 @@ function vatts(options) {
313
268
  timee.end(`Routes and components loaded in ${Date.now() - now}ms`);
314
269
  if (isProduction) {
315
270
  const time = console_1.default.dynamicLine(`Starting client build`);
316
- // Spinner
317
271
  const spinnerFrames = ['|', '/', '-', '\\'];
318
272
  let frameIndex = 0;
319
273
  const spinner = setInterval(() => {
320
274
  time.update(` ${console_1.Colors.FgYellow}${spinnerFrames[frameIndex]}${console_1.Colors.Reset} Building...`);
321
275
  frameIndex = (frameIndex + 1) % spinnerFrames.length;
322
- }, 100); // muda a cada 100ms
276
+ }, 100);
323
277
  const now = Date.now();
324
278
  await (0, builder_1.buildWithChunks)(entryPoint, outDir, isProduction);
325
279
  const elapsed = Date.now() - now;
326
- clearInterval(spinner); // para o spinner
327
- time.update(""); // limpa a linha
280
+ clearInterval(spinner);
281
+ time.update("");
328
282
  time.end(`Client build completed in ${elapsed}ms`);
329
- // Notifica o hot reload manager que o build foi concluído
330
283
  if (hotReloadManager) {
331
284
  hotReloadManager.onBuildComplete(true);
332
285
  }
@@ -341,13 +294,10 @@ function vatts(options) {
341
294
  }
342
295
  },
343
296
  executeInstrumentation: () => {
344
- // verificar se dir/src/instrumentation.(tsx/jsx/js/ts) existe com regex
345
297
  const instrumentationFile = fs_1.default.readdirSync(path_1.default.join(dir, 'src')).find(file => /^vattsweb\.(tsx|jsx|js|ts)$/.test(file));
346
298
  if (instrumentationFile) {
347
299
  const instrumentationPath = path_1.default.join(dir, 'src', instrumentationFile);
348
- // dar require, e executar a função principal do arquivo
349
300
  const instrumentation = require(instrumentationPath);
350
- // Registra o listener de hot reload se existir
351
301
  if (instrumentation.hotReloadListener && typeof instrumentation.hotReloadListener === 'function') {
352
302
  if (hotReloadManager) {
353
303
  hotReloadManager.setHotReloadListener(instrumentation.hotReloadListener);
@@ -366,17 +316,14 @@ function vatts(options) {
366
316
  },
367
317
  getRequestHandler: () => {
368
318
  return async (req, res) => {
369
- // Detecta o framework e cria request/response genéricos
370
319
  const adapter = factory_1.FrameworkAdapterFactory.detectFramework(req, res);
371
320
  const genericReq = adapter.parseRequest(req);
372
321
  const genericRes = adapter.createResponse(res);
373
- // Adiciona informações do hweb na requisição genérica
374
322
  genericReq.hwebDev = dev;
375
323
  genericReq.hotReloadManager = hotReloadManager;
376
324
  const { hostname } = req.headers;
377
325
  const method = (genericReq.method || 'GET').toUpperCase();
378
326
  const pathname = new URL(genericReq.url, `http://${hostname}:${port}`).pathname;
379
- // RPC endpoint (antes das rotas de backend)
380
327
  if (pathname === types_1.RPC_ENDPOINT && method === 'POST') {
381
328
  try {
382
329
  const result = await (0, server_1.executeRpc)({
@@ -393,12 +340,9 @@ function vatts(options) {
393
340
  return;
394
341
  }
395
342
  }
396
- // 1. Verifica se é WebSocket upgrade para hot reload
397
343
  if (pathname === '/hweb-hotreload/' && genericReq.headers.upgrade === 'websocket' && hotReloadManager) {
398
- // Framework vai chamar o evento 'upgrade' do servidor HTTP
399
344
  return;
400
345
  }
401
- // 2. Primeiro verifica se é um arquivo estático da pasta public
402
346
  if (pathname !== '/' && !pathname.startsWith('/api/') && !pathname.startsWith('/.vatts')) {
403
347
  const publicDir = path_1.default.join(dir, 'public');
404
348
  if (!isSuspiciousPathname(pathname)) {
@@ -427,7 +371,6 @@ function vatts(options) {
427
371
  '.zip': 'application/zip'
428
372
  };
429
373
  genericRes.header('Content-Type', contentTypes[ext] || 'application/octet-stream');
430
- // Para arquivos estáticos, usamos o método nativo do framework
431
374
  if (adapter.type === 'express') {
432
375
  res.sendFile(filePath);
433
376
  }
@@ -456,7 +399,6 @@ function vatts(options) {
456
399
  '.map': 'application/json'
457
400
  };
458
401
  genericRes.header('Content-Type', contentTypes[ext] || 'text/plain');
459
- // Para arquivos estáticos, usamos o método nativo do framework
460
402
  if (adapter.type === 'express') {
461
403
  res.sendFile(filePath);
462
404
  }
@@ -472,18 +414,13 @@ function vatts(options) {
472
414
  }
473
415
  }
474
416
  }
475
- // 4. REMOVIDO: Verificação de arquivos React UMD - não precisamos mais
476
- // O React agora será bundlado diretamente no main.js
477
- // 5. Verifica se é uma rota de API (backend)
478
417
  const backendMatch = (0, router_1.findMatchingBackendRoute)(pathname, method);
479
418
  if (backendMatch) {
480
419
  try {
481
420
  const handler = backendMatch.route[method];
482
421
  if (handler) {
483
422
  const hwebReq = new http_1.VattsRequest(genericReq);
484
- // Executa middlewares e depois o handler final
485
423
  const hwebRes = await executeMiddlewareChain(backendMatch.route.middleware, handler, hwebReq, backendMatch.params);
486
- // Aplica a resposta usando o adapter correto
487
424
  hwebRes._applyTo(genericRes);
488
425
  return;
489
426
  }
@@ -494,24 +431,41 @@ function vatts(options) {
494
431
  return;
495
432
  }
496
433
  }
497
- // 6. Por último, tenta renderizar uma página (frontend) ou 404
434
+ // Renderização de Página (Frontend)
498
435
  const pageMatch = (0, router_1.findMatchingRoute)(pathname);
436
+ // Determina o objeto de resposta "cru" para o stream do React
437
+ // Se for Fastify, o Writable stream está em res.raw. Se for Express/Native, é o próprio res.
438
+ const rawRes = (res.raw || res);
499
439
  if (!pageMatch) {
500
- // Em vez de enviar texto simples, renderiza a página 404 React
501
440
  try {
502
- // Cria uma "rota falsa" para a página 404
503
441
  const notFoundRoute = {
504
442
  pattern: '/__404__',
505
- component: () => null, // Componente vazio, será tratado no cliente
443
+ component: () => null, // Será renderizado via SSR se tiver componente de 404 definido no Client
506
444
  componentPath: '__404__'
507
445
  };
508
- const html = await (0, renderer_1.render)({
446
+ // Tenta usar componente 404 customizado se houver
447
+ const notFound = (0, router_1.getNotFound)();
448
+ if (notFound) {
449
+ // Carrega o componente 404 real para o SSR funcionar
450
+ try {
451
+ const nfModule = require(path_1.default.resolve(process.cwd(), notFound.componentPath));
452
+ // @ts-ignore
453
+ notFoundRoute.component = nfModule.default || nfModule;
454
+ }
455
+ catch (e) { }
456
+ }
457
+ // Define status 404 antes de iniciar o stream
458
+ if (rawRes.statusCode)
459
+ rawRes.statusCode = 404; // Native/Fastify
460
+ if (rawRes.status)
461
+ rawRes.status(404); // Express (mas pode quebrar se for raw, melhor usar statusCode)
462
+ await (0, renderer_1.renderAsStream)({
509
463
  req: genericReq,
464
+ res: rawRes,
510
465
  route: notFoundRoute,
511
466
  params: {},
512
467
  allRoutes: frontendRoutes
513
468
  });
514
- genericRes.status(404).header('Content-Type', 'text/html').send(html);
515
469
  return;
516
470
  }
517
471
  catch (error) {
@@ -521,26 +475,28 @@ function vatts(options) {
521
475
  }
522
476
  }
523
477
  try {
524
- const html = await (0, renderer_1.render)({
478
+ // Renderização via Stream (SSR + Streaming)
479
+ await (0, renderer_1.renderAsStream)({
525
480
  req: genericReq,
481
+ res: rawRes,
526
482
  route: pageMatch.route,
527
483
  params: pageMatch.params,
528
484
  allRoutes: frontendRoutes
529
485
  });
530
- genericRes.status(200).header('Content-Type', 'text/html').send(html);
531
486
  }
532
487
  catch (error) {
533
488
  console_1.default.error(`Error rendering page ${pathname}:`, error);
534
- genericRes.status(500).text('Internal server error');
489
+ // Se o stream já começou, não dá pra enviar erro 500 limpo.
490
+ // O renderAsStream tenta lidar com isso no onError.
491
+ if (!rawRes.headersSent) {
492
+ genericRes.status(500).text('Internal server error');
493
+ }
535
494
  }
536
495
  };
537
496
  },
538
- // Método para configurar WebSocket upgrade nos servidores Express e Fastify
539
497
  setupWebSocket: (server) => {
540
- // Detecta se é um servidor Express ou Fastify
541
498
  const isExpressServer = factory_1.FrameworkAdapterFactory.getCurrentAdapter() instanceof express_1.ExpressAdapter;
542
499
  const actualServer = isExpressServer ? server : (server.server || server);
543
- // Usa o sistema coordenado de WebSocket upgrade que integra hot-reload e rotas de usuário
544
500
  (0, router_1.setupWebSocketUpgrade)(actualServer, hotReloadManager);
545
501
  },
546
502
  stop: () => {
@@ -2,6 +2,7 @@ import { RouteConfig } from './types';
2
2
  import type { GenericRequest } from './types/framework';
3
3
  interface RenderOptions {
4
4
  req: GenericRequest;
5
+ res: any;
5
6
  route: RouteConfig & {
6
7
  componentPath: string;
7
8
  };
@@ -10,5 +11,6 @@ interface RenderOptions {
10
11
  componentPath: string;
11
12
  })[];
12
13
  }
13
- export declare function render({ req, route, params, allRoutes }: RenderOptions): Promise<string>;
14
+ export declare function renderAsStream({ req, res, route, params, allRoutes }: RenderOptions): Promise<void>;
15
+ export declare function render(options: any): Promise<string>;
14
16
  export {};