@stonecrop/nuxt-grafserv 0.12.7 → 0.13.0

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
@@ -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 postgraphile
42
+ pnpm add @stonecrop/nuxt-grafserv
43
43
 
44
44
  # Using yarn
45
- yarn add @stonecrop/nuxt-grafserv postgraphile
45
+ yarn add @stonecrop/nuxt-grafserv
46
46
 
47
47
  # Using npm
48
- npm install @stonecrop/nuxt-grafserv postgraphile
48
+ npm install @stonecrop/nuxt-grafserv
49
49
  ```
50
50
 
51
- 2. Create your preset file `server/graphile.preset.ts`:
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
- const preset = {
58
- extends: [PostGraphileAmberPreset],
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` | ✅ | Path to PostGraphile preset file (e.g., './server/graphile.preset.ts') |
121
- | `url` | `string` | ❌ | GraphQL endpoint URL (default: '/graphql/') |
122
- | `graphiql` | `boolean` | ❌ | Enable GraphiQL IDE (default: true in dev, false in prod) |
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
- > **Important:** The preset must be a file path, not an inline object. See "Why File-Based Presets?" section below.
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
- preset: './server/graphile.preset.ts',
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 { PostGraphileAmberPreset } from 'postgraphile/presets/amber'
143
- import { makePgService } from 'postgraphile/adaptors/pg'
160
+ import { createStonecropPreset, makePgService, createStonecropPlugin } from '@stonecrop/graphql-middleware'
144
161
 
145
- const preset = {
146
- extends: [PostGraphileAmberPreset],
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: '/graphql/') |
169
- | `graphiql` | `boolean` | ❌ | Enable GraphiQL IDE (default: true in dev, false in prod) |
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 { PostGraphileAmberPreset } from 'postgraphile/presets/amber'
274
- import { makePgService } from 'postgraphile/adaptors/pg'
288
+ import { createStonecropPreset, makePgService } from '@stonecrop/graphql-middleware'
275
289
 
276
290
  const preset = {
277
- extends: [PostGraphileAmberPreset],
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 { PostGraphileAmberPreset } from 'postgraphile/presets/amber'
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: [PostGraphileAmberPreset],
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
- - `processRequest` - Process all incoming requests
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
- import { PostGraphileAmberPreset } from 'postgraphile/presets/amber'
378
- import { makePgService } from 'postgraphile/adaptors/pg'
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
- const preset = {
382
- extends: [PostGraphileAmberPreset],
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
- ### Advanced Configuration
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 { PostGraphileAmberPreset } from 'postgraphile/presets/amber'
422
+ import { createStonecropPreset } from '@stonecrop/graphql-middleware'
454
423
 
455
424
  const preset = {
456
- extends: [PostGraphileAmberPreset],
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 { PostGraphileAmberPreset } from 'postgraphile/presets/amber'
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: [PostGraphileAmberPreset],
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
- pnpm install
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
- pnpm run dev:prepare
558
+ rushx dev:prepare
586
559
 
587
- # Develop with the playground
588
- pnpm run dev
560
+ # Develop with the playground (from nuxt_grafserv/)
561
+ rushx dev
589
562
 
590
563
  # Build the module
591
- pnpm run build
564
+ rushx build
592
565
 
593
566
  # Run ESLint
594
- pnpm run lint
567
+ rushx lint
595
568
 
596
569
  # Run tests
597
- pnpm run test
598
- pnpm run test:watch
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 { PostGraphileAmberPreset } from 'postgraphile/presets/amber'
21
- * import { makePgService } from 'postgraphile/adaptors/pg'
33
+ * import { createStonecropPreset, makePgService, createStonecropPlugin } from '@stonecrop/graphql-middleware'
22
34
  *
23
35
  * export default {
24
- * extends: [PostGraphileAmberPreset],
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: string;
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stonecrop/nuxt-grafserv",
3
3
  "configKey": "grafserv",
4
- "version": "0.12.7",
4
+ "version": "0.13.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
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
- let presetPath = resolveForVirtualModule(options.preset);
82
- if (!existsSync(presetPath)) {
83
- const extensions = [".ts", ".js", ".mjs"];
84
- let found = false;
85
- for (const ext of extensions) {
86
- const pathWithExt = presetPath + ext;
87
- if (existsSync(pathWithExt)) {
88
- presetPath = pathWithExt;
89
- found = true;
90
- break;
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
- if (!found) {
94
- throw new Error(
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 { PostGraphileAmberPreset } from 'postgraphile/presets/amber'
102
- import { makePgService } from 'postgraphile/adaptors/pg'
156
+ import { createStonecropPreset, makePgService, createStonecropPlugin } from '@stonecrop/graphql-middleware'
103
157
 
104
158
  export default {
105
- extends: [PostGraphileAmberPreset],
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 = config.externals || {};
167
- config.externals.external = config.externals.external || [];
168
- config.externals.external.push(
169
- "graphql",
170
- "grafast",
171
- "postgraphile",
172
- "graphile-config",
173
- "graphile-build",
174
- "@graphql-tools/schema",
175
- "@graphql-tools/load",
176
- "@graphql-tools/graphql-file-loader"
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
- nuxt.hook("nitro:config", (config) => {
188
- config.handlers = config.handlers || [];
189
- config.handlers.push({
190
- route: options.url || "/graphql/",
191
- handler: resolve("./runtime/handler")
192
- });
193
- config.handlers.push({
194
- route: "/ruru-static/**",
195
- handler: resolve("./runtime/ruru-static")
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,2 @@
1
+ declare const _default: import("nitropack/types").NitroAppPlugin;
2
+ export default _default;
@@ -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.12.7",
3
+ "version": "0.13.0",
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.1",
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.12.7"
47
+ "@stonecrop/graphql-middleware": "0.13.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@eslint/js": "^10.0.1",