@stonecrop/nuxt-grafserv 0.7.3 → 0.7.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/README.md CHANGED
@@ -3,21 +3,31 @@
3
3
  [![npm version][npm-version-src]][npm-version-href]
4
4
  [![npm downloads][npm-downloads-src]][npm-downloads-href]
5
5
  [![License][license-src]][license-href]
6
- [![Nuxt][nuxt-src]][nuxt-href]
7
6
 
8
7
  Pluggable Grafserv GraphQL server as a Nuxt Module. Uses the Grafast execution engine for high-performance GraphQL.
9
8
 
10
- - [✨  Release Notes](/CHANGELOG.md)
11
-
12
9
  ## Features
13
10
 
14
11
  - 🚀  Grafserv Server Integration
15
- - ⚡️  Grafast Execution Engine (faster than graphql-js)
12
+ - ⚡️  Grafast Execution Engine (faster than [`graphql-js`](https://github.com/graphql/graphql-js))
16
13
  - 🔄  Schema Stitching Support
17
- - 🛠  Middleware Support
14
+ - 🔍  Graphile Preset System for Advanced Configuration
18
15
  - 📝  TypeScript Support
19
- - 🔍  GraphiQL Interface
16
+ - 🔍  GraphiQL/Ruru Interface
20
17
  - ⚡️  Hot Module Reloading
18
+ - 🎯  Separate Route Handlers for GraphQL/UI and Static Assets
19
+
20
+ ## Architecture
21
+
22
+ This module uses modern Grafserv patterns with three key components:
23
+
24
+ 1. **Preset-Based Configuration**: Leverages Graphile's preset system for extensibility and plugin support
25
+ 2. **Separate Route Handlers**: Two dedicated handlers for GraphQL operations/UI and static assets
26
+ 3. **Objects Structure**: Uses Grafast's modern `objects/interfaces/enums` schema building pattern for better type safety
27
+
28
+ The module automatically registers these handlers:
29
+ - `{url}` - Unified GraphQL operations and Ruru UI endpoint
30
+ - `/ruru-static/**` - Static assets for the Ruru IDE
21
31
 
22
32
  ## Quick Setup
23
33
 
@@ -40,48 +50,54 @@ npm install @stonecrop/nuxt-grafserv
40
50
  export default defineNuxtConfig({
41
51
  modules: ['@stonecrop/nuxt-grafserv'],
42
52
  grafserv: {
43
- schema: './server/**/*.graphql',
44
- resolvers: './server/resolvers.ts',
45
- url: '/graphql/',
53
+ schema: 'server/**/*.graphql',
54
+ resolvers: 'server/resolvers.ts',
55
+ url: '/graphql/', // Serves both GraphQL API and Ruru UI
46
56
  }
47
57
  })
48
58
  ```
49
59
 
50
60
  ## Configuration
51
61
 
52
- Here's a full example of all available options:
62
+ ### All Available Options
63
+
64
+ | Option | Type | Default | Description |
65
+ |--------|------|---------|-------------|
66
+ | `schema` | `string \| string[] \| SchemaProvider` | `'server/**/*.graphql'` | Path(s) to GraphQL schema files or schema provider function |
67
+ | `resolvers` | `string` | `'server/resolvers.ts'` | Path to resolvers file |
68
+ | `url` | `string` | `'/graphql/'` | GraphQL endpoint URL (also serves Ruru UI) |
69
+ | `graphiql` | `boolean` | `true` in dev, `false` in prod | Enable GraphiQL IDE |
70
+ | `preset` | `GraphileConfig.Preset` | `undefined` | Custom Graphile preset for advanced configuration |
71
+
72
+ ### Full Configuration Example
53
73
 
54
74
  ```ts
55
75
  export default defineNuxtConfig({
56
76
  modules: ['@stonecrop/nuxt-grafserv'],
57
77
  grafserv: {
58
- // Path to your GraphQL schema files
59
- schema: './server/**/*.graphql',
60
-
61
- // Path to your resolvers
62
- resolvers: './server/resolvers.ts',
63
-
64
- // GraphQL endpoint URL (default: /graphql/)
65
- url: '/graphql/',
66
-
67
- // Enable GraphiQL IDE (default: true in dev, false in prod)
78
+ // Schema configuration
79
+ schema: 'server/**/*.graphql',
80
+ resolvers: 'server/resolvers.ts',
81
+
82
+ // Endpoints
83
+ url: '/graphql/', // Serves both GraphQL API and Ruru UI
68
84
  graphiql: true,
69
-
70
- // Middleware functions
71
- middleware: [
72
- async (ctx, next) => {
73
- const start = Date.now()
74
- const result = await next()
75
- console.log(`Request took ${Date.now() - start}ms`)
76
- return result
85
+
86
+ // Graphile preset with grafserv options
87
+ preset: {
88
+ grafserv: {
89
+ websockets: false,
90
+ graphqlOverGET: true, // Enable GET requests for queries
91
+ maxRequestLength: 100000,
92
+ allowedRequestContentTypes: [
93
+ 'application/json',
94
+ 'application/graphql+json'
95
+ ]
96
+ },
97
+ grafast: {
98
+ explain: true, // Enable plan diagrams
77
99
  }
78
- ],
79
-
80
- // Grafserv-specific options
81
- grafserv: {
82
- websockets: false,
83
- introspection: true
84
- }
100
+ },
85
101
  }
86
102
  })
87
103
  ```
@@ -104,41 +120,277 @@ type Mutation {
104
120
  2. Create your resolvers (`server/resolvers.ts`):
105
121
 
106
122
  ```typescript
107
- export default {
123
+ import { constant, lambda, access, object, filter, type GrafastSchemaConfig } from 'grafast'
124
+
125
+ const resolvers: GrafastSchemaConfig['objects'] = {
108
126
  Query: {
109
- hello: () => 'world',
110
- ping: () => true
127
+ plans: {
128
+ hello: () => constant('world'),
129
+ ping: () => constant(true)
130
+ }
111
131
  },
112
132
  Mutation: {
113
- echo: (_: unknown, { message }: { message: string }) => message
133
+ plans: {
134
+ echo: (_source, fieldArgs) => {
135
+ const { $message } = fieldArgs
136
+ return $message
137
+ }
138
+ }
114
139
  }
115
140
  }
141
+
142
+ export default resolvers
116
143
  ```
117
144
 
118
- ## Middleware
145
+ ## Advanced Usage
146
+
147
+ ### Middleware via Grafserv Plugins
119
148
 
120
- Add middleware functions to process requests:
149
+ For middleware functionality (authentication, logging, etc.), use Grafserv plugins. The CLI installer creates a `server/plugins.ts` file with examples.
150
+
151
+ #### Inline Plugin Configuration
121
152
 
122
153
  ```ts
123
- grafserv: {
124
- middleware: [
125
- // Logging middleware
126
- async (ctx, next) => {
127
- const start = Date.now()
128
- const result = await next()
129
- console.log(`Request took ${Date.now() - start}ms`)
130
- return result
131
- },
132
- // Authentication middleware
133
- async (ctx, next) => {
134
- const token = ctx.req.headers.get('authorization')
135
- if (!token) throw new Error('Unauthorized')
136
- return next()
154
+ export default defineNuxtConfig({
155
+ grafserv: {
156
+ preset: {
157
+ plugins: [
158
+ {
159
+ name: 'request-logging',
160
+ version: '1.0.0',
161
+ grafserv: {
162
+ middleware: {
163
+ processGraphQLRequestBody: async (next, event) => {
164
+ const start = Date.now()
165
+ console.log('[GraphQL] Request started')
166
+
167
+ const result = await next()
168
+
169
+ console.log(`[GraphQL] Completed in ${Date.now() - start}ms`)
170
+ return result
171
+ }
172
+ }
173
+ }
174
+ }
175
+ ]
137
176
  }
138
- ]
177
+ }
178
+ })
179
+ ```
180
+
181
+ #### Using External Plugin File
182
+
183
+ ```ts
184
+ // nuxt.config.ts
185
+ import plugins from './server/plugins'
186
+
187
+ export default defineNuxtConfig({
188
+ grafserv: {
189
+ schema: 'server/schema.graphql',
190
+ resolvers: 'server/resolvers.ts',
191
+ preset: {
192
+ plugins
193
+ }
194
+ }
195
+ })
196
+ ```
197
+
198
+ ```ts
199
+ // server/plugins.ts
200
+ import type { GraphileConfig } from 'graphile-config'
201
+
202
+ const loggingPlugin: GraphileConfig.Plugin = {
203
+ name: 'request-logging',
204
+ version: '1.0.0',
205
+ grafserv: {
206
+ middleware: {
207
+ processGraphQLRequestBody: async (next, event) => {
208
+ console.log('Processing:', event.request.url)
209
+ return next()
210
+ }
211
+ }
212
+ }
213
+ }
214
+
215
+ const authPlugin: GraphileConfig.Plugin = {
216
+ name: 'authentication',
217
+ version: '1.0.0',
218
+ grafserv: {
219
+ middleware: {
220
+ processGraphQLRequestBody: async (next, event) => {
221
+ const token = event.request.headers.get('authorization')
222
+ // TODO: Validate token and add user to context
223
+ return next()
224
+ }
225
+ }
226
+ }
139
227
  }
228
+
229
+ export default [loggingPlugin, authPlugin]
140
230
  ```
141
231
 
232
+ #### Available Middleware Hooks
233
+
234
+ - `processRequest` - Process all incoming requests
235
+ - `processGraphQLRequestBody` - Process GraphQL request bodies
236
+ - `ruruHTML` - Customize Ruru IDE HTML generation
237
+ - `onSubscribe` - Handle GraphQL subscriptions
238
+
239
+ ## Advanced Usage
240
+
241
+ ### Custom Graphile Preset
242
+
243
+ Leverage the full power of the Graphile ecosystem with custom presets:
244
+
245
+ ```ts
246
+ import { PostGraphileAmberPreset } from 'postgraphile/presets/amber'
247
+
248
+ export default defineNuxtConfig({
249
+ grafserv: {
250
+ preset: {
251
+ extends: [PostGraphileAmberPreset],
252
+ grafast: {
253
+ explain: true, // Enable plan diagrams for debugging
254
+ context: {
255
+ // Custom context available in all resolvers
256
+ apiVersion: '1.0'
257
+ }
258
+ },
259
+ grafserv: {
260
+ graphqlOverGET: true,
261
+ maxRequestLength: 200000
262
+ }
263
+ }
264
+ }
265
+ })
266
+ ```
267
+
268
+ ### Accessing Grafserv Instance
269
+
270
+ For advanced use cases, you can access the grafserv instance directly:
271
+
272
+ ```ts
273
+ // server/api/custom.ts
274
+ import { getGrafservInstance } from '@stonecrop/nuxt-grafserv/runtime/handler'
275
+ import { useRuntimeConfig } from '#imports'
276
+
277
+ export default defineEventHandler(async (event) => {
278
+ const config = useRuntimeConfig()
279
+ const serv = await getGrafservInstance(config.grafserv)
280
+
281
+ // Access grafserv methods or configuration
282
+ const preset = serv.getPreset()
283
+
284
+ return { preset }
285
+ })
286
+ ```
287
+
288
+ ### Schema Building with Objects Structure
289
+
290
+ The module uses Grafast's modern objects structure for better type safety and performance. Leverage Grafast's standard steps for common operations:
291
+
292
+ ```typescript
293
+ import { constant, lambda, access, object, filter, type GrafastSchemaConfig } from 'grafast'
294
+
295
+ const resolvers: GrafastSchemaConfig['objects'] = {
296
+ Query: {
297
+ plans: {
298
+ // Static values use constant()
299
+ hello: () => constant('world'),
300
+
301
+ // Arguments accessed via fieldArgs with $ prefix
302
+ user: (_source, fieldArgs) => {
303
+ const { $id } = fieldArgs
304
+ return lambda($id, (id) => getUserById(id))
305
+ },
306
+
307
+ // Use filter() for list filtering
308
+ userOrders: (_source, fieldArgs) => {
309
+ const { $userId } = fieldArgs
310
+ const $allOrders = constant(getAllOrders())
311
+ return filter($allOrders, $order =>
312
+ lambda([access($order, 'userId'), $userId],
313
+ ([orderUserId, userId]) => orderUserId === userId
314
+ )
315
+ )
316
+ }
317
+ }
318
+ },
319
+
320
+ Mutation: {
321
+ plans: {
322
+ // Use object() to compose objects from steps
323
+ createUser: (_source, fieldArgs) => {
324
+ const { $name, $email } = fieldArgs
325
+ const $id = constant(generateId())
326
+ const $now = constant(new Date().toISOString())
327
+
328
+ const $user = object({
329
+ id: $id,
330
+ name: $name,
331
+ email: $email,
332
+ role: constant('user'),
333
+ createdAt: $now,
334
+ updatedAt: $now
335
+ })
336
+
337
+ return lambda($user, user => {
338
+ saveUser(user)
339
+ return user
340
+ })
341
+ }
342
+ }
343
+ },
344
+
345
+ // Field resolvers for types
346
+ User: {
347
+ plans: {
348
+ fullName: ($user) => {
349
+ return lambda($user, (user) => {
350
+ const typed = user as { firstName: string; lastName: string }
351
+ return `${typed.firstName} ${typed.lastName}`
352
+ })
353
+ }
354
+ }
355
+ },
356
+
357
+ // Related type resolvers
358
+ Order: {
359
+ plans: {
360
+ // Use access() to extract properties before lookups
361
+ user: ($order) => {
362
+ const $userId = access($order, 'userId')
363
+ return lambda($userId, userId => getUserById(userId as string) ?? null)
364
+ }
365
+ }
366
+ }
367
+ }
368
+
369
+ export default resolvers
370
+ ```
371
+
372
+ **Key Concepts:**
373
+ - All resolvers return **steps** (constant, lambda, access, object, filter, etc.), not plain values
374
+ - Arguments are accessed via `fieldArgs.$argumentName` (note the `$` prefix)
375
+ - Use `constant()` for static values
376
+ - Use `lambda()` to transform step values at execution time
377
+ - Use `access()` to extract object properties (more efficient than lambda for simple property access)
378
+ - Use `object()` to compose objects from multiple steps
379
+ - Use `filter()` for list filtering operations
380
+ - Source objects (`$source`, `$user`, `$order`, etc.) are also steps that need `lambda()` or `access()` to work with their properties
381
+
382
+ **Standard Steps Reference:**
383
+ - `constant()` - Create a step from a static value
384
+ - `lambda()` - Transform step values with execution-time callbacks
385
+ - `access()` - Extract properties from object steps
386
+ - `object()` - Compose objects from multiple steps
387
+ - `filter()` - Filter list steps based on conditions
388
+ - `list()` - Create lists from step tuples
389
+ - `first()`/`last()` - Get first/last elements from lists
390
+ - `loadOne()`/`loadMany()` - Batch data loading (DataLoader-style)
391
+
392
+ For the complete list, see [Grafast Standard Steps](https://grafast.org/grafast/standard-steps)
393
+
142
394
  ## Development
143
395
 
144
396
  ```bash
@@ -162,10 +414,6 @@ pnpm run test
162
414
  pnpm run test:watch
163
415
  ```
164
416
 
165
- ## License
166
-
167
- [MIT License](./LICENSE)
168
-
169
417
  <!-- Badges -->
170
418
  [npm-version-src]: https://img.shields.io/npm/v/@stonecrop/nuxt-grafserv/latest.svg?style=flat&colorA=020420&colorB=00DC82
171
419
  [npm-version-href]: https://npmjs.com/package/@stonecrop/nuxt-grafserv
@@ -175,6 +423,3 @@ pnpm run test:watch
175
423
 
176
424
  [license-src]: https://img.shields.io/npm/l/@stonecrop/nuxt-grafserv.svg?style=flat&colorA=020420&colorB=00DC82
177
425
  [license-href]: https://npmjs.com/package/@stonecrop/nuxt-grafserv
178
-
179
- [nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt.js
180
- [nuxt-href]: https://nuxt.com
package/dist/module.d.mts CHANGED
@@ -2,18 +2,6 @@ import { NuxtModule } from '@nuxt/schema';
2
2
  import { GraphQLSchema } from 'graphql';
3
3
  import { GraphileConfig } from 'graphile-config';
4
4
 
5
- /**
6
- * Context provided to GraphQL resolvers
7
- */
8
- type GrafastContext = {
9
- req: Request;
10
- params: Record<string, string>;
11
- [key: string]: unknown;
12
- };
13
- /**
14
- * Middleware function type for request processing
15
- */
16
- type MiddlewareFunction = (context: GrafastContext, next: () => Promise<GrafastContext>) => Promise<GrafastContext>;
17
5
  /**
18
6
  * Schema provider function - returns a GraphQL schema
19
7
  */
@@ -30,32 +18,11 @@ interface ModuleOptions {
30
18
  url?: string;
31
19
  /** Whether to enable GraphiQL IDE (default: true in dev, false in prod) */
32
20
  graphiql?: boolean;
33
- /**
34
- * Path to middleware file that exports an array of middleware functions.
35
- * This is the recommended approach as it preserves imports/dependencies.
36
- * Example: './server/middleware.ts'
37
- */
38
- middlewarePath?: string;
39
- /**
40
- * Middleware functions to process requests (inline).
41
- * Note: Inline middleware cannot reference external modules.
42
- * For middleware with dependencies, use middlewarePath instead.
43
- * @deprecated Use middlewarePath for middleware with external dependencies
44
- */
45
- middleware?: MiddlewareFunction[];
46
21
  /** Custom Graphile preset to extend (for advanced grafast configuration) */
47
22
  preset?: GraphileConfig.Preset;
48
- /** Additional Graphile plugins */
49
- plugins?: GraphileConfig.Plugin[];
50
- /** Grafserv options */
51
- grafserv?: {
52
- /** Whether to enable the GraphQL websocket endpoint */
53
- websockets?: boolean;
54
- /** Whether to enable introspection (default: true in dev) */
55
- introspection?: boolean;
56
- };
57
23
  }
58
24
 
59
25
  declare const module$1: NuxtModule<ModuleOptions>;
60
26
 
61
27
  export { module$1 as default };
28
+ export type { ModuleOptions, SchemaProvider };
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
- "name": "nuxt-grafserv",
2
+ "name": "@stonecrop/nuxt-grafserv",
3
3
  "configKey": "grafserv",
4
- "version": "0.7.3",
4
+ "version": "0.7.5",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -1,63 +1,74 @@
1
1
  import { join } from 'node:path';
2
+ import { createRequire } from 'node:module';
2
3
  import { useLogger, defineNuxtModule, createResolver } from '@nuxt/kit';
3
4
 
4
- const logger = useLogger("nuxt-grafserv");
5
+ const logger = useLogger("@stonecrop/nuxt-grafserv");
5
6
  const module$1 = defineNuxtModule({
6
7
  meta: {
7
- name: "nuxt-grafserv",
8
+ name: "@stonecrop/nuxt-grafserv",
8
9
  configKey: "grafserv"
9
10
  },
10
11
  defaults: (_nuxt) => ({
11
- schema: "./server/**/*.graphql",
12
- resolvers: "./server/resolvers.ts",
12
+ schema: "server/**/*.graphql",
13
+ resolvers: "server/resolvers.ts",
13
14
  url: "/graphql/",
14
15
  graphiql: void 0,
15
16
  // Will default based on dev mode
16
- middleware: [],
17
17
  plugins: [],
18
- grafserv: {
19
- websockets: false,
20
- introspection: void 0
21
- // Will default based on dev mode
18
+ preset: {
19
+ grafserv: {
20
+ websockets: false
21
+ }
22
22
  }
23
23
  }),
24
24
  setup(options, nuxt) {
25
25
  const { resolve } = createResolver(import.meta.url);
26
+ const require = createRequire(import.meta.url);
26
27
  nuxt.hook("nitro:config", (config) => {
27
- const resolverPath = options.resolvers ? join(nuxt.options.srcDir, options.resolvers) : void 0;
28
+ config.alias = config.alias || {};
29
+ config.alias["#grafserv-server"] = join(nuxt.options.rootDir, "server");
30
+ const resolveForSchema = (path) => {
31
+ if (path.startsWith("/")) {
32
+ return path;
33
+ }
34
+ return join(nuxt.options.rootDir, path);
35
+ };
36
+ const resolveForVirtualModule = (path) => {
37
+ if (path.startsWith("/")) {
38
+ return path;
39
+ }
40
+ return join(nuxt.options.rootDir, path);
41
+ };
42
+ logger.info(`Nitro srcDir: ${config.srcDir}`);
43
+ logger.info(`Nuxt srcDir: ${nuxt.options.srcDir}`);
44
+ logger.info(`Nuxt rootDir: ${nuxt.options.rootDir}`);
45
+ logger.info(`Grafserv server alias: ${config.alias["#grafserv-server"]}`);
46
+ const resolverPath = options.resolvers ? resolveForVirtualModule(options.resolvers) : void 0;
47
+ logger.info(`Resolved resolver path: ${resolverPath}`);
28
48
  config.runtimeConfig = config.runtimeConfig || {};
29
49
  config.runtimeConfig.grafserv = {
30
50
  ...options,
31
- // Resolve schema paths
32
- schema: typeof options.schema === "string" ? join(nuxt.options.srcDir, options.schema) : Array.isArray(options.schema) ? options.schema.map((s) => join(nuxt.options.srcDir, s)) : options.schema
51
+ // Pass resolved resolver path for direct import
52
+ resolversPath: resolverPath,
53
+ // Resolve schema paths from project root
54
+ schema: typeof options.schema === "string" ? resolveForSchema(options.schema) : Array.isArray(options.schema) ? options.schema.map((s) => resolveForSchema(s)) : options.schema
33
55
  // function passed through
34
56
  };
35
57
  config.virtual = config.virtual || {};
36
58
  if (resolverPath) {
37
59
  config.virtual["#internal/grafserv/resolvers"] = `export { default } from '${resolverPath}'`;
38
60
  }
39
- let middlewareCode;
40
- if (options.middlewarePath) {
41
- const middlewarePath = join(nuxt.options.srcDir, options.middlewarePath);
42
- middlewareCode = `export { default } from '${middlewarePath}'`;
43
- } else if (options.middleware?.length) {
44
- logger.warn("Inline middleware is deprecated. Use middlewarePath for middleware with external dependencies.");
45
- middlewareCode = `export default [${options.middleware.map((fn) => fn.toString()).join(",")}]`;
46
- } else {
47
- middlewareCode = "export default []";
48
- }
49
- config.virtual["#internal/grafserv/middleware"] = middlewareCode;
50
61
  config.externals = config.externals || {};
51
- config.externals.inline = config.externals.inline || [];
52
- config.externals.inline.push(
62
+ config.externals.external = config.externals.external || [];
63
+ config.externals.external.push(
53
64
  "grafast",
54
- "grafserv",
55
- "grafserv/h3/v1",
56
- "graphile-config",
57
65
  "@graphql-tools/schema",
58
66
  "@graphql-tools/load",
59
67
  "@graphql-tools/graphql-file-loader"
60
68
  );
69
+ const grafastPath = require.resolve("grafast");
70
+ config.alias = config.alias || {};
71
+ config.alias["grafast"] = grafastPath;
61
72
  });
62
73
  nuxt.hook("nitro:config", (config) => {
63
74
  config.handlers = config.handlers || [];
@@ -66,18 +77,18 @@ const module$1 = defineNuxtModule({
66
77
  handler: resolve("./runtime/handler")
67
78
  });
68
79
  config.handlers.push({
69
- route: "/graphql/cache",
70
- handler: resolve("./runtime/cache")
80
+ route: "/ruru-static/**",
81
+ handler: resolve("./runtime/ruru-static")
71
82
  });
72
83
  config.handlers.push({
73
- route: "/ruru-static/**",
74
- handler: resolve("./runtime/handler")
84
+ route: "/graphql/cache",
85
+ handler: resolve("./runtime/cache")
75
86
  });
76
87
  });
77
88
  if (options.url) {
78
89
  nuxt.hook("devtools:customTabs", (tabs) => {
79
90
  tabs.push({
80
- name: "nuxt-grafserv",
91
+ name: "@stonecrop/nuxt-grafserv",
81
92
  title: "GraphQL (Grafserv)",
82
93
  icon: "simple-icons:graphql",
83
94
  view: {
@@ -93,7 +104,6 @@ const module$1 = defineNuxtModule({
93
104
  const isSchemaFile = path.endsWith(".graphql");
94
105
  const isResolverFile = options.resolvers && path.includes(options.resolvers.replace("./", ""));
95
106
  if (isSchemaFile || isResolverFile) {
96
- logger.info(`${path} changed`);
97
107
  if (!cacheClearing) {
98
108
  cacheClearing = true;
99
109
  try {
@@ -0,0 +1,6 @@
1
+ /**
2
+ * GraphQL operations handler
3
+ * Handles POST requests with GraphQL queries, mutations, and subscriptions
4
+ */
5
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<Buffer<ArrayBufferLike> | import("grafserv").JSONValue | undefined>>;
6
+ export default _default;
@@ -0,0 +1,9 @@
1
+ import { defineEventHandler } from "h3";
2
+ import { useRuntimeConfig } from "nitropack/runtime";
3
+ import { getGrafservInstance } from "./handler.js";
4
+ export default defineEventHandler(async (event) => {
5
+ const config = useRuntimeConfig();
6
+ const options = config.grafserv;
7
+ const serv = await getGrafservInstance(options);
8
+ return serv.handleGraphQLEvent(event);
9
+ });
@@ -1,9 +1,17 @@
1
+ import { grafserv } from 'grafserv/h3/v1';
2
+ import type { ModuleOptions } from '../types.js';
3
+ /**
4
+ * Get or create the grafserv instance
5
+ * Exported for use by separate handler files
6
+ */
7
+ export declare function getGrafservInstance(options: ModuleOptions): Promise<ReturnType<typeof grafserv>>;
1
8
  /**
2
9
  * Clear the cached instances (useful for development hot reload)
3
10
  */
4
11
  export declare function clearGrafservCache(): Promise<void>;
5
12
  /**
6
- * Main H3 event handler for GraphQL requests
13
+ * Main H3 event handler for GraphQL requests and Ruru UI
14
+ * Routes between GraphQL operations and GraphiQL UI based on request type
7
15
  */
8
16
  declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<Buffer<ArrayBufferLike> | import("grafserv").JSONValue | undefined>>;
9
17
  export default _default;
@@ -1,22 +1,9 @@
1
- import { grafserv } from "grafserv/h3/v1";
2
- import { makeGrafastSchema } from "grafast";
3
1
  import { GraphQLFileLoader } from "@graphql-tools/graphql-file-loader";
4
2
  import { loadTypedefs } from "@graphql-tools/load";
3
+ import { grafserv } from "grafserv/h3/v1";
4
+ import { makeGrafastSchema } from "grafast";
5
5
  import { defineEventHandler } from "h3";
6
6
  import { useRuntimeConfig } from "nitropack/runtime";
7
- let middlewareFunctions = null;
8
- async function getMiddleware() {
9
- if (middlewareFunctions !== null) {
10
- return middlewareFunctions;
11
- }
12
- try {
13
- const mod = await import("#internal/grafserv/middleware");
14
- middlewareFunctions = mod.default || [];
15
- } catch {
16
- middlewareFunctions = [];
17
- }
18
- return middlewareFunctions;
19
- }
20
7
  let grafservInstance = null;
21
8
  let cachedSchema = null;
22
9
  async function loadTypeDefsFromFiles(schemaPath) {
@@ -35,49 +22,48 @@ async function getSchema(options) {
35
22
  schema = await options.schema();
36
23
  } else if (options.schema) {
37
24
  const typeDefDocs = await loadTypeDefsFromFiles(options.schema);
38
- let plans = {};
25
+ const objects = {};
39
26
  if (options.resolvers) {
40
27
  try {
41
28
  const resolverModule = await import("#internal/grafserv/resolvers");
42
29
  const resolvers = resolverModule.default || resolverModule;
43
- console.log("[@stonecrop/nuxt-grafserv] Resolvers loaded:", Object.keys(resolvers));
30
+ console.debug("[@stonecrop/nuxt-grafserv] Resolvers loaded:", Object.keys(resolvers));
44
31
  for (const typeName of Object.keys(resolvers)) {
45
- plans[typeName] = {};
46
32
  const typeResolvers = resolvers[typeName];
47
- for (const fieldName of Object.keys(typeResolvers)) {
48
- const resolver = typeResolvers[fieldName];
49
- if (typeof resolver === "function") {
50
- plans[typeName][fieldName] = { resolve: resolver };
51
- } else {
52
- plans[typeName][fieldName] = resolver;
53
- }
33
+ if (typeResolvers && typeof typeResolvers === "object" && "plans" in typeResolvers) {
34
+ objects[typeName] = typeResolvers;
35
+ } else {
36
+ objects[typeName] = { plans: typeResolvers };
54
37
  }
55
38
  }
56
39
  } catch (e) {
40
+ console.error("[@stonecrop/nuxt-grafserv] Error loading resolvers:", e);
57
41
  console.warn("[@stonecrop/nuxt-grafserv] Could not load resolvers:", e);
58
42
  }
59
43
  }
60
- schema = makeGrafastSchema({
61
- typeDefs: typeDefDocs,
62
- plans
63
- });
44
+ try {
45
+ schema = makeGrafastSchema({
46
+ typeDefs: typeDefDocs,
47
+ objects
48
+ });
49
+ console.debug("[@stonecrop/nuxt-grafserv] Grafast schema created successfully");
50
+ } catch (error) {
51
+ console.error("[@stonecrop/nuxt-grafserv] Error creating Grafast schema:", error);
52
+ throw error;
53
+ }
64
54
  } else {
65
55
  throw new Error("[@stonecrop/nuxt-grafserv] No schema provided. Configure schema path or provider function.");
66
56
  }
67
57
  cachedSchema = schema;
68
58
  return schema;
69
59
  }
70
- async function getGrafservInstance(options) {
60
+ export async function getGrafservInstance(options) {
71
61
  if (grafservInstance) {
62
+ console.log("[@stonecrop/nuxt-grafserv] Returning cached grafserv instance");
72
63
  return grafservInstance;
73
64
  }
74
65
  const schema = await getSchema(options);
75
- const isDev = process.env.NODE_ENV === "development";
76
- grafservInstance = grafserv({
77
- schema,
78
- graphiql: options.graphiql ?? isDev,
79
- websockets: options.grafserv?.websockets ?? false
80
- });
66
+ grafservInstance = grafserv({ schema, preset: options.preset });
81
67
  console.log("[@stonecrop/nuxt-grafserv] Grafserv instance created");
82
68
  return grafservInstance;
83
69
  }
@@ -86,46 +72,16 @@ export async function clearGrafservCache() {
86
72
  cachedSchema = null;
87
73
  console.log("[@stonecrop/nuxt-grafserv] Cache cleared");
88
74
  }
89
- async function applyMiddleware(context, middleware) {
90
- if (!middleware || middleware.length === 0) {
91
- return context;
92
- }
93
- const applyNext = async (index) => {
94
- if (index >= middleware.length) {
95
- return context;
96
- }
97
- return middleware[index](context, () => applyNext(index + 1));
98
- };
99
- return applyNext(0);
100
- }
101
75
  export default defineEventHandler(async (event) => {
102
76
  const config = useRuntimeConfig();
103
77
  const options = config.grafserv;
104
78
  try {
105
- if (event.node.req.url?.includes("/__grafserv_cache_clear") && process.env.NODE_ENV === "development") {
106
- await clearGrafservCache();
107
- return { success: true, message: "Cache cleared" };
108
- }
109
- const { req } = event.node;
110
- const context = {
111
- req: new Request(new URL(req.url || "/", `http://${req.headers.host || "localhost"}`), {
112
- method: req.method,
113
- headers: req.headers
114
- }),
115
- params: event.context.params || {}
116
- };
117
- const middleware = await getMiddleware();
118
- await applyMiddleware(context, middleware);
119
79
  const serv = await getGrafservInstance(options);
120
- const url = event.node.req.url || "";
121
- const method = event.node.req.method || "GET";
122
- if (url.includes("/ruru-static/")) {
123
- return serv.handleGraphiqlStaticEvent(event);
124
- }
125
- if (method === "GET" && url.match(/\/graphql\/?(\?.*)?$/)) {
126
- return serv.handleGraphiqlEvent(event);
80
+ const graphqlResult = await serv.handleGraphQLEvent(event);
81
+ if (graphqlResult !== null) {
82
+ return graphqlResult;
127
83
  }
128
- return serv.handleGraphQLEvent(event);
84
+ return serv.handleGraphiqlEvent(event);
129
85
  } catch (error) {
130
86
  console.error("[@stonecrop/nuxt-grafserv] Error in GraphQL handler:", error);
131
87
  throw error;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Ruru/GraphiQL static assets handler
3
+ * Serves CSS, JavaScript, and other static files for the GraphQL IDE
4
+ */
5
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<Buffer<ArrayBufferLike> | import("grafserv").JSONValue | undefined>>;
6
+ export default _default;
@@ -0,0 +1,9 @@
1
+ import { defineEventHandler } from "h3";
2
+ import { useRuntimeConfig } from "nitropack/runtime";
3
+ import { getGrafservInstance } from "./handler.js";
4
+ export default defineEventHandler(async (event) => {
5
+ const config = useRuntimeConfig();
6
+ const options = config.grafserv;
7
+ const serv = await getGrafservInstance(options);
8
+ return serv.handleGraphiqlStaticEvent(event);
9
+ });
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Ruru/GraphiQL UI handler
3
+ * Serves the GraphQL IDE interface for exploring and testing the API
4
+ */
5
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<Buffer<ArrayBufferLike> | import("grafserv").JSONValue | undefined>>;
6
+ export default _default;
@@ -0,0 +1,9 @@
1
+ import { defineEventHandler } from "h3";
2
+ import { useRuntimeConfig } from "nitropack/runtime";
3
+ import { getGrafservInstance } from "./handler.js";
4
+ export default defineEventHandler(async (event) => {
5
+ const config = useRuntimeConfig();
6
+ const options = config.grafserv;
7
+ const serv = await getGrafservInstance(options);
8
+ return serv.handleGraphiqlEvent(event);
9
+ });
package/dist/types.d.mts CHANGED
@@ -1,7 +1,3 @@
1
- import type { NuxtModule } from '@nuxt/schema'
2
-
3
- import type { default as Module } from './module.mjs'
4
-
5
- export type ModuleOptions = typeof Module extends NuxtModule<infer O> ? Partial<O> : Record<string, any>
6
-
7
1
  export { default } from './module.mjs'
2
+
3
+ export { type ModuleOptions, type SchemaProvider } from './module.mjs'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stonecrop/nuxt-grafserv",
3
- "version": "0.7.3",
3
+ "version": "0.7.5",
4
4
  "description": "Pluggable Grafserv GraphQL server as Nuxt Module",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -19,16 +19,16 @@
19
19
  "exports": {
20
20
  ".": {
21
21
  "import": "./dist/module.mjs",
22
- "types": "./dist/types.d.mts"
22
+ "types": "./dist/module.d.mts"
23
23
  },
24
24
  "./runtime/*": "./dist/runtime/*"
25
25
  },
26
26
  "main": "./dist/module.mjs",
27
- "types": "./dist/types.d.mts",
27
+ "types": "./dist/module.d.mts",
28
28
  "typesVersions": {
29
29
  "*": {
30
30
  ".": [
31
- "./dist/types.d.mts"
31
+ "./dist/module.d.mts"
32
32
  ]
33
33
  }
34
34
  },
@@ -40,14 +40,14 @@
40
40
  "@graphql-tools/load": "^8.1.8",
41
41
  "@graphql-tools/stitch": "^10.1.8",
42
42
  "@graphql-tools/wrap": "^11.1.4",
43
- "grafast": "^1.0.0-rc.3",
44
- "grafserv": "^1.0.0-rc.3",
45
- "graphile-config": "^1.0.0-rc.2",
43
+ "grafast": "^1.0.0-rc.4",
44
+ "grafserv": "^1.0.0-rc.4",
45
+ "graphile-config": "^1.0.0-rc.3",
46
46
  "graphql": "^16.12.0",
47
- "@stonecrop/graphql-middleware": "0.7.3"
47
+ "@stonecrop/graphql-middleware": "0.7.5"
48
48
  },
49
49
  "devDependencies": {
50
- "@casl/ability": "^6.7.5",
50
+ "@casl/ability": "^6.8.0",
51
51
  "@eslint/js": "^9.39.2",
52
52
  "@nuxt/devtools": "^3.1.1",
53
53
  "@nuxt/eslint-config": "^1.12.1",
@@ -57,8 +57,8 @@
57
57
  "@nuxt/test-utils": "^3.23.0",
58
58
  "@vitest/coverage-istanbul": "^4.0.17",
59
59
  "@types/node": "^22.19.5",
60
- "h3": "^1.15.4",
61
- "nitropack": "^2.13.0",
60
+ "h3": "^1.15.5",
61
+ "nitropack": "^2.13.1",
62
62
  "changelogen": "^0.6.2",
63
63
  "eslint": "^9.39.2",
64
64
  "jsdom": "^27.4.0",
@@ -67,12 +67,12 @@
67
67
  "vite": "^7.3.1",
68
68
  "vitest": "^4.0.17",
69
69
  "vue-tsc": "^3.2.2",
70
- "@stonecrop/casl-middleware": "0.7.3",
71
- "@stonecrop/rockfoil": "0.7.3"
70
+ "@stonecrop/casl-middleware": "0.7.5",
71
+ "@stonecrop/rockfoil": "0.7.5"
72
72
  },
73
73
  "peerDependencies": {
74
- "@stonecrop/rockfoil": "0.7.3",
75
- "@stonecrop/casl-middleware": "0.7.3"
74
+ "@stonecrop/casl-middleware": "0.7.5",
75
+ "@stonecrop/rockfoil": "0.7.5"
76
76
  },
77
77
  "peerDependenciesMeta": {
78
78
  "@stonecrop/casl-middleware": {
@@ -93,6 +93,7 @@
93
93
  "lint": "eslint .",
94
94
  "test": "vitest run",
95
95
  "test:ci": "vitest run --run",
96
+ "test:coverage": "vitest run --coverage",
96
97
  "test:watch": "vitest watch",
97
98
  "test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
98
99
  }