nitro-graphql 1.6.1 → 1.7.0-beta.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.
@@ -0,0 +1,59 @@
1
+ //#region src/templates/subscribe-client.ts
2
+ /**
3
+ * Template for GraphQL subscription client config
4
+ * Small config file that users can customize (like ofetch.ts pattern)
5
+ */
6
+ const subscribeClientTemplate = `// This file is auto-generated once by nitro-graphql for quick start
7
+ // You can modify this file to customize the subscription client
8
+ // The implementation comes from nitro-graphql/subscribe - you'll get updates automatically!
9
+ //
10
+ // Usage Examples:
11
+ // ---------------
12
+ // Basic subscription:
13
+ // subscriptionClient.subscribe(query, variables, onData, onError)
14
+ //
15
+ // Multiplexed subscriptions (shared connection):
16
+ // const session = subscriptionClient.createSession()
17
+ // session.subscribe(query1, vars1, onData1)
18
+ // session.subscribe(query2, vars2, onData2)
19
+ //
20
+ // With authentication:
21
+ // Edit connectionParams below to add auth headers
22
+
23
+ import type {
24
+ ConnectionState,
25
+ SubscriptionClient,
26
+ SubscriptionClientConfig,
27
+ SubscriptionHandle,
28
+ SubscriptionSession,
29
+ } from 'nitro-graphql/subscribe'
30
+ import { createSubscriptionClient } from 'nitro-graphql/subscribe'
31
+
32
+ // Re-export types for convenience
33
+ export type { ConnectionState, SubscriptionClient, SubscriptionHandle, SubscriptionSession }
34
+
35
+ // Configure the subscription client
36
+ // Customize these settings according to your needs
37
+ const config: SubscriptionClientConfig = {
38
+ // WebSocket endpoint (default: '/api/graphql/ws')
39
+ wsEndpoint: '/api/graphql/ws',
40
+
41
+ // Connection timeout in ms (default: 10000)
42
+ connectionTimeoutMs: 10000,
43
+
44
+ // Max reconnection attempts (default: 5)
45
+ maxRetries: 5,
46
+
47
+ // Authentication params sent with connection_init
48
+ // Can be a function for dynamic values (e.g., JWT tokens)
49
+ // connectionParams: () => ({
50
+ // authorization: \`Bearer \${getToken()}\`,
51
+ // }),
52
+ }
53
+
54
+ // Export configured client instance
55
+ export const subscriptionClient = createSubscriptionClient(config)
56
+ `;
57
+
58
+ //#endregion
59
+ export { subscribeClientTemplate };
@@ -187,12 +187,27 @@ interface PathsConfig {
187
187
  /** Types directory (default: '{buildDir}/types') */
188
188
  typesDir?: string;
189
189
  }
190
+ /**
191
+ * WebSocket subscriptions configuration
192
+ */
193
+ interface SubscriptionsConfig {
194
+ /** Enable WebSocket subscriptions support */
195
+ enabled?: boolean;
196
+ /** WebSocket endpoint path (default: '/api/graphql/ws') */
197
+ endpoint?: string;
198
+ /** WebSocket protocol (currently only graphql-ws is supported) */
199
+ protocol?: 'graphql-ws';
200
+ }
190
201
  interface NitroGraphQLOptions {
191
202
  framework: 'graphql-yoga' | 'apollo-server';
192
203
  endpoint?: {
193
204
  graphql?: string;
194
205
  healthCheck?: string;
206
+ /** WebSocket endpoint path (default: '/api/graphql/ws') */
207
+ ws?: string;
195
208
  };
209
+ /** WebSocket subscriptions configuration */
210
+ subscriptions?: SubscriptionsConfig;
196
211
  playground?: boolean;
197
212
  typedefs?: string[];
198
213
  resolvers?: Array<IResolvers<any, any>>;
@@ -243,4 +258,4 @@ interface NitroGraphQLOptions {
243
258
  paths?: PathsConfig;
244
259
  }
245
260
  //#endregion
246
- export { ClientUtilsConfig, CodegenClientConfig, CodegenServerConfig, ExternalGraphQLService, ExternalServicePaths, FederationConfig, FileGenerationConfig, GenImport, GenericSdkConfig, NitroGraphQLOptions, PathsConfig, ScaffoldConfig, SdkConfig, TypesConfig };
261
+ export { ClientUtilsConfig, CodegenClientConfig, CodegenServerConfig, ExternalGraphQLService, ExternalServicePaths, FederationConfig, FileGenerationConfig, GenImport, GenericSdkConfig, NitroGraphQLOptions, PathsConfig, ScaffoldConfig, SdkConfig, SubscriptionsConfig, TypesConfig };
@@ -1,5 +1,5 @@
1
- import { ApolloServer, BaseContext, ContextFunction } from "@apollo/server";
2
1
  import { EventHandler, H3Event } from "h3";
2
+ import { ApolloServer, BaseContext, ContextFunction } from "@apollo/server";
3
3
  import { WithRequired } from "@apollo/utils.withrequired";
4
4
  import { Hooks } from "crossws";
5
5
 
@@ -1,5 +1,5 @@
1
- import { HeaderMap } from "@apollo/server";
2
1
  import { eventHandler, getHeaders, isMethod, readBody, setHeaders } from "h3";
2
+ import { HeaderMap } from "@apollo/server";
3
3
 
4
4
  //#region src/utils/apollo.ts
5
5
  function startServerAndCreateH3Handler(server, options) {
@@ -23,7 +23,7 @@ declare function loadExternalSchema(service: ExternalGraphQLService, buildDir?:
23
23
  */
24
24
  declare function downloadAndSaveSchema(service: ExternalGraphQLService, buildDir: string): Promise<string | undefined>;
25
25
  declare function loadGraphQLDocuments(patterns: string | string[]): Promise<Source[]>;
26
- declare function generateClientTypes(schema: GraphQLSchema, docs: Source[], config?: CodegenClientConfig, sdkConfig?: GenericSdkConfig, outputPath?: string, serviceName?: string, virtualTypesPath?: string): Promise<false | {
26
+ declare function generateClientTypes(schema: GraphQLSchema, docs: Source[], config?: CodegenClientConfig, sdkConfig?: GenericSdkConfig, outputPath?: string, serviceName?: string, virtualTypesPath?: string, subscriptionsEnabled?: boolean): Promise<false | {
27
27
  types: string;
28
28
  sdk: string;
29
29
  }>;
@@ -34,5 +34,19 @@ declare function generateExternalClientTypes(service: ExternalGraphQLService, sc
34
34
  types: string;
35
35
  sdk: string;
36
36
  } | false>;
37
+ /**
38
+ * Extract subscription info from documents
39
+ */
40
+ interface SubscriptionInfo {
41
+ name: string;
42
+ methodName: string;
43
+ fieldName: string;
44
+ hasVariables: boolean;
45
+ }
46
+ declare function extractSubscriptions(docs: Source[]): SubscriptionInfo[];
47
+ /**
48
+ * Generate subscription builder code (Drizzle-style API) + Vue Composables
49
+ */
50
+ declare function generateSubscriptionBuilder(docs: Source[], subscriptionsEnabled: boolean): string;
37
51
  //#endregion
38
- export { GraphQLLoadSchemaOptions, GraphQLTypeDefPointer, downloadAndSaveSchema, generateClientTypes, generateExternalClientTypes, graphQLLoadSchemaSync, loadExternalSchema, loadGraphQLDocuments };
52
+ export { GraphQLLoadSchemaOptions, GraphQLTypeDefPointer, SubscriptionInfo, downloadAndSaveSchema, extractSubscriptions, generateClientTypes, generateExternalClientTypes, generateSubscriptionBuilder, graphQLLoadSchemaSync, loadExternalSchema, loadGraphQLDocuments };
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { consola as consola$1 } from "consola";
3
3
  import { defu as defu$1 } from "defu";
4
4
  import { dirname, resolve } from "pathe";
5
- import { parse } from "graphql";
5
+ import { Kind, parse } from "graphql";
6
6
  import { printSchemaWithDirectives } from "@graphql-tools/utils";
7
7
  import { createHash } from "node:crypto";
8
8
  import { codegen } from "@graphql-codegen/core";
@@ -165,7 +165,7 @@ async function loadGraphQLDocuments(patterns) {
165
165
  else throw e;
166
166
  }
167
167
  }
168
- async function generateClientTypes(schema, docs, config = {}, sdkConfig = {}, outputPath, serviceName, virtualTypesPath) {
168
+ async function generateClientTypes(schema, docs, config = {}, sdkConfig = {}, outputPath, serviceName, virtualTypesPath, subscriptionsEnabled) {
169
169
  if (docs.length === 0 && !serviceName) {
170
170
  consola$1.info("No client GraphQL files found. Skipping client type generation.");
171
171
  return false;
@@ -265,14 +265,16 @@ export function getSdk(requester: Requester): Sdk {
265
265
  typescriptGenericSdk: { plugin: plugin$1 }
266
266
  }
267
267
  });
268
+ const sdkContent = (await Promise.all(sdkOutput.map(async (config$1) => {
269
+ return {
270
+ file: config$1.filename,
271
+ content: await codegen(config$1)
272
+ };
273
+ })))[0]?.content || "";
274
+ const subscriptionBuilder = generateSubscriptionBuilder(docs, subscriptionsEnabled ?? false);
268
275
  return {
269
276
  types: output,
270
- sdk: (await Promise.all(sdkOutput.map(async (config$1) => {
271
- return {
272
- file: config$1.filename,
273
- content: await codegen(config$1)
274
- };
275
- })))[0]?.content || ""
277
+ sdk: subscriptionBuilder ? sdkContent + subscriptionBuilder : sdkContent
276
278
  };
277
279
  } catch (error) {
278
280
  consola$1.warn(`[graphql${serviceLabel}] Client type generation failed:`, error);
@@ -283,8 +285,406 @@ export function getSdk(requester: Requester): Sdk {
283
285
  * Generate client types for external GraphQL service
284
286
  */
285
287
  async function generateExternalClientTypes(service, schema, docs, virtualTypesPath) {
286
- return generateClientTypes(schema, docs, service.codegen?.client || {}, service.codegen?.clientSDK || {}, void 0, service.name, virtualTypesPath);
288
+ return generateClientTypes(schema, docs, service.codegen?.client || {}, service.codegen?.clientSDK || {}, void 0, service.name, virtualTypesPath, false);
289
+ }
290
+ /**
291
+ * Convert PascalCase to camelCase
292
+ */
293
+ function toCamelCase(str) {
294
+ return str.charAt(0).toLowerCase() + str.slice(1);
295
+ }
296
+ function extractSubscriptions(docs) {
297
+ const subscriptions = [];
298
+ for (const doc of docs) {
299
+ if (!doc.document) continue;
300
+ for (const def of doc.document.definitions) if (def.kind === Kind.OPERATION_DEFINITION && def.operation === "subscription") {
301
+ const operationDef = def;
302
+ const name = operationDef.name?.value;
303
+ if (!name) continue;
304
+ const firstSelection = operationDef.selectionSet.selections[0];
305
+ if (firstSelection.kind !== Kind.FIELD) continue;
306
+ const fieldName = firstSelection.name.value;
307
+ const hasVariables = (operationDef.variableDefinitions?.length || 0) > 0;
308
+ subscriptions.push({
309
+ name,
310
+ methodName: toCamelCase(name),
311
+ fieldName,
312
+ hasVariables
313
+ });
314
+ }
315
+ }
316
+ return subscriptions;
317
+ }
318
+ /**
319
+ * Generate subscription builder code (Drizzle-style API) + Vue Composables
320
+ */
321
+ function generateSubscriptionBuilder(docs, subscriptionsEnabled) {
322
+ if (!subscriptionsEnabled) return "";
323
+ const subscriptions = extractSubscriptions(docs);
324
+ if (subscriptions.length === 0) return "";
325
+ let output = `
326
+ // === Subscription Imports ===
327
+ import { ref, onUnmounted, computed } from 'vue'
328
+ import type { Ref } from 'vue'
329
+ import type {
330
+ ConnectionState,
331
+ SubscriptionHandle,
332
+ SubscriptionSession,
333
+ SubscriptionTransport,
334
+ TransportOptions,
335
+ } from 'nitro-graphql/subscribe'
336
+ import { subscriptionClient } from './subscribe'
337
+
338
+ // === Subscription Types ===
339
+ export type { ConnectionState, SubscriptionHandle, SubscriptionSession, SubscriptionTransport, TransportOptions }
340
+
341
+ // Forward declaration for UseSubscriptionSessionReturn (defined below)
342
+ export interface UseSubscriptionSessionReturn {
343
+ /** The underlying session object */
344
+ session: SubscriptionSession
345
+ /** Subscribe using the shared session (updates reactive refs) */
346
+ subscribe: <TData = unknown>(
347
+ query: string,
348
+ variables: unknown,
349
+ onData?: (data: TData) => void,
350
+ onError?: (error: Error) => void,
351
+ ) => SubscriptionHandle
352
+ /** Close all subscriptions and the connection */
353
+ close: () => void
354
+ /** Is the session connected (reactive) */
355
+ isConnected: Ref<boolean>
356
+ /** Current connection state (reactive) */
357
+ state: Ref<ConnectionState>
358
+ /** Number of active subscriptions (reactive) */
359
+ subscriptionCount: Ref<number>
360
+ }
361
+
362
+ export interface UseSubscriptionOptions<T> {
363
+ /** Auto-start subscription on mount (default: false) */
364
+ immediate?: boolean
365
+ /** Callback when subscription starts */
366
+ onStart?: () => void
367
+ /** Callback when subscription stops */
368
+ onStop?: () => void
369
+ /** Callback when data is received */
370
+ onData?: (data: T) => void
371
+ /** Callback when error occurs */
372
+ onError?: (error: Error) => void
373
+ /** Callback when WebSocket connects */
374
+ onConnected?: () => void
375
+ /** Callback when WebSocket reconnects */
376
+ onReconnected?: () => void
377
+ /** Callback when WebSocket disconnects */
378
+ onDisconnected?: () => void
379
+ /** Callback when connection state changes */
380
+ onStateChange?: (state: ConnectionState) => void
381
+ /** Use existing session for multiplexing (pass result from useSubscriptionSession) */
382
+ session?: UseSubscriptionSessionReturn
383
+ /** Use SSE transport instead of WebSocket (shorthand for transport: 'sse') */
384
+ sse?: boolean
385
+ /** Transport type: 'websocket' (default), 'sse', or 'auto' (WS first, SSE fallback) */
386
+ transport?: SubscriptionTransport
387
+ }
388
+
389
+ export interface UseSubscriptionReturn<T> {
390
+ /** Reactive subscription data */
391
+ data: Ref<T | null>
392
+ /** Reactive error state */
393
+ error: Ref<Error | null>
394
+ /** Is subscription active */
395
+ isActive: Ref<boolean>
396
+ /** Connection state */
397
+ state: Ref<ConnectionState>
398
+ /** Active transport type ('websocket' | 'sse') */
399
+ transport: Ref<'websocket' | 'sse'>
400
+ /** Start subscription */
401
+ start: () => void
402
+ /** Stop subscription */
403
+ stop: () => void
404
+ /** Restart subscription */
405
+ restart: () => void
406
+ }
407
+
408
+ // === Subscription Builder (Drizzle-style API) ===
409
+ interface SubscriptionBuilder<TData> {
410
+ onData(fn: (data: TData) => void): SubscriptionBuilder<TData>
411
+ onError(fn: (error: Error) => void): SubscriptionBuilder<TData>
412
+ start(): SubscriptionHandle
413
+ subscribe(fn: (data: TData) => void): SubscriptionHandle
414
+ }
415
+
416
+ function createSubscriptionBuilder<TData>(query: string, variables: unknown): SubscriptionBuilder<TData> {
417
+ let onDataFn: ((data: TData) => void) | undefined
418
+ let onErrorFn: ((error: Error) => void) | undefined
419
+
420
+ const builder: SubscriptionBuilder<TData> = {
421
+ onData(fn: (data: TData) => void) {
422
+ onDataFn = fn
423
+ return builder
424
+ },
425
+ onError(fn: (error: Error) => void) {
426
+ onErrorFn = fn
427
+ return builder
428
+ },
429
+ start(): SubscriptionHandle {
430
+ return subscriptionClient.subscribe(query, variables, onDataFn, onErrorFn)
431
+ },
432
+ subscribe(fn: (data: TData) => void): SubscriptionHandle {
433
+ return subscriptionClient.subscribe(query, variables, fn, undefined)
434
+ },
435
+ }
436
+
437
+ return builder
438
+ }
439
+
440
+ export const subscription = {
441
+ `;
442
+ for (const sub of subscriptions) if (sub.hasVariables) output += ` ${sub.methodName}(variables: Types.${sub.name}SubscriptionVariables): SubscriptionBuilder<Types.${sub.name}Subscription['${sub.fieldName}']> {
443
+ return createSubscriptionBuilder<Types.${sub.name}Subscription['${sub.fieldName}']>(${sub.name}Document, variables)
444
+ },
445
+ `;
446
+ else output += ` ${sub.methodName}(): SubscriptionBuilder<Types.${sub.name}Subscription['${sub.fieldName}']> {
447
+ return createSubscriptionBuilder<Types.${sub.name}Subscription['${sub.fieldName}']>(${sub.name}Document, undefined)
448
+ },
449
+ `;
450
+ output += `}
451
+
452
+ // === Framework-Agnostic Session (for non-Vue usage) ===
453
+ /**
454
+ * Create a multiplexed subscription session (framework-agnostic)
455
+ * All subscriptions share a single WebSocket connection.
456
+ *
457
+ * @example
458
+ * // Vanilla JS / Node.js / React / etc.
459
+ * const session = createSubscriptionSession()
460
+ * const sub1 = session.subscribe(query1, vars1, onData1)
461
+ * const sub2 = session.subscribe(query2, vars2, onData2)
462
+ * // Both use the same WebSocket connection
463
+ * sub1.unsubscribe()
464
+ * session.close() // Close all
465
+ *
466
+ * @returns SubscriptionSession - Framework-agnostic session object
467
+ */
468
+ export function createSubscriptionSession(): SubscriptionSession {
469
+ return subscriptionClient.createSession()
470
+ }
471
+
472
+ // === Vue Composable: useSubscriptionSession (Multiplexing) ===
473
+ export interface UseSubscriptionSessionReturn {
474
+ /** The underlying session object */
475
+ session: SubscriptionSession
476
+ /** Subscribe using the shared session */
477
+ subscribe: <TData = unknown>(
478
+ query: string,
479
+ variables: unknown,
480
+ onData?: (data: TData) => void,
481
+ onError?: (error: Error) => void,
482
+ ) => SubscriptionHandle
483
+ /** Close all subscriptions and the connection */
484
+ close: () => void
485
+ /** Is the session connected (reactive) */
486
+ isConnected: Ref<boolean>
487
+ /** Current connection state (reactive) */
488
+ state: Ref<ConnectionState>
489
+ /** Number of active subscriptions (reactive) */
490
+ subscriptionCount: Ref<number>
491
+ }
492
+
493
+ /**
494
+ * Vue composable for multiplexed subscription session
495
+ * Provides reactive state and automatic cleanup on unmount.
496
+ *
497
+ * @example
498
+ * // Vue 3 component
499
+ * const session = useSubscriptionSession()
500
+ * const { data } = useCountdown({ from: 10 }, { session })
501
+ * // Session auto-closes on component unmount
502
+ *
503
+ * @returns UseSubscriptionSessionReturn - Vue-reactive session wrapper
504
+ */
505
+ export function useSubscriptionSession(): UseSubscriptionSessionReturn {
506
+ const session = subscriptionClient.createSession()
507
+
508
+ // Use refs for reactivity (session getters are not reactive)
509
+ const isConnected = ref(session.isConnected)
510
+ const state = ref<ConnectionState>(session.state)
511
+ const subscriptionCount = ref(session.subscriptionCount)
512
+
513
+ // Update refs when session state changes
514
+ function updateRefs() {
515
+ isConnected.value = session.isConnected
516
+ state.value = session.state
517
+ subscriptionCount.value = session.subscriptionCount
518
+ }
519
+
520
+ // Subscribe to session state changes for automatic reactivity
521
+ const unsubscribeStateChange = session.onStateChange(() => {
522
+ updateRefs()
523
+ })
524
+
525
+ function subscribe<TData = unknown>(
526
+ query: string,
527
+ variables: unknown,
528
+ onData?: (data: TData) => void,
529
+ onError?: (error: Error) => void,
530
+ ): SubscriptionHandle {
531
+ return session.subscribe(query, variables, onData as (data: unknown) => void, onError)
532
+ }
533
+
534
+ function close() {
535
+ session.close()
536
+ }
537
+
538
+ onUnmounted(() => {
539
+ unsubscribeStateChange()
540
+ close()
541
+ })
542
+
543
+ return {
544
+ session,
545
+ subscribe,
546
+ close,
547
+ isConnected,
548
+ state,
549
+ subscriptionCount,
550
+ }
551
+ }
552
+
553
+ // === Vue Composables ===
554
+ function createUseSubscription<TData, TVariables = undefined>(
555
+ query: string,
556
+ getVariables: () => TVariables,
557
+ ): (options?: UseSubscriptionOptions<TData>) => UseSubscriptionReturn<TData> {
558
+ return (options: UseSubscriptionOptions<TData> = {}): UseSubscriptionReturn<TData> => {
559
+ const data = ref<TData | null>(null) as Ref<TData | null>
560
+ const error = ref<Error | null>(null)
561
+ const isActive = ref(false)
562
+ const state = ref<ConnectionState>('idle')
563
+ const transport = ref<'websocket' | 'sse'>('websocket')
564
+ let handle: SubscriptionHandle | null = null
565
+
566
+ // Resolve transport options
567
+ const transportOptions: TransportOptions = {
568
+ sse: options.sse,
569
+ transport: options.transport,
570
+ }
571
+
572
+ function start() {
573
+ stop()
574
+ isActive.value = true
575
+ error.value = null
576
+ options.onStart?.()
577
+
578
+ const variables = getVariables()
579
+
580
+ if (options.session) {
581
+ // Use existing session for multiplexing (WebSocket only)
582
+ handle = options.session.subscribe<TData>(
583
+ query,
584
+ variables,
585
+ (d: TData) => {
586
+ data.value = d
587
+ options.onData?.(d)
588
+ },
589
+ (e: Error) => {
590
+ error.value = e
591
+ options.onError?.(e)
592
+ },
593
+ )
594
+ transport.value = 'websocket'
595
+ } else {
596
+ // Create dedicated connection with transport selection
597
+ handle = subscriptionClient.subscribe<TData>(
598
+ query,
599
+ variables,
600
+ (d: TData) => {
601
+ data.value = d
602
+ options.onData?.(d)
603
+ },
604
+ (e: Error) => {
605
+ error.value = e
606
+ options.onError?.(e)
607
+ },
608
+ transportOptions,
609
+ )
610
+ // Update transport ref from handle
611
+ transport.value = handle.transport
612
+ }
613
+ }
614
+
615
+ function stop() {
616
+ if (handle) {
617
+ handle.unsubscribe()
618
+ handle = null
619
+ isActive.value = false
620
+ options.onStop?.()
621
+ }
622
+ }
623
+
624
+ function restart() {
625
+ stop()
626
+ start()
627
+ }
628
+
629
+ if (options.immediate) {
630
+ start()
631
+ }
632
+
633
+ onUnmounted(stop)
634
+
635
+ return { data, error, isActive, state, transport, start, stop, restart }
636
+ }
637
+ }
638
+
639
+ // === Subscription Return Types ===
640
+ `;
641
+ for (const sub of subscriptions) {
642
+ const typeName = `Types.${sub.name}Subscription['${sub.fieldName}']`;
643
+ output += `/** Return type for use${sub.name} composable */
644
+ export type Use${sub.name}Return = UseSubscriptionReturn<${typeName}>
645
+ `;
646
+ }
647
+ output += `
648
+ // === Vue Composables ===
649
+ `;
650
+ for (const sub of subscriptions) {
651
+ const typeName = `Types.${sub.name}Subscription['${sub.fieldName}']`;
652
+ const varsType = `Types.${sub.name}SubscriptionVariables`;
653
+ if (sub.hasVariables) output += `/**
654
+ * Vue composable for ${sub.name} subscription
655
+ * @param variables - Subscription variables
656
+ * @param options - Subscription options (immediate, onData, onError, session, etc.)
657
+ * @returns Reactive subscription state: { data, error, isActive, state, start, stop, restart }
658
+ */
659
+ export function use${sub.name}(
660
+ variables: ${varsType},
661
+ options?: UseSubscriptionOptions<${typeName}>,
662
+ ): Use${sub.name}Return {
663
+ return createUseSubscription<${typeName}, ${varsType}>(
664
+ ${sub.name}Document,
665
+ () => variables,
666
+ )(options)
667
+ }
668
+
669
+ `;
670
+ else output += `/**
671
+ * Vue composable for ${sub.name} subscription
672
+ * @param options - Subscription options (immediate, onData, onError, session, etc.)
673
+ * @returns Reactive subscription state: { data, error, isActive, state, start, stop, restart }
674
+ */
675
+ export function use${sub.name}(
676
+ options?: UseSubscriptionOptions<${typeName}>,
677
+ ): Use${sub.name}Return {
678
+ return createUseSubscription<${typeName}, undefined>(
679
+ ${sub.name}Document,
680
+ () => undefined,
681
+ )(options)
682
+ }
683
+
684
+ `;
685
+ }
686
+ return output;
287
687
  }
288
688
 
289
689
  //#endregion
290
- export { downloadAndSaveSchema, generateClientTypes, generateExternalClientTypes, graphQLLoadSchemaSync, loadExternalSchema, loadGraphQLDocuments };
690
+ export { downloadAndSaveSchema, extractSubscriptions, generateClientTypes, generateExternalClientTypes, generateSubscriptionBuilder, graphQLLoadSchemaSync, loadExternalSchema, loadGraphQLDocuments };
@@ -1,6 +1,6 @@
1
1
  import { GraphQLSchema } from "graphql";
2
- import { ApolloServerOptions } from "@apollo/server";
3
2
  import { H3Event } from "h3";
3
+ import { ApolloServerOptions } from "@apollo/server";
4
4
  import { YogaServerOptions } from "graphql-yoga";
5
5
  import { NPMConfig, Resolvers, ResolversTypes } from "#graphql/server";
6
6
  import { StandardSchemaV1 } from "nitro-graphql";
@@ -22,11 +22,15 @@ function generateGraphQLIndexFile(nitro, clientDir, externalServices = []) {
22
22
  let indexContent = `// This file is auto-generated once by nitro-graphql for quick start
23
23
  // You can modify this file according to your needs
24
24
  //
25
- // Export your main GraphQL service (auto-generated)
25
+ // === Query/Mutation Client ===
26
26
  export * from './default/ofetch'
27
27
 
28
- // Export external GraphQL services (auto-generated for existing services)
29
- // When you add new external services, don't forget to add their exports here:
28
+ // === Subscription Composables (Vue) ===
29
+ // Export useCountdown, useGreetings, useSubscriptionSession, etc.
30
+ export * from './default/sdk'
31
+
32
+ // === External GraphQL Services ===
33
+ // When you add new external services, add their exports here:
30
34
  // export * from './yourServiceName/ofetch'
31
35
  `;
32
36
  for (const service of externalServices) indexContent += `export * from './${service.name}/ofetch'\n`;
@@ -280,7 +284,7 @@ async function generateMainClientTypes(nitro) {
280
284
  return;
281
285
  }
282
286
  const graphqlString = readFileSync(schemaFilePath, "utf-8");
283
- const types = await generateClientTypes(nitro.options.graphql?.federation?.enabled === true ? buildSubgraphSchema([{ typeDefs: parse(graphqlString) }]) : buildSchema(graphqlString), loadDocs, nitro.options.graphql?.codegen?.client ?? {}, nitro.options.graphql?.codegen?.clientSDK ?? {});
287
+ const types = await generateClientTypes(nitro.options.graphql?.federation?.enabled === true ? buildSubgraphSchema([{ typeDefs: parse(graphqlString) }]) : buildSchema(graphqlString), loadDocs, nitro.options.graphql?.codegen?.client ?? {}, nitro.options.graphql?.codegen?.clientSDK ?? {}, void 0, void 0, void 0, nitro.options.graphql?.subscriptions?.enabled ?? false);
284
288
  if (types === false) return;
285
289
  const placeholders = getDefaultPaths(nitro);
286
290
  const typesConfig = getTypesConfig(nitro);
@@ -0,0 +1,25 @@
1
+ import { GraphQLSchema } from "graphql";
2
+ import { Peer } from "crossws";
3
+
4
+ //#region src/utils/ws-protocol.d.ts
5
+ declare function devLog(message: string, ...args: any[]): void;
6
+ interface GraphQLWSMessage {
7
+ id?: string;
8
+ type: string;
9
+ payload?: any;
10
+ }
11
+ declare function sendMessage(peer: Peer, message: Record<string, any>): void;
12
+ declare function sendErrorMessage(peer: Peer, id: string | undefined, errors: Array<{
13
+ message: string;
14
+ locations?: any;
15
+ path?: any;
16
+ }>): void;
17
+ declare function sendNextMessage(peer: Peer, id: string, payload: any): void;
18
+ declare function sendCompleteMessage(peer: Peer, id: string): void;
19
+ declare function handleConnectionInit(peer: Peer): void;
20
+ declare function handlePing(peer: Peer): void;
21
+ declare function handleSubscribe(peer: Peer, msg: GraphQLWSMessage, schema: GraphQLSchema, subscriptions: Map<string, AsyncIterator<any>>): Promise<void>;
22
+ declare function handleComplete(msg: GraphQLWSMessage, subscriptions: Map<string, AsyncIterator<any>>): Promise<void>;
23
+ declare function cleanupSubscriptions(subscriptions: Map<string, AsyncIterator<any>>): Promise<void>;
24
+ //#endregion
25
+ export { GraphQLWSMessage, cleanupSubscriptions, devLog, handleComplete, handleConnectionInit, handlePing, handleSubscribe, sendCompleteMessage, sendErrorMessage, sendMessage, sendNextMessage };