@bhooai/nexus-core 0.1.0 → 0.1.5
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/package.json +4 -2
- package/src/config/defaults.ts +1 -1
- package/src/config/schema.ts +6 -0
- package/src/config/types.ts +10 -0
- package/src/http/Server.ts +1 -1
- package/src/http/uploads.ts +11 -2
package/package.json
CHANGED
package/src/config/defaults.ts
CHANGED
package/src/config/schema.ts
CHANGED
|
@@ -155,6 +155,7 @@ export const nexusConfigSchema = z.object({
|
|
|
155
155
|
}),
|
|
156
156
|
cluster: z.object({
|
|
157
157
|
enabled: z.boolean(),
|
|
158
|
+
role: z.enum(['backend', 'files', 'database', 'ai']).optional(),
|
|
158
159
|
failOpenSingleNode: z.boolean(),
|
|
159
160
|
lbHost: z.string().min(1),
|
|
160
161
|
lbPort: z.number().int().min(1).max(65535),
|
|
@@ -162,6 +163,11 @@ export const nexusConfigSchema = z.object({
|
|
|
162
163
|
nodeAgentPort: z.number().int().min(1).max(65535),
|
|
163
164
|
registryFile: z.string().min(1),
|
|
164
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(),
|
|
165
171
|
autoscale: z.object({
|
|
166
172
|
enabled: z.boolean(),
|
|
167
173
|
mode: z.enum(['auto', 'manual']),
|
package/src/config/types.ts
CHANGED
|
@@ -295,6 +295,10 @@ export interface ClusterAutoscaleConfig {
|
|
|
295
295
|
export interface ClusterConfig {
|
|
296
296
|
/** Turn the cluster (registry + LB + autoscaler) on. */
|
|
297
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;
|
|
298
302
|
/** When false, the LB fails open to a single direct node (no scaling). */
|
|
299
303
|
failOpenSingleNode: boolean;
|
|
300
304
|
/** Bind address of the central cluster's public load balancer. */
|
|
@@ -309,6 +313,12 @@ export interface ClusterConfig {
|
|
|
309
313
|
registryFile: string;
|
|
310
314
|
/** Shared pairing secret minted at node setup; pasted into the central. */
|
|
311
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 }>;
|
|
312
322
|
autoscale: ClusterAutoscaleConfig;
|
|
313
323
|
}
|
|
314
324
|
|
package/src/http/Server.ts
CHANGED
package/src/http/uploads.ts
CHANGED
|
@@ -41,7 +41,11 @@ export function registerUploadRoutes(router: Router, options: UploadRouteOptions
|
|
|
41
41
|
const allowedTypes = new Set(options.allowedTypes ?? []);
|
|
42
42
|
const directory = resolve(options.directory);
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
// The upload handler accepts both the configured path (`/uploads`) and any
|
|
45
|
+
// sub-path (`/uploads/images`, `/uploads/files`, ...) so the cluster's
|
|
46
|
+
// upload-path allocation can route different prefixes to different file nodes
|
|
47
|
+
// while every node still serves the same multipart endpoint.
|
|
48
|
+
const handler = async (ctx: import('./index.js').RequestContext) => {
|
|
45
49
|
const files = (ctx.state.files as UploadedFile[] | undefined) ?? [];
|
|
46
50
|
if (files.length === 0) throw new ValidationError('At least one file is required');
|
|
47
51
|
if (files.length > maxFiles) throw new ValidationError(`A maximum of ${maxFiles} files may be uploaded`);
|
|
@@ -71,7 +75,12 @@ export function registerUploadRoutes(router: Router, options: UploadRouteOptions
|
|
|
71
75
|
});
|
|
72
76
|
}
|
|
73
77
|
ctx.json({ files: saved }, 201);
|
|
74
|
-
}
|
|
78
|
+
};
|
|
79
|
+
const mw = options.middleware ?? [];
|
|
80
|
+
router.post(path, handler, mw);
|
|
81
|
+
// Also accept sub-paths (/uploads/images, /uploads/files, ...) so upload-path
|
|
82
|
+
// allocation in the cluster LB can target specific file nodes by prefix.
|
|
83
|
+
router.post(`${path}/*`, handler, mw);
|
|
75
84
|
}
|
|
76
85
|
|
|
77
86
|
function safeExtension(filename: string): string {
|