@adaptic/backend-legacy 0.0.974 → 0.0.975
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/plugins/http-status-mapper.d.ts +33 -0
- package/esm/plugins/http-status-mapper.d.ts.map +1 -0
- package/esm/plugins/http-status-mapper.js.map +1 -0
- package/esm/plugins/http-status-mapper.mjs +87 -0
- package/esm/plugins/index.d.ts +1 -0
- package/esm/plugins/index.d.ts.map +1 -1
- package/esm/plugins/index.js.map +1 -1
- package/esm/plugins/index.mjs +1 -0
- package/package.json +1 -1
- package/server.cjs +2 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Status Mapper Plugin for Apollo Server 5.
|
|
3
|
+
*
|
|
4
|
+
* Maps well-known GraphQL error codes to their semantically-correct HTTP
|
|
5
|
+
* status codes. Apollo Server 5 defaults to HTTP 500 for any error thrown
|
|
6
|
+
* inside the `context` function (wrapped as ContextFunctionError) and to
|
|
7
|
+
* HTTP 200 for errors thrown inside resolvers — neither default is correct
|
|
8
|
+
* for an authentication failure, and the 500 default actively harms
|
|
9
|
+
* consumers: Apollo Client's observable pipeline crashes on a 5xx response
|
|
10
|
+
* with a GraphQL-shaped body (`Cannot read properties of undefined (reading
|
|
11
|
+
* 'write')`), so the awaited `client.query(...)` Promise neither resolves
|
|
12
|
+
* nor rejects. Downstream `try/catch` blocks never run, and any UI that
|
|
13
|
+
* gates rendering on a `setIsLoading(false)` in `finally` is locked into a
|
|
14
|
+
* permanent loading state.
|
|
15
|
+
*
|
|
16
|
+
* This plugin runs in `willSendResponse` and inspects every GraphQL error in
|
|
17
|
+
* the final response body. If any error carries `extensions.code` in the
|
|
18
|
+
* lookup table below, the response's HTTP status is upgraded accordingly.
|
|
19
|
+
* Doing it here (rather than at each throw site) means we get the same
|
|
20
|
+
* mapping whether the error originated in a context function, a resolver,
|
|
21
|
+
* an `AuthChecker`, or a directive — and a future code path that throws
|
|
22
|
+
* UNAUTHENTICATED cannot accidentally regress to a 500.
|
|
23
|
+
*
|
|
24
|
+
* Mapping policy:
|
|
25
|
+
* UNAUTHENTICATED → 401 (most common; the bug above)
|
|
26
|
+
* FORBIDDEN → 403 (AuthChecker rejections per CORTEX-P0-001)
|
|
27
|
+
* BAD_USER_INPUT → 400 (GraphQL validation already handles syntax; this
|
|
28
|
+
* covers semantic input rejection from validators)
|
|
29
|
+
* Anything else → unchanged (200 for in-body errors, 500 for fatal)
|
|
30
|
+
*/
|
|
31
|
+
import type { ApolloServerPlugin } from '@apollo/server';
|
|
32
|
+
export declare function createHttpStatusMapperPlugin<TContext extends object = object>(): ApolloServerPlugin<TContext>;
|
|
33
|
+
//# sourceMappingURL=http-status-mapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-status-mapper.d.ts","sourceRoot":"","sources":["../../../src/plugins/http-status-mapper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAGnB,MAAM,gBAAgB,CAAC;AAgCxB,wBAAgB,4BAA4B,CAC1C,QAAQ,SAAS,MAAM,GAAG,MAAM,KAC7B,kBAAkB,CAAC,QAAQ,CAAC,CA2BhC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-status-mapper.js","sourceRoot":"","sources":["../../../src/plugins/http-status-mapper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AASH,MAAM,mBAAmB,GAA2B;IAClD,eAAe,EAAE,GAAG;IACpB,SAAS,EAAE,GAAG;IACd,cAAc,EAAE,GAAG;CACpB,CAAC;AAEF;;;;;GAKG;AACH,SAAS,gBAAgB,CACvB,MAAwC;IAExC,IAAI,IAAwB,CAAC;IAC7B,MAAM,QAAQ,GAA2B,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;IACpE,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC;QAClC,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,SAAS;QACvC,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,MAAM;YAAE,SAAS;QACtB,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YAC1E,IAAI,GAAG,MAAM,CAAC;QAChB,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,4BAA4B;IAG1C,OAAO;QACL,KAAK,CAAC,eAAe;YACnB,OAAO;gBACL,KAAK,CAAC,gBAAgB,CACpB,cAA+D;oBAE/D,MAAM,EAAE,QAAQ,EAAE,GAAG,cAAc,CAAC;oBACpC,MAAM,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAC;oBAC1B,kEAAkE;oBAClE,iEAAiE;oBACjE,4DAA4D;oBAC5D,8DAA8D;oBAC9D,UAAU;oBACV,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ;wBAAE,OAAO;oBACnC,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;oBACxC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;wBAAE,OAAO;oBAC3C,MAAM,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;oBACxC,IAAI,MAAM,KAAK,SAAS;wBAAE,OAAO;oBACjC,4DAA4D;oBAC5D,+DAA+D;oBAC/D,qEAAqE;oBACrE,QAAQ,CAAC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;gBAChC,CAAC;aACF,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Status Mapper Plugin for Apollo Server 5.
|
|
3
|
+
*
|
|
4
|
+
* Maps well-known GraphQL error codes to their semantically-correct HTTP
|
|
5
|
+
* status codes. Apollo Server 5 defaults to HTTP 500 for any error thrown
|
|
6
|
+
* inside the `context` function (wrapped as ContextFunctionError) and to
|
|
7
|
+
* HTTP 200 for errors thrown inside resolvers — neither default is correct
|
|
8
|
+
* for an authentication failure, and the 500 default actively harms
|
|
9
|
+
* consumers: Apollo Client's observable pipeline crashes on a 5xx response
|
|
10
|
+
* with a GraphQL-shaped body (`Cannot read properties of undefined (reading
|
|
11
|
+
* 'write')`), so the awaited `client.query(...)` Promise neither resolves
|
|
12
|
+
* nor rejects. Downstream `try/catch` blocks never run, and any UI that
|
|
13
|
+
* gates rendering on a `setIsLoading(false)` in `finally` is locked into a
|
|
14
|
+
* permanent loading state.
|
|
15
|
+
*
|
|
16
|
+
* This plugin runs in `willSendResponse` and inspects every GraphQL error in
|
|
17
|
+
* the final response body. If any error carries `extensions.code` in the
|
|
18
|
+
* lookup table below, the response's HTTP status is upgraded accordingly.
|
|
19
|
+
* Doing it here (rather than at each throw site) means we get the same
|
|
20
|
+
* mapping whether the error originated in a context function, a resolver,
|
|
21
|
+
* an `AuthChecker`, or a directive — and a future code path that throws
|
|
22
|
+
* UNAUTHENTICATED cannot accidentally regress to a 500.
|
|
23
|
+
*
|
|
24
|
+
* Mapping policy:
|
|
25
|
+
* UNAUTHENTICATED → 401 (most common; the bug above)
|
|
26
|
+
* FORBIDDEN → 403 (AuthChecker rejections per CORTEX-P0-001)
|
|
27
|
+
* BAD_USER_INPUT → 400 (GraphQL validation already handles syntax; this
|
|
28
|
+
* covers semantic input rejection from validators)
|
|
29
|
+
* Anything else → unchanged (200 for in-body errors, 500 for fatal)
|
|
30
|
+
*/
|
|
31
|
+
const CODE_TO_HTTP_STATUS = {
|
|
32
|
+
UNAUTHENTICATED: 401,
|
|
33
|
+
FORBIDDEN: 403,
|
|
34
|
+
BAD_USER_INPUT: 400,
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Returns the highest-priority HTTP status implied by the GraphQL errors in
|
|
38
|
+
* the response, or undefined if no mapping applies. Priority order: 401 over
|
|
39
|
+
* 403 over 400 — auth failures trump everything else because they're the
|
|
40
|
+
* primary signal a client needs to refresh its token / reauthenticate.
|
|
41
|
+
*/
|
|
42
|
+
function deriveHttpStatus(errors) {
|
|
43
|
+
let best;
|
|
44
|
+
const priority = { 401: 3, 403: 2, 400: 1 };
|
|
45
|
+
for (const err of errors) {
|
|
46
|
+
const code = err.extensions?.code;
|
|
47
|
+
if (typeof code !== 'string')
|
|
48
|
+
continue;
|
|
49
|
+
const status = CODE_TO_HTTP_STATUS[code];
|
|
50
|
+
if (!status)
|
|
51
|
+
continue;
|
|
52
|
+
if (best === undefined || (priority[status] ?? 0) > (priority[best] ?? 0)) {
|
|
53
|
+
best = status;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return best;
|
|
57
|
+
}
|
|
58
|
+
export function createHttpStatusMapperPlugin() {
|
|
59
|
+
return {
|
|
60
|
+
async requestDidStart() {
|
|
61
|
+
return {
|
|
62
|
+
async willSendResponse(requestContext) {
|
|
63
|
+
const { response } = requestContext;
|
|
64
|
+
const { body } = response;
|
|
65
|
+
// Only the `single` response kind carries a single `errors` array
|
|
66
|
+
// we can inspect synchronously. Incremental delivery (`@defer` /
|
|
67
|
+
// `@stream`) uses `incremental` and would require per-chunk
|
|
68
|
+
// mapping; we don't use those features yet, so this is a safe
|
|
69
|
+
// narrow.
|
|
70
|
+
if (body.kind !== 'single')
|
|
71
|
+
return;
|
|
72
|
+
const errors = body.singleResult.errors;
|
|
73
|
+
if (!errors || errors.length === 0)
|
|
74
|
+
return;
|
|
75
|
+
const status = deriveHttpStatus(errors);
|
|
76
|
+
if (status === undefined)
|
|
77
|
+
return;
|
|
78
|
+
// Apollo Server only sets `http.status` for fatal errors by
|
|
79
|
+
// default; assigning here overrides that. The `http` object is
|
|
80
|
+
// always present on the response when reached via expressMiddleware.
|
|
81
|
+
response.http.status = status;
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=http-status-mapper.js.map
|
package/esm/plugins/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAE,4BAA4B,EAAE,MAAM,sBAAsB,CAAC"}
|
package/esm/plugins/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/plugins/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/plugins/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAE,4BAA4B,EAAE,MAAM,sBAAsB,CAAC"}
|
package/esm/plugins/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adaptic/backend-legacy",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.975",
|
|
4
4
|
"description": "Backend executable CRUD functions with dynamic variables construction, and type definitions for the Adaptic AI platform.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "index.d.ts",
|
package/server.cjs
CHANGED
|
@@ -54,6 +54,7 @@ const ws_1 = require("ws");
|
|
|
54
54
|
const ws_2 = require("graphql-ws/lib/use/ws");
|
|
55
55
|
const auth_1 = require("./middleware/auth.cjs");
|
|
56
56
|
const audit_logger_1 = require("./middleware/audit-logger.cjs");
|
|
57
|
+
const http_status_mapper_1 = require("./plugins/http-status-mapper.cjs");
|
|
57
58
|
const prismaClient_1 = __importStar(require("./prismaClient.cjs"));
|
|
58
59
|
const health_1 = require("./health.cjs");
|
|
59
60
|
const child_process_1 = require("child_process");
|
|
@@ -139,6 +140,7 @@ const startServer = async () => {
|
|
|
139
140
|
plugins: [
|
|
140
141
|
(0, drainHttpServer_1.ApolloServerPluginDrainHttpServer)({ httpServer }),
|
|
141
142
|
(0, audit_logger_1.createAuditLogPlugin)(),
|
|
143
|
+
(0, http_status_mapper_1.createHttpStatusMapperPlugin)(),
|
|
142
144
|
],
|
|
143
145
|
formatError: (err) => {
|
|
144
146
|
var _a, _b;
|