@stonecrop/nuxt-grafserv 0.7.4 → 0.7.6
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 +435 -62
- package/dist/module.d.mts +15 -37
- package/dist/module.json +2 -2
- package/dist/module.mjs +62 -35
- package/dist/runtime/graphql.d.ts +6 -0
- package/dist/runtime/graphql.js +9 -0
- package/dist/runtime/handler.d.ts +9 -1
- package/dist/runtime/handler.js +52 -73
- package/dist/runtime/ruru-static.d.ts +6 -0
- package/dist/runtime/ruru-static.js +9 -0
- package/dist/runtime/ruru.d.ts +6 -0
- package/dist/runtime/ruru.js +9 -0
- package/dist/types.d.mts +2 -6
- package/package.json +18 -15
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
|
-
-
|
|
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: '
|
|
44
|
-
resolvers: '
|
|
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
|
-
|
|
62
|
+
### All Available Options
|
|
63
|
+
|
|
64
|
+
| Option | Type | Default | Description |
|
|
65
|
+
|--------|------|---------|-------------|
|
|
66
|
+
| `schema` | `string \| string[] \| SchemaProvider \| PostGraphileInstance` | `'server/**/*.graphql'` | Path(s) to GraphQL schema files, schema provider function, or PostGraphile instance |
|
|
67
|
+
| `resolvers` | `string` | `undefined` | Path to resolvers file (optional - not needed for PostGraphile) |
|
|
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
|
-
//
|
|
59
|
-
schema: '
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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,405 @@ type Mutation {
|
|
|
104
120
|
2. Create your resolvers (`server/resolvers.ts`):
|
|
105
121
|
|
|
106
122
|
```typescript
|
|
107
|
-
|
|
123
|
+
import { constant, lambda, access, object, filter, type GrafastSchemaConfig } from 'grafast'
|
|
124
|
+
|
|
125
|
+
const resolvers: GrafastSchemaConfig['objects'] = {
|
|
108
126
|
Query: {
|
|
109
|
-
|
|
110
|
-
|
|
127
|
+
plans: {
|
|
128
|
+
hello: () => constant('world'),
|
|
129
|
+
ping: () => constant(true)
|
|
130
|
+
}
|
|
111
131
|
},
|
|
112
132
|
Mutation: {
|
|
113
|
-
|
|
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
|
-
##
|
|
145
|
+
## Advanced Usage
|
|
146
|
+
|
|
147
|
+
### Middleware via Grafserv Plugins
|
|
148
|
+
|
|
149
|
+
For middleware functionality (authentication, logging, etc.), use Grafserv plugins. The CLI installer creates a `server/plugins.ts` file with examples.
|
|
119
150
|
|
|
120
|
-
|
|
151
|
+
#### Inline Plugin Configuration
|
|
121
152
|
|
|
122
153
|
```ts
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
+
]
|
|
176
|
+
}
|
|
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
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export default [loggingPlugin, authPlugin]
|
|
230
|
+
```
|
|
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
|
+
### PostGraphile Integration
|
|
242
|
+
|
|
243
|
+
This module has first-class support for PostGraphile, enabling instant GraphQL APIs from PostgreSQL databases. PostGraphile generates the complete schema and resolvers, so you don't need to define them manually.
|
|
244
|
+
|
|
245
|
+
#### Setup with PostGraphile
|
|
246
|
+
|
|
247
|
+
1. Install PostGraphile:
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
pnpm add postgraphile @graphile/crystal graphile-build-pg
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
2. Create a PostGraphile schema provider (`server/graphql/schema.ts`):
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
import { postgraphile } from 'postgraphile'
|
|
257
|
+
import type { GraphQLSchema } from 'graphql'
|
|
258
|
+
|
|
259
|
+
// Define your PostGraphile preset
|
|
260
|
+
const preset = {
|
|
261
|
+
pgServices: [
|
|
262
|
+
{
|
|
263
|
+
name: 'main',
|
|
264
|
+
connectionString: process.env.DATABASE_URL || 'postgres://localhost/mydb',
|
|
265
|
+
schemas: ['public'],
|
|
266
|
+
},
|
|
267
|
+
],
|
|
268
|
+
grafserv: {
|
|
269
|
+
websockets: false, // Disable websockets for Nuxt compatibility
|
|
270
|
+
},
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Create PostGraphile instance
|
|
274
|
+
const pgl = postgraphile(preset)
|
|
275
|
+
|
|
276
|
+
// Export schema provider function
|
|
277
|
+
export async function createPostGraphileSchema(): Promise<GraphQLSchema> {
|
|
278
|
+
const { schema } = await pgl.getSchemaResult()
|
|
279
|
+
return schema
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Export the instance if you need direct access
|
|
283
|
+
export { pgl }
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
3. Configure Nuxt to use the PostGraphile schema:
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
// nuxt.config.ts
|
|
290
|
+
import { createPostGraphileSchema } from './server/graphql/schema'
|
|
291
|
+
|
|
292
|
+
export default defineNuxtConfig({
|
|
293
|
+
modules: ['@stonecrop/nuxt-grafserv'],
|
|
294
|
+
grafserv: {
|
|
295
|
+
// Use PostGraphile schema provider function
|
|
296
|
+
schema: createPostGraphileSchema,
|
|
297
|
+
// No resolvers needed - PostGraphile generates everything
|
|
298
|
+
url: '/graphql',
|
|
299
|
+
graphiql: true, // Enable Ruru IDE
|
|
300
|
+
}
|
|
301
|
+
})
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
#### Direct PostGraphile Instance Usage
|
|
305
|
+
|
|
306
|
+
You can also pass a PostGraphile instance directly:
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
// nuxt.config.ts
|
|
310
|
+
import { pgl } from './server/graphql/schema'
|
|
311
|
+
|
|
312
|
+
export default defineNuxtConfig({
|
|
313
|
+
grafserv: {
|
|
314
|
+
schema: pgl, // Pass PostGraphile instance directly
|
|
315
|
+
url: '/graphql',
|
|
316
|
+
}
|
|
317
|
+
})
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
#### PostGraphile with Custom Plugins
|
|
321
|
+
|
|
322
|
+
Enhance your PostGraphile setup with custom plugins:
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
// server/graphql/schema.ts
|
|
326
|
+
import { postgraphile } from 'postgraphile'
|
|
327
|
+
import { PostGraphileAmberPreset } from 'postgraphile/presets/amber'
|
|
328
|
+
import PgSimplifyInflectorPlugin from '@graphile-contrib/pg-simplify-inflector'
|
|
329
|
+
|
|
330
|
+
const preset = {
|
|
331
|
+
extends: [PostGraphileAmberPreset],
|
|
332
|
+
plugins: [PgSimplifyInflectorPlugin],
|
|
333
|
+
pgServices: [
|
|
334
|
+
{
|
|
335
|
+
name: 'main',
|
|
336
|
+
connectionString: process.env.DATABASE_URL,
|
|
337
|
+
schemas: ['public'],
|
|
131
338
|
},
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
339
|
+
],
|
|
340
|
+
grafserv: {
|
|
341
|
+
websockets: false,
|
|
342
|
+
},
|
|
343
|
+
grafast: {
|
|
344
|
+
explain: process.env.NODE_ENV === 'development', // Enable plan diagrams in dev
|
|
345
|
+
},
|
|
346
|
+
schema: {
|
|
347
|
+
// Add custom behavior overrides
|
|
348
|
+
defaultBehavior: 'connection', // Enable Relay-style connections by default
|
|
349
|
+
},
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const pgl = postgraphile(preset)
|
|
353
|
+
|
|
354
|
+
export async function createPostGraphileSchema() {
|
|
355
|
+
const { schema } = await pgl.getSchemaResult()
|
|
356
|
+
return schema
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
#### Benefits of PostGraphile Integration
|
|
361
|
+
|
|
362
|
+
- **Zero Schema Definition**: Automatically generates GraphQL schema from PostgreSQL
|
|
363
|
+
- **Auto-Generated Resolvers**: All queries and mutations created from database schema
|
|
364
|
+
- **Real-time Subscriptions**: Live query support via PostgreSQL LISTEN/NOTIFY
|
|
365
|
+
- **Row-Level Security**: Leverages PostgreSQL RLS for authorization
|
|
366
|
+
- **High Performance**: Uses Grafast execution engine with intelligent batching
|
|
367
|
+
- **Type Safety**: Full TypeScript support for generated schema
|
|
368
|
+
|
|
369
|
+
### Custom Graphile Preset
|
|
370
|
+
|
|
371
|
+
Leverage the full power of the Graphile ecosystem with custom presets:
|
|
372
|
+
|
|
373
|
+
```ts
|
|
374
|
+
import { PostGraphileAmberPreset } from 'postgraphile/presets/amber'
|
|
375
|
+
|
|
376
|
+
export default defineNuxtConfig({
|
|
377
|
+
grafserv: {
|
|
378
|
+
preset: {
|
|
379
|
+
extends: [PostGraphileAmberPreset],
|
|
380
|
+
grafast: {
|
|
381
|
+
explain: true, // Enable plan diagrams for debugging
|
|
382
|
+
context: {
|
|
383
|
+
// Custom context available in all resolvers
|
|
384
|
+
apiVersion: '1.0'
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
grafserv: {
|
|
388
|
+
graphqlOverGET: true,
|
|
389
|
+
maxRequestLength: 200000
|
|
390
|
+
}
|
|
137
391
|
}
|
|
138
|
-
|
|
392
|
+
}
|
|
393
|
+
})
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Accessing Grafserv Instance
|
|
397
|
+
|
|
398
|
+
For advanced use cases, you can access the grafserv instance directly:
|
|
399
|
+
|
|
400
|
+
```ts
|
|
401
|
+
// server/api/custom.ts
|
|
402
|
+
import { getGrafservInstance } from '@stonecrop/nuxt-grafserv/runtime/handler'
|
|
403
|
+
import { useRuntimeConfig } from '#imports'
|
|
404
|
+
|
|
405
|
+
export default defineEventHandler(async (event) => {
|
|
406
|
+
const config = useRuntimeConfig()
|
|
407
|
+
const serv = await getGrafservInstance(config.grafserv)
|
|
408
|
+
|
|
409
|
+
// Access grafserv methods or configuration
|
|
410
|
+
const preset = serv.getPreset()
|
|
411
|
+
|
|
412
|
+
return { preset }
|
|
413
|
+
})
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Schema Building with Objects Structure
|
|
417
|
+
|
|
418
|
+
The module uses Grafast's modern objects structure for better type safety and performance. Leverage Grafast's standard steps for common operations:
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
import { constant, lambda, access, object, filter, type GrafastSchemaConfig } from 'grafast'
|
|
422
|
+
|
|
423
|
+
const resolvers: GrafastSchemaConfig['objects'] = {
|
|
424
|
+
Query: {
|
|
425
|
+
plans: {
|
|
426
|
+
// Static values use constant()
|
|
427
|
+
hello: () => constant('world'),
|
|
428
|
+
|
|
429
|
+
// Arguments accessed via fieldArgs with $ prefix
|
|
430
|
+
user: (_source, fieldArgs) => {
|
|
431
|
+
const { $id } = fieldArgs
|
|
432
|
+
return lambda($id, (id) => getUserById(id))
|
|
433
|
+
},
|
|
434
|
+
|
|
435
|
+
// Use filter() for list filtering
|
|
436
|
+
userOrders: (_source, fieldArgs) => {
|
|
437
|
+
const { $userId } = fieldArgs
|
|
438
|
+
const $allOrders = constant(getAllOrders())
|
|
439
|
+
return filter($allOrders, $order =>
|
|
440
|
+
lambda([access($order, 'userId'), $userId],
|
|
441
|
+
([orderUserId, userId]) => orderUserId === userId
|
|
442
|
+
)
|
|
443
|
+
)
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
|
|
448
|
+
Mutation: {
|
|
449
|
+
plans: {
|
|
450
|
+
// Use object() to compose objects from steps
|
|
451
|
+
createUser: (_source, fieldArgs) => {
|
|
452
|
+
const { $name, $email } = fieldArgs
|
|
453
|
+
const $id = constant(generateId())
|
|
454
|
+
const $now = constant(new Date().toISOString())
|
|
455
|
+
|
|
456
|
+
const $user = object({
|
|
457
|
+
id: $id,
|
|
458
|
+
name: $name,
|
|
459
|
+
email: $email,
|
|
460
|
+
role: constant('user'),
|
|
461
|
+
createdAt: $now,
|
|
462
|
+
updatedAt: $now
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
return lambda($user, user => {
|
|
466
|
+
saveUser(user)
|
|
467
|
+
return user
|
|
468
|
+
})
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
|
|
473
|
+
// Field resolvers for types
|
|
474
|
+
User: {
|
|
475
|
+
plans: {
|
|
476
|
+
fullName: ($user) => {
|
|
477
|
+
return lambda($user, (user) => {
|
|
478
|
+
const typed = user as { firstName: string; lastName: string }
|
|
479
|
+
return `${typed.firstName} ${typed.lastName}`
|
|
480
|
+
})
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
},
|
|
484
|
+
|
|
485
|
+
// Related type resolvers
|
|
486
|
+
Order: {
|
|
487
|
+
plans: {
|
|
488
|
+
// Use access() to extract properties before lookups
|
|
489
|
+
user: ($order) => {
|
|
490
|
+
const $userId = access($order, 'userId')
|
|
491
|
+
return lambda($userId, userId => getUserById(userId as string) ?? null)
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
139
495
|
}
|
|
496
|
+
|
|
497
|
+
export default resolvers
|
|
140
498
|
```
|
|
141
499
|
|
|
500
|
+
**Key Concepts:**
|
|
501
|
+
- All resolvers return **steps** (constant, lambda, access, object, filter, etc.), not plain values
|
|
502
|
+
- Arguments are accessed via `fieldArgs.$argumentName` (note the `$` prefix)
|
|
503
|
+
- Use `constant()` for static values
|
|
504
|
+
- Use `lambda()` to transform step values at execution time
|
|
505
|
+
- Use `access()` to extract object properties (more efficient than lambda for simple property access)
|
|
506
|
+
- Use `object()` to compose objects from multiple steps
|
|
507
|
+
- Use `filter()` for list filtering operations
|
|
508
|
+
- Source objects (`$source`, `$user`, `$order`, etc.) are also steps that need `lambda()` or `access()` to work with their properties
|
|
509
|
+
|
|
510
|
+
**Standard Steps Reference:**
|
|
511
|
+
- `constant()` - Create a step from a static value
|
|
512
|
+
- `lambda()` - Transform step values with execution-time callbacks
|
|
513
|
+
- `access()` - Extract properties from object steps
|
|
514
|
+
- `object()` - Compose objects from multiple steps
|
|
515
|
+
- `filter()` - Filter list steps based on conditions
|
|
516
|
+
- `list()` - Create lists from step tuples
|
|
517
|
+
- `first()`/`last()` - Get first/last elements from lists
|
|
518
|
+
- `loadOne()`/`loadMany()` - Batch data loading (DataLoader-style)
|
|
519
|
+
|
|
520
|
+
For the complete list, see [Grafast Standard Steps](https://grafast.org/grafast/standard-steps)
|
|
521
|
+
|
|
142
522
|
## Development
|
|
143
523
|
|
|
144
524
|
```bash
|
|
@@ -162,10 +542,6 @@ pnpm run test
|
|
|
162
542
|
pnpm run test:watch
|
|
163
543
|
```
|
|
164
544
|
|
|
165
|
-
## License
|
|
166
|
-
|
|
167
|
-
[MIT License](./LICENSE)
|
|
168
|
-
|
|
169
545
|
<!-- Badges -->
|
|
170
546
|
[npm-version-src]: https://img.shields.io/npm/v/@stonecrop/nuxt-grafserv/latest.svg?style=flat&colorA=020420&colorB=00DC82
|
|
171
547
|
[npm-version-href]: https://npmjs.com/package/@stonecrop/nuxt-grafserv
|
|
@@ -175,6 +551,3 @@ pnpm run test:watch
|
|
|
175
551
|
|
|
176
552
|
[license-src]: https://img.shields.io/npm/l/@stonecrop/nuxt-grafserv.svg?style=flat&colorA=020420&colorB=00DC82
|
|
177
553
|
[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
|
@@ -1,61 +1,39 @@
|
|
|
1
1
|
import { NuxtModule } from '@nuxt/schema';
|
|
2
|
+
import { PromiseOrDirect } from 'grafast';
|
|
2
3
|
import { GraphQLSchema } from 'graphql';
|
|
3
4
|
import { GraphileConfig } from 'graphile-config';
|
|
4
5
|
|
|
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
6
|
/**
|
|
18
7
|
* Schema provider function - returns a GraphQL schema
|
|
19
8
|
*/
|
|
20
9
|
type SchemaProvider = () => GraphQLSchema | Promise<GraphQLSchema>;
|
|
10
|
+
/**
|
|
11
|
+
* PostGraphile instance interface (minimal type for compatibility)
|
|
12
|
+
*/
|
|
13
|
+
interface PostGraphileInstance {
|
|
14
|
+
getSchema(): PromiseOrDirect<GraphQLSchema>;
|
|
15
|
+
getSchemaResult(): PromiseOrDirect<{
|
|
16
|
+
schema: GraphQLSchema;
|
|
17
|
+
resolvedPreset: GraphileConfig.ResolvedPreset;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
21
20
|
/**
|
|
22
21
|
* Configuration for the Grafast module
|
|
23
22
|
*/
|
|
24
23
|
interface ModuleOptions {
|
|
25
|
-
/** Path to schema file(s)
|
|
26
|
-
schema?: string | string[] | SchemaProvider;
|
|
27
|
-
/** Path to resolvers file (for .graphql schema files) */
|
|
24
|
+
/** Path to schema file(s), a schema provider function, or a PostGraphile instance */
|
|
25
|
+
schema?: string | string[] | SchemaProvider | PostGraphileInstance;
|
|
26
|
+
/** Path to resolvers file (optional, only needed for .graphql schema files without PostGraphile) */
|
|
28
27
|
resolvers?: string;
|
|
29
28
|
/** GraphQL endpoint URL (default: '/graphql/') */
|
|
30
29
|
url?: string;
|
|
31
30
|
/** Whether to enable GraphiQL IDE (default: true in dev, false in prod) */
|
|
32
31
|
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
32
|
/** Custom Graphile preset to extend (for advanced grafast configuration) */
|
|
47
33
|
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
34
|
}
|
|
58
35
|
|
|
59
36
|
declare const module$1: NuxtModule<ModuleOptions>;
|
|
60
37
|
|
|
61
38
|
export { module$1 as default };
|
|
39
|
+
export type { ModuleOptions, SchemaProvider };
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,63 +1,91 @@
|
|
|
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: "
|
|
12
|
-
resolvers:
|
|
12
|
+
schema: "server/**/*.graphql",
|
|
13
|
+
resolvers: void 0,
|
|
14
|
+
// Optional - not needed for PostGraphile setups
|
|
13
15
|
url: "/graphql/",
|
|
14
16
|
graphiql: void 0,
|
|
15
17
|
// Will default based on dev mode
|
|
16
|
-
middleware: [],
|
|
17
18
|
plugins: [],
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
preset: {
|
|
20
|
+
grafserv: {
|
|
21
|
+
websockets: false
|
|
22
|
+
}
|
|
22
23
|
}
|
|
23
24
|
}),
|
|
24
25
|
setup(options, nuxt) {
|
|
25
26
|
const { resolve } = createResolver(import.meta.url);
|
|
27
|
+
const require = createRequire(import.meta.url);
|
|
26
28
|
nuxt.hook("nitro:config", (config) => {
|
|
27
|
-
|
|
29
|
+
config.alias = config.alias || {};
|
|
30
|
+
config.alias["#grafserv-server"] = join(nuxt.options.rootDir, "server");
|
|
31
|
+
const resolveForSchema = (path) => {
|
|
32
|
+
if (path.startsWith("/")) {
|
|
33
|
+
return path;
|
|
34
|
+
}
|
|
35
|
+
return join(nuxt.options.rootDir, path);
|
|
36
|
+
};
|
|
37
|
+
const resolveForVirtualModule = (path) => {
|
|
38
|
+
if (path.startsWith("/")) {
|
|
39
|
+
return path;
|
|
40
|
+
}
|
|
41
|
+
return join(nuxt.options.rootDir, path);
|
|
42
|
+
};
|
|
43
|
+
logger.info(`Nitro srcDir: ${config.srcDir}`);
|
|
44
|
+
logger.info(`Nuxt srcDir: ${nuxt.options.srcDir}`);
|
|
45
|
+
logger.info(`Nuxt rootDir: ${nuxt.options.rootDir}`);
|
|
46
|
+
logger.info(`Grafserv server alias: ${config.alias["#grafserv-server"]}`);
|
|
47
|
+
const resolverPath = options.resolvers ? resolveForVirtualModule(options.resolvers) : void 0;
|
|
48
|
+
if (resolverPath) {
|
|
49
|
+
logger.info(`Resolved resolver path: ${resolverPath}`);
|
|
50
|
+
} else {
|
|
51
|
+
logger.info("No resolvers configured (normal for PostGraphile setups)");
|
|
52
|
+
}
|
|
53
|
+
let runtimeSchema;
|
|
54
|
+
if (typeof options.schema === "function" || options.schema && typeof options.schema === "object" && "getSchema" in options.schema) {
|
|
55
|
+
runtimeSchema = options.schema;
|
|
56
|
+
logger.info("Using schema provider function or PostGraphile instance");
|
|
57
|
+
} else if (typeof options.schema === "string") {
|
|
58
|
+
runtimeSchema = resolveForSchema(options.schema);
|
|
59
|
+
logger.info(`Resolved schema path: ${runtimeSchema}`);
|
|
60
|
+
} else if (Array.isArray(options.schema)) {
|
|
61
|
+
runtimeSchema = options.schema.map((s) => resolveForSchema(s));
|
|
62
|
+
logger.info(`Resolved schema paths: ${runtimeSchema.join(", ")}`);
|
|
63
|
+
} else {
|
|
64
|
+
runtimeSchema = options.schema;
|
|
65
|
+
}
|
|
28
66
|
config.runtimeConfig = config.runtimeConfig || {};
|
|
29
67
|
config.runtimeConfig.grafserv = {
|
|
30
68
|
...options,
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
//
|
|
69
|
+
// Pass resolved resolver path for direct import
|
|
70
|
+
resolversPath: resolverPath,
|
|
71
|
+
// Pass schema (either resolved paths or function/instance)
|
|
72
|
+
schema: runtimeSchema
|
|
34
73
|
};
|
|
35
74
|
config.virtual = config.virtual || {};
|
|
36
75
|
if (resolverPath) {
|
|
37
76
|
config.virtual["#internal/grafserv/resolvers"] = `export { default } from '${resolverPath}'`;
|
|
38
77
|
}
|
|
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
78
|
config.externals = config.externals || {};
|
|
51
|
-
config.externals.
|
|
52
|
-
config.externals.
|
|
79
|
+
config.externals.external = config.externals.external || [];
|
|
80
|
+
config.externals.external.push(
|
|
53
81
|
"grafast",
|
|
54
|
-
"grafserv",
|
|
55
|
-
"grafserv/h3/v1",
|
|
56
|
-
"graphile-config",
|
|
57
82
|
"@graphql-tools/schema",
|
|
58
83
|
"@graphql-tools/load",
|
|
59
84
|
"@graphql-tools/graphql-file-loader"
|
|
60
85
|
);
|
|
86
|
+
const grafastPath = require.resolve("grafast");
|
|
87
|
+
config.alias = config.alias || {};
|
|
88
|
+
config.alias["grafast"] = grafastPath;
|
|
61
89
|
});
|
|
62
90
|
nuxt.hook("nitro:config", (config) => {
|
|
63
91
|
config.handlers = config.handlers || [];
|
|
@@ -66,18 +94,18 @@ const module$1 = defineNuxtModule({
|
|
|
66
94
|
handler: resolve("./runtime/handler")
|
|
67
95
|
});
|
|
68
96
|
config.handlers.push({
|
|
69
|
-
route: "/
|
|
70
|
-
handler: resolve("./runtime/
|
|
97
|
+
route: "/ruru-static/**",
|
|
98
|
+
handler: resolve("./runtime/ruru-static")
|
|
71
99
|
});
|
|
72
100
|
config.handlers.push({
|
|
73
|
-
route: "/
|
|
74
|
-
handler: resolve("./runtime/
|
|
101
|
+
route: "/graphql/cache",
|
|
102
|
+
handler: resolve("./runtime/cache")
|
|
75
103
|
});
|
|
76
104
|
});
|
|
77
105
|
if (options.url) {
|
|
78
106
|
nuxt.hook("devtools:customTabs", (tabs) => {
|
|
79
107
|
tabs.push({
|
|
80
|
-
name: "nuxt-grafserv",
|
|
108
|
+
name: "@stonecrop/nuxt-grafserv",
|
|
81
109
|
title: "GraphQL (Grafserv)",
|
|
82
110
|
icon: "simple-icons:graphql",
|
|
83
111
|
view: {
|
|
@@ -93,7 +121,6 @@ const module$1 = defineNuxtModule({
|
|
|
93
121
|
const isSchemaFile = path.endsWith(".graphql");
|
|
94
122
|
const isResolverFile = options.resolvers && path.includes(options.resolvers.replace("./", ""));
|
|
95
123
|
if (isSchemaFile || isResolverFile) {
|
|
96
|
-
logger.info(`${path} changed`);
|
|
97
124
|
if (!cacheClearing) {
|
|
98
125
|
cacheClearing = true;
|
|
99
126
|
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;
|
package/dist/runtime/handler.js
CHANGED
|
@@ -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) {
|
|
@@ -26,58 +13,80 @@ async function loadTypeDefsFromFiles(schemaPath) {
|
|
|
26
13
|
});
|
|
27
14
|
return sources.map((source) => source.document).filter(Boolean);
|
|
28
15
|
}
|
|
16
|
+
function isPostGraphileInstance(value) {
|
|
17
|
+
return value !== null && typeof value === "object" && "getSchema" in value && typeof value.getSchema === "function";
|
|
18
|
+
}
|
|
29
19
|
async function getSchema(options) {
|
|
30
20
|
if (cachedSchema) {
|
|
31
21
|
return cachedSchema;
|
|
32
22
|
}
|
|
33
23
|
let schema;
|
|
34
|
-
if (
|
|
24
|
+
if (isPostGraphileInstance(options.schema)) {
|
|
25
|
+
console.debug("[@stonecrop/nuxt-grafserv] Using PostGraphile instance for schema");
|
|
26
|
+
try {
|
|
27
|
+
if ("getSchemaResult" in options.schema && typeof options.schema.getSchemaResult === "function") {
|
|
28
|
+
const result = await options.schema.getSchemaResult();
|
|
29
|
+
schema = result.schema;
|
|
30
|
+
} else {
|
|
31
|
+
schema = await options.schema.getSchema();
|
|
32
|
+
}
|
|
33
|
+
console.debug("[@stonecrop/nuxt-grafserv] PostGraphile schema loaded successfully");
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error("[@stonecrop/nuxt-grafserv] Error loading PostGraphile schema:", error);
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
} else if (typeof options.schema === "function") {
|
|
35
39
|
schema = await options.schema();
|
|
36
40
|
} else if (options.schema) {
|
|
37
41
|
const typeDefDocs = await loadTypeDefsFromFiles(options.schema);
|
|
38
|
-
|
|
42
|
+
const objects = {};
|
|
39
43
|
if (options.resolvers) {
|
|
40
44
|
try {
|
|
41
45
|
const resolverModule = await import("#internal/grafserv/resolvers");
|
|
42
46
|
const resolvers = resolverModule.default || resolverModule;
|
|
43
|
-
console.
|
|
47
|
+
console.debug("[@stonecrop/nuxt-grafserv] Resolvers loaded:", Object.keys(resolvers));
|
|
44
48
|
for (const typeName of Object.keys(resolvers)) {
|
|
45
|
-
plans[typeName] = {};
|
|
46
49
|
const typeResolvers = resolvers[typeName];
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
} else {
|
|
52
|
-
plans[typeName][fieldName] = resolver;
|
|
53
|
-
}
|
|
50
|
+
if (typeResolvers && typeof typeResolvers === "object" && "plans" in typeResolvers) {
|
|
51
|
+
objects[typeName] = typeResolvers;
|
|
52
|
+
} else {
|
|
53
|
+
objects[typeName] = { plans: typeResolvers };
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
} catch (e) {
|
|
57
|
-
console.
|
|
57
|
+
console.error("[@stonecrop/nuxt-grafserv] Error loading resolvers:", e);
|
|
58
|
+
console.warn("[@stonecrop/nuxt-grafserv] Continuing without resolvers - this is normal for PostGraphile setups");
|
|
58
59
|
}
|
|
60
|
+
} else {
|
|
61
|
+
console.debug(
|
|
62
|
+
"[@stonecrop/nuxt-grafserv] No resolvers specified - using schema-only mode (normal for PostGraphile)"
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
schema = makeGrafastSchema({
|
|
67
|
+
typeDefs: typeDefDocs,
|
|
68
|
+
objects
|
|
69
|
+
});
|
|
70
|
+
console.debug("[@stonecrop/nuxt-grafserv] Grafast schema created successfully");
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error("[@stonecrop/nuxt-grafserv] Error creating Grafast schema:", error);
|
|
73
|
+
throw error;
|
|
59
74
|
}
|
|
60
|
-
schema = makeGrafastSchema({
|
|
61
|
-
typeDefs: typeDefDocs,
|
|
62
|
-
plans
|
|
63
|
-
});
|
|
64
75
|
} else {
|
|
65
|
-
throw new Error(
|
|
76
|
+
throw new Error(
|
|
77
|
+
"[@stonecrop/nuxt-grafserv] No schema provided. Configure schema path, provider function, or PostGraphile instance."
|
|
78
|
+
);
|
|
66
79
|
}
|
|
67
80
|
cachedSchema = schema;
|
|
68
81
|
return schema;
|
|
69
82
|
}
|
|
70
|
-
async function getGrafservInstance(options) {
|
|
83
|
+
export async function getGrafservInstance(options) {
|
|
71
84
|
if (grafservInstance) {
|
|
85
|
+
console.log("[@stonecrop/nuxt-grafserv] Returning cached grafserv instance");
|
|
72
86
|
return grafservInstance;
|
|
73
87
|
}
|
|
74
88
|
const schema = await getSchema(options);
|
|
75
|
-
|
|
76
|
-
grafservInstance = grafserv({
|
|
77
|
-
schema,
|
|
78
|
-
graphiql: options.graphiql ?? isDev,
|
|
79
|
-
websockets: options.grafserv?.websockets ?? false
|
|
80
|
-
});
|
|
89
|
+
grafservInstance = grafserv({ schema, preset: options.preset });
|
|
81
90
|
console.log("[@stonecrop/nuxt-grafserv] Grafserv instance created");
|
|
82
91
|
return grafservInstance;
|
|
83
92
|
}
|
|
@@ -86,46 +95,16 @@ export async function clearGrafservCache() {
|
|
|
86
95
|
cachedSchema = null;
|
|
87
96
|
console.log("[@stonecrop/nuxt-grafserv] Cache cleared");
|
|
88
97
|
}
|
|
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
98
|
export default defineEventHandler(async (event) => {
|
|
102
99
|
const config = useRuntimeConfig();
|
|
103
100
|
const options = config.grafserv;
|
|
104
101
|
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
102
|
const serv = await getGrafservInstance(options);
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return serv.handleGraphiqlStaticEvent(event);
|
|
124
|
-
}
|
|
125
|
-
if (method === "GET" && url.match(/\/graphql\/?(\?.*)?$/)) {
|
|
126
|
-
return serv.handleGraphiqlEvent(event);
|
|
103
|
+
const graphqlResult = await serv.handleGraphQLEvent(event);
|
|
104
|
+
if (graphqlResult !== null) {
|
|
105
|
+
return graphqlResult;
|
|
127
106
|
}
|
|
128
|
-
return serv.
|
|
107
|
+
return serv.handleGraphiqlEvent(event);
|
|
129
108
|
} catch (error) {
|
|
130
109
|
console.error("[@stonecrop/nuxt-grafserv] Error in GraphQL handler:", error);
|
|
131
110
|
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
|
+
"version": "0.7.6",
|
|
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/
|
|
22
|
+
"types": "./dist/module.d.mts"
|
|
23
23
|
},
|
|
24
24
|
"./runtime/*": "./dist/runtime/*"
|
|
25
25
|
},
|
|
26
26
|
"main": "./dist/module.mjs",
|
|
27
|
-
"types": "./dist/
|
|
27
|
+
"types": "./dist/module.d.mts",
|
|
28
28
|
"typesVersions": {
|
|
29
29
|
"*": {
|
|
30
30
|
".": [
|
|
31
|
-
"./dist/
|
|
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.
|
|
44
|
-
"grafserv": "^1.0.0-rc.
|
|
45
|
-
"graphile-config": "^1.0.0-rc.
|
|
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.
|
|
47
|
+
"@stonecrop/graphql-middleware": "0.7.6"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@casl/ability": "^6.
|
|
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,22 +57,23 @@
|
|
|
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.
|
|
61
|
-
"nitropack": "^2.13.
|
|
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",
|
|
65
65
|
"nuxt": "^4.2.2",
|
|
66
|
+
"postgraphile": "^5.0.0-rc.4",
|
|
66
67
|
"typescript": "^5.9.3",
|
|
67
68
|
"vite": "^7.3.1",
|
|
68
69
|
"vitest": "^4.0.17",
|
|
69
70
|
"vue-tsc": "^3.2.2",
|
|
70
|
-
"@stonecrop/casl-middleware": "0.7.
|
|
71
|
-
"@stonecrop/rockfoil": "0.7.
|
|
71
|
+
"@stonecrop/casl-middleware": "0.7.6",
|
|
72
|
+
"@stonecrop/rockfoil": "0.7.6"
|
|
72
73
|
},
|
|
73
74
|
"peerDependencies": {
|
|
74
|
-
"@stonecrop/casl-middleware": "0.7.
|
|
75
|
-
"@stonecrop/rockfoil": "0.7.
|
|
75
|
+
"@stonecrop/casl-middleware": "0.7.6",
|
|
76
|
+
"@stonecrop/rockfoil": "0.7.6"
|
|
76
77
|
},
|
|
77
78
|
"peerDependenciesMeta": {
|
|
78
79
|
"@stonecrop/casl-middleware": {
|
|
@@ -93,6 +94,8 @@
|
|
|
93
94
|
"lint": "eslint .",
|
|
94
95
|
"test": "vitest run",
|
|
95
96
|
"test:ci": "vitest run --run",
|
|
97
|
+
"test:coverage": "vitest run --coverage",
|
|
98
|
+
"test:e2e": "vitest run test/e2e",
|
|
96
99
|
"test:watch": "vitest watch",
|
|
97
100
|
"test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
|
|
98
101
|
}
|