@bhooai/nexus-core 2.0.12 → 2.0.13
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
CHANGED
|
@@ -36,8 +36,8 @@ import { createLazyDb, registerAdminRoutes, RequestLogBuffer } from './adminModu
|
|
|
36
36
|
import { registerAuthRoutes } from './authModule.js';
|
|
37
37
|
import { issueCsrfToken, getCsrfToken, csrf, authToken, requireRole } from '@bhooai/nexus-auth';
|
|
38
38
|
import { connect } from '@bhooai/nexus-data';
|
|
39
|
-
import { createGateway, createFederatedGateway, graphqlHttpHandler, getExplorerHtml, helloSubgraph } from '@bhooai/nexus-graphql';
|
|
40
|
-
import type { Subgraph } from '@bhooai/nexus-graphql';
|
|
39
|
+
import { createGateway, createFederatedGateway, graphqlHttpHandler, getExplorerHtml, helloSubgraph, SubscriptionServer } from '@bhooai/nexus-graphql';
|
|
40
|
+
import type { Subgraph, GraphQLContext } from '@bhooai/nexus-graphql';
|
|
41
41
|
|
|
42
42
|
export interface CreateNexusAppOptions {
|
|
43
43
|
/** Identifier for this backend, used in admin, telemetry, logs. */
|
|
@@ -59,6 +59,11 @@ export interface CreateNexusAppOptions {
|
|
|
59
59
|
/** Hooks for extending boot. */
|
|
60
60
|
beforeStart?: (app: NexusApp) => Promise<void> | void;
|
|
61
61
|
afterStart?: (app: NexusApp) => Promise<void> | void;
|
|
62
|
+
/**
|
|
63
|
+
* Extra values injected into every GraphQL resolver context (e.g. a payments
|
|
64
|
+
* service). Merged with the default `{ request, user }` context.
|
|
65
|
+
*/
|
|
66
|
+
graphqlContext?: Record<string, unknown>;
|
|
62
67
|
}
|
|
63
68
|
|
|
64
69
|
export interface NexusApp {
|
|
@@ -249,6 +254,8 @@ export async function createNexusApp(opts: CreateNexusAppOptions = {}): Promise<
|
|
|
249
254
|
// GraphQL gateway — builtin hello + discovered subgraphs (federated)
|
|
250
255
|
// ------------------------------------------------------------------
|
|
251
256
|
let graphqlMounted = false;
|
|
257
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
258
|
+
let graphqlGateway: any = null;
|
|
252
259
|
{
|
|
253
260
|
const explorerEnabled = (config.graphql as unknown as { explorer?: boolean })?.explorer ?? true;
|
|
254
261
|
const helloEnabled = (config.graphql as unknown as { hello?: boolean })?.hello !== false;
|
|
@@ -295,6 +302,7 @@ export async function createNexusApp(opts: CreateNexusAppOptions = {}): Promise<
|
|
|
295
302
|
gateway,
|
|
296
303
|
introspection: config.graphql?.introspection ?? true,
|
|
297
304
|
requireMutationCsrf: config.graphql?.requireMutationCsrf ?? true,
|
|
305
|
+
context: (ctx) => ({ request: ctx, user: (ctx.state.user as GraphQLContext['user']), ...opts.graphqlContext }),
|
|
298
306
|
});
|
|
299
307
|
if (explorerEnabled) {
|
|
300
308
|
const explorerHtml = getExplorerHtml({
|
|
@@ -314,6 +322,7 @@ export async function createNexusApp(opts: CreateNexusAppOptions = {}): Promise<
|
|
|
314
322
|
}
|
|
315
323
|
router.add('POST', graphqlPath, handler);
|
|
316
324
|
graphqlMounted = true;
|
|
325
|
+
graphqlGateway = gateway;
|
|
317
326
|
if (subgraphs.length === 1) {
|
|
318
327
|
console.log(`[${name}] GraphQL gateway mounted at ${graphqlPath} (${subgraphs[0]!.name} subgraph${explorerEnabled ? ' + explorer at /graphiql' : ''})`);
|
|
319
328
|
} else {
|
|
@@ -340,7 +349,7 @@ export async function createNexusApp(opts: CreateNexusAppOptions = {}): Promise<
|
|
|
340
349
|
if (helloEnabled) {
|
|
341
350
|
// hello is available even in fallback — route through its gateway
|
|
342
351
|
const gw = createGateway({ subgraph: helloSubgraph });
|
|
343
|
-
const h = graphqlHttpHandler({ gateway: gw, introspection: config.graphql?.introspection ?? true, requireMutationCsrf: config.graphql?.requireMutationCsrf ?? true });
|
|
352
|
+
const h = graphqlHttpHandler({ gateway: gw, introspection: config.graphql?.introspection ?? true, requireMutationCsrf: config.graphql?.requireMutationCsrf ?? true, context: (ctx) => ({ request: ctx, user: (ctx.state.user as GraphQLContext['user']), ...opts.graphqlContext }) });
|
|
344
353
|
return h(ctx);
|
|
345
354
|
}
|
|
346
355
|
ctx.json({ errors: [{ message: 'No GraphQL subgraph found. Add one with: npx nexus make:subgraph <name> (then restart)' }] }, 404);
|
|
@@ -350,7 +359,7 @@ export async function createNexusApp(opts: CreateNexusAppOptions = {}): Promise<
|
|
|
350
359
|
try {
|
|
351
360
|
if (helloEnabled) {
|
|
352
361
|
const gw = createGateway({ subgraph: helloSubgraph });
|
|
353
|
-
router.add('POST', graphqlPath, graphqlHttpHandler({ gateway: gw, introspection: config.graphql?.introspection ?? true, requireMutationCsrf: config.graphql?.requireMutationCsrf ?? true }));
|
|
362
|
+
router.add('POST', graphqlPath, graphqlHttpHandler({ gateway: gw, introspection: config.graphql?.introspection ?? true, requireMutationCsrf: config.graphql?.requireMutationCsrf ?? true, context: (ctx) => ({ request: ctx, user: (ctx.state.user as GraphQLContext['user']), ...opts.graphqlContext }) }));
|
|
354
363
|
} else {
|
|
355
364
|
router.add('POST', graphqlPath, async (ctx) => ctx.json({ errors: [{ message: 'No GraphQL subgraph found. Add one with: npx nexus make:subgraph <name>' }] }, 404));
|
|
356
365
|
}
|
|
@@ -526,6 +535,21 @@ export async function createNexusApp(opts: CreateNexusAppOptions = {}): Promise<
|
|
|
526
535
|
if ((e as any)?.code !== 'ERR_MODULE_NOT_FOUND') console.warn('[realtime] ws init failed:', e);
|
|
527
536
|
}
|
|
528
537
|
|
|
538
|
+
// ------------------------------------------------------------------
|
|
539
|
+
// GraphQL subscriptions over WebSocket — mount when a gateway exists
|
|
540
|
+
// and config.graphql.subscriptions is enabled.
|
|
541
|
+
// ------------------------------------------------------------------
|
|
542
|
+
const graphqlSubscriptions = (config.graphql as unknown as { subscriptions?: boolean })?.subscriptions ?? true;
|
|
543
|
+
if (graphqlGateway && graphqlSubscriptions && typeof graphqlGateway.subscribe === 'function') {
|
|
544
|
+
try {
|
|
545
|
+
const subPath = `${config.graphql?.path ?? '/graphql'}/ws`;
|
|
546
|
+
new SubscriptionServer({ httpServer: server.httpServer, gateway: graphqlGateway, path: subPath });
|
|
547
|
+
console.log(`[${name}] GraphQL subscriptions mounted at ${subPath}`);
|
|
548
|
+
} catch (err) {
|
|
549
|
+
console.warn(`[${name}] failed to mount GraphQL subscriptions:`, err);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
529
553
|
// Public uploads: serve GET /uploads/* from storage/uploads (file-storage example).
|
|
530
554
|
// This was missing — 404 `No route for GET /uploads/media/...`.
|
|
531
555
|
server.use(serveStatic(join(projectRoot, 'storage', 'uploads'), { prefix: '/uploads' }));
|
|
@@ -74,5 +74,11 @@ export async function mergeRuntimeJson(projectRoot: string, patch: Record<string
|
|
|
74
74
|
if (patch.ai && typeof patch.ai === 'object' && existing.ai && typeof existing.ai === 'object') {
|
|
75
75
|
merged.ai = { ...(existing as { ai: Record<string, unknown> }).ai, ...(patch.ai as Record<string, unknown>) };
|
|
76
76
|
}
|
|
77
|
+
// Deep-merge `payments` so toggling one provider's enabled/sandbox state does
|
|
78
|
+
// NOT wipe the other providers' persisted state (shallow merge would replace
|
|
79
|
+
// the whole payments object).
|
|
80
|
+
if (patch.payments && typeof patch.payments === 'object' && existing.payments && typeof existing.payments === 'object') {
|
|
81
|
+
merged.payments = { ...(existing as { payments: Record<string, unknown> }).payments, ...(patch.payments as Record<string, unknown>) };
|
|
82
|
+
}
|
|
77
83
|
await writeRuntimeJson(projectRoot, stripRedactionMask(merged) as Record<string, unknown>);
|
|
78
84
|
}
|