@bhooai/nexus-core 0.1.5 → 2.0.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/src/config/env.ts CHANGED
@@ -84,6 +84,7 @@ const PAIR_FIELDS: Record<string, string> = {
84
84
  'cpu_high': 'cpuHigh',
85
85
  'rps_per_node_high': 'rpsPerNodeHigh',
86
86
  'rps_per_node_low': 'rpsPerNodeLow',
87
+ 'server_url': 'serverUrl',
87
88
  };
88
89
 
89
90
  export function configFromEnv(env: NodeJS.ProcessEnv = process.env): DeepPartial<NexusConfig> {
@@ -47,7 +47,7 @@ export const nexusConfigSchema = z.object({
47
47
  }),
48
48
  auth: z.object({
49
49
  jwt: z.object({
50
- secret: z.string().min(8, 'jwt.secret must be at least 8 characters'),
50
+ secret: z.string().min(8, 'jwt.secret must be at least 8 characters').optional(),
51
51
  accessTtl: z.number().int().positive(),
52
52
  refreshTtl: z.number().int().positive(),
53
53
  issuer: z.string(),
@@ -153,32 +153,6 @@ export const nexusConfigSchema = z.object({
153
153
  host: z.string().min(1),
154
154
  enabled: z.boolean(),
155
155
  }),
156
- cluster: z.object({
157
- enabled: z.boolean(),
158
- role: z.enum(['backend', 'files', 'database', 'ai']).optional(),
159
- failOpenSingleNode: z.boolean(),
160
- lbHost: z.string().min(1),
161
- lbPort: z.number().int().min(1).max(65535),
162
- nodeAgentHost: z.string().min(1),
163
- nodeAgentPort: z.number().int().min(1).max(65535),
164
- registryFile: z.string().min(1),
165
- token: z.string(),
166
- pathPins: z.array(z.object({
167
- prefix: z.string().min(1),
168
- nodeId: z.string().min(1),
169
- role: z.enum(['backend', 'files', 'database', 'ai']).optional(),
170
- })).optional(),
171
- autoscale: z.object({
172
- enabled: z.boolean(),
173
- mode: z.enum(['auto', 'manual']),
174
- minNodes: z.number().int().min(1),
175
- maxNodes: z.number().int().min(1),
176
- cooldownMs: z.number().int().nonnegative(),
177
- cpuHigh: z.number().min(0).max(100),
178
- rpsPerNodeHigh: z.number().nonnegative(),
179
- rpsPerNodeLow: z.number().nonnegative(),
180
- }),
181
- }),
182
156
  });
183
157
 
184
158
  export type ValidatedNexusConfig = z.infer<typeof nexusConfigSchema>;
@@ -52,7 +52,9 @@ export interface RedisConfig {
52
52
  }
53
53
 
54
54
  export interface JwtConfig {
55
- secret: string;
55
+ /** Secret used to sign access + refresh tokens.
56
+ * Loaded from env NEXUS_AUTH_JWT_SECRET; never store in nexus.config.ts. */
57
+ secret?: string;
56
58
  /** Access token TTL in seconds. */
57
59
  accessTtl: number;
58
60
  /** Refresh token TTL in seconds. */
@@ -238,90 +240,6 @@ export interface AdminConfig {
238
240
  enabled: boolean;
239
241
  }
240
242
 
241
- /** Node type — the service profile a node advertises and exposes. */
242
- export type NodeRole = 'backend' | 'files' | 'database' | 'ai';
243
-
244
- /** A registered remote node in the central registry. */
245
- export interface NodeInfo {
246
- /** Stable node id (hostname-derived slug or caller-provided). */
247
- id: string;
248
- /** Base URL that reaches the node's agent, e.g. "https://node.example.com:7575". */
249
- baseUrl: string;
250
- /** The node's role profile. */
251
- role: NodeRole;
252
- /** Node reported version string. */
253
- version?: string;
254
- /** First successful registration time (ISO). */
255
- registeredAt: string;
256
- /** Last successful health/link time (ISO). */
257
- lastSeenAt: string;
258
- /** Last known metric sample. */
259
- lastMetrics?: NodeMetrics;
260
- /** Linked (discovered yet unverified) vs ready nodes. */
261
- status: 'pending' | 'ready' | 'unreachable';
262
- }
263
-
264
- /** Rolling snapshot of a node's load, sampled by the central autoscaler. */
265
- export interface NodeMetrics {
266
- /** Requests per second the node reports receiving (if role exposes it). */
267
- rps: number;
268
- /** CPU usage of the node process (0-100). */
269
- cpu: number;
270
- /** Memory usage of the node process in MiB. */
271
- memoryMb: number;
272
- /** Unix ms timestamp the sample was taken. */
273
- sampledAt: number;
274
- }
275
-
276
- export interface ClusterAutoscaleConfig {
277
- /** Enable the autoscaler entirely. */
278
- enabled: boolean;
279
- /** Scale decision mode: "auto" (deterministic + AI) or "manual". */
280
- mode: 'auto' | 'manual';
281
- /** Minimum number of backend nodes the LB keeps ready. */
282
- minNodes: number;
283
- /** Maximum number of backend nodes the LB may scale to. */
284
- maxNodes: number;
285
- /** Minimum wait (ms) between two autoscale decisions. */
286
- cooldownMs: number;
287
- /** CPU% at/above which the cluster considers a node overloaded. */
288
- cpuHigh: number;
289
- /** RPS per backend node at/above which the cluster scales up. */
290
- rpsPerNodeHigh: number;
291
- /** RPS per backend node below which the cluster scales down. */
292
- rpsPerNodeLow: number;
293
- }
294
-
295
- export interface ClusterConfig {
296
- /** Turn the cluster (registry + LB + autoscaler) on. */
297
- enabled: boolean;
298
- /** When set, this server is a cluster node (slave) of this role — `nexus dev`
299
- * auto-starts a node agent alongside the backend so a master can link it.
300
- * Undefined/empty on the root/central server. */
301
- role?: NodeRole;
302
- /** When false, the LB fails open to a single direct node (no scaling). */
303
- failOpenSingleNode: boolean;
304
- /** Bind address of the central cluster's public load balancer. */
305
- lbHost: string;
306
- /** Port of the central cluster's public load balancer. */
307
- lbPort: number;
308
- /** Bind address of the node agent's control API (per node side). */
309
- nodeAgentHost: string;
310
- /** Port of the node agent's control API (per node side). */
311
- nodeAgentPort: number;
312
- /** File path (relative to this config's root) for the persisted registry. */
313
- registryFile: string;
314
- /** Shared pairing secret minted at node setup; pasted into the central. */
315
- token: string;
316
- /** Path-prefix -> node-id pins: requests whose URL starts with `prefix` are
317
- * always routed to `nodeId` (fail-closed 503 when that node is down, instead
318
- * of round-robining). Used to allocate upload paths to specific file nodes,
319
- * e.g. `/upload/users` -> slave-1. Optional `role` guards that the pinned
320
- * node must be of that role (defensive). */
321
- pathPins?: Array<{ prefix: string; nodeId: string; role?: NodeRole }>;
322
- autoscale: ClusterAutoscaleConfig;
323
- }
324
-
325
243
  export interface NexusConfig {
326
244
  env: Env;
327
245
  server: ServerConfig;
@@ -341,7 +259,6 @@ export interface NexusConfig {
341
259
  plugins: PluginsConfig;
342
260
  frontend: FrontendConfig;
343
261
  admin: AdminConfig;
344
- cluster: ClusterConfig;
345
262
  }
346
263
 
347
264
  /** Deep partial used for user config files and runtime overrides. */
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './config/index.js';
2
2
  export * from './di/index.js';
3
3
  export * from './errors.js';
4
- export * from './http/index.js';
4
+ export * from './http/index.js';
5
+ export * from './app/index.js';
@@ -19,13 +19,19 @@ describe('configFromEnv', () => {
19
19
  expect(cfg.server?.port).toBe(5000);
20
20
  expect(cfg.server?.https).toBe(true);
21
21
  });
22
+
23
+ it("maps NEXUS_AI_SERVER_URL to { ai: { serverUrl } } (not ai.server.url)", () => {
24
+ const cfg = configFromEnv({ NEXUS_AI_SERVER_URL: 'http://ai:8000' });
25
+ expect((cfg as any).ai?.serverUrl).toBe('http://ai:8000');
26
+ expect((cfg as any).ai?.server?.url).toBeUndefined();
27
+ });
22
28
  });
23
29
 
24
30
  describe('mergeConfig', () => {
25
31
  it('applies user overrides over defaults and validates', () => {
26
32
  const cfg = mergeConfig({ server: { port: 5000 }, auth: { jwt: { secret: 'supersecret' } } });
27
33
  expect(cfg.server.port).toBe(5000);
28
- expect(cfg.server.host).toBe('0.0.0.0'); // default retained
34
+ expect(cfg.server.host).toBe('127.0.0.1'); // default retained
29
35
  expect(cfg.auth.jwt.secret).toBe('supersecret');
30
36
  });
31
37