@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bhooai/nexus-core",
3
- "version": "2.0.12",
3
+ "version": "2.0.13",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -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
  }