vatts 1.0.2-alpha.3 → 1.0.2-alpha.4

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/bin/vatts.js CHANGED
@@ -22,14 +22,17 @@ require('ts-node').register();
22
22
  const { registerLoaders } = require('../loaders');
23
23
  registerLoaders();
24
24
  const { program } = require('commander');
25
+ const fs = require('fs');
26
+ const path = require('path');
27
+ const { Writable } = require('stream');
28
+ // Importa o Console do framework
29
+ const ConsoleModule = require('../api/console');
30
+ const Console = ConsoleModule.default;
31
+ const { Levels, Colors } = ConsoleModule;
25
32
  program
26
33
  .version('1.0.0')
27
34
  .description('CLI to manage the application.');
28
- // --- Comando DEV ---
29
- const fs = require('fs');
30
- const path = require('path');
31
- // 'program' já deve estar definido no seu arquivo
32
- // const { program } = require('commander');
35
+ // --- Helpers ---
33
36
  /**
34
37
  * Função centralizada para iniciar a aplicação
35
38
  * @param {object} options - Opções vindas do commander
@@ -45,84 +48,75 @@ function initializeApp(options, isDev) {
45
48
  };
46
49
  // 1. Verifica se a flag --ssl foi ativada
47
50
  if (options.ssl) {
48
- const C = require("../api/console");
49
- const { Levels } = C;
50
- const Console = C.default;
51
51
  const sslDir = path.resolve(process.cwd(), 'certs');
52
- const keyPath = path.join(sslDir, 'key.pem'); // Padrão 1: key.pem
53
- const certPath = path.join(sslDir, 'cert.pem'); // Padrão 2: cert.pem
54
- // (Você pode mudar para 'cert.key' se preferir, apenas ajuste os nomes aqui)
52
+ const keyPath = path.join(sslDir, 'key.pem');
53
+ const certPath = path.join(sslDir, 'cert.pem');
55
54
  // 2. Verifica se os arquivos existem
56
55
  if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
57
56
  appOptions.ssl = {
58
57
  key: keyPath,
59
58
  cert: certPath
60
59
  };
61
- // 3. Adiciona a porta de redirecionamento (útil para o initNativeServer)
60
+ // 3. Adiciona a porta de redirecionamento
62
61
  appOptions.ssl.redirectPort = options.httpRedirectPort || 80;
63
62
  }
64
63
  else {
65
- Console.logWithout(Levels.ERROR, null, `Ensure that './certs/key.pem' and './certs/cert.pem' exist.`, `--ssl flag was used, but the files were not found.`);
66
- process.exit(1); // Encerra o processo com erro
64
+ Console.error(`SSL Error: Ensure that './certs/key.pem' and './certs/cert.pem' exist.`);
65
+ process.exit(1);
67
66
  }
68
67
  }
69
68
  // 4. Inicia o helper com as opções
70
- const teste = require("../helpers");
71
- const t = teste.default(appOptions);
72
- t.init();
69
+ const helperModule = require("../helpers");
70
+ const helper = helperModule.default(appOptions);
71
+ helper.init();
73
72
  }
74
- // --- Comando DEV ---
75
- program
76
- .command('dev')
77
- .description('Starts the application in development mode.')
78
- .option('-p, --port <number>', 'Specifies the port to run on', '3000')
79
- .option('-H, --hostname <string>', 'Specifies the hostname to run on', '0.0.0.0')
80
- .option('--ssl', 'Activates HTTPS/SSL mode (requires ./ssl/key.pem and ./ssl/cert.pem)')
81
- .option('--http-redirect-port <number>', 'Port for HTTP->HTTPS redirection', '80')
82
- .action((options) => {
83
- initializeApp(options, true); // Chama a função com dev: true
84
- });
85
- // --- Comando START (Produção) ---
86
- program
87
- .command('start')
88
- .description('Starts the application in production mode.')
89
- .option('-p, --port <number>', 'Specifies the port to run on', '3000')
90
- .option('-H, --hostname <string>', 'Specifies the hostname to run on', '0.0.0.0')
91
- .option('--ssl', 'Activates HTTPS/SSL mode (requires ./ssl/key.pem and ./ssl/cert.pem)')
92
- .option('--http-redirect-port <number>', 'Port for HTTP->HTTPS redirection', '80')
93
- .action((options) => {
94
- initializeApp(options, false); // Chama a função com dev: false
95
- });
96
73
  /**
97
74
  * Função corrigida para copiar diretórios recursivamente.
98
- * Ela agora verifica se um item é um arquivo ou um diretório.
99
75
  */
100
76
  function copyDirRecursive(src, dest) {
101
77
  try {
102
- // Garante que o diretório de destino exista
103
78
  fs.mkdirSync(dest, { recursive: true });
104
- // Usamos { withFileTypes: true } para evitar uma chamada extra de fs.statSync
105
79
  const entries = fs.readdirSync(src, { withFileTypes: true });
106
80
  for (let entry of entries) {
107
81
  const srcPath = path.join(src, entry.name);
108
82
  const destPath = path.join(dest, entry.name);
109
83
  if (entry.isDirectory()) {
110
- // Se for um diretório, chama a si mesma (recursão)
111
84
  copyDirRecursive(srcPath, destPath);
112
85
  }
113
86
  else {
114
- // Se for um arquivo, apenas copia
115
87
  fs.copyFileSync(srcPath, destPath);
116
88
  }
117
89
  }
118
90
  }
119
91
  catch (error) {
120
- console.error(`❌ Erro ao copiar ${src} para ${dest}:`, error);
121
- // Lança o erro para parar o processo de exportação se a cópia falhar
92
+ Console.error(`Error copying ${src} to ${dest}:`, error);
122
93
  throw error;
123
94
  }
124
95
  }
125
- // --- INÍCIO DO SEU CÓDIGO (AGORA CORRIGIDO) ---
96
+ // --- Comandos ---
97
+ // Comando DEV
98
+ program
99
+ .command('dev')
100
+ .description('Starts the application in development mode.')
101
+ .option('-p, --port <number>', 'Specifies the port to run on', '3000')
102
+ .option('-H, --hostname <string>', 'Specifies the hostname to run on', '0.0.0.0')
103
+ .option('--ssl', 'Activates HTTPS/SSL mode (requires ./ssl/key.pem and ./ssl/cert.pem)')
104
+ .option('--http-redirect-port <number>', 'Port for HTTP->HTTPS redirection', '80')
105
+ .action((options) => {
106
+ initializeApp(options, true);
107
+ });
108
+ // Comando START (Produção)
109
+ program
110
+ .command('start')
111
+ .description('Starts the application in production mode.')
112
+ .option('-p, --port <number>', 'Specifies the port to run on', '3000')
113
+ .option('-H, --hostname <string>', 'Specifies the hostname to run on', '0.0.0.0')
114
+ .option('--ssl', 'Activates HTTPS/SSL mode (requires ./ssl/key.pem and ./ssl/cert.pem)')
115
+ .option('--http-redirect-port <number>', 'Port for HTTP->HTTPS redirection', '80')
116
+ .action((options) => {
117
+ initializeApp(options, false);
118
+ });
119
+ // Comando EXPORT
126
120
  program
127
121
  .command('export')
128
122
  .description('Exports the application as static HTML to the "exported" folder.')
@@ -132,94 +126,82 @@ program
132
126
  .option('--output-h-data <file>', 'Write the data-h value from <script id="__vatts_data__" data-h="..."> into this file')
133
127
  .action(async (options) => {
134
128
  const projectDir = process.cwd();
135
- // Resolve output:
136
- // - se vier absoluto, usa como está
137
- // - se vier relativo, resolve a partir do projeto
129
+ // Resolve output
138
130
  const outputInput = typeof options.output === 'string' && options.output.trim().length
139
131
  ? options.output.trim()
140
132
  : 'exported';
141
133
  const exportDir = path.isAbsolute(outputInput)
142
134
  ? path.resolve(outputInput)
143
135
  : path.resolve(projectDir, outputInput);
144
- // Proteções: nunca permitir apagar a raiz do drive (ex: D:\) ou o root do SO (ex: C:\)
145
136
  const exportDirResolved = path.resolve(exportDir);
146
- const exportDirRoot = path.parse(exportDirResolved).root; // ex: 'D:\\'
137
+ const exportDirRoot = path.parse(exportDirResolved).root;
147
138
  const projectDirResolved = path.resolve(projectDir);
148
139
  if (exportDirResolved === exportDirRoot) {
149
- throw new Error(`Refusing to use output directory at drive root: ${exportDirResolved}`);
140
+ Console.error(`Refusing to use output directory at drive root: ${exportDirResolved}`);
141
+ process.exit(1);
150
142
  }
151
- // Também evita `--output .` ou `--output ..` que cairia no diretório do projeto (perigoso)
152
- // Regra: output precisa estar dentro do projeto, ou dentro de uma subpasta do projeto.
153
- // (mantém comportamento esperado e evita deletar o repo inteiro por acidente)
154
143
  const relExportToProject = path.relative(projectDirResolved, exportDirResolved);
155
144
  if (relExportToProject === '' || relExportToProject === '.' || relExportToProject.startsWith('..')) {
156
- throw new Error(`Refusing to export to ${exportDirResolved}. Use a subfolder like "exported" or an explicit path inside the project.`);
145
+ Console.error(`Refusing to export to ${exportDirResolved}. Use a subfolder like "exported" or an explicit path inside the project.`);
146
+ process.exit(1);
157
147
  }
158
- // assetsDir: sempre relativo ao exportDir (evita escrever fora sem querer)
159
- // Permite usar '.' (raiz do output)
148
+ // assetsDir
160
149
  const assetsDirInputRaw = typeof options.assetsDir === 'string' ? options.assetsDir : '.vatts';
161
150
  const assetsDirInput = assetsDirInputRaw.trim().length ? assetsDirInputRaw.trim() : '.';
162
151
  const assetsDirResolved = path.resolve(exportDirResolved, assetsDirInput);
163
152
  const relAssetsToExport = path.relative(exportDirResolved, assetsDirResolved);
164
153
  if (relAssetsToExport.startsWith('..') || path.isAbsolute(relAssetsToExport)) {
165
- throw new Error(`Invalid --assets-dir: must be inside output directory. Received: ${assetsDirInputRaw}`);
154
+ Console.error(`Invalid --assets-dir: must be inside output directory. Received: ${assetsDirInputRaw}`);
155
+ process.exit(1);
166
156
  }
167
- // Normaliza a pasta para uso em URL no HTML
168
- // Se assets-dir for '.', relAssetsToExport vira '' e assetsBaseHref vira './'
169
157
  const assetsDirUrl = relAssetsToExport.split(path.sep).join('/').replace(/^\.?\/?/, '');
170
- const assetsBaseHref = './' + (assetsDirUrl.length ? assetsDirUrl + '/' : '');
171
- console.log('🚀 Starting export...\n');
158
+ const assetsBaseHref = '/.' + (assetsDirUrl.length ? assetsDirUrl + '/' : '');
159
+ Console.info('Starting export process...');
172
160
  try {
173
- // 1. Cria a pasta exported (limpa se já existir)
161
+ // 1. Limpa pasta de exportação
174
162
  if (fs.existsSync(exportDirResolved)) {
175
- console.log('🗑️ Cleaning existing export folder...');
163
+ Console.info('Cleaning existing export folder...');
176
164
  fs.rmSync(exportDirResolved, { recursive: true, force: true });
177
165
  }
178
166
  fs.mkdirSync(exportDirResolved, { recursive: true });
179
- console.log('✅ Export folder created\n');
180
- // 2. Inicializa e prepara o build
181
- console.log('🔨 Building application...');
182
- // ATENÇÃO: Ajuste o caminho deste 'require' conforme a estrutura do seu projeto!
183
- const teste = require("../helpers");
184
- const app = teste.default({ dev: false, port: 3000, hostname: '0.0.0.0', framework: 'native' });
167
+ // 2. Build
168
+ Console.info('Building application...');
169
+ const helperModule = require("../helpers");
170
+ // Usando dev: false para produção
171
+ const app = helperModule.default({ dev: false, port: 3000, hostname: '0.0.0.0', framework: 'native' });
185
172
  await app.prepare();
186
- console.log('Build complete\n');
173
+ Console.success('Build complete.');
174
+ // 3. Copia JavaScript
187
175
  const distDir = path.join(projectDirResolved, '.vatts');
188
176
  if (fs.existsSync(distDir)) {
189
- console.log('📦 Copying JavaScript files...');
177
+ Console.info('Copying JavaScript files...');
190
178
  const exportDistDir = assetsDirResolved;
191
179
  copyDirRecursive(distDir, exportDistDir);
192
- console.log(`✅ JavaScript files copied to: ${path.relative(exportDirResolved, exportDistDir) || '.'}\n`);
193
180
  }
194
- // 4. Copia a pasta public se existir
181
+ // 4. Copia Public
195
182
  const publicDir = path.join(projectDirResolved, 'public');
196
183
  if (fs.existsSync(publicDir)) {
197
- console.log('📁 Copying public files...');
198
- const exportPublicDir = path.join(exportDirResolved, 'public');
199
- copyDirRecursive(publicDir, exportPublicDir);
200
- console.log('✅ Public files copied\n');
184
+ Console.info('Copying public files...');
185
+ copyDirRecursive(publicDir, exportDirResolved);
201
186
  }
202
- // 5. Gera o index.html (opcional) / ou só renderiza para extrair h-data
187
+ // 5. Gera index.html
203
188
  const shouldExtractHData = typeof options.outputHData === 'string' && options.outputHData.trim().length > 0;
204
189
  const shouldRenderHtml = Boolean(options.html) || shouldExtractHData;
205
190
  if (shouldRenderHtml) {
206
191
  const writeHtmlToDisk = Boolean(options.html);
207
192
  if (writeHtmlToDisk) {
208
- console.log('📝 Generating index.html...');
193
+ Console.info('Generating index.html...');
209
194
  }
210
195
  else {
211
- console.log('🧩 Rendering HTML to extract h-data (--output-h-data)...');
196
+ Console.info('Rendering HTML for h-data extraction...');
212
197
  }
213
- // ATENÇÃO: Ajuste os caminhos destes 'requires' conforme a estrutura do seu projeto!
214
- const { render } = require('../renderer');
198
+ const { renderAsStream } = require('../renderer');
215
199
  const { loadRoutes, loadLayout, loadNotFound } = require('../router');
216
- // Carrega as rotas para gerar o HTML
217
200
  const userWebDir = path.join(projectDirResolved, 'src', 'web');
218
201
  const userWebRoutesDir = path.join(userWebDir, 'routes');
219
202
  const routes = loadRoutes(userWebRoutesDir);
220
203
  loadLayout(userWebDir);
221
204
  loadNotFound(userWebDir);
222
- // Gera HTML para a rota raiz
223
205
  const rootRoute = routes.find(r => r.pattern === '/') || routes[0];
224
206
  if (rootRoute) {
225
207
  const mockReq = {
@@ -229,49 +211,58 @@ program
229
211
  hwebDev: false,
230
212
  hotReloadManager: null
231
213
  };
232
- const html = await render({
214
+ let html = '';
215
+ let resolveStream;
216
+ const streamComplete = new Promise(r => resolveStream = r);
217
+ const mockRes = new Writable({
218
+ write(chunk, encoding, callback) {
219
+ html += chunk.toString();
220
+ callback();
221
+ }
222
+ });
223
+ mockRes.setHeader = () => { };
224
+ mockRes.statusCode = 200;
225
+ mockRes.on('finish', () => {
226
+ resolveStream();
227
+ });
228
+ await renderAsStream({
233
229
  req: mockReq,
230
+ res: mockRes,
234
231
  route: rootRoute,
235
232
  params: {},
236
233
  allRoutes: routes
237
234
  });
235
+ await streamComplete;
238
236
  if (shouldExtractHData) {
239
237
  const m = html.match(/<script\b[^>]*\bid=["']__vatts_data__["'][^>]*\bdata-h=["']([^"']*)["'][^>]*>/i);
240
238
  if (!m || typeof m[1] !== 'string') {
241
239
  throw new Error('Could not find <script id="__vatts_data__" data-h="..."> in rendered HTML.');
242
240
  }
243
241
  const hDataValue = m[1];
244
- // O path do arquivo é relativo ao projeto por padrão
245
242
  const outputHDataPathInput = options.outputHData.trim();
246
243
  const outputHDataPath = path.isAbsolute(outputHDataPathInput)
247
244
  ? path.resolve(outputHDataPathInput)
248
245
  : path.resolve(projectDirResolved, outputHDataPathInput);
249
246
  fs.mkdirSync(path.dirname(outputHDataPath), { recursive: true });
250
247
  fs.writeFileSync(outputHDataPath, hDataValue, 'utf8');
251
- console.log(`✅ h-data written to: ${outputHDataPath}`);
248
+ Console.success(`h-data written to: ${path.relative(projectDirResolved, outputHDataPath)}`);
252
249
  }
253
250
  if (writeHtmlToDisk) {
254
251
  const scriptReplaced = html.replace(/\/_vatts\//g, assetsBaseHref);
255
252
  const indexPath = path.join(exportDirResolved, 'index.html');
256
253
  fs.writeFileSync(indexPath, scriptReplaced, 'utf8');
257
- console.log('index.html generated\n');
258
- }
259
- else {
260
- console.log('✅ HTML rendered (no index.html written)\n');
254
+ Console.success('index.html generated.');
261
255
  }
262
256
  }
263
257
  }
264
258
  else {
265
- console.log('⏭️ Skipping index.html generation (--no-html)\n');
259
+ Console.info('Skipping index.html generation.');
266
260
  }
267
- console.log('🎉 Export completed successfully!');
268
- console.log(`📂 Files exported to: ${exportDirResolved}\n`);
261
+ Console.success(`Export completed successfully to: ${path.relative(projectDirResolved, exportDirResolved)}`);
269
262
  }
270
263
  catch (error) {
271
- // Logar o erro completo (com stack trace) é mais útil
272
- console.error('❌ Error during export:', error);
264
+ Console.error('Error during export:', error);
273
265
  process.exit(1);
274
266
  }
275
267
  });
276
- // Faz o "parse" dos argumentos passados na linha de comando
277
268
  program.parse(process.argv);
package/dist/builder.js CHANGED
@@ -304,7 +304,8 @@ function createRollupConfig(entryPoint, outdir, isProduction) {
304
304
  replace({
305
305
  preventAssignment: true,
306
306
  values: {
307
- 'process.env.NODE_ENV': JSON.stringify(isProduction ? 'production' : 'development')
307
+ 'process.env.NODE_ENV': JSON.stringify(isProduction ? 'production' : 'development'),
308
+ 'proccess.env.PORT': JSON.stringify(process.vatts.port || 3000)
308
309
  }
309
310
  }),
310
311
  tsconfigPathsPlugin(process.cwd()),
@@ -1,58 +1,15 @@
1
- export interface RouterEvents {
2
- beforeNavigate?: (url: string) => boolean | Promise<boolean>;
3
- afterNavigate?: (url: string) => void;
4
- }
5
- declare class Router {
6
- private events;
1
+ type RouteListener = () => void;
2
+ export declare class Router {
7
3
  private listeners;
8
- /**
9
- * Navega para uma nova rota
10
- */
11
- push(url: string): Promise<void>;
12
- /**
13
- * Substitui a entrada atual do histórico
14
- */
15
- replace(url: string): Promise<void>;
16
- /**
17
- * Volta uma página no histórico
18
- */
19
- back(): void;
20
- /**
21
- * Avança uma página no histórico
22
- */
23
- forward(): void;
24
- /**
25
- * Recarrega a página atual (re-renderiza o componente)
26
- */
27
- refresh(): void;
28
- /**
29
- * Obtém a URL atual
30
- */
4
+ constructor();
31
5
  get pathname(): string;
32
- /**
33
- * Obtém os query parameters atuais
34
- */
35
- get query(): URLSearchParams;
36
- /**
37
- * Obtém a URL completa atual
38
- */
39
- get url(): string;
40
- /**
41
- * Adiciona event listeners para eventos de roteamento
42
- */
43
- on(events: RouterEvents): void;
44
- /**
45
- * Remove event listeners
46
- */
47
- off(): void;
48
- /**
49
- * Adiciona um listener para mudanças de rota
50
- */
51
- subscribe(listener: () => void): () => void;
52
- /**
53
- * Dispara evento de navegação para todos os listeners
54
- */
55
- private triggerNavigation;
6
+ get search(): string;
7
+ get hash(): string;
8
+ push(path: string): void;
9
+ replace(path: string): void;
10
+ back(): void;
11
+ subscribe(listener: RouteListener): () => void;
12
+ private notify;
56
13
  }
57
14
  export declare const router: Router;
58
- export default router;
15
+ export {};
@@ -16,116 +16,61 @@
16
16
  * limitations under the License.
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.router = void 0;
19
+ exports.router = exports.Router = void 0;
20
20
  class Router {
21
21
  constructor() {
22
- this.events = {};
23
22
  this.listeners = new Set();
24
- }
25
- /**
26
- * Navega para uma nova rota
27
- */
28
- async push(url) {
29
- // Callback antes de navegar
30
- if (this.events.beforeNavigate) {
31
- const shouldProceed = await this.events.beforeNavigate(url);
32
- if (shouldProceed === false)
33
- return;
34
- }
35
- // Atualiza a URL na barra de endereço
36
- window.history.pushState({ path: url }, '', url);
37
- // Dispara evento para o roteador capturar de forma assíncrona
38
- setTimeout(() => this.triggerNavigation(), 0);
39
- // Callback após navegar
40
- if (this.events.afterNavigate) {
41
- this.events.afterNavigate(url);
42
- }
43
- }
44
- /**
45
- * Substitui a entrada atual do histórico
46
- */
47
- async replace(url) {
48
- // Callback antes de navegar
49
- if (this.events.beforeNavigate) {
50
- const shouldProceed = await this.events.beforeNavigate(url);
51
- if (shouldProceed === false)
52
- return;
53
- }
54
- // Substitui a URL atual no histórico
55
- window.history.replaceState({ path: url }, '', url);
56
- // Dispara evento para o roteador capturar de forma assíncrona
57
- setTimeout(() => this.triggerNavigation(), 0);
58
- // Callback após navegar
59
- if (this.events.afterNavigate) {
60
- this.events.afterNavigate(url);
23
+ // Só adiciona listener no lado do cliente
24
+ if (typeof window !== 'undefined') {
25
+ window.addEventListener('popstate', () => {
26
+ this.notify();
27
+ });
61
28
  }
62
29
  }
63
- /**
64
- * Volta uma página no histórico
65
- */
66
- back() {
67
- window.history.back();
68
- }
69
- /**
70
- * Avança uma página no histórico
71
- */
72
- forward() {
73
- window.history.forward();
74
- }
75
- /**
76
- * Recarrega a página atual (re-renderiza o componente)
77
- */
78
- refresh() {
79
- setTimeout(() => this.triggerNavigation(), 0);
80
- }
81
- /**
82
- * Obtém a URL atual
83
- */
84
30
  get pathname() {
31
+ if (typeof window === 'undefined') {
32
+ return '/'; // Retorno seguro para SSR
33
+ }
85
34
  return window.location.pathname;
86
35
  }
87
- /**
88
- * Obtém os query parameters atuais
89
- */
90
- get query() {
91
- return new URLSearchParams(window.location.search);
36
+ get search() {
37
+ if (typeof window === 'undefined') {
38
+ return '';
39
+ }
40
+ return window.location.search;
41
+ }
42
+ get hash() {
43
+ if (typeof window === 'undefined') {
44
+ return '';
45
+ }
46
+ return window.location.hash;
92
47
  }
93
- /**
94
- * Obtém a URL completa atual
95
- */
96
- get url() {
97
- return window.location.href;
48
+ push(path) {
49
+ if (typeof window !== 'undefined') {
50
+ window.history.pushState({}, '', path);
51
+ this.notify();
52
+ }
98
53
  }
99
- /**
100
- * Adiciona event listeners para eventos de roteamento
101
- */
102
- on(events) {
103
- this.events = { ...this.events, ...events };
54
+ replace(path) {
55
+ if (typeof window !== 'undefined') {
56
+ window.history.replaceState({}, '', path);
57
+ this.notify();
58
+ }
104
59
  }
105
- /**
106
- * Remove event listeners
107
- */
108
- off() {
109
- this.events = {};
60
+ back() {
61
+ if (typeof window !== 'undefined') {
62
+ window.history.back();
63
+ }
110
64
  }
111
- /**
112
- * Adiciona um listener para mudanças de rota
113
- */
114
65
  subscribe(listener) {
115
66
  this.listeners.add(listener);
116
- return () => this.listeners.delete(listener);
67
+ return () => {
68
+ this.listeners.delete(listener);
69
+ };
117
70
  }
118
- /**
119
- * Dispara evento de navegação para todos os listeners
120
- */
121
- triggerNavigation() {
122
- // Dispara o evento nativo para o roteador do hweb capturar
123
- window.dispatchEvent(new PopStateEvent('popstate'));
124
- // Notifica todos os listeners customizados
71
+ notify() {
125
72
  this.listeners.forEach(listener => listener());
126
73
  }
127
74
  }
128
- // Instância singleton do router
75
+ exports.Router = Router;
129
76
  exports.router = new Router();
130
- // Para compatibilidade, também exporta como default
131
- exports.default = exports.router;
@@ -17,7 +17,7 @@ export type RpcClient<TApi extends Record<string, any> = Record<string, any>> =
17
17
  * Typing:
18
18
  * - Without a generic: `const api = importServer('...')` -> `api.anyFn<MyReturn>()`
19
19
  * - With a generic: `importServer<typeof import('../../backend/helper')>('...')`
20
- * gives argument + default return types, while still allowing overrides.
20
+ * gives argument + default return types, while still allowing overrides.
21
21
  *
22
22
  * Note: server functions can be defined as `(req, ...args)`; the first arg is injected
23
23
  * by the server, so the client signature automatically drops that first parameter.
@@ -28,6 +28,21 @@ function asErrorMessage(err) {
28
28
  return 'Unknown error';
29
29
  }
30
30
  }
31
+ // Detecta se estamos rodando no Node.js
32
+ const isServer = typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
33
+ function getRpcEndpoint() {
34
+ if (isServer) {
35
+ // Tenta pegar a porta das variáveis de ambiente ou usa 3000 como fallback.
36
+ // Nota: Se você usar uma porta customizada no vatts.config.ts sem setar ENV,
37
+ // precisará garantir que process.env.PORT esteja alinhado.
38
+ const port = process.env.PORT || 3000;
39
+ // Em SSR, sempre usamos HTTP e Loopback IP (127.0.0.1) para garantir
40
+ // que a requisição chegue no próprio servidor localmente sem sair pra rede externa.
41
+ return `http://127.0.0.1:${port}${types_1.RPC_ENDPOINT}`;
42
+ }
43
+ // No cliente (browser), URL relativa funciona perfeitamente
44
+ return types_1.RPC_ENDPOINT;
45
+ }
31
46
  /**
32
47
  * `importServer("src/backend/index.ts")` returns a Proxy where every property is
33
48
  * a function that performs a POST to `/api/rpc`.
@@ -35,7 +50,7 @@ function asErrorMessage(err) {
35
50
  * Typing:
36
51
  * - Without a generic: `const api = importServer('...')` -> `api.anyFn<MyReturn>()`
37
52
  * - With a generic: `importServer<typeof import('../../backend/helper')>('...')`
38
- * gives argument + default return types, while still allowing overrides.
53
+ * gives argument + default return types, while still allowing overrides.
39
54
  *
40
55
  * Note: server functions can be defined as `(req, ...args)`; the first arg is injected
41
56
  * by the server, so the client signature automatically drops that first parameter.
@@ -71,9 +86,11 @@ function importServer(file) {
71
86
  }
72
87
  }
73
88
  };
89
+ // Resolve a URL correta (Absoluta no server, Relativa no client)
90
+ const endpoint = getRpcEndpoint();
74
91
  let res;
75
92
  try {
76
- res = await fetch(types_1.RPC_ENDPOINT, {
93
+ res = await fetch(endpoint, {
77
94
  method: 'POST',
78
95
  headers: {
79
96
  'Content-Type': 'application/json'