nyte 1.0.0 → 1.0.1
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/hotReload.js +3 -2
- package/dist/rpc/annotations.d.ts +13 -0
- package/dist/rpc/annotations.js +22 -0
- package/dist/rpc/server.js +18 -2
- package/package.json +6 -1
- package/src/hotReload.ts +3 -2
- package/src/rpc/annotations.ts +30 -0
- package/src/rpc/server.ts +22 -3
package/dist/hotReload.js
CHANGED
|
@@ -213,6 +213,7 @@ class HotReloadManager {
|
|
|
213
213
|
});
|
|
214
214
|
try {
|
|
215
215
|
this.frontendChangeCallback?.();
|
|
216
|
+
this.backendApiChangeCallback?.();
|
|
216
217
|
await Promise.race([buildPromise, timeoutPromise]);
|
|
217
218
|
dm.end(`Build complete for ${path.basename(filePath)}, reloading frontend.`);
|
|
218
219
|
this.frontendChangeCallback?.();
|
|
@@ -230,13 +231,13 @@ class HotReloadManager {
|
|
|
230
231
|
}
|
|
231
232
|
// Se for arquivo de backend, recarrega o módulo e notifica
|
|
232
233
|
if (isBackendFile) {
|
|
233
|
-
console_1.default.logWithout(console_1.Levels.INFO,
|
|
234
|
+
console_1.default.logWithout(console_1.Levels.INFO, undefined, `Reloading backend...`);
|
|
234
235
|
this.backendApiChangeCallback?.();
|
|
235
236
|
this.notifyClients('backend-api-reload', { file: filePath, event: 'change' });
|
|
236
237
|
}
|
|
237
238
|
// Fallback: se não for nem frontend nem backend detectado, recarrega tudo
|
|
238
239
|
if (!isFrontendFile && !isBackendFile) {
|
|
239
|
-
console_1.default.logWithout(console_1.Levels.INFO,
|
|
240
|
+
console_1.default.logWithout(console_1.Levels.INFO, undefined, `Reloading application...`);
|
|
240
241
|
this.frontendChangeCallback?.();
|
|
241
242
|
this.backendApiChangeCallback?.();
|
|
242
243
|
this.notifyClients('src-reload', { file: filePath, event: 'change' });
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Símbolo único para marcar funções expostas.
|
|
3
|
+
* Usamos Symbol para garantir que não possa ser falsificado via JSON no payload.
|
|
4
|
+
*/
|
|
5
|
+
export declare const RPC_EXPOSED_KEY: unique symbol;
|
|
6
|
+
type AnyFn = (...args: any[]) => any;
|
|
7
|
+
/**
|
|
8
|
+
* Marca uma ou mais funções como seguras para RPC.
|
|
9
|
+
*/
|
|
10
|
+
export default function Expose<T extends AnyFn>(fn: T): T;
|
|
11
|
+
export default function Expose<T extends AnyFn[]>(fns: [...T]): T;
|
|
12
|
+
export default function Expose<T extends AnyFn[]>(...fns: T): T;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RPC_EXPOSED_KEY = void 0;
|
|
4
|
+
exports.default = Expose;
|
|
5
|
+
/**
|
|
6
|
+
* Símbolo único para marcar funções expostas.
|
|
7
|
+
* Usamos Symbol para garantir que não possa ser falsificado via JSON no payload.
|
|
8
|
+
*/
|
|
9
|
+
exports.RPC_EXPOSED_KEY = Symbol('__rpc_exposed__');
|
|
10
|
+
function Expose(...input) {
|
|
11
|
+
const fns = Array.isArray(input[0]) ? input[0] : input;
|
|
12
|
+
for (const fn of fns) {
|
|
13
|
+
if (typeof fn !== 'function') {
|
|
14
|
+
throw new TypeError('Expose aceita apenas funções');
|
|
15
|
+
}
|
|
16
|
+
fn[exports.RPC_EXPOSED_KEY] = true;
|
|
17
|
+
}
|
|
18
|
+
// Retorno:
|
|
19
|
+
// - se veio uma função → retorna ela
|
|
20
|
+
// - se veio lista → retorna a lista
|
|
21
|
+
return input.length === 1 ? input[0] : input;
|
|
22
|
+
}
|
package/dist/rpc/server.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* you may not use this file except in compliance with the License.
|
|
8
8
|
* You may obtain a copy of the License at
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
11
|
*
|
|
12
12
|
* Unless required by applicable law or agreed to in writing, software
|
|
13
13
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
@@ -23,6 +23,8 @@ exports.executeRpc = executeRpc;
|
|
|
23
23
|
const fs_1 = __importDefault(require("fs"));
|
|
24
24
|
const path_1 = __importDefault(require("path"));
|
|
25
25
|
const http_1 = require("../api/http");
|
|
26
|
+
// Importamos a chave para verificar a anotação de segurança
|
|
27
|
+
const annotations_1 = require("./annotations");
|
|
26
28
|
const DEFAULT_ALLOWED_SERVER_DIRS = ['src/backend'];
|
|
27
29
|
function normalizeToPosix(p) {
|
|
28
30
|
return p.replace(/\\/g, '/');
|
|
@@ -52,7 +54,7 @@ function tryResolveWithinAllowedDirs(projectDir, allowedDirs, requestedFile) {
|
|
|
52
54
|
const baseAbs = path_1.default.resolve(projectDir, d);
|
|
53
55
|
// Interpret client path as relative to src/web (where it's typically authored)
|
|
54
56
|
const fromWebAbs = path_1.default.resolve(projectDir, 'src/web', req);
|
|
55
|
-
// Map: <project>/src/backend/*
|
|
57
|
+
// Map: <project>/src/backend/* (coming from ../../backend/* from web code)
|
|
56
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);
|
|
57
59
|
// Also accept callers passing a backend-relative path like "./auth" or "auth"
|
|
58
60
|
const fromBackendAbs = path_1.default.resolve(baseAbs, req);
|
|
@@ -155,6 +157,20 @@ async function executeRpc(ctx, payload) {
|
|
|
155
157
|
if (typeof fnValue !== 'function') {
|
|
156
158
|
return { success: false, error: `RPC function not found: ${fnName}` };
|
|
157
159
|
}
|
|
160
|
+
// --- SECURITY CHECK (Expose Annotation or Allowlist) ---
|
|
161
|
+
// 1. Verifica se a função possui o Symbol definido em annotations.ts (Via wrapper Expose())
|
|
162
|
+
const isAnnotated = fnValue[annotations_1.RPC_EXPOSED_KEY];
|
|
163
|
+
// 2. Verifica se o módulo exporta uma lista explícita chamada 'exposed' ou 'rpcMethods'
|
|
164
|
+
// Isso permite o uso de "export function" sem wrapper, listando os nomes no final do arquivo.
|
|
165
|
+
const allowList = mod.exposed || mod.rpcMethods;
|
|
166
|
+
const isListed = Array.isArray(allowList) && allowList.includes(fnName);
|
|
167
|
+
if (!isAnnotated && !isListed) {
|
|
168
|
+
return {
|
|
169
|
+
success: false,
|
|
170
|
+
error: `Function '${fnName}' is not exposed via RPC. Mark it with Expose() or add it to an exported 'exposed' array.`
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
// ------------------------------------------
|
|
158
174
|
const rpcRequest = ctx.request ? new http_1.NyteRequest(ctx.request) : buildRpcRequestFromPayload(payload);
|
|
159
175
|
const result = await fnValue(rpcRequest, ...payload.args);
|
|
160
176
|
return { success: true, return: result };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nyte",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Nyte.js is a high-level framework for building web applications with ease and speed. It provides a robust set of tools and features to streamline development and enhance productivity.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -47,6 +47,11 @@
|
|
|
47
47
|
"types": "./dist/eslint/index.d.ts",
|
|
48
48
|
"import": "./dist/eslint/index.js",
|
|
49
49
|
"require": "./dist/eslint/index.js"
|
|
50
|
+
},
|
|
51
|
+
"./rpc": {
|
|
52
|
+
"types": "./dist/rpc/annotations.d.ts",
|
|
53
|
+
"import": "./dist/rpc/annotations.js",
|
|
54
|
+
"require": "./dist/rpc/annotations.js"
|
|
50
55
|
}
|
|
51
56
|
},
|
|
52
57
|
"peerDependencies": {
|
package/src/hotReload.ts
CHANGED
|
@@ -219,6 +219,7 @@ export class HotReloadManager {
|
|
|
219
219
|
|
|
220
220
|
try {
|
|
221
221
|
this.frontendChangeCallback?.();
|
|
222
|
+
this.backendApiChangeCallback?.();
|
|
222
223
|
await Promise.race([buildPromise, timeoutPromise]);
|
|
223
224
|
dm.end(`Build complete for ${path.basename(filePath)}, reloading frontend.`);
|
|
224
225
|
this.frontendChangeCallback?.();
|
|
@@ -235,14 +236,14 @@ export class HotReloadManager {
|
|
|
235
236
|
|
|
236
237
|
// Se for arquivo de backend, recarrega o módulo e notifica
|
|
237
238
|
if (isBackendFile) {
|
|
238
|
-
Console.logWithout(Levels.INFO,
|
|
239
|
+
Console.logWithout(Levels.INFO, undefined,`Reloading backend...`);
|
|
239
240
|
this.backendApiChangeCallback?.();
|
|
240
241
|
this.notifyClients('backend-api-reload', { file: filePath, event: 'change' });
|
|
241
242
|
}
|
|
242
243
|
|
|
243
244
|
// Fallback: se não for nem frontend nem backend detectado, recarrega tudo
|
|
244
245
|
if (!isFrontendFile && !isBackendFile) {
|
|
245
|
-
Console.logWithout(Levels.INFO,
|
|
246
|
+
Console.logWithout(Levels.INFO, undefined,`Reloading application...`);
|
|
246
247
|
this.frontendChangeCallback?.();
|
|
247
248
|
this.backendApiChangeCallback?.();
|
|
248
249
|
this.notifyClients('src-reload', { file: filePath, event: 'change' });
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Símbolo único para marcar funções expostas.
|
|
3
|
+
* Usamos Symbol para garantir que não possa ser falsificado via JSON no payload.
|
|
4
|
+
*/
|
|
5
|
+
export const RPC_EXPOSED_KEY = Symbol('__rpc_exposed__');
|
|
6
|
+
|
|
7
|
+
type AnyFn = (...args: any[]) => any;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Marca uma ou mais funções como seguras para RPC.
|
|
11
|
+
*/
|
|
12
|
+
export default function Expose<T extends AnyFn>(fn: T): T;
|
|
13
|
+
export default function Expose<T extends AnyFn[]>(fns: [...T]): T;
|
|
14
|
+
export default function Expose<T extends AnyFn[]>(...fns: T): T;
|
|
15
|
+
export default function Expose(...input: any[]): any {
|
|
16
|
+
const fns: AnyFn[] =
|
|
17
|
+
Array.isArray(input[0]) ? input[0] : input;
|
|
18
|
+
|
|
19
|
+
for (const fn of fns) {
|
|
20
|
+
if (typeof fn !== 'function') {
|
|
21
|
+
throw new TypeError('Expose aceita apenas funções');
|
|
22
|
+
}
|
|
23
|
+
(fn as any)[RPC_EXPOSED_KEY] = true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Retorno:
|
|
27
|
+
// - se veio uma função → retorna ela
|
|
28
|
+
// - se veio lista → retorna a lista
|
|
29
|
+
return input.length === 1 ? input[0] : input;
|
|
30
|
+
}
|
package/src/rpc/server.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* you may not use this file except in compliance with the License.
|
|
7
7
|
* You may obtain a copy of the License at
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
10
|
*
|
|
11
11
|
* Unless required by applicable law or agreed to in writing, software
|
|
12
12
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
@@ -20,6 +20,8 @@ import path from 'path';
|
|
|
20
20
|
import { RpcRequestPayload, RpcResponsePayload } from './types';
|
|
21
21
|
import { NyteRequest } from '../api/http';
|
|
22
22
|
import type { GenericRequest } from '../types/framework';
|
|
23
|
+
// Importamos a chave para verificar a anotação de segurança
|
|
24
|
+
import { RPC_EXPOSED_KEY } from './annotations';
|
|
23
25
|
|
|
24
26
|
const DEFAULT_ALLOWED_SERVER_DIRS = ['src/backend'] as const;
|
|
25
27
|
|
|
@@ -64,7 +66,7 @@ function tryResolveWithinAllowedDirs(projectDir: string, allowedDirs: string[],
|
|
|
64
66
|
// Interpret client path as relative to src/web (where it's typically authored)
|
|
65
67
|
const fromWebAbs = path.resolve(projectDir, 'src/web', req);
|
|
66
68
|
|
|
67
|
-
// Map: <project>/src/backend/*
|
|
69
|
+
// Map: <project>/src/backend/* (coming from ../../backend/* from web code)
|
|
68
70
|
const mappedFromWebAbs = fromWebAbs.replace(
|
|
69
71
|
path.resolve(projectDir, 'backend') + path.sep,
|
|
70
72
|
path.resolve(projectDir, 'src', 'backend') + path.sep
|
|
@@ -180,6 +182,23 @@ export async function executeRpc(ctx: RpcExecutionContext, payload: any): Promis
|
|
|
180
182
|
return { success: false, error: `RPC function not found: ${fnName}` };
|
|
181
183
|
}
|
|
182
184
|
|
|
185
|
+
// --- SECURITY CHECK (Expose Annotation or Allowlist) ---
|
|
186
|
+
// 1. Verifica se a função possui o Symbol definido em annotations.ts (Via wrapper Expose())
|
|
187
|
+
const isAnnotated = (fnValue as any)[RPC_EXPOSED_KEY];
|
|
188
|
+
|
|
189
|
+
// 2. Verifica se o módulo exporta uma lista explícita chamada 'exposed' ou 'rpcMethods'
|
|
190
|
+
// Isso permite o uso de "export function" sem wrapper, listando os nomes no final do arquivo.
|
|
191
|
+
const allowList = mod.exposed || mod.rpcMethods;
|
|
192
|
+
const isListed = Array.isArray(allowList) && allowList.includes(fnName);
|
|
193
|
+
|
|
194
|
+
if (!isAnnotated && !isListed) {
|
|
195
|
+
return {
|
|
196
|
+
success: false,
|
|
197
|
+
error: `Function '${fnName}' is not exposed via RPC. Mark it with Expose() or add it to an exported 'exposed' array.`
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
// ------------------------------------------
|
|
201
|
+
|
|
183
202
|
const rpcRequest = ctx.request ? new NyteRequest(ctx.request) : buildRpcRequestFromPayload(payload);
|
|
184
203
|
const result = await fnValue(rpcRequest, ...payload.args);
|
|
185
204
|
return { success: true, return: result };
|
|
@@ -187,4 +206,4 @@ export async function executeRpc(ctx: RpcExecutionContext, payload: any): Promis
|
|
|
187
206
|
const message = typeof err?.message === 'string' ? err.message : 'Unknown RPC error';
|
|
188
207
|
return { success: false, error: message };
|
|
189
208
|
}
|
|
190
|
-
}
|
|
209
|
+
}
|