@constructive-io/graphql-server 5.34.6 → 5.35.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
@@ -70,7 +70,6 @@ Runs an Express server that wires CORS, uploads, domain parsing, auth, and PostG
70
70
  - Automatic GraphQL API generation from PostgreSQL schemas
71
71
  - RLS-aware authentication and per-request `pgSettings`
72
72
  - Meta-schema routing by domain + subdomain
73
- - File uploads via `graphql-upload`
74
73
  - GraphiQL and health check endpoints
75
74
  - Schema cache flush via `/flush` or database notifications
76
75
  - Opt-in observability for memory, DB activity, and Graphile build debugging
@@ -101,7 +100,6 @@ For the operational workflow, sampler output, and heap snapshot usage, see [docs
101
100
  - `GET /healthz` -> health check
102
101
  - `GET /graphiql` -> GraphiQL UI
103
102
  - `GET /graphql` / `POST /graphql` -> GraphQL endpoint
104
- - `POST /graphql` (multipart) -> file uploads
105
103
  - `POST /flush` -> clears cached Graphile schema for the current API; mounted only when `API_FLUSH_TOKEN` is set, and requires `Authorization: Bearer $API_FLUSH_TOKEN`. Without the variable the route is not served (404) — operators deploying a schema-cache flush must set it on the server and on every caller. The `LISTEN/NOTIFY` invalidation path is unaffected.
106
104
  - `GET /debug/memory` -> memory/process/Graphile debug snapshot when observability is enabled
107
105
  - `GET /debug/db` -> PostgreSQL activity/locks/pool debug snapshot when observability is enabled
@@ -16,12 +16,6 @@ const resolveProtection = async (req) => {
16
16
  const resolved = await req.constructive?.useModule('requestProtection');
17
17
  return resolved ?? DEFAULT_REQUEST_PROTECTION;
18
18
  };
19
- /**
20
- * Multipart requests carry file bodies, which are streamed by the upload
21
- * plugin and bounded by the upload limits rather than by the GraphQL request
22
- * size: applying a JSON-sized cap to them would reject every upload.
23
- */
24
- const isMultipart = (req) => (req.get('content-type') ?? '').toLowerCase().startsWith('multipart/form-data');
25
19
  /**
26
20
  * Express middleware that resolves request protection and enforces the one
27
21
  * bound that has to be checked before the body is read.
@@ -50,8 +44,7 @@ export const createRequestProtectionMiddleware = () => {
50
44
  req.requestProtection = protection;
51
45
  const declaredLength = Number(req.get('content-length') ?? '');
52
46
  if (Number.isFinite(declaredLength) &&
53
- declaredLength > protection.maxRequestBytes &&
54
- !isMultipart(req)) {
47
+ declaredLength > protection.maxRequestBytes) {
55
48
  recordRefusal(req, 'request_too_large');
56
49
  respondWithGraphQLError(res, errors.REQUEST_TOO_LARGE({ bytes: declaredLength, limit: protection.maxRequestBytes }));
57
50
  return;
package/esm/server.js CHANGED
@@ -7,7 +7,6 @@ import { healthz, poweredBy, svcCache, trustProxy } from '@pgpmjs/server-utils';
7
7
  import cookieParser from 'cookie-parser';
8
8
  import express from 'express';
9
9
  import { closeAllCaches, graphileCache } from 'graphile-cache';
10
- import graphqlUpload from 'graphql-upload';
11
10
  import { getPgPool } from 'pg-cache';
12
11
  import { createAgenticRouter } from './agentic';
13
12
  import { closeDebugDatabasePools } from './diagnostics/debug-db-snapshot';
@@ -26,7 +25,6 @@ import { favicon } from './middleware/favicon';
26
25
  import { createFlushMiddleware, flushService } from './middleware/flush';
27
26
  import { createFnRouter } from './middleware/fn';
28
27
  import { graphile } from './middleware/graphile';
29
- import { multipartBridge } from './middleware/multipart-bridge';
30
28
  import { createDebugDatabaseMiddleware } from './middleware/observability/debug-db';
31
29
  import { debugMemory } from './middleware/observability/debug-memory';
32
30
  import { localObservabilityOnly } from './middleware/observability/guard';
@@ -128,12 +126,6 @@ class Server {
128
126
  app.use(poweredBy('constructive'));
129
127
  app.use(cookieParser());
130
128
  app.use(cors(fallbackOrigin));
131
- app.use('/graphql', graphqlUpload.graphqlUploadExpress({
132
- maxFileSize: 10 * 1024 * 1024, // 10 MB
133
- maxFiles: 10
134
- }));
135
- // Rewrite Content-Type after graphql-upload so grafserv accepts the request
136
- app.use('/graphql', multipartBridge);
137
129
  app.use(parseDomains());
138
130
  app.use(clientIpMiddleware);
139
131
  app.use(requestIdMiddleware());
@@ -19,12 +19,6 @@ const resolveProtection = async (req) => {
19
19
  const resolved = await req.constructive?.useModule('requestProtection');
20
20
  return resolved ?? express_context_1.DEFAULT_REQUEST_PROTECTION;
21
21
  };
22
- /**
23
- * Multipart requests carry file bodies, which are streamed by the upload
24
- * plugin and bounded by the upload limits rather than by the GraphQL request
25
- * size: applying a JSON-sized cap to them would reject every upload.
26
- */
27
- const isMultipart = (req) => (req.get('content-type') ?? '').toLowerCase().startsWith('multipart/form-data');
28
22
  /**
29
23
  * Express middleware that resolves request protection and enforces the one
30
24
  * bound that has to be checked before the body is read.
@@ -53,8 +47,7 @@ const createRequestProtectionMiddleware = () => {
53
47
  req.requestProtection = protection;
54
48
  const declaredLength = Number(req.get('content-length') ?? '');
55
49
  if (Number.isFinite(declaredLength) &&
56
- declaredLength > protection.maxRequestBytes &&
57
- !isMultipart(req)) {
50
+ declaredLength > protection.maxRequestBytes) {
58
51
  (0, recorder_1.recordRefusal)(req, 'request_too_large');
59
52
  (0, graphql_response_1.respondWithGraphQLError)(res, errors_1.errors.REQUEST_TOO_LARGE({ bytes: declaredLength, limit: protection.maxRequestBytes }));
60
53
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constructive-io/graphql-server",
3
- "version": "5.34.6",
3
+ "version": "5.35.0",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "Constructive GraphQL Server",
6
6
  "main": "index.js",
@@ -43,7 +43,7 @@
43
43
  "@agentic-kit/ollama": "2.16.0",
44
44
  "@constructive-io/csrf": "^0.31.0",
45
45
  "@constructive-io/errors": "^0.25.0",
46
- "@constructive-io/express-context": "^0.36.0",
46
+ "@constructive-io/express-context": "^0.37.0",
47
47
  "@constructive-io/graphql-env": "^3.37.2",
48
48
  "@constructive-io/graphql-types": "^3.36.1",
49
49
  "@constructive-io/llm-env": "^0.16.0",
@@ -71,12 +71,11 @@
71
71
  "graphile-function-bindings": "^1.19.2",
72
72
  "graphile-function-source": "^0.3.2",
73
73
  "graphile-plugin-utils": "^1.14.1",
74
- "graphile-presigned-url-plugin": "^1.23.5",
74
+ "graphile-presigned-url-plugin": "^1.24.0",
75
75
  "graphile-scoped-introspection": "^0.6.2",
76
- "graphile-settings": "^6.30.5",
76
+ "graphile-settings": "^6.31.0",
77
77
  "graphile-utils": "5.0.3",
78
78
  "graphql": "16.13.0",
79
- "graphql-upload": "^13.0.0",
80
79
  "lru-cache": "^11.2.7",
81
80
  "pg": "^8.21.0",
82
81
  "pg-cache": "^3.30.0",
@@ -90,7 +89,6 @@
90
89
  "@types/cookie-parser": "^1.4.10",
91
90
  "@types/cors": "^2.8.17",
92
91
  "@types/express": "^5.0.6",
93
- "@types/graphql-upload": "^8.0.12",
94
92
  "@types/pg": "^8.20.4",
95
93
  "@types/supertest": "^7.2.1",
96
94
  "graphile-test": "5.19.2",
@@ -99,5 +97,5 @@
99
97
  "supertest": "^7.2.2",
100
98
  "ts-node": "^10.9.2"
101
99
  },
102
- "gitHead": "723c0a02471517c3015a218dd179fa9f53b47980"
100
+ "gitHead": "b579521f138b5f9a2922ed1066e2ad5dfe2a7073"
103
101
  }
package/server.js CHANGED
@@ -13,7 +13,6 @@ const server_utils_1 = require("@pgpmjs/server-utils");
13
13
  const cookie_parser_1 = __importDefault(require("cookie-parser"));
14
14
  const express_1 = __importDefault(require("express"));
15
15
  const graphile_cache_1 = require("graphile-cache");
16
- const graphql_upload_1 = __importDefault(require("graphql-upload"));
17
16
  const pg_cache_1 = require("pg-cache");
18
17
  const agentic_1 = require("./agentic");
19
18
  const debug_db_snapshot_1 = require("./diagnostics/debug-db-snapshot");
@@ -32,7 +31,6 @@ const favicon_1 = require("./middleware/favicon");
32
31
  const flush_1 = require("./middleware/flush");
33
32
  const fn_1 = require("./middleware/fn");
34
33
  const graphile_1 = require("./middleware/graphile");
35
- const multipart_bridge_1 = require("./middleware/multipart-bridge");
36
34
  const debug_db_1 = require("./middleware/observability/debug-db");
37
35
  const debug_memory_1 = require("./middleware/observability/debug-memory");
38
36
  const guard_1 = require("./middleware/observability/guard");
@@ -135,12 +133,6 @@ class Server {
135
133
  app.use((0, server_utils_1.poweredBy)('constructive'));
136
134
  app.use((0, cookie_parser_1.default)());
137
135
  app.use((0, cors_1.cors)(fallbackOrigin));
138
- app.use('/graphql', graphql_upload_1.default.graphqlUploadExpress({
139
- maxFileSize: 10 * 1024 * 1024, // 10 MB
140
- maxFiles: 10
141
- }));
142
- // Rewrite Content-Type after graphql-upload so grafserv accepts the request
143
- app.use('/graphql', multipart_bridge_1.multipartBridge);
144
136
  app.use((0, url_domains_1.middleware)());
145
137
  app.use(client_ip_1.clientIpMiddleware);
146
138
  app.use((0, express_context_1.requestIdMiddleware)());
@@ -1,30 +0,0 @@
1
- /**
2
- * Bridge middleware between graphql-upload and grafserv (PostGraphile v5).
3
- *
4
- * graphql-upload's `graphqlUploadExpress()` parses multipart/form-data requests
5
- * and sets `req.body` to a standard GraphQL operation object, but leaves the
6
- * original Content-Type header unchanged. grafserv rejects any Content-Type it
7
- * doesn't recognize (415 Unsupported Media Type) because its allowlist only
8
- * includes `application/json` and `application/graphql`.
9
- *
10
- * This middleware rewrites the Content-Type header to `application/json` after
11
- * graphql-upload has finished parsing, so grafserv accepts the request.
12
- *
13
- * This is the standard bridge pattern for grafserv + graphql-upload.
14
- * See: https://github.com/graphql/graphql-http/discussions/36
15
- *
16
- * Must be registered immediately after `graphqlUploadExpress()` in the
17
- * middleware chain — it relies on graphql-upload having already parsed the
18
- * multipart body into `req.body`.
19
- *
20
- * Security note: multipart/form-data is a CORS "simple request" content type,
21
- * which means browsers send it without a preflight OPTIONS check. The CSRF risk
22
- * is mitigated by the CORS middleware that runs before this middleware in the
23
- * Express pipeline.
24
- */
25
- export const multipartBridge = (req, _res, next) => {
26
- if (req.body != null && req.headers['content-type']?.startsWith('multipart/form-data')) {
27
- req.headers['content-type'] = 'application/json';
28
- }
29
- next();
30
- };
@@ -1,26 +0,0 @@
1
- import type { RequestHandler } from 'express';
2
- /**
3
- * Bridge middleware between graphql-upload and grafserv (PostGraphile v5).
4
- *
5
- * graphql-upload's `graphqlUploadExpress()` parses multipart/form-data requests
6
- * and sets `req.body` to a standard GraphQL operation object, but leaves the
7
- * original Content-Type header unchanged. grafserv rejects any Content-Type it
8
- * doesn't recognize (415 Unsupported Media Type) because its allowlist only
9
- * includes `application/json` and `application/graphql`.
10
- *
11
- * This middleware rewrites the Content-Type header to `application/json` after
12
- * graphql-upload has finished parsing, so grafserv accepts the request.
13
- *
14
- * This is the standard bridge pattern for grafserv + graphql-upload.
15
- * See: https://github.com/graphql/graphql-http/discussions/36
16
- *
17
- * Must be registered immediately after `graphqlUploadExpress()` in the
18
- * middleware chain — it relies on graphql-upload having already parsed the
19
- * multipart body into `req.body`.
20
- *
21
- * Security note: multipart/form-data is a CORS "simple request" content type,
22
- * which means browsers send it without a preflight OPTIONS check. The CSRF risk
23
- * is mitigated by the CORS middleware that runs before this middleware in the
24
- * Express pipeline.
25
- */
26
- export declare const multipartBridge: RequestHandler;
@@ -1,34 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.multipartBridge = void 0;
4
- /**
5
- * Bridge middleware between graphql-upload and grafserv (PostGraphile v5).
6
- *
7
- * graphql-upload's `graphqlUploadExpress()` parses multipart/form-data requests
8
- * and sets `req.body` to a standard GraphQL operation object, but leaves the
9
- * original Content-Type header unchanged. grafserv rejects any Content-Type it
10
- * doesn't recognize (415 Unsupported Media Type) because its allowlist only
11
- * includes `application/json` and `application/graphql`.
12
- *
13
- * This middleware rewrites the Content-Type header to `application/json` after
14
- * graphql-upload has finished parsing, so grafserv accepts the request.
15
- *
16
- * This is the standard bridge pattern for grafserv + graphql-upload.
17
- * See: https://github.com/graphql/graphql-http/discussions/36
18
- *
19
- * Must be registered immediately after `graphqlUploadExpress()` in the
20
- * middleware chain — it relies on graphql-upload having already parsed the
21
- * multipart body into `req.body`.
22
- *
23
- * Security note: multipart/form-data is a CORS "simple request" content type,
24
- * which means browsers send it without a preflight OPTIONS check. The CSRF risk
25
- * is mitigated by the CORS middleware that runs before this middleware in the
26
- * Express pipeline.
27
- */
28
- const multipartBridge = (req, _res, next) => {
29
- if (req.body != null && req.headers['content-type']?.startsWith('multipart/form-data')) {
30
- req.headers['content-type'] = 'application/json';
31
- }
32
- next();
33
- };
34
- exports.multipartBridge = multipartBridge;