@stonecrop/nuxt-grafserv 0.12.8 → 0.13.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.
- package/README.md +88 -113
- package/dist/module.d.mts +58 -8
- package/dist/module.json +1 -1
- package/dist/module.mjs +116 -64
- package/dist/runtime/startup-check.d.ts +2 -0
- package/dist/runtime/startup-check.js +8 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -39,32 +39,19 @@ For PostGraphile users, this is the recommended configuration approach:
|
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
41
|
# Using pnpm
|
|
42
|
-
pnpm add @stonecrop/nuxt-grafserv
|
|
42
|
+
pnpm add @stonecrop/nuxt-grafserv
|
|
43
43
|
|
|
44
44
|
# Using yarn
|
|
45
|
-
yarn add @stonecrop/nuxt-grafserv
|
|
45
|
+
yarn add @stonecrop/nuxt-grafserv
|
|
46
46
|
|
|
47
47
|
# Using npm
|
|
48
|
-
npm install @stonecrop/nuxt-grafserv
|
|
48
|
+
npm install @stonecrop/nuxt-grafserv
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
2.
|
|
52
|
-
|
|
53
|
-
```ts
|
|
54
|
-
import { PostGraphileAmberPreset } from 'postgraphile/presets/amber'
|
|
55
|
-
import { makePgService } from 'postgraphile/adaptors/pg'
|
|
51
|
+
2. Set `DATABASE_URL` in your environment (`.env` or shell):
|
|
56
52
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
pgServices: [
|
|
60
|
-
makePgService({
|
|
61
|
-
connectionString: process.env.DATABASE_URL || 'postgresql://localhost/mydb',
|
|
62
|
-
schemas: ['public'],
|
|
63
|
-
}),
|
|
64
|
-
],
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export default preset
|
|
53
|
+
```
|
|
54
|
+
DATABASE_URL=postgresql://localhost/mydb
|
|
68
55
|
```
|
|
69
56
|
|
|
70
57
|
3. Configure in `nuxt.config.ts`:
|
|
@@ -74,13 +61,42 @@ export default defineNuxtConfig({
|
|
|
74
61
|
modules: ['@stonecrop/nuxt-grafserv'],
|
|
75
62
|
grafserv: {
|
|
76
63
|
type: 'postgraphile', // Required: specify configuration type
|
|
77
|
-
preset: './server/graphile.preset.ts', // Path to preset file
|
|
78
64
|
url: '/graphql',
|
|
79
65
|
graphiql: true,
|
|
66
|
+
// fieldCasing: 'pascal', // optional — defaults to 'camel'
|
|
67
|
+
// schemas: ['public', 'auth'], // optional — defaults to ['public']
|
|
68
|
+
// explain: true, // optional — enables Ruru Explain tab (dev only)
|
|
80
69
|
}
|
|
81
70
|
})
|
|
82
71
|
```
|
|
83
72
|
|
|
73
|
+
For custom plugins, a non-standard connection, or advanced configuration, create a preset file:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
// server/graphile.preset.ts
|
|
77
|
+
import { createStonecropPreset, makePgService, createStonecropPlugin } from '@stonecrop/graphql-middleware'
|
|
78
|
+
|
|
79
|
+
export default {
|
|
80
|
+
extends: [createStonecropPreset()],
|
|
81
|
+
pgServices: [
|
|
82
|
+
makePgService({
|
|
83
|
+
connectionString: process.env.DATABASE_URL,
|
|
84
|
+
schemas: ['public'],
|
|
85
|
+
}),
|
|
86
|
+
],
|
|
87
|
+
plugins: [createStonecropPlugin(), MyCustomPlugin],
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Then reference it in `nuxt.config.ts`:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
grafserv: {
|
|
95
|
+
type: 'postgraphile',
|
|
96
|
+
preset: './server/graphile.preset.ts',
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
84
100
|
### Custom Schema Configuration
|
|
85
101
|
|
|
86
102
|
For custom GraphQL schemas with your own resolvers:
|
|
@@ -117,43 +133,42 @@ Use `type: 'postgraphile'` for PostGraphile-based GraphQL APIs:
|
|
|
117
133
|
| Option | Type | Required | Description |
|
|
118
134
|
|--------|------|----------|-------------|
|
|
119
135
|
| `type` | `'postgraphile'` | ✅ | Configuration type discriminator |
|
|
120
|
-
| `preset` | `string` |
|
|
121
|
-
| `
|
|
122
|
-
| `
|
|
136
|
+
| `preset` | `string` | ❌ | Path to preset file. When omitted, a default preset is synthesized from `DATABASE_URL`. |
|
|
137
|
+
| `fieldCasing` | `'camel' \| 'pascal'` | ❌ | Column → field name casing (default: `'camel'`). Ignored when `preset` is set. |
|
|
138
|
+
| `schemas` | `string[]` | ❌ | PostgreSQL schemas to expose (default: `['public']`). Ignored when `preset` is set. |
|
|
139
|
+
| `explain` | `boolean` | ❌ | Enable Ruru Explain tab. **Never use in production.** Ignored when `preset` is set. |
|
|
140
|
+
| `url` | `string` | ❌ | GraphQL endpoint URL (default: `/graphql/`) |
|
|
141
|
+
| `graphiql` | `boolean` | ❌ | Enable GraphiQL IDE (default: `true`) |
|
|
123
142
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
**Example:**
|
|
143
|
+
**Minimal example (no preset file):**
|
|
127
144
|
|
|
128
145
|
```ts
|
|
129
|
-
// nuxt.config.ts
|
|
146
|
+
// nuxt.config.ts — DATABASE_URL is read from process.env automatically
|
|
130
147
|
export default defineNuxtConfig({
|
|
131
148
|
grafserv: {
|
|
132
149
|
type: 'postgraphile',
|
|
133
|
-
|
|
134
|
-
url: '/graphql',
|
|
150
|
+
url: '/graphql/',
|
|
135
151
|
graphiql: true,
|
|
136
152
|
}
|
|
137
153
|
})
|
|
138
154
|
```
|
|
139
155
|
|
|
156
|
+
**Explicit preset file (for custom plugins and advanced configuration):**
|
|
157
|
+
|
|
140
158
|
```ts
|
|
141
159
|
// server/graphile.preset.ts
|
|
142
|
-
import {
|
|
143
|
-
import { makePgService } from 'postgraphile/adaptors/pg'
|
|
160
|
+
import { createStonecropPreset, makePgService, createStonecropPlugin } from '@stonecrop/graphql-middleware'
|
|
144
161
|
|
|
145
|
-
|
|
146
|
-
extends: [
|
|
162
|
+
export default {
|
|
163
|
+
extends: [createStonecropPreset()],
|
|
147
164
|
pgServices: [
|
|
148
165
|
makePgService({
|
|
149
166
|
connectionString: process.env.DATABASE_URL,
|
|
150
167
|
schemas: ['public'],
|
|
151
168
|
}),
|
|
152
169
|
],
|
|
153
|
-
plugins: [MyCustomPlugin],
|
|
170
|
+
plugins: [createStonecropPlugin(), MyCustomPlugin],
|
|
154
171
|
}
|
|
155
|
-
|
|
156
|
-
export default preset
|
|
157
172
|
```
|
|
158
173
|
|
|
159
174
|
### Schema Configuration
|
|
@@ -165,8 +180,8 @@ Use `type: 'schema'` for custom GraphQL schemas with Grafast resolvers:
|
|
|
165
180
|
| `type` | `'schema'` | ✅ | Configuration type discriminator |
|
|
166
181
|
| `schema` | `string \| string[] \| SchemaProvider` | ✅ | Path(s) to .graphql files or schema provider function |
|
|
167
182
|
| `resolvers` | `string` | ❌ | Path to resolvers file (required for .graphql files) |
|
|
168
|
-
| `url` | `string` | ❌ | GraphQL endpoint URL (default:
|
|
169
|
-
| `graphiql` | `boolean` | ❌ | Enable GraphiQL IDE (default: true
|
|
183
|
+
| `url` | `string` | ❌ | GraphQL endpoint URL (default: `/graphql/`) |
|
|
184
|
+
| `graphiql` | `boolean` | ❌ | Enable GraphiQL IDE (default: `true`) |
|
|
170
185
|
|
|
171
186
|
**Example with files:**
|
|
172
187
|
|
|
@@ -176,7 +191,7 @@ export default defineNuxtConfig({
|
|
|
176
191
|
type: 'schema',
|
|
177
192
|
schema: 'server/**/*.graphql',
|
|
178
193
|
resolvers: 'server/resolvers.ts',
|
|
179
|
-
url: '/graphql',
|
|
194
|
+
url: '/graphql/',
|
|
180
195
|
}
|
|
181
196
|
})
|
|
182
197
|
```
|
|
@@ -270,11 +285,10 @@ For database-backed GraphQL APIs:
|
|
|
270
285
|
1. Create your preset file `server/graphile.preset.ts`:
|
|
271
286
|
|
|
272
287
|
```ts
|
|
273
|
-
import {
|
|
274
|
-
import { makePgService } from 'postgraphile/adaptors/pg'
|
|
288
|
+
import { createStonecropPreset, makePgService } from '@stonecrop/graphql-middleware'
|
|
275
289
|
|
|
276
290
|
const preset = {
|
|
277
|
-
extends: [
|
|
291
|
+
extends: [createStonecropPreset()],
|
|
278
292
|
pgServices: [
|
|
279
293
|
makePgService({
|
|
280
294
|
connectionString: process.env.DATABASE_URL,
|
|
@@ -347,12 +361,11 @@ Import the plugins in your preset:
|
|
|
347
361
|
|
|
348
362
|
```ts
|
|
349
363
|
// server/graphile/graphile.preset.ts
|
|
350
|
-
import {
|
|
351
|
-
import { makePgService } from 'postgraphile/adaptors/pg'
|
|
364
|
+
import { createStonecropPreset, makePgService } from '@stonecrop/graphql-middleware'
|
|
352
365
|
import plugins from './plugins'
|
|
353
366
|
|
|
354
367
|
const preset = {
|
|
355
|
-
extends: [
|
|
368
|
+
extends: [createStonecropPreset()],
|
|
356
369
|
pgServices: [makePgService({ /* ... */ })],
|
|
357
370
|
plugins
|
|
358
371
|
}
|
|
@@ -362,78 +375,34 @@ export default preset
|
|
|
362
375
|
|
|
363
376
|
#### Available Middleware Hooks
|
|
364
377
|
|
|
365
|
-
|
|
366
|
-
- `processGraphQLRequestBody` - Process GraphQL request bodies
|
|
367
|
-
- `ruruHTML` - Customize Ruru IDE HTML generation
|
|
368
|
-
- `onSubscribe` - Handle GraphQL subscriptions
|
|
378
|
+
These are hooks provided by the `grafserv` package itself via the Graphile preset system — they are not specific to `@stonecrop/nuxt-grafserv`. The hook used in the example above (`processGraphQLRequestBody`) is the most common entry point. For the full list of available hooks, refer to the [Grafserv documentation](https://grafast.org/grafserv/).
|
|
369
379
|
|
|
370
380
|
## PostGraphile Integration
|
|
371
381
|
|
|
372
|
-
PostGraphile v5+ automatically generates your GraphQL schema from PostgreSQL.
|
|
382
|
+
PostGraphile v5+ automatically generates your GraphQL schema from PostgreSQL. When using a custom preset file (`preset` option), you have access to all PostGraphile preset keys — these are PostGraphile features, not Stonecrop-specific ones.
|
|
373
383
|
|
|
374
384
|
### With Community Plugins
|
|
375
385
|
|
|
386
|
+
Third-party PostGraphile plugins can be added via the `plugins` array in a custom preset file:
|
|
387
|
+
|
|
376
388
|
```typescript
|
|
377
|
-
|
|
378
|
-
import { makePgService } from '
|
|
389
|
+
// server/graphile.preset.ts
|
|
390
|
+
import { createStonecropPreset, makePgService, createStonecropPlugin } from '@stonecrop/graphql-middleware'
|
|
379
391
|
import PgSimplifyInflectorPlugin from '@graphile-contrib/pg-simplify-inflector'
|
|
380
392
|
|
|
381
|
-
|
|
382
|
-
extends: [
|
|
383
|
-
plugins: [PgSimplifyInflectorPlugin],
|
|
393
|
+
export default {
|
|
394
|
+
extends: [createStonecropPreset()],
|
|
395
|
+
plugins: [createStonecropPlugin(), PgSimplifyInflectorPlugin],
|
|
384
396
|
pgServices: [
|
|
385
397
|
makePgService({
|
|
386
398
|
connectionString: process.env.DATABASE_URL,
|
|
387
399
|
schemas: ['public'],
|
|
388
400
|
}),
|
|
389
401
|
],
|
|
390
|
-
schema: {
|
|
391
|
-
defaultBehavior: 'connection', // Enable Relay-style connections
|
|
392
|
-
},
|
|
393
|
-
grafast: {
|
|
394
|
-
explain: process.env.NODE_ENV === 'development', // Plan diagrams in dev
|
|
395
|
-
},
|
|
396
402
|
}
|
|
397
|
-
|
|
398
|
-
export default preset
|
|
399
403
|
```
|
|
400
404
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
```typescript
|
|
404
|
-
import { PostGraphileAmberPreset } from 'postgraphile/presets/amber'
|
|
405
|
-
import { makePgService } from 'postgraphile/adaptors/pg'
|
|
406
|
-
|
|
407
|
-
const preset = {
|
|
408
|
-
extends: [PostGraphileAmberPreset],
|
|
409
|
-
pgServices: [
|
|
410
|
-
makePgService({
|
|
411
|
-
connectionString: process.env.DATABASE_URL,
|
|
412
|
-
schemas: ['public', 'app_private'],
|
|
413
|
-
superuserConnectionString: process.env.SUPERUSER_DATABASE_URL, // For watch mode
|
|
414
|
-
pubsub: true, // Enable LISTEN/NOTIFY for subscriptions
|
|
415
|
-
}),
|
|
416
|
-
],
|
|
417
|
-
gather: {
|
|
418
|
-
// Smart tags for schema customization
|
|
419
|
-
pgJwtTypes: 'app_public.jwt_token',
|
|
420
|
-
},
|
|
421
|
-
schema: {
|
|
422
|
-
// Behavior overrides
|
|
423
|
-
defaultBehavior: '-insert -update -delete', // Read-only by default
|
|
424
|
-
pgJwtSecret: process.env.JWT_SECRET,
|
|
425
|
-
},
|
|
426
|
-
grafast: {
|
|
427
|
-
explain: true,
|
|
428
|
-
context: (requestContext) => ({
|
|
429
|
-
// Custom context for all resolvers
|
|
430
|
-
userId: requestContext.user?.id,
|
|
431
|
-
}),
|
|
432
|
-
},
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
export default preset
|
|
436
|
-
```
|
|
405
|
+
For further PostGraphile preset configuration (behaviours, JWT, subscriptions, smart tags, etc.), refer to the [PostGraphile v5 documentation](https://postgraphile.org/postgraphile/5/).
|
|
437
406
|
|
|
438
407
|
### Why File-Based Presets?
|
|
439
408
|
|
|
@@ -450,10 +419,10 @@ This approach avoids GraphQL module duplication, follows PostGraphile's recommen
|
|
|
450
419
|
**Your preset file needs a default export:**
|
|
451
420
|
|
|
452
421
|
```typescript
|
|
453
|
-
import {
|
|
422
|
+
import { createStonecropPreset } from '@stonecrop/graphql-middleware'
|
|
454
423
|
|
|
455
424
|
const preset = {
|
|
456
|
-
extends: [
|
|
425
|
+
extends: [createStonecropPreset()],
|
|
457
426
|
// ...
|
|
458
427
|
}
|
|
459
428
|
|
|
@@ -466,13 +435,12 @@ Relative imports in preset files work with file extensions:
|
|
|
466
435
|
|
|
467
436
|
```typescript
|
|
468
437
|
// server/graphile/graphile.preset.ts
|
|
469
|
-
import {
|
|
470
|
-
import { makePgService } from 'postgraphile/adaptors/pg'
|
|
438
|
+
import { createStonecropPreset, makePgService } from '@stonecrop/graphql-middleware'
|
|
471
439
|
import { MyPlugin } from './plugins/my-plugin'
|
|
472
440
|
import { AnotherPlugin } from './plugins/another'
|
|
473
441
|
|
|
474
442
|
const preset = {
|
|
475
|
-
extends: [
|
|
443
|
+
extends: [createStonecropPreset()],
|
|
476
444
|
pgServices: [makePgService({/*...*/})],
|
|
477
445
|
plugins: [MyPlugin, AnotherPlugin],
|
|
478
446
|
}
|
|
@@ -577,27 +545,34 @@ See [Grafast Standard Steps](https://grafast.org/grafast/standard-steps) for the
|
|
|
577
545
|
|
|
578
546
|
## Development
|
|
579
547
|
|
|
548
|
+
This package lives inside the Stonecrop Rush monorepo. Use `rushx` instead of `pnpm run` to invoke scripts.
|
|
549
|
+
|
|
580
550
|
```bash
|
|
581
|
-
# Install dependencies
|
|
582
|
-
|
|
551
|
+
# Install all monorepo dependencies (run from the repo root)
|
|
552
|
+
rush install
|
|
553
|
+
|
|
554
|
+
# Bootstrap the playground database (first time only — requires Docker)
|
|
555
|
+
cd playground && node scripts/bootstrap.mjs
|
|
583
556
|
|
|
584
557
|
# Generate type stubs
|
|
585
|
-
|
|
558
|
+
rushx dev:prepare
|
|
586
559
|
|
|
587
|
-
# Develop with the playground
|
|
588
|
-
|
|
560
|
+
# Develop with the playground (from nuxt_grafserv/)
|
|
561
|
+
rushx dev
|
|
589
562
|
|
|
590
563
|
# Build the module
|
|
591
|
-
|
|
564
|
+
rushx build
|
|
592
565
|
|
|
593
566
|
# Run ESLint
|
|
594
|
-
|
|
567
|
+
rushx lint
|
|
595
568
|
|
|
596
569
|
# Run tests
|
|
597
|
-
|
|
598
|
-
|
|
570
|
+
rushx test
|
|
571
|
+
rushx test:watch
|
|
599
572
|
```
|
|
600
573
|
|
|
574
|
+
The playground runs against a PostgreSQL container managed by Docker Compose. The bootstrap script handles container startup, `.env` creation, and running the initial migration. On subsequent runs `rushx dev` is all that's needed (the container persists via a named volume).
|
|
575
|
+
|
|
601
576
|
<!-- Badges -->
|
|
602
577
|
[npm-version-src]: https://img.shields.io/npm/v/@stonecrop/nuxt-grafserv/latest.svg?style=flat&colorA=020420&colorB=00DC82
|
|
603
578
|
[npm-version-href]: https://npmjs.com/package/@stonecrop/nuxt-grafserv
|
package/dist/module.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { NuxtModule } from '@nuxt/schema';
|
|
2
2
|
import { GraphQLSchema } from 'graphql';
|
|
3
|
+
import { FieldCasing } from '@stonecrop/graphql-middleware';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Schema provider function - returns a GraphQL schema
|
|
@@ -16,19 +17,30 @@ type SchemaProvider = () => GraphQLSchema | Promise<GraphQLSchema>;
|
|
|
16
17
|
*
|
|
17
18
|
* @example
|
|
18
19
|
* ```typescript
|
|
20
|
+
* // Minimal config — no preset file needed. Uses DATABASE_URL automatically.
|
|
21
|
+
* export default defineNuxtConfig({
|
|
22
|
+
* modules: ['@stonecrop/nuxt-grafserv'],
|
|
23
|
+
* grafserv: {
|
|
24
|
+
* type: 'postgraphile',
|
|
25
|
+
* // fieldCasing: 'pascal', // optional — defaults to 'camel'
|
|
26
|
+
* // schemas: ['public', 'auth'], // optional — defaults to ['public']
|
|
27
|
+
* // explain: true, // optional — enables Ruru Explain tab; never set in production
|
|
28
|
+
* },
|
|
29
|
+
* })
|
|
30
|
+
*
|
|
31
|
+
* // With an explicit preset file (for custom plugins, connection, etc.):
|
|
19
32
|
* // 1. Create server/graphile.preset.ts
|
|
20
|
-
* import {
|
|
21
|
-
* import { makePgService } from 'postgraphile/adaptors/pg'
|
|
33
|
+
* import { createStonecropPreset, makePgService, createStonecropPlugin } from '@stonecrop/graphql-middleware'
|
|
22
34
|
*
|
|
23
35
|
* export default {
|
|
24
|
-
* extends: [
|
|
36
|
+
* extends: [createStonecropPreset()],
|
|
25
37
|
* pgServices: [
|
|
26
38
|
* makePgService({
|
|
27
39
|
* connectionString: process.env.DATABASE_URL,
|
|
28
40
|
* schemas: ['public'],
|
|
29
41
|
* }),
|
|
30
42
|
* ],
|
|
31
|
-
* plugins: [MyCustomPlugin],
|
|
43
|
+
* plugins: [createStonecropPlugin(), MyCustomPlugin],
|
|
32
44
|
* }
|
|
33
45
|
*
|
|
34
46
|
* // 2. Reference the preset file in nuxt.config.ts
|
|
@@ -47,17 +59,55 @@ interface PostGraphileConfig {
|
|
|
47
59
|
/** Configuration type discriminator */
|
|
48
60
|
type: 'postgraphile';
|
|
49
61
|
/**
|
|
50
|
-
* Path to PostGraphile preset file
|
|
62
|
+
* Path to PostGraphile preset file.
|
|
63
|
+
*
|
|
64
|
+
* When omitted, a default preset is synthesized using `DATABASE_URL` for the
|
|
65
|
+
* connection string and `fieldCasing`/`schemas`/`explain` from this config.
|
|
66
|
+
* A warning is emitted at startup if `DATABASE_URL` is not set.
|
|
51
67
|
*
|
|
52
68
|
* The preset file must export the preset configuration using default export:
|
|
53
69
|
* `export default { extends: [...], pgServices: [...], plugins: [...] }`
|
|
54
70
|
*
|
|
55
|
-
* A PostGraphile instance will be created from this preset at build time.
|
|
56
|
-
*
|
|
57
71
|
* @example './server/graphile.preset.ts'
|
|
58
72
|
* @example './server/graphile.preset.js'
|
|
59
73
|
*/
|
|
60
|
-
preset
|
|
74
|
+
preset?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Column-to-field name casing for the synthesized preset.
|
|
77
|
+
* - `'camel'` (default): `my_column` → `myColumn` (PostGraphile Amber default)
|
|
78
|
+
* - `'pascal'`: `my_column` → `MyColumn`
|
|
79
|
+
*
|
|
80
|
+
* Ignored when an explicit `preset` file is provided.
|
|
81
|
+
*/
|
|
82
|
+
fieldCasing?: FieldCasing;
|
|
83
|
+
/**
|
|
84
|
+
* PostgreSQL schemas to expose. Defaults to `['public']`.
|
|
85
|
+
* Ignored when an explicit `preset` file is provided.
|
|
86
|
+
*/
|
|
87
|
+
schemas?: string[];
|
|
88
|
+
/**
|
|
89
|
+
* Enable the Ruru Explain tab (shows Grafast plan + SQL for each query).
|
|
90
|
+
* **Never enable in production** — it exposes query internals to any client.
|
|
91
|
+
* Ignored when an explicit `preset` file is provided.
|
|
92
|
+
*
|
|
93
|
+
* @default false
|
|
94
|
+
*/
|
|
95
|
+
explain?: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Enable developer-friendly debug mode. When `true`, the synthesized preset
|
|
98
|
+
* automatically sets `grafast.explain: true`, injects `createDebugPlugin()`,
|
|
99
|
+
* and configures `grafserv.maskError` as a pass-through so full error details
|
|
100
|
+
* are visible in the GraphQL response.
|
|
101
|
+
*
|
|
102
|
+
* This is a convenience switch for local development — it is the equivalent of
|
|
103
|
+
* setting `explain: true` plus the debug plugin plus unmasked errors at once.
|
|
104
|
+
* **Never enable in production**.
|
|
105
|
+
*
|
|
106
|
+
* Ignored when an explicit `preset` file is provided.
|
|
107
|
+
*
|
|
108
|
+
* @default false
|
|
109
|
+
*/
|
|
110
|
+
debug?: boolean;
|
|
61
111
|
/** GraphQL endpoint URL (default: '/graphql/') */
|
|
62
112
|
url?: string;
|
|
63
113
|
/** Whether to enable GraphiQL IDE (default: true in dev, false in prod) */
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import { useLogger, defineNuxtModule, createResolver } from '@nuxt/kit';
|
|
4
|
+
import { useLogger, defineNuxtModule, createResolver, addServerPlugin, addServerHandler } from '@nuxt/kit';
|
|
5
5
|
|
|
6
6
|
const logger = useLogger("@stonecrop/nuxt-grafserv");
|
|
7
7
|
function validateConfig(options) {
|
|
@@ -13,12 +13,6 @@ Example: grafserv: { type: 'postgraphile', preset: { ... } }`
|
|
|
13
13
|
}
|
|
14
14
|
if (options.type === "postgraphile") {
|
|
15
15
|
const config = options;
|
|
16
|
-
if (!config.preset) {
|
|
17
|
-
throw new Error(
|
|
18
|
-
`[@stonecrop/nuxt-grafserv] PostGraphile configuration error: 'preset' field is required when type is 'postgraphile'.
|
|
19
|
-
Example: { type: 'postgraphile', preset: { extends: [PostGraphileAmberPreset], ... } }`
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
16
|
if ("schema" in config || "resolvers" in config) {
|
|
23
17
|
logger.warn(
|
|
24
18
|
`PostGraphile configuration should not include 'schema' or 'resolvers' fields. The schema is generated from the 'preset' configuration.`
|
|
@@ -78,48 +72,108 @@ const module$1 = defineNuxtModule({
|
|
|
78
72
|
logger.info(`Grafserv server alias: ${config.alias["#grafserv-server"]}`);
|
|
79
73
|
if (options.type === "postgraphile") {
|
|
80
74
|
logger.info("Using PostGraphile preset configuration");
|
|
81
|
-
|
|
82
|
-
if (!
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
75
|
+
config.virtual = config.virtual || {};
|
|
76
|
+
if (!options.preset) {
|
|
77
|
+
logger.info("Synthesizing PostGraphile preset from DATABASE_URL");
|
|
78
|
+
const explain = options.debug ? true : options.explain ?? false;
|
|
79
|
+
const debug = options.debug ?? false;
|
|
80
|
+
const fieldCasing = options.fieldCasing || "camel";
|
|
81
|
+
const schemas = JSON.stringify(options.schemas || ["public"]);
|
|
82
|
+
const extraImports = debug ? `, createDebugPlugin` : ``;
|
|
83
|
+
const pluginsList = debug ? `createStonecropPlugin({ debug: true }), createDebugPlugin()` : `createStonecropPlugin({ debug: false })`;
|
|
84
|
+
const grafservBlock = !debug ? `` : `
|
|
85
|
+
grafserv: {
|
|
86
|
+
maskError(error) {
|
|
87
|
+
console.error('maskError was called with the following error:');
|
|
88
|
+
console.error(error);
|
|
89
|
+
console.error('which had an originalError of:');
|
|
90
|
+
console.error(error.originalError);
|
|
91
|
+
const { GraphQLError } = require('postgraphile/graphql');
|
|
92
|
+
const { isSafeError } = require('postgraphile/grafast');
|
|
93
|
+
if (error.originalError instanceof GraphQLError) {
|
|
94
|
+
return error;
|
|
95
|
+
} else if (error.originalError && isSafeError(error.originalError)) {
|
|
96
|
+
return new GraphQLError(
|
|
97
|
+
error.originalError.message,
|
|
98
|
+
error.nodes,
|
|
99
|
+
error.source,
|
|
100
|
+
error.positions,
|
|
101
|
+
error.path,
|
|
102
|
+
error.originalError,
|
|
103
|
+
error.originalError.extensions ?? null,
|
|
104
|
+
);
|
|
105
|
+
} else {
|
|
106
|
+
const { createHash } = require('node:crypto');
|
|
107
|
+
const hash = createHash('sha1').update(String(error)).digest('base64url');
|
|
108
|
+
console.error(\`Masked GraphQL error (hash: '\${hash}')\`, error);
|
|
109
|
+
return new GraphQLError(
|
|
110
|
+
\`An error occurred (logged with hash: '\${hash}')\`,
|
|
111
|
+
error.nodes,
|
|
112
|
+
error.source,
|
|
113
|
+
error.positions,
|
|
114
|
+
error.path,
|
|
115
|
+
error.originalError,
|
|
116
|
+
{},
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
`;
|
|
122
|
+
config.virtual["#internal/grafserv/pgl"] = `
|
|
123
|
+
import { postgraphile } from 'postgraphile'
|
|
124
|
+
import { createStonecropPreset, makePgService, createStonecropPlugin${extraImports} } from '@stonecrop/graphql-middleware'
|
|
125
|
+
|
|
126
|
+
const synthesizedPreset = {
|
|
127
|
+
extends: [createStonecropPreset({ fieldCasing: '${fieldCasing}' })],
|
|
128
|
+
pgServices: [makePgService({ connectionString: process.env.DATABASE_URL, schemas: ${schemas} })],
|
|
129
|
+
plugins: [${pluginsList}],
|
|
130
|
+
grafast: { explain: ${explain} },${grafservBlock}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export const pgl = postgraphile(synthesizedPreset)
|
|
134
|
+
`;
|
|
135
|
+
} else {
|
|
136
|
+
let presetPath = resolveForVirtualModule(options.preset);
|
|
137
|
+
if (!existsSync(presetPath)) {
|
|
138
|
+
const extensions = [".ts", ".js", ".mjs"];
|
|
139
|
+
let found = false;
|
|
140
|
+
for (const ext of extensions) {
|
|
141
|
+
const pathWithExt = presetPath + ext;
|
|
142
|
+
if (existsSync(pathWithExt)) {
|
|
143
|
+
presetPath = pathWithExt;
|
|
144
|
+
found = true;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
91
147
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
`[@stonecrop/nuxt-grafserv] Preset file not found: ${presetPath}
|
|
148
|
+
if (!found) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`[@stonecrop/nuxt-grafserv] Preset file not found: ${presetPath}
|
|
96
151
|
Tried extensions: ${extensions.join(", ")}
|
|
97
152
|
|
|
98
153
|
Create the preset file with your PostGraphile configuration:
|
|
99
154
|
|
|
100
155
|
// ${options.preset}.ts (or .js, .mjs)
|
|
101
|
-
import {
|
|
102
|
-
import { makePgService } from 'postgraphile/adaptors/pg'
|
|
156
|
+
import { createStonecropPreset, makePgService, createStonecropPlugin } from '@stonecrop/graphql-middleware'
|
|
103
157
|
|
|
104
158
|
export default {
|
|
105
|
-
extends: [
|
|
159
|
+
extends: [createStonecropPreset()],
|
|
106
160
|
pgServices: [makePgService({ connectionString: process.env.DATABASE_URL, schemas: ['public'] })],
|
|
107
|
-
plugins: [],
|
|
161
|
+
plugins: [createStonecropPlugin()],
|
|
108
162
|
}
|
|
109
163
|
`
|
|
110
|
-
|
|
164
|
+
);
|
|
165
|
+
}
|
|
111
166
|
}
|
|
167
|
+
logger.info(`Resolved preset path: ${presetPath}`);
|
|
168
|
+
config.virtual["#internal/grafserv/pgl"] = [
|
|
169
|
+
`import { postgraphile } from 'postgraphile'`,
|
|
170
|
+
`import preset from '${presetPath}'`,
|
|
171
|
+
``,
|
|
172
|
+
`// Create PostGraphile instance with the preset`,
|
|
173
|
+
`// This handles schema building, watch mode, and lifecycle`,
|
|
174
|
+
`export const pgl = postgraphile(preset)`
|
|
175
|
+
].join("\n");
|
|
112
176
|
}
|
|
113
|
-
logger.info(`Resolved preset path: ${presetPath}`);
|
|
114
|
-
config.virtual = config.virtual || {};
|
|
115
|
-
config.virtual["#internal/grafserv/pgl"] = [
|
|
116
|
-
`import { postgraphile } from 'postgraphile'`,
|
|
117
|
-
`import preset from '${presetPath}'`,
|
|
118
|
-
``,
|
|
119
|
-
`// Create PostGraphile instance with the preset`,
|
|
120
|
-
`// This handles schema building, watch mode, and lifecycle`,
|
|
121
|
-
`export const pgl = postgraphile(preset)`
|
|
122
|
-
].join("\n");
|
|
123
177
|
config.virtual["#internal/grafserv/resolvers"] = "export default null";
|
|
124
178
|
config.runtimeConfig = config.runtimeConfig || {};
|
|
125
179
|
config.runtimeConfig.grafserv = {
|
|
@@ -163,18 +217,21 @@ export default {
|
|
|
163
217
|
config.virtual["#internal/grafserv/resolvers"] = "export default null";
|
|
164
218
|
}
|
|
165
219
|
}
|
|
166
|
-
config.externals =
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
220
|
+
config.externals = {
|
|
221
|
+
...config.externals,
|
|
222
|
+
external: [
|
|
223
|
+
...config.externals?.external ?? [],
|
|
224
|
+
"graphql",
|
|
225
|
+
"grafast",
|
|
226
|
+
"postgraphile",
|
|
227
|
+
"graphile-config",
|
|
228
|
+
"graphile-build",
|
|
229
|
+
"@dataplan/pg",
|
|
230
|
+
"@graphql-tools/schema",
|
|
231
|
+
"@graphql-tools/load",
|
|
232
|
+
"@graphql-tools/graphql-file-loader"
|
|
233
|
+
]
|
|
234
|
+
};
|
|
178
235
|
const grafastPath = require.resolve("grafast");
|
|
179
236
|
config.alias = config.alias || {};
|
|
180
237
|
config.alias["grafast"] = grafastPath;
|
|
@@ -184,21 +241,16 @@ export default {
|
|
|
184
241
|
config.typescript.tsConfig.compilerOptions.allowImportingTsExtensions = true;
|
|
185
242
|
config.typescript.tsConfig.compilerOptions.moduleResolution = "bundler";
|
|
186
243
|
});
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
config.handlers.push({
|
|
198
|
-
route: "/graphql/cache",
|
|
199
|
-
handler: resolve("./runtime/cache")
|
|
200
|
-
});
|
|
201
|
-
});
|
|
244
|
+
if (options.type === "postgraphile" && !options.preset) {
|
|
245
|
+
addServerPlugin(resolve("./runtime/startup-check"));
|
|
246
|
+
}
|
|
247
|
+
addServerHandler({ route: options.url || "/graphql/", handler: resolve("./runtime/handler") });
|
|
248
|
+
if (options.graphiql) {
|
|
249
|
+
addServerHandler({ route: "/ruru-static/**", handler: resolve("./runtime/ruru-static") });
|
|
250
|
+
}
|
|
251
|
+
if (nuxt.options.dev) {
|
|
252
|
+
addServerHandler({ route: "/graphql/cache", handler: resolve("./runtime/cache") });
|
|
253
|
+
}
|
|
202
254
|
if (options.url) {
|
|
203
255
|
nuxt.hook("devtools:customTabs", (tabs) => {
|
|
204
256
|
tabs.push({
|
|
@@ -217,7 +269,7 @@ export default {
|
|
|
217
269
|
nuxt.hook("builder:watch", async (event, path) => {
|
|
218
270
|
const isSchemaFile = path.endsWith(".graphql");
|
|
219
271
|
const isResolverFile = options.type === "schema" && options.resolvers && path.includes(options.resolvers.replace("./", ""));
|
|
220
|
-
const isPresetFile = options.type === "postgraphile" && path.includes(options.preset.replace("./", ""));
|
|
272
|
+
const isPresetFile = options.type === "postgraphile" && !!options.preset && path.includes(options.preset.replace("./", ""));
|
|
221
273
|
if (isSchemaFile || isResolverFile || isPresetFile) {
|
|
222
274
|
if (!cacheClearing) {
|
|
223
275
|
cacheClearing = true;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { defineNitroPlugin } from "nitropack/runtime";
|
|
2
|
+
export default defineNitroPlugin(() => {
|
|
3
|
+
if (!process.env.DATABASE_URL) {
|
|
4
|
+
console.warn(
|
|
5
|
+
"[@stonecrop/nuxt-grafserv] DATABASE_URL is not set. The synthesized PostGraphile preset will fail to connect. Set DATABASE_URL or provide an explicit preset file via the `preset` option."
|
|
6
|
+
);
|
|
7
|
+
}
|
|
8
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stonecrop/nuxt-grafserv",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"description": "Pluggable Grafserv GraphQL server as Nuxt Module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -40,11 +40,11 @@
|
|
|
40
40
|
"@graphql-tools/load": "^8.1.10",
|
|
41
41
|
"@graphql-tools/stitch": "^10.1.18",
|
|
42
42
|
"@graphql-tools/wrap": "^11.1.14",
|
|
43
|
-
"grafast": "^1.0.
|
|
43
|
+
"grafast": "^1.0.2",
|
|
44
44
|
"grafserv": "^1.0.0",
|
|
45
45
|
"graphql": "^16.14.0",
|
|
46
46
|
"postgraphile": "^5.0.3",
|
|
47
|
-
"@stonecrop/graphql-middleware": "0.
|
|
47
|
+
"@stonecrop/graphql-middleware": "0.13.1"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@eslint/js": "^10.0.1",
|