@constructive-io/graphql-server 5.23.0 → 5.25.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/esm/middleware/standing.js +36 -0
- package/esm/plugins/error-events-plugin.js +21 -18
- package/esm/server.js +4 -0
- package/middleware/standing.d.ts +19 -0
- package/middleware/standing.js +40 -0
- package/package.json +15 -15
- package/plugins/error-events-plugin.d.ts +13 -11
- package/plugins/error-events-plugin.js +23 -20
- package/server.js +4 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import './types'; // for Request type
|
|
2
|
+
import { errors } from '@constructive-io/errors';
|
|
3
|
+
import { respondWithGraphQLError } from '../errors/graphql-response';
|
|
4
|
+
/**
|
|
5
|
+
* Express middleware that refuses requests pinned to a database that is not in
|
|
6
|
+
* good standing.
|
|
7
|
+
*
|
|
8
|
+
* The decision is the `standing` loader's (system-controlled
|
|
9
|
+
* `suspended_at`/`suspended_reason` on `metaschema_public.database`, cached for
|
|
10
|
+
* a few seconds), so an established client keeps being served for at most that
|
|
11
|
+
* window after a suspension. A database the plane does not know is refused the
|
|
12
|
+
* same way: a request pinned to it has nothing to run against. A lookup failure
|
|
13
|
+
* is handed to the error handler — "could not verify" is not "allowed".
|
|
14
|
+
*
|
|
15
|
+
* A request without a resolved database (no context, or a plane without
|
|
16
|
+
* `metaschema_public`) has nothing to be suspended and passes through.
|
|
17
|
+
*
|
|
18
|
+
* Mount after the context middleware and before the GraphQL handler.
|
|
19
|
+
*/
|
|
20
|
+
export const createStandingMiddleware = () => {
|
|
21
|
+
return async (req, res, next) => {
|
|
22
|
+
let standing;
|
|
23
|
+
try {
|
|
24
|
+
standing = await req.constructive?.useModule('standing');
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
next(e);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (standing && (!standing.exists || standing.suspended)) {
|
|
31
|
+
respondWithGraphQLError(res, errors.ACCESS_SUSPENDED(standing.reason ? { reason: standing.reason } : {}), { status: 403 });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
next();
|
|
35
|
+
};
|
|
36
|
+
};
|
|
@@ -5,7 +5,6 @@ import { escapeIdentifier } from 'pg';
|
|
|
5
5
|
import { withPgClient } from 'pg-query-context';
|
|
6
6
|
import { normalizeError } from '../middleware/mask-error';
|
|
7
7
|
const log = new Logger('error-events');
|
|
8
|
-
export const GRAPHQL_ERROR_EVENT = 'graphql.error';
|
|
9
8
|
const getExpressRequest = (requestContext) => requestContext?.expressv4?.req;
|
|
10
9
|
/**
|
|
11
10
|
* The first structured, public-classified registry code among the errors.
|
|
@@ -20,23 +19,31 @@ const refusalCode = (errors) => {
|
|
|
20
19
|
}
|
|
21
20
|
return undefined;
|
|
22
21
|
};
|
|
23
|
-
export const
|
|
22
|
+
export const recordErrorSql = (events) => {
|
|
23
|
+
if (!events.recordError) {
|
|
24
|
+
throw new Error(`events module ${events.privateSchemaName} has no record_error function`);
|
|
25
|
+
}
|
|
26
|
+
return `SELECT ${escapeIdentifier(events.privateSchemaName)}.${escapeIdentifier(events.recordError)}($1, $2::uuid, $3::jsonb)`;
|
|
27
|
+
};
|
|
24
28
|
/**
|
|
25
|
-
* Records
|
|
26
|
-
*
|
|
27
|
-
* transaction, so the event is written afterwards in a fresh transaction
|
|
28
|
-
* the same request claims, via the tenant's events module
|
|
29
|
+
* Records a refused authenticated mutation as an event named after the error
|
|
30
|
+
* code raised by `errors.raise_error`. The refusal rolled back the mutation's
|
|
31
|
+
* own transaction, so the event is written afterwards in a fresh transaction
|
|
32
|
+
* under the same request claims, via the tenant's events module
|
|
33
|
+
* `record_error`, which classifies the code as an error that earns no ladder
|
|
34
|
+
* progress.
|
|
29
35
|
*
|
|
30
|
-
* The server carries no policy
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* drive writes. The client response is
|
|
36
|
+
* The server carries no policy: the code is the event, and the database decides
|
|
37
|
+
* what it means — a ladder's `revoked_by` names the code directly (e.g.
|
|
38
|
+
* PRINCIPAL_CHILD_WIDENS demoting a principal on the trust ladder). Endpoints
|
|
39
|
+
* without an events module record nothing. Unauthenticated requests are never
|
|
40
|
+
* recorded, so anonymous traffic cannot drive writes. The client response is
|
|
41
|
+
* never altered.
|
|
35
42
|
*/
|
|
36
43
|
export const createErrorEventsPlugin = (pool) => ({
|
|
37
44
|
name: 'ErrorEventsPlugin',
|
|
38
45
|
version: '0.0.0',
|
|
39
|
-
description: 'Records
|
|
46
|
+
description: 'Records a refused authenticated mutation as its error code through the tenant events module.',
|
|
40
47
|
grafast: {
|
|
41
48
|
middleware: {
|
|
42
49
|
async execute(next, event) {
|
|
@@ -60,14 +67,10 @@ export const createErrorEventsPlugin = (pool) => ({
|
|
|
60
67
|
if (!events || !pgSettings)
|
|
61
68
|
return result;
|
|
62
69
|
const operation = args.operationName ?? getOperationAST(args.document)?.name?.value ?? null;
|
|
63
|
-
await withPgClient(pool, pgSettings, (client) => client.query(
|
|
64
|
-
GRAPHQL_ERROR_EVENT,
|
|
65
|
-
actorId,
|
|
66
|
-
JSON.stringify({ code, operation })
|
|
67
|
-
]));
|
|
70
|
+
await withPgClient(pool, pgSettings, (client) => client.query(recordErrorSql(events), [code, actorId, JSON.stringify({ operation })]));
|
|
68
71
|
}
|
|
69
72
|
catch (err) {
|
|
70
|
-
log.error(`${label} failed to record
|
|
73
|
+
log.error(`${label} failed to record refusal ${code} for ${actorId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
71
74
|
}
|
|
72
75
|
return result;
|
|
73
76
|
}
|
package/esm/server.js
CHANGED
|
@@ -33,6 +33,7 @@ import { localObservabilityOnly } from './middleware/observability/guard';
|
|
|
33
33
|
import { createRequestLogger } from './middleware/observability/request-logger';
|
|
34
34
|
import { createRequestProtectionMiddleware } from './middleware/request-protection';
|
|
35
35
|
import { getRoutingSchema } from './middleware/routing';
|
|
36
|
+
import { createStandingMiddleware } from './middleware/standing';
|
|
36
37
|
import { createPlatformRefusalRecorder, installRefusalRecorder } from './refusals/recorder';
|
|
37
38
|
const log = new Logger('server');
|
|
38
39
|
/**
|
|
@@ -146,6 +147,9 @@ class Server {
|
|
|
146
147
|
loaders: createDefaultRegistry(),
|
|
147
148
|
routingSchema: getRoutingSchema(effectiveOpts)
|
|
148
149
|
}));
|
|
150
|
+
// A suspended (or unknown) database is refused before anything is spent on
|
|
151
|
+
// the request; billing and platform admins set that state, the loader reads it.
|
|
152
|
+
app.use(createStandingMiddleware());
|
|
149
153
|
// Resolve the tenant's protection bounds before anything can spend budget
|
|
150
154
|
// on the request (and before the GraphQL handler reads them for pgSettings).
|
|
151
155
|
app.use(createRequestProtectionMiddleware());
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import './types';
|
|
2
|
+
import type { RequestHandler } from 'express';
|
|
3
|
+
/**
|
|
4
|
+
* Express middleware that refuses requests pinned to a database that is not in
|
|
5
|
+
* good standing.
|
|
6
|
+
*
|
|
7
|
+
* The decision is the `standing` loader's (system-controlled
|
|
8
|
+
* `suspended_at`/`suspended_reason` on `metaschema_public.database`, cached for
|
|
9
|
+
* a few seconds), so an established client keeps being served for at most that
|
|
10
|
+
* window after a suspension. A database the plane does not know is refused the
|
|
11
|
+
* same way: a request pinned to it has nothing to run against. A lookup failure
|
|
12
|
+
* is handed to the error handler — "could not verify" is not "allowed".
|
|
13
|
+
*
|
|
14
|
+
* A request without a resolved database (no context, or a plane without
|
|
15
|
+
* `metaschema_public`) has nothing to be suspended and passes through.
|
|
16
|
+
*
|
|
17
|
+
* Mount after the context middleware and before the GraphQL handler.
|
|
18
|
+
*/
|
|
19
|
+
export declare const createStandingMiddleware: () => RequestHandler;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createStandingMiddleware = void 0;
|
|
4
|
+
require("./types"); // for Request type
|
|
5
|
+
const errors_1 = require("@constructive-io/errors");
|
|
6
|
+
const graphql_response_1 = require("../errors/graphql-response");
|
|
7
|
+
/**
|
|
8
|
+
* Express middleware that refuses requests pinned to a database that is not in
|
|
9
|
+
* good standing.
|
|
10
|
+
*
|
|
11
|
+
* The decision is the `standing` loader's (system-controlled
|
|
12
|
+
* `suspended_at`/`suspended_reason` on `metaschema_public.database`, cached for
|
|
13
|
+
* a few seconds), so an established client keeps being served for at most that
|
|
14
|
+
* window after a suspension. A database the plane does not know is refused the
|
|
15
|
+
* same way: a request pinned to it has nothing to run against. A lookup failure
|
|
16
|
+
* is handed to the error handler — "could not verify" is not "allowed".
|
|
17
|
+
*
|
|
18
|
+
* A request without a resolved database (no context, or a plane without
|
|
19
|
+
* `metaschema_public`) has nothing to be suspended and passes through.
|
|
20
|
+
*
|
|
21
|
+
* Mount after the context middleware and before the GraphQL handler.
|
|
22
|
+
*/
|
|
23
|
+
const createStandingMiddleware = () => {
|
|
24
|
+
return async (req, res, next) => {
|
|
25
|
+
let standing;
|
|
26
|
+
try {
|
|
27
|
+
standing = await req.constructive?.useModule('standing');
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
next(e);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (standing && (!standing.exists || standing.suspended)) {
|
|
34
|
+
(0, graphql_response_1.respondWithGraphQLError)(res, errors_1.errors.ACCESS_SUSPENDED(standing.reason ? { reason: standing.reason } : {}), { status: 403 });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
next();
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
exports.createStandingMiddleware = createStandingMiddleware;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constructive-io/graphql-server",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.25.0",
|
|
4
4
|
"author": "Constructive <developers@constructive.io>",
|
|
5
5
|
"description": "Constructive GraphQL Server",
|
|
6
6
|
"main": "index.js",
|
|
@@ -43,19 +43,19 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@agentic-kit/ollama": "2.14.0",
|
|
45
45
|
"@constructive-io/csrf": "^0.29.1",
|
|
46
|
-
"@constructive-io/errors": "^0.
|
|
47
|
-
"@constructive-io/express-context": "^0.
|
|
48
|
-
"@constructive-io/graphql-env": "^3.32.
|
|
49
|
-
"@constructive-io/graphql-types": "^3.31.
|
|
46
|
+
"@constructive-io/errors": "^0.14.0",
|
|
47
|
+
"@constructive-io/express-context": "^0.31.0",
|
|
48
|
+
"@constructive-io/graphql-env": "^3.32.1",
|
|
49
|
+
"@constructive-io/graphql-types": "^3.31.1",
|
|
50
50
|
"@constructive-io/llm-env": "^0.14.1",
|
|
51
|
-
"@constructive-io/query-builder": "^3.13.
|
|
51
|
+
"@constructive-io/query-builder": "^3.13.6",
|
|
52
52
|
"@constructive-io/s3-utils": "^2.33.0",
|
|
53
53
|
"@constructive-io/url-domains": "^2.30.1",
|
|
54
54
|
"@graphile-contrib/pg-many-to-many": "2.0.0-rc.2",
|
|
55
|
-
"@pgpmjs/env": "^2.43.
|
|
55
|
+
"@pgpmjs/env": "^2.43.5",
|
|
56
56
|
"@pgpmjs/logger": "^2.25.1",
|
|
57
|
-
"@pgpmjs/server-utils": "^3.27.
|
|
58
|
-
"@pgpmjs/types": "^2.53.
|
|
57
|
+
"@pgpmjs/server-utils": "^3.27.5",
|
|
58
|
+
"@pgpmjs/types": "^2.53.3",
|
|
59
59
|
"cors": "^2.8.6",
|
|
60
60
|
"deepmerge": "^4.3.1",
|
|
61
61
|
"express": "^5.2.1",
|
|
@@ -64,16 +64,16 @@
|
|
|
64
64
|
"grafserv": "1.0.1",
|
|
65
65
|
"graphile-build": "5.1.1",
|
|
66
66
|
"graphile-build-pg": "5.1.3",
|
|
67
|
-
"graphile-cache": "^4.12.
|
|
67
|
+
"graphile-cache": "^4.12.5",
|
|
68
68
|
"graphile-config": "1.1.0",
|
|
69
|
-
"graphile-function-bindings": "^1.14.
|
|
70
|
-
"graphile-settings": "^6.21.
|
|
69
|
+
"graphile-function-bindings": "^1.14.6",
|
|
70
|
+
"graphile-settings": "^6.21.8",
|
|
71
71
|
"graphile-utils": "5.0.3",
|
|
72
72
|
"graphql": "16.13.0",
|
|
73
73
|
"graphql-upload": "^13.0.0",
|
|
74
74
|
"lru-cache": "^11.2.7",
|
|
75
75
|
"pg": "^8.21.0",
|
|
76
|
-
"pg-cache": "^3.27.
|
|
76
|
+
"pg-cache": "^3.27.5",
|
|
77
77
|
"pg-env": "^1.31.1",
|
|
78
78
|
"pg-query-context": "^2.30.1",
|
|
79
79
|
"pg-sql2": "5.0.1",
|
|
@@ -90,11 +90,11 @@
|
|
|
90
90
|
"@types/request-ip": "^0.0.41",
|
|
91
91
|
"@types/supertest": "^7.2.1",
|
|
92
92
|
"cookie-parser": "^1.4.7",
|
|
93
|
-
"graphile-test": "5.14.
|
|
93
|
+
"graphile-test": "5.14.6",
|
|
94
94
|
"makage": "^0.8.0",
|
|
95
95
|
"nodemon": "^3.1.14",
|
|
96
96
|
"supertest": "^7.2.2",
|
|
97
97
|
"ts-node": "^10.9.2"
|
|
98
98
|
},
|
|
99
|
-
"gitHead": "
|
|
99
|
+
"gitHead": "27be33dc1ba8351e2a2c2bbb99b9c0798c0813b9"
|
|
100
100
|
}
|
|
@@ -2,18 +2,20 @@ import '../middleware/types';
|
|
|
2
2
|
import type { EventsConfig } from '@constructive-io/express-context';
|
|
3
3
|
import type { GraphileConfig } from 'graphile-config';
|
|
4
4
|
import { type Pool } from 'pg';
|
|
5
|
-
export declare const
|
|
6
|
-
export declare const recordEventSql: (events: EventsConfig) => string;
|
|
5
|
+
export declare const recordErrorSql: (events: EventsConfig) => string;
|
|
7
6
|
/**
|
|
8
|
-
* Records
|
|
9
|
-
*
|
|
10
|
-
* transaction, so the event is written afterwards in a fresh transaction
|
|
11
|
-
* the same request claims, via the tenant's events module
|
|
7
|
+
* Records a refused authenticated mutation as an event named after the error
|
|
8
|
+
* code raised by `errors.raise_error`. The refusal rolled back the mutation's
|
|
9
|
+
* own transaction, so the event is written afterwards in a fresh transaction
|
|
10
|
+
* under the same request claims, via the tenant's events module
|
|
11
|
+
* `record_error`, which classifies the code as an error that earns no ladder
|
|
12
|
+
* progress.
|
|
12
13
|
*
|
|
13
|
-
* The server carries no policy
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* drive writes. The client response is
|
|
14
|
+
* The server carries no policy: the code is the event, and the database decides
|
|
15
|
+
* what it means — a ladder's `revoked_by` names the code directly (e.g.
|
|
16
|
+
* PRINCIPAL_CHILD_WIDENS demoting a principal on the trust ladder). Endpoints
|
|
17
|
+
* without an events module record nothing. Unauthenticated requests are never
|
|
18
|
+
* recorded, so anonymous traffic cannot drive writes. The client response is
|
|
19
|
+
* never altered.
|
|
18
20
|
*/
|
|
19
21
|
export declare const createErrorEventsPlugin: (pool: Pool) => GraphileConfig.Plugin;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createErrorEventsPlugin = exports.
|
|
3
|
+
exports.createErrorEventsPlugin = exports.recordErrorSql = void 0;
|
|
4
4
|
require("../middleware/types"); // for Request type
|
|
5
5
|
const logger_1 = require("@pgpmjs/logger");
|
|
6
6
|
const graphql_1 = require("graphql");
|
|
@@ -8,7 +8,6 @@ const pg_1 = require("pg");
|
|
|
8
8
|
const pg_query_context_1 = require("pg-query-context");
|
|
9
9
|
const mask_error_1 = require("../middleware/mask-error");
|
|
10
10
|
const log = new logger_1.Logger('error-events');
|
|
11
|
-
exports.GRAPHQL_ERROR_EVENT = 'graphql.error';
|
|
12
11
|
const getExpressRequest = (requestContext) => requestContext?.expressv4?.req;
|
|
13
12
|
/**
|
|
14
13
|
* The first structured, public-classified registry code among the errors.
|
|
@@ -23,24 +22,32 @@ const refusalCode = (errors) => {
|
|
|
23
22
|
}
|
|
24
23
|
return undefined;
|
|
25
24
|
};
|
|
26
|
-
const
|
|
27
|
-
|
|
25
|
+
const recordErrorSql = (events) => {
|
|
26
|
+
if (!events.recordError) {
|
|
27
|
+
throw new Error(`events module ${events.privateSchemaName} has no record_error function`);
|
|
28
|
+
}
|
|
29
|
+
return `SELECT ${(0, pg_1.escapeIdentifier)(events.privateSchemaName)}.${(0, pg_1.escapeIdentifier)(events.recordError)}($1, $2::uuid, $3::jsonb)`;
|
|
30
|
+
};
|
|
31
|
+
exports.recordErrorSql = recordErrorSql;
|
|
28
32
|
/**
|
|
29
|
-
* Records
|
|
30
|
-
*
|
|
31
|
-
* transaction, so the event is written afterwards in a fresh transaction
|
|
32
|
-
* the same request claims, via the tenant's events module
|
|
33
|
+
* Records a refused authenticated mutation as an event named after the error
|
|
34
|
+
* code raised by `errors.raise_error`. The refusal rolled back the mutation's
|
|
35
|
+
* own transaction, so the event is written afterwards in a fresh transaction
|
|
36
|
+
* under the same request claims, via the tenant's events module
|
|
37
|
+
* `record_error`, which classifies the code as an error that earns no ladder
|
|
38
|
+
* progress.
|
|
33
39
|
*
|
|
34
|
-
* The server carries no policy
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* drive writes. The client response is
|
|
40
|
+
* The server carries no policy: the code is the event, and the database decides
|
|
41
|
+
* what it means — a ladder's `revoked_by` names the code directly (e.g.
|
|
42
|
+
* PRINCIPAL_CHILD_WIDENS demoting a principal on the trust ladder). Endpoints
|
|
43
|
+
* without an events module record nothing. Unauthenticated requests are never
|
|
44
|
+
* recorded, so anonymous traffic cannot drive writes. The client response is
|
|
45
|
+
* never altered.
|
|
39
46
|
*/
|
|
40
47
|
const createErrorEventsPlugin = (pool) => ({
|
|
41
48
|
name: 'ErrorEventsPlugin',
|
|
42
49
|
version: '0.0.0',
|
|
43
|
-
description: 'Records
|
|
50
|
+
description: 'Records a refused authenticated mutation as its error code through the tenant events module.',
|
|
44
51
|
grafast: {
|
|
45
52
|
middleware: {
|
|
46
53
|
async execute(next, event) {
|
|
@@ -64,14 +71,10 @@ const createErrorEventsPlugin = (pool) => ({
|
|
|
64
71
|
if (!events || !pgSettings)
|
|
65
72
|
return result;
|
|
66
73
|
const operation = args.operationName ?? (0, graphql_1.getOperationAST)(args.document)?.name?.value ?? null;
|
|
67
|
-
await (0, pg_query_context_1.withPgClient)(pool, pgSettings, (client) => client.query((0, exports.
|
|
68
|
-
exports.GRAPHQL_ERROR_EVENT,
|
|
69
|
-
actorId,
|
|
70
|
-
JSON.stringify({ code, operation })
|
|
71
|
-
]));
|
|
74
|
+
await (0, pg_query_context_1.withPgClient)(pool, pgSettings, (client) => client.query((0, exports.recordErrorSql)(events), [code, actorId, JSON.stringify({ operation })]));
|
|
72
75
|
}
|
|
73
76
|
catch (err) {
|
|
74
|
-
log.error(`${label} failed to record
|
|
77
|
+
log.error(`${label} failed to record refusal ${code} for ${actorId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
75
78
|
}
|
|
76
79
|
return result;
|
|
77
80
|
}
|
package/server.js
CHANGED
|
@@ -39,6 +39,7 @@ const guard_1 = require("./middleware/observability/guard");
|
|
|
39
39
|
const request_logger_1 = require("./middleware/observability/request-logger");
|
|
40
40
|
const request_protection_1 = require("./middleware/request-protection");
|
|
41
41
|
const routing_1 = require("./middleware/routing");
|
|
42
|
+
const standing_1 = require("./middleware/standing");
|
|
42
43
|
const recorder_1 = require("./refusals/recorder");
|
|
43
44
|
const log = new logger_1.Logger('server');
|
|
44
45
|
/**
|
|
@@ -153,6 +154,9 @@ class Server {
|
|
|
153
154
|
loaders: (0, express_context_1.createDefaultRegistry)(),
|
|
154
155
|
routingSchema: (0, routing_1.getRoutingSchema)(effectiveOpts)
|
|
155
156
|
}));
|
|
157
|
+
// A suspended (or unknown) database is refused before anything is spent on
|
|
158
|
+
// the request; billing and platform admins set that state, the loader reads it.
|
|
159
|
+
app.use((0, standing_1.createStandingMiddleware)());
|
|
156
160
|
// Resolve the tenant's protection bounds before anything can spend budget
|
|
157
161
|
// on the request (and before the GraphQL handler reads them for pgSettings).
|
|
158
162
|
app.use((0, request_protection_1.createRequestProtectionMiddleware)());
|