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 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, console_1.Colors.BgRed, `Reloading backend...`);
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, console_1.Colors.BgRed, `Reloading application...`);
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
+ }
@@ -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
- * http://www.apache.org/licenses/LICENSE-2.0
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/* (coming from ../../backend/* from web code)
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.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, Colors.BgRed,`Reloading backend...`);
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, Colors.BgRed,`Reloading application...`);
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
- * http://www.apache.org/licenses/LICENSE-2.0
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/* (coming from ../../backend/* from web code)
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
+ }