vatts 1.2.0-alpha.1 → 1.2.0-test.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.
- package/dist/adapters/express.js +5 -1
- package/dist/adapters/factory.js +58 -21
- package/dist/adapters/fastify.js +5 -1
- package/dist/adapters/native.js +5 -1
- package/dist/api/console.js +25 -17
- package/dist/api/framework.js +22 -15
- package/dist/api/http.js +7 -2
- package/dist/builder.js +19 -10
- package/dist/client/clientRouter.js +6 -2
- package/dist/client/rpc.js +7 -4
- package/dist/env/env.js +18 -11
- package/dist/global/global.d.ts +177 -122
- package/dist/helpers.js +108 -67
- package/dist/hotReload.d.ts +6 -0
- package/dist/hotReload.js +179 -31
- package/dist/index.js +159 -115
- package/dist/loaders.js +29 -13
- package/dist/react/BuildingPage.d.ts +2 -1
- package/dist/react/BuildingPage.js +47 -4
- package/dist/react/DefaultNotFound.d.ts +2 -1
- package/dist/react/DefaultNotFound.js +92 -17
- package/dist/react/DevIndicator.js +66 -23
- package/dist/react/ErrorModal.js +91 -40
- package/dist/react/Link.d.ts +2 -2
- package/dist/react/Link.js +27 -5
- package/dist/react/client.js +16 -5
- package/dist/react/entry.client.js +70 -30
- package/dist/react/image/Image.js +8 -3
- package/dist/react/renderer-react.js +53 -25
- package/dist/renderer.d.ts +4 -0
- package/dist/renderer.js +13 -5
- package/dist/router.js +82 -63
- package/dist/rpc/annotations.js +7 -3
- package/dist/rpc/server.js +21 -15
- package/dist/rpc/types.js +4 -1
- package/dist/types/framework.js +2 -1
- package/dist/types.js +2 -1
- package/dist/vue/App.vue +34 -37
- package/dist/vue/BuildingPage.vue +118 -102
- package/dist/vue/ErrorModal.vue +19 -37
- package/dist/vue/Link.vue +8 -7
- package/dist/vue/client.js +16 -6
- package/dist/vue/entry.client.js +8 -3
- package/dist/vue/image/Image.vue +25 -19
- package/dist/vue/renderer.vue.js +80 -26
- package/package.json +25 -12
- package/dist/global/global.js +0 -17
package/dist/router.js
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.clearAllRouteCache = clearAllRouteCache;
|
|
7
|
+
exports.clearFileCache = clearFileCache;
|
|
8
|
+
exports.loadLayout = loadLayout;
|
|
9
|
+
exports.getLayout = getLayout;
|
|
10
|
+
exports.loadPathRoutes = loadPathRoutes;
|
|
11
|
+
exports.loadRoutes = loadRoutes;
|
|
12
|
+
exports.findMatchingRoute = findMatchingRoute;
|
|
13
|
+
exports.loadBackendRoutes = loadBackendRoutes;
|
|
14
|
+
exports.findMatchingBackendRoute = findMatchingBackendRoute;
|
|
15
|
+
exports.loadNotFound = loadNotFound;
|
|
16
|
+
exports.getNotFound = getNotFound;
|
|
17
|
+
exports.processWebSocketRoutes = processWebSocketRoutes;
|
|
18
|
+
exports.findMatchingWebSocketRoute = findMatchingWebSocketRoute;
|
|
19
|
+
exports.setupWebSocketUpgrade = setupWebSocketUpgrade;
|
|
1
20
|
/*
|
|
2
21
|
* This file is part of the Vatts.js Project.
|
|
3
22
|
* Copyright (c) 2026 itsmuzin
|
|
@@ -14,14 +33,14 @@
|
|
|
14
33
|
* See the License for the specific language governing permissions and
|
|
15
34
|
* limitations under the License.
|
|
16
35
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
36
|
+
const fs_1 = __importDefault(require("fs"));
|
|
37
|
+
const path_1 = __importDefault(require("path"));
|
|
38
|
+
const ws_1 = require("ws");
|
|
39
|
+
const url_1 = require("url");
|
|
40
|
+
const console_1 = __importDefault(require("./api/console"));
|
|
41
|
+
const factory_1 = require("./adapters/factory");
|
|
42
|
+
const http_1 = require("./api/http");
|
|
43
|
+
const helpers_1 = require("./helpers");
|
|
25
44
|
// --- Estado Global ---
|
|
26
45
|
let allRoutes = [];
|
|
27
46
|
let allBackendRoutes = [];
|
|
@@ -62,12 +81,12 @@ function safeClearCache(filePath) {
|
|
|
62
81
|
// Ignora erro se arquivo não for resolvível
|
|
63
82
|
}
|
|
64
83
|
}
|
|
65
|
-
|
|
84
|
+
function clearAllRouteCache() {
|
|
66
85
|
loadedFiles.forEach(file => safeClearCache(file));
|
|
67
86
|
loadedFiles.clear();
|
|
68
87
|
}
|
|
69
|
-
|
|
70
|
-
const absolutePath =
|
|
88
|
+
function clearFileCache(changedFilePath) {
|
|
89
|
+
const absolutePath = path_1.default.resolve(changedFilePath);
|
|
71
90
|
// Só tenta limpar se realmente já foi carregado (evita I/O desnecessário)
|
|
72
91
|
if (loadedFiles.has(absolutePath)) {
|
|
73
92
|
safeClearCache(absolutePath);
|
|
@@ -107,19 +126,19 @@ function requireWithoutStyles(modulePath) {
|
|
|
107
126
|
}
|
|
108
127
|
}
|
|
109
128
|
// --- Carregamento de Layout (Otimizado - Sem I/O de Disco) ---
|
|
110
|
-
|
|
129
|
+
function loadLayout(webDir) {
|
|
111
130
|
const extensions = ['layout.tsx', 'layout.jsx', 'layout.vue'];
|
|
112
131
|
let layoutFile = null;
|
|
113
132
|
for (const ext of extensions) {
|
|
114
|
-
const fullPath =
|
|
115
|
-
if (
|
|
133
|
+
const fullPath = path_1.default.join(webDir, ext);
|
|
134
|
+
if (fs_1.default.existsSync(fullPath)) {
|
|
116
135
|
layoutFile = fullPath;
|
|
117
136
|
break;
|
|
118
137
|
}
|
|
119
138
|
}
|
|
120
139
|
if (layoutFile) {
|
|
121
|
-
const absolutePath =
|
|
122
|
-
const componentPath =
|
|
140
|
+
const absolutePath = path_1.default.resolve(layoutFile);
|
|
141
|
+
const componentPath = path_1.default.relative(process.cwd(), layoutFile).replace(/\\/g, '/');
|
|
123
142
|
try {
|
|
124
143
|
// Se já carregamos antes, limpa o cache para garantir reload
|
|
125
144
|
if (loadedFiles.has(absolutePath)) {
|
|
@@ -135,7 +154,7 @@ export function loadLayout(webDir) {
|
|
|
135
154
|
return layoutComponent;
|
|
136
155
|
}
|
|
137
156
|
catch (error) {
|
|
138
|
-
|
|
157
|
+
console_1.default.error(`Error loading layout ${layoutFile}:`, error);
|
|
139
158
|
layoutComponent = { componentPath };
|
|
140
159
|
return layoutComponent;
|
|
141
160
|
}
|
|
@@ -143,12 +162,12 @@ export function loadLayout(webDir) {
|
|
|
143
162
|
layoutComponent = null;
|
|
144
163
|
return null;
|
|
145
164
|
}
|
|
146
|
-
|
|
165
|
+
function getLayout() { return layoutComponent; }
|
|
147
166
|
// --- Carregamento de Rotas Frontend ---
|
|
148
167
|
// Helper para converter o caminho do arquivo no padrão de URL (Next.js style)
|
|
149
168
|
function convertPathToRoutePattern(absolutePath, routesDir) {
|
|
150
169
|
// 1. Pega o caminho relativo e normaliza as barras
|
|
151
|
-
let relPath =
|
|
170
|
+
let relPath = path_1.default.relative(routesDir, absolutePath).replace(/\\/g, '/');
|
|
152
171
|
// 2. Remove o nome do arquivo (page.tsx, page.ts, page.jsx, page.js ou page.vue) do final
|
|
153
172
|
relPath = relPath.replace(/\/?page\.(?:tsx|ts|jsx|js|vue)$/, '');
|
|
154
173
|
// 3. Remove os "Route Groups" do Next.js, ex: (auth)/login vira /login
|
|
@@ -159,21 +178,21 @@ function convertPathToRoutePattern(absolutePath, routesDir) {
|
|
|
159
178
|
// 5. Garante que comece com "/"
|
|
160
179
|
return '/' + relPath;
|
|
161
180
|
}
|
|
162
|
-
|
|
163
|
-
if (!
|
|
181
|
+
function loadPathRoutes(routesDir) {
|
|
182
|
+
if (!fs_1.default.existsSync(routesDir)) {
|
|
164
183
|
allRoutes = [];
|
|
165
184
|
return [];
|
|
166
185
|
}
|
|
167
186
|
const loaded = [];
|
|
168
187
|
const cwdPath = process.cwd();
|
|
169
188
|
const scanAndLoad = (dir) => {
|
|
170
|
-
const entries =
|
|
189
|
+
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
171
190
|
for (const entry of entries) {
|
|
172
191
|
const name = entry.name;
|
|
173
192
|
// Ignora arquivos/pastas ocultas, de sistema ou de componentes (ex: _components)
|
|
174
193
|
if (name.startsWith('.') || name.startsWith('_'))
|
|
175
194
|
continue;
|
|
176
|
-
const fullPath =
|
|
195
|
+
const fullPath = path_1.default.join(dir, name);
|
|
177
196
|
if (entry.isDirectory()) {
|
|
178
197
|
if (name === 'backend' || name === 'api')
|
|
179
198
|
continue;
|
|
@@ -182,7 +201,7 @@ export function loadPathRoutes(routesDir) {
|
|
|
182
201
|
else if (entry.isFile() && (name === 'page.tsx' || name === 'page.ts' || name === 'page.jsx' || name === 'page.js' || name === 'page.vue')) {
|
|
183
202
|
// SÓ carrega se for um arquivo "page"
|
|
184
203
|
try {
|
|
185
|
-
const absolutePath =
|
|
204
|
+
const absolutePath = path_1.default.resolve(fullPath);
|
|
186
205
|
// OTIMIZAÇÃO: Limpa cache apenas se já existia
|
|
187
206
|
if (loadedFiles.has(absolutePath)) {
|
|
188
207
|
safeClearCache(absolutePath);
|
|
@@ -192,7 +211,7 @@ export function loadPathRoutes(routesDir) {
|
|
|
192
211
|
// O "default" agora é o Componente React/Vue em si
|
|
193
212
|
const PageComponent = routeModule.default;
|
|
194
213
|
if (PageComponent) {
|
|
195
|
-
const componentPath =
|
|
214
|
+
const componentPath = path_1.default.relative(cwdPath, fullPath).replace(/\\/g, '/');
|
|
196
215
|
// Gera o pattern baseado na pasta (ex: /blog/[id])
|
|
197
216
|
const pattern = convertPathToRoutePattern(absolutePath, routesDir);
|
|
198
217
|
// Monta o config dinamicamente
|
|
@@ -213,7 +232,7 @@ export function loadPathRoutes(routesDir) {
|
|
|
213
232
|
}
|
|
214
233
|
}
|
|
215
234
|
catch (error) {
|
|
216
|
-
|
|
235
|
+
console_1.default.error(`Error loading page ${fullPath}:`, error);
|
|
217
236
|
}
|
|
218
237
|
}
|
|
219
238
|
}
|
|
@@ -232,11 +251,11 @@ export function loadPathRoutes(routesDir) {
|
|
|
232
251
|
allRoutes = loaded;
|
|
233
252
|
return allRoutes.map(r => ({ ...r.config, componentPath: r.componentPath }));
|
|
234
253
|
}
|
|
235
|
-
|
|
236
|
-
if (config?.pathRouter == true) {
|
|
237
|
-
return loadPathRoutes(
|
|
254
|
+
function loadRoutes(routesDir) {
|
|
255
|
+
if (helpers_1.config?.pathRouter == true) {
|
|
256
|
+
return loadPathRoutes(path_1.default.join(routesDir, "../"));
|
|
238
257
|
}
|
|
239
|
-
if (!
|
|
258
|
+
if (!fs_1.default.existsSync(routesDir)) {
|
|
240
259
|
allRoutes = [];
|
|
241
260
|
return [];
|
|
242
261
|
}
|
|
@@ -244,13 +263,13 @@ export function loadRoutes(routesDir) {
|
|
|
244
263
|
const cwdPath = process.cwd();
|
|
245
264
|
// Função recursiva otimizada
|
|
246
265
|
const scanAndLoad = (dir) => {
|
|
247
|
-
const entries =
|
|
266
|
+
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
248
267
|
for (const entry of entries) {
|
|
249
268
|
const name = entry.name;
|
|
250
269
|
// Skip arquivos ocultos ou de sistema para acelerar scan
|
|
251
270
|
if (name.startsWith('.') || name.startsWith('_'))
|
|
252
271
|
continue;
|
|
253
|
-
const fullPath =
|
|
272
|
+
const fullPath = path_1.default.join(dir, name);
|
|
254
273
|
if (entry.isDirectory()) {
|
|
255
274
|
if (name === 'backend')
|
|
256
275
|
continue;
|
|
@@ -258,7 +277,7 @@ export function loadRoutes(routesDir) {
|
|
|
258
277
|
}
|
|
259
278
|
else if (entry.isFile() && (name.endsWith('.tsx') || name.endsWith('.ts') || name.endsWith(".jsx") || name.endsWith(".js") || name.endsWith(".vue"))) {
|
|
260
279
|
try {
|
|
261
|
-
const absolutePath =
|
|
280
|
+
const absolutePath = path_1.default.resolve(fullPath);
|
|
262
281
|
// OTIMIZAÇÃO CRÍTICA: Só limpa cache se o arquivo já foi carregado antes.
|
|
263
282
|
// Isso evita chamadas caras de require.resolve() na inicialização do servidor.
|
|
264
283
|
if (loadedFiles.has(absolutePath)) {
|
|
@@ -275,7 +294,7 @@ export function loadRoutes(routesDir) {
|
|
|
275
294
|
};
|
|
276
295
|
}
|
|
277
296
|
if (defaultConfig?.pattern && defaultConfig?.component) {
|
|
278
|
-
const componentPath =
|
|
297
|
+
const componentPath = path_1.default.relative(cwdPath, fullPath).replace(/\\/g, '/');
|
|
279
298
|
// OTIMIZAÇÃO: Pré-compila a regex aqui
|
|
280
299
|
const regex = compileRoutePatternWithGroups(defaultConfig.pattern);
|
|
281
300
|
loaded.push({
|
|
@@ -288,7 +307,7 @@ export function loadRoutes(routesDir) {
|
|
|
288
307
|
}
|
|
289
308
|
}
|
|
290
309
|
catch (error) {
|
|
291
|
-
|
|
310
|
+
console_1.default.error(`Error loading route ${fullPath}:`, error);
|
|
292
311
|
}
|
|
293
312
|
}
|
|
294
313
|
}
|
|
@@ -298,7 +317,7 @@ export function loadRoutes(routesDir) {
|
|
|
298
317
|
allRoutes = loaded;
|
|
299
318
|
return allRoutes.map(r => ({ ...r.config, componentPath: r.componentPath }));
|
|
300
319
|
}
|
|
301
|
-
|
|
320
|
+
function findMatchingRoute(pathname) {
|
|
302
321
|
// OTIMIZAÇÃO: Loop usando regex pré-compilada. Muito mais rápido.
|
|
303
322
|
for (const route of allRoutes) {
|
|
304
323
|
const match = pathname.match(route.regex);
|
|
@@ -320,10 +339,10 @@ function getMiddlewaresForDir(dir) {
|
|
|
320
339
|
const files = ['middleware.ts', 'middleware.js'];
|
|
321
340
|
let middlewares = [];
|
|
322
341
|
for (const file of files) {
|
|
323
|
-
const fullPath =
|
|
324
|
-
if (
|
|
342
|
+
const fullPath = path_1.default.join(dir, file);
|
|
343
|
+
if (fs_1.default.existsSync(fullPath)) {
|
|
325
344
|
try {
|
|
326
|
-
const absolutePath =
|
|
345
|
+
const absolutePath = path_1.default.resolve(fullPath);
|
|
327
346
|
if (loadedFiles.has(absolutePath)) {
|
|
328
347
|
safeClearCache(absolutePath);
|
|
329
348
|
}
|
|
@@ -345,29 +364,29 @@ function getMiddlewaresForDir(dir) {
|
|
|
345
364
|
break;
|
|
346
365
|
}
|
|
347
366
|
catch (e) {
|
|
348
|
-
|
|
367
|
+
console_1.default.error(`Error loading middleware ${fullPath}`, e);
|
|
349
368
|
}
|
|
350
369
|
}
|
|
351
370
|
}
|
|
352
371
|
middlewareCache.set(dir, middlewares);
|
|
353
372
|
return middlewares;
|
|
354
373
|
}
|
|
355
|
-
|
|
356
|
-
if (!
|
|
374
|
+
function loadBackendRoutes(backendRoutesDir) {
|
|
375
|
+
if (!fs_1.default.existsSync(backendRoutesDir)) {
|
|
357
376
|
allBackendRoutes = [];
|
|
358
377
|
return;
|
|
359
378
|
}
|
|
360
379
|
middlewareCache.clear(); // Limpa cache de middleware ao recarregar rotas
|
|
361
380
|
const loaded = [];
|
|
362
381
|
const scanAndLoadAPI = (dir) => {
|
|
363
|
-
const entries =
|
|
382
|
+
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
364
383
|
// Primeiro carrega middleware deste diretório para cachear
|
|
365
384
|
getMiddlewaresForDir(dir);
|
|
366
385
|
for (const entry of entries) {
|
|
367
386
|
const name = entry.name;
|
|
368
387
|
if (name.startsWith('.') || name.startsWith('_'))
|
|
369
388
|
continue;
|
|
370
|
-
const fullPath =
|
|
389
|
+
const fullPath = path_1.default.join(dir, name);
|
|
371
390
|
if (entry.isDirectory()) {
|
|
372
391
|
scanAndLoadAPI(fullPath);
|
|
373
392
|
}
|
|
@@ -375,7 +394,7 @@ export function loadBackendRoutes(backendRoutesDir) {
|
|
|
375
394
|
if (name.startsWith('middleware'))
|
|
376
395
|
continue;
|
|
377
396
|
try {
|
|
378
|
-
const absolutePath =
|
|
397
|
+
const absolutePath = path_1.default.resolve(fullPath);
|
|
379
398
|
if (loadedFiles.has(absolutePath)) {
|
|
380
399
|
safeClearCache(absolutePath);
|
|
381
400
|
}
|
|
@@ -399,7 +418,7 @@ export function loadBackendRoutes(backendRoutesDir) {
|
|
|
399
418
|
}
|
|
400
419
|
}
|
|
401
420
|
catch (e) {
|
|
402
|
-
|
|
421
|
+
console_1.default.error(`Error loading API route ${fullPath}`, e);
|
|
403
422
|
}
|
|
404
423
|
}
|
|
405
424
|
}
|
|
@@ -409,7 +428,7 @@ export function loadBackendRoutes(backendRoutesDir) {
|
|
|
409
428
|
// Processa WebSockets após carregar rotas HTTP
|
|
410
429
|
processWebSocketRoutes();
|
|
411
430
|
}
|
|
412
|
-
|
|
431
|
+
function findMatchingBackendRoute(pathname, method) {
|
|
413
432
|
const methodUpper = method.toUpperCase();
|
|
414
433
|
for (const route of allBackendRoutes) {
|
|
415
434
|
// @ts-ignore
|
|
@@ -426,13 +445,13 @@ export function findMatchingBackendRoute(pathname, method) {
|
|
|
426
445
|
return null;
|
|
427
446
|
}
|
|
428
447
|
// --- 404 Not Found ---
|
|
429
|
-
|
|
448
|
+
function loadNotFound(webDir) {
|
|
430
449
|
const files = ['notFound.tsx', 'notFound.jsx', 'notFound.vue'];
|
|
431
450
|
for (const file of files) {
|
|
432
|
-
const fullPath =
|
|
433
|
-
if (
|
|
434
|
-
const absolutePath =
|
|
435
|
-
const componentPath =
|
|
451
|
+
const fullPath = path_1.default.join(webDir, file);
|
|
452
|
+
if (fs_1.default.existsSync(fullPath)) {
|
|
453
|
+
const absolutePath = path_1.default.resolve(fullPath);
|
|
454
|
+
const componentPath = path_1.default.relative(process.cwd(), fullPath).replace(/\\/g, '/');
|
|
436
455
|
if (loadedFiles.has(absolutePath)) {
|
|
437
456
|
safeClearCache(absolutePath);
|
|
438
457
|
}
|
|
@@ -446,9 +465,9 @@ export function loadNotFound(webDir) {
|
|
|
446
465
|
notFoundComponent = null;
|
|
447
466
|
return null;
|
|
448
467
|
}
|
|
449
|
-
|
|
468
|
+
function getNotFound() { return notFoundComponent; }
|
|
450
469
|
// --- WebSocket (Mantendo lógica, otimizando lookup) ---
|
|
451
|
-
|
|
470
|
+
function processWebSocketRoutes() {
|
|
452
471
|
allWebSocketRoutes = allBackendRoutes
|
|
453
472
|
.filter(r => r.config.WS)
|
|
454
473
|
.map(r => ({
|
|
@@ -458,7 +477,7 @@ export function processWebSocketRoutes() {
|
|
|
458
477
|
middleware: r.config.middleware
|
|
459
478
|
}));
|
|
460
479
|
}
|
|
461
|
-
|
|
480
|
+
function findMatchingWebSocketRoute(pathname) {
|
|
462
481
|
for (const wsRoute of allWebSocketRoutes) {
|
|
463
482
|
const match = pathname.match(wsRoute.regex);
|
|
464
483
|
if (match) {
|
|
@@ -477,7 +496,7 @@ export function findMatchingWebSocketRoute(pathname) {
|
|
|
477
496
|
function handleWebSocketConnection(ws, req, hwebReq) {
|
|
478
497
|
if (!req.url)
|
|
479
498
|
return;
|
|
480
|
-
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
499
|
+
const url = new url_1.URL(req.url, `http://${req.headers.host}`);
|
|
481
500
|
const match = findMatchingWebSocketRoute(url.pathname);
|
|
482
501
|
if (!match) {
|
|
483
502
|
ws.close(1000, 'Route not found');
|
|
@@ -491,7 +510,7 @@ function handleWebSocketConnection(ws, req, hwebReq) {
|
|
|
491
510
|
params: match.params,
|
|
492
511
|
query: Object.fromEntries(url.searchParams.entries()),
|
|
493
512
|
send: (data) => {
|
|
494
|
-
if (ws.readyState === WebSocket.OPEN) {
|
|
513
|
+
if (ws.readyState === ws_1.WebSocket.OPEN) {
|
|
495
514
|
ws.send(typeof data === 'string' ? data : JSON.stringify(data));
|
|
496
515
|
}
|
|
497
516
|
},
|
|
@@ -500,7 +519,7 @@ function handleWebSocketConnection(ws, req, hwebReq) {
|
|
|
500
519
|
const msg = typeof data === 'string' ? data : JSON.stringify(data);
|
|
501
520
|
const excludeSet = new Set(exclude || []);
|
|
502
521
|
for (const conn of wsConnections) {
|
|
503
|
-
if (conn.readyState === WebSocket.OPEN && !excludeSet.has(conn)) {
|
|
522
|
+
if (conn.readyState === ws_1.WebSocket.OPEN && !excludeSet.has(conn)) {
|
|
504
523
|
conn.send(msg);
|
|
505
524
|
}
|
|
506
525
|
}
|
|
@@ -514,16 +533,16 @@ function handleWebSocketConnection(ws, req, hwebReq) {
|
|
|
514
533
|
ws.close(1011, 'Internal server error');
|
|
515
534
|
}
|
|
516
535
|
}
|
|
517
|
-
|
|
536
|
+
function setupWebSocketUpgrade(server, hotReloadManager) {
|
|
518
537
|
if (server.listeners('upgrade').length > 0)
|
|
519
538
|
return;
|
|
520
539
|
server.on('upgrade', (request, socket, head) => {
|
|
521
|
-
const adapter = FrameworkAdapterFactory.getCurrentAdapter();
|
|
540
|
+
const adapter = factory_1.FrameworkAdapterFactory.getCurrentAdapter();
|
|
522
541
|
if (!adapter) {
|
|
523
542
|
socket.destroy();
|
|
524
543
|
return;
|
|
525
544
|
}
|
|
526
|
-
const { pathname } = new URL(request.url, `http://${request.headers.host}`);
|
|
545
|
+
const { pathname } = new url_1.URL(request.url, `http://${request.headers.host}`);
|
|
527
546
|
// Prioridade 1: Hot Reload
|
|
528
547
|
if (pathname === '/hweb-hotreload/') {
|
|
529
548
|
if (hotReloadManager)
|
|
@@ -535,12 +554,12 @@ export function setupWebSocketUpgrade(server, hotReloadManager) {
|
|
|
535
554
|
// Prioridade 2: Rotas App
|
|
536
555
|
const match = findMatchingWebSocketRoute(pathname);
|
|
537
556
|
if (match) {
|
|
538
|
-
const wss = new
|
|
557
|
+
const wss = new ws_1.WebSocketServer({ noServer: true, perMessageDeflate: false, maxPayload: 1024 * 1024 });
|
|
539
558
|
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
540
559
|
wsConnections.add(ws);
|
|
541
560
|
ws.on('close', () => wsConnections.delete(ws));
|
|
542
561
|
ws.on('error', () => wsConnections.delete(ws));
|
|
543
|
-
const hwebReq = new VattsRequest(adapter.parseRequest(request));
|
|
562
|
+
const hwebReq = new http_1.VattsRequest(adapter.parseRequest(request));
|
|
544
563
|
handleWebSocketConnection(ws, request, hwebReq);
|
|
545
564
|
});
|
|
546
565
|
return;
|
package/dist/rpc/annotations.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RPC_EXPOSED_KEY = void 0;
|
|
4
|
+
exports.default = Expose;
|
|
1
5
|
/**
|
|
2
6
|
* Símbolo único para marcar funções expostas.
|
|
3
7
|
* Usamos Symbol para garantir que não possa ser falsificado via JSON no payload.
|
|
4
8
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
9
|
+
exports.RPC_EXPOSED_KEY = Symbol('__rpc_exposed__');
|
|
10
|
+
function Expose(...input) {
|
|
7
11
|
const fns = Array.isArray(input[0]) ? input[0] : input;
|
|
8
12
|
for (const fn of fns) {
|
|
9
13
|
if (typeof fn !== 'function') {
|
|
10
14
|
throw new TypeError('Expose aceita apenas funções');
|
|
11
15
|
}
|
|
12
|
-
fn[RPC_EXPOSED_KEY] = true;
|
|
16
|
+
fn[exports.RPC_EXPOSED_KEY] = true;
|
|
13
17
|
}
|
|
14
18
|
// Retorno:
|
|
15
19
|
// - se veio uma função → retorna ela
|
package/dist/rpc/server.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/*
|
|
2
3
|
* This file is part of the Vatts.js Project.
|
|
3
4
|
* Copyright (c) 2026 itsmuzin
|
|
@@ -14,11 +15,16 @@
|
|
|
14
15
|
* See the License for the specific language governing permissions and
|
|
15
16
|
* limitations under the License.
|
|
16
17
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
19
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
20
|
+
};
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.executeRpc = executeRpc;
|
|
23
|
+
const fs_1 = __importDefault(require("fs"));
|
|
24
|
+
const path_1 = __importDefault(require("path"));
|
|
25
|
+
const http_1 = require("../api/http");
|
|
20
26
|
// Importamos a chave para verificar a anotação de segurança
|
|
21
|
-
|
|
27
|
+
const annotations_1 = require("./annotations");
|
|
22
28
|
const DEFAULT_ALLOWED_SERVER_DIRS = ['src/backend'];
|
|
23
29
|
function normalizeToPosix(p) {
|
|
24
30
|
return p.replace(/\\/g, '/');
|
|
@@ -45,19 +51,19 @@ function validatePayload(payload) {
|
|
|
45
51
|
function tryResolveWithinAllowedDirs(projectDir, allowedDirs, requestedFile) {
|
|
46
52
|
const req = requestedFile.replace(/\\/g, '/').replace(/^\.(?:\/|\\)/, '');
|
|
47
53
|
for (const d of allowedDirs) {
|
|
48
|
-
const baseAbs =
|
|
54
|
+
const baseAbs = path_1.default.resolve(projectDir, d);
|
|
49
55
|
// Interpret client path as relative to src/web (where it's typically authored)
|
|
50
|
-
const fromWebAbs =
|
|
56
|
+
const fromWebAbs = path_1.default.resolve(projectDir, 'src/web', req);
|
|
51
57
|
// Map: <project>/src/backend/* (coming from ../../backend/* from web code)
|
|
52
|
-
const mappedFromWebAbs = fromWebAbs.replace(
|
|
58
|
+
const mappedFromWebAbs = fromWebAbs.replace(path_1.default.resolve(projectDir, 'backend') + path_1.default.sep, path_1.default.resolve(projectDir, 'src', 'backend') + path_1.default.sep);
|
|
53
59
|
// Also accept callers passing a backend-relative path like "./auth" or "auth"
|
|
54
|
-
const fromBackendAbs =
|
|
60
|
+
const fromBackendAbs = path_1.default.resolve(baseAbs, req);
|
|
55
61
|
const candidateAbsList = [mappedFromWebAbs, fromBackendAbs];
|
|
56
62
|
for (const candidateAbs of candidateAbsList) {
|
|
57
|
-
const baseWithSep = baseAbs.endsWith(
|
|
63
|
+
const baseWithSep = baseAbs.endsWith(path_1.default.sep) ? baseAbs : baseAbs + path_1.default.sep;
|
|
58
64
|
if (!candidateAbs.startsWith(baseWithSep) && candidateAbs !== baseAbs)
|
|
59
65
|
continue;
|
|
60
|
-
if (!
|
|
66
|
+
if (!fs_1.default.existsSync(baseAbs))
|
|
61
67
|
continue;
|
|
62
68
|
return candidateAbs;
|
|
63
69
|
}
|
|
@@ -97,9 +103,9 @@ function buildRpcRequestFromPayload(payload) {
|
|
|
97
103
|
cookies: payload.request?.cookies || toCookies(cookieHeader),
|
|
98
104
|
raw: null
|
|
99
105
|
};
|
|
100
|
-
return new VattsRequest(req);
|
|
106
|
+
return new http_1.VattsRequest(req);
|
|
101
107
|
}
|
|
102
|
-
|
|
108
|
+
async function executeRpc(ctx, payload) {
|
|
103
109
|
try {
|
|
104
110
|
if (!validatePayload(payload)) {
|
|
105
111
|
return { success: false, error: 'Invalid RPC payload' };
|
|
@@ -113,7 +119,7 @@ export async function executeRpc(ctx, payload) {
|
|
|
113
119
|
if (!absFile) {
|
|
114
120
|
return { success: false, error: 'File not allowed for RPC' };
|
|
115
121
|
}
|
|
116
|
-
if (!absFile.startsWith(
|
|
122
|
+
if (!absFile.startsWith(path_1.default.resolve(ctx.projectDir) + path_1.default.sep)) {
|
|
117
123
|
return { success: false, error: 'Invalid file path' };
|
|
118
124
|
}
|
|
119
125
|
// Ensure fresh code in dev
|
|
@@ -153,7 +159,7 @@ export async function executeRpc(ctx, payload) {
|
|
|
153
159
|
}
|
|
154
160
|
// --- SECURITY CHECK (Expose Annotation or Allowlist) ---
|
|
155
161
|
// 1. Verifica se a função possui o Symbol definido em annotations.ts (Via wrapper Expose())
|
|
156
|
-
const isAnnotated = fnValue[RPC_EXPOSED_KEY];
|
|
162
|
+
const isAnnotated = fnValue[annotations_1.RPC_EXPOSED_KEY];
|
|
157
163
|
// 2. Verifica se o módulo exporta uma lista explícita chamada 'exposed' ou 'rpcMethods'
|
|
158
164
|
// Isso permite o uso de "export function" sem wrapper, listando os nomes no final do arquivo.
|
|
159
165
|
const allowList = mod.exposed || mod.rpcMethods;
|
|
@@ -165,7 +171,7 @@ export async function executeRpc(ctx, payload) {
|
|
|
165
171
|
};
|
|
166
172
|
}
|
|
167
173
|
// ------------------------------------------
|
|
168
|
-
const rpcRequest = ctx.request ? new VattsRequest(ctx.request) : buildRpcRequestFromPayload(payload);
|
|
174
|
+
const rpcRequest = ctx.request ? new http_1.VattsRequest(ctx.request) : buildRpcRequestFromPayload(payload);
|
|
169
175
|
// If the function declares a first parameter, we assume it's the injected request.
|
|
170
176
|
// Otherwise, call it only with payload args.
|
|
171
177
|
// it might be 0, so we treat length>=1 as "expects req".
|
package/dist/rpc/types.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/*
|
|
2
3
|
* This file is part of the Vatts.js Project.
|
|
3
4
|
* Copyright (c) 2026 itsmuzin
|
|
@@ -14,4 +15,6 @@
|
|
|
14
15
|
* See the License for the specific language governing permissions and
|
|
15
16
|
* limitations under the License.
|
|
16
17
|
*/
|
|
17
|
-
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.RPC_ENDPOINT = void 0;
|
|
20
|
+
exports.RPC_ENDPOINT = '/api/rpc';
|
package/dist/types/framework.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
package/dist/types.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|