@btravstack/graphql-server 0.27.0 → 0.28.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 +3 -1
- package/dist/index.cjs +17 -6
- package/dist/index.d.cts +8 -1
- package/dist/index.d.mts +8 -1
- package/dist/index.mjs +17 -6
- package/package.json +11 -9
package/README.md
CHANGED
|
@@ -11,7 +11,9 @@ selected request kind actually forks. Pass the same `units` record to
|
|
|
11
11
|
`httpServer({ unit: units })` so the runtime can apply unit test overrides.
|
|
12
12
|
Yoga plugins and CORS policy are explicit
|
|
13
13
|
options; the wildcard CORS default and Yoga console logging are disabled, and
|
|
14
|
-
a masked defect is reported to `Observers` instead.
|
|
14
|
+
a masked defect is reported to `Observers` instead. GraphiQL and
|
|
15
|
+
introspection stay off unless `GRAPHQL_DEVELOPER_TOOLS=true` (or
|
|
16
|
+
`developerTools: true`) turns both on. `fieldResult(result)`
|
|
15
17
|
answers a resolver from an `AsyncResult`, with a refusal on its own field's
|
|
16
18
|
path.
|
|
17
19
|
Preflights use Yoga's CORS policy before authentication, and Yoga plugins are
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
let graphql_yoga = require("graphql-yoga");
|
|
3
|
+
let _btravstack_config = require("@btravstack/config");
|
|
3
4
|
let _btravstack_core = require("@btravstack/core");
|
|
4
5
|
let _btravstack_di = require("@btravstack/di");
|
|
5
6
|
let _btravstack_http_server_internal = require("@btravstack/http-server/internal");
|
|
@@ -33,6 +34,10 @@ const dispose = Symbol("graphql.dispose");
|
|
|
33
34
|
/** What failed: GraphQL locates a resolver's error by wrapping it, and a log line walks `cause`, not `originalError`. */
|
|
34
35
|
const causeOf = (error) => typeof error === "object" && error !== null && "originalError" in error && error.originalError ? error.originalError : error;
|
|
35
36
|
const pathOf = (error) => typeof error === "object" && error !== null && "path" in error && Array.isArray(error.path) ? error.path.join(".") : void 0;
|
|
37
|
+
const noIntrospection = (context) => ({ Field: (node) => {
|
|
38
|
+
if (node.name.value === "__schema" || node.name.value === "__type") context.reportError((0, graphql_yoga.createGraphQLError)(`GraphQL introspection is not allowed, but the query contained ${node.name.value}`, { nodes: [node] }));
|
|
39
|
+
} });
|
|
40
|
+
const withoutIntrospection = { onValidate: ({ addValidationRule }) => addValidationRule(noIntrospection) };
|
|
36
41
|
const graphql = (api, options) => {
|
|
37
42
|
const mount = options.prefix ?? "/graphql";
|
|
38
43
|
let end = mount.length;
|
|
@@ -40,14 +45,16 @@ const graphql = (api, options) => {
|
|
|
40
45
|
const prefix = mount.slice(0, end);
|
|
41
46
|
const requirements = options.requires;
|
|
42
47
|
const schemes = [...new Set(requirements?.flatMap((requirement) => Object.keys(requirement)) ?? [])];
|
|
48
|
+
const developerToolsSchema = _btravstack_config.Config.object({ developerTools: _btravstack_config.Config.pinned(options.developerTools, _btravstack_config.Config.boolean("GRAPHQL_DEVELOPER_TOOLS", { default: false })) });
|
|
43
49
|
const typed = _btravstack_di.Provider.member(_btravstack_http_server_internal.HttpHandler)({
|
|
44
50
|
inject: {
|
|
51
|
+
env: _btravstack_config.Env,
|
|
45
52
|
config: _btravstack_http_server_internal.HttpConfig,
|
|
46
53
|
units: _btravstack_http_server_internal.HttpUnit,
|
|
47
54
|
observers: _btravstack_core.Observers,
|
|
48
55
|
...(0, _btravstack_http_server_internal.schemeDeps)(schemes)
|
|
49
56
|
},
|
|
50
|
-
|
|
57
|
+
make: (services) => _btravstack_config.Config.parse("GraphqlConfig", developerToolsSchema)(services.env).map(({ developerTools }) => {
|
|
51
58
|
const config = services.config;
|
|
52
59
|
const units = services.units;
|
|
53
60
|
const observers = services.observers;
|
|
@@ -59,9 +66,13 @@ const graphql = (api, options) => {
|
|
|
59
66
|
schema: options.schema,
|
|
60
67
|
graphqlEndpoint: prefix,
|
|
61
68
|
cors: options.cors ?? (config.corsOrigin === "" ? false : { origin: config.corsOrigin.split(",").map((origin) => origin.trim()) }),
|
|
62
|
-
plugins: [
|
|
63
|
-
|
|
64
|
-
|
|
69
|
+
plugins: [
|
|
70
|
+
{ onRequest: ({ serverContext, fetchAPI, endResponse }) => {
|
|
71
|
+
if (serverContext.refusalStatus !== void 0) endResponse(new fetchAPI.Response(null, { status: serverContext.refusalStatus }));
|
|
72
|
+
} },
|
|
73
|
+
...developerTools ? [] : [withoutIntrospection],
|
|
74
|
+
...options.plugins ?? []
|
|
75
|
+
],
|
|
65
76
|
maskedErrors: { maskError: (error, message, isDev) => {
|
|
66
77
|
const masked = (0, graphql_yoga.maskError)(error, message, isDev);
|
|
67
78
|
if (masked !== error) (0, _btravstack_core.observe)(observers, {
|
|
@@ -77,7 +88,7 @@ const graphql = (api, options) => {
|
|
|
77
88
|
return masked;
|
|
78
89
|
} },
|
|
79
90
|
logging: false,
|
|
80
|
-
graphiql:
|
|
91
|
+
graphiql: developerTools,
|
|
81
92
|
landingPage: false,
|
|
82
93
|
multipart: false,
|
|
83
94
|
maxRequestBodySize: config.bodyLimit === 0 ? false : config.bodyLimit,
|
|
@@ -122,7 +133,7 @@ const graphql = (api, options) => {
|
|
|
122
133
|
},
|
|
123
134
|
[dispose]: () => yoga.dispose()
|
|
124
135
|
};
|
|
125
|
-
},
|
|
136
|
+
}),
|
|
126
137
|
onStop: (answerer) => answerer[dispose]()
|
|
127
138
|
});
|
|
128
139
|
return Object.assign(typed, { authenticators: api.providers });
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { GraphQLError, GraphQLSchema } from "graphql";
|
|
2
2
|
import { AsyncResult, Result } from "unthrown";
|
|
3
3
|
import { IncomingMessage } from "node:http";
|
|
4
|
+
import { ConfigInvalid, EnvReading } from "@btravstack/config";
|
|
4
5
|
import { OneScheme, Requirements } from "@btravstack/contract";
|
|
5
6
|
import { Observers } from "@btravstack/core";
|
|
6
7
|
import { AnyPort, Provider, Scope } from "@btravstack/di";
|
|
@@ -64,6 +65,12 @@ type GraphqlOptions<A extends Authenticators, Units extends UnitsOf<A>, R extend
|
|
|
64
65
|
readonly schema: GraphQLSchema;
|
|
65
66
|
readonly prefix?: `/${string}`;
|
|
66
67
|
readonly cors?: YogaCors;
|
|
68
|
+
/**
|
|
69
|
+
* GraphiQL and introspection, both or neither — pins
|
|
70
|
+
* `GRAPHQL_DEVELOPER_TOOLS`, which defaults to `false`, so a deployment
|
|
71
|
+
* exposes neither unless it says so.
|
|
72
|
+
*/
|
|
73
|
+
readonly developerTools?: boolean;
|
|
67
74
|
/**
|
|
68
75
|
* Yoga plugins. Each may be typed by the context it reads — a
|
|
69
76
|
* `Plugin<{ unit: … }>` — and is checked against the {@link GraphqlContext}
|
|
@@ -74,7 +81,7 @@ type GraphqlOptions<A extends Authenticators, Units extends UnitsOf<A>, R extend
|
|
|
74
81
|
readonly unit?: U & UnitGate<U, Units, KindOf<R>>;
|
|
75
82
|
readonly requires?: R & RequiresGate<R, { readonly [K in keyof A]: A[K]["scope"]; }>;
|
|
76
83
|
};
|
|
77
|
-
export declare const graphql: <A extends Authenticators, Units extends UnitsOf<A> = Record<never, never>, const R extends Requirements & { readonly [I in keyof R]: OneScheme<R[I]>; } = never, U extends Readonly<Record<string, AnyPort>> = Record<never, never>, const PS extends readonly unknown[] = []>(api: HttpAuth<A>, options: GraphqlOptions<A, Units, R, U, PS>) => Provider<HttpHandler,
|
|
84
|
+
export declare const graphql: <A extends Authenticators, Units extends UnitsOf<A> = Record<never, never>, const R extends Requirements & { readonly [I in keyof R]: OneScheme<R[I]>; } = never, U extends Readonly<Record<string, AnyPort>> = Record<never, never>, const PS extends readonly unknown[] = []>(api: HttpAuth<A>, options: GraphqlOptions<A, Units, R, U, PS>) => Provider<HttpHandler, ConfigInvalid, HttpConfig | HttpUnit | Observers | Scope | SchemePortsOf<R> | UnitsNeedsOf<Units> | EnvReading<never, "GRAPHQL_DEVELOPER_TOOLS">> & {
|
|
78
85
|
authenticators: readonly import("@btravstack/http-server/internal").SchemeProviders<A>[];
|
|
79
86
|
};
|
|
80
87
|
//#endregion
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Plugin, YogaInitialContext, YogaServerOptions } from "graphql-yoga";
|
|
2
|
+
import { ConfigInvalid, EnvReading } from "@btravstack/config";
|
|
2
3
|
import { Observers } from "@btravstack/core";
|
|
3
4
|
import { AnyPort, Provider, Scope } from "@btravstack/di";
|
|
4
5
|
import { Authenticators, HttpAuth, HttpConfig, HttpHandler, HttpUnit, KindOf, Kinds, Principal, RequiresGate, SchemePortsOf, SchemesFrom, SchemesOf, UnitFor, UnitsNeedsOf, UnitsOf } from "@btravstack/http-server/internal";
|
|
@@ -64,6 +65,12 @@ type GraphqlOptions<A extends Authenticators, Units extends UnitsOf<A>, R extend
|
|
|
64
65
|
readonly schema: GraphQLSchema;
|
|
65
66
|
readonly prefix?: `/${string}`;
|
|
66
67
|
readonly cors?: YogaCors;
|
|
68
|
+
/**
|
|
69
|
+
* GraphiQL and introspection, both or neither — pins
|
|
70
|
+
* `GRAPHQL_DEVELOPER_TOOLS`, which defaults to `false`, so a deployment
|
|
71
|
+
* exposes neither unless it says so.
|
|
72
|
+
*/
|
|
73
|
+
readonly developerTools?: boolean;
|
|
67
74
|
/**
|
|
68
75
|
* Yoga plugins. Each may be typed by the context it reads — a
|
|
69
76
|
* `Plugin<{ unit: … }>` — and is checked against the {@link GraphqlContext}
|
|
@@ -74,7 +81,7 @@ type GraphqlOptions<A extends Authenticators, Units extends UnitsOf<A>, R extend
|
|
|
74
81
|
readonly unit?: U & UnitGate<U, Units, KindOf<R>>;
|
|
75
82
|
readonly requires?: R & RequiresGate<R, { readonly [K in keyof A]: A[K]["scope"]; }>;
|
|
76
83
|
};
|
|
77
|
-
export declare const graphql: <A extends Authenticators, Units extends UnitsOf<A> = Record<never, never>, const R extends Requirements & { readonly [I in keyof R]: OneScheme<R[I]>; } = never, U extends Readonly<Record<string, AnyPort>> = Record<never, never>, const PS extends readonly unknown[] = []>(api: HttpAuth<A>, options: GraphqlOptions<A, Units, R, U, PS>) => Provider<HttpHandler,
|
|
84
|
+
export declare const graphql: <A extends Authenticators, Units extends UnitsOf<A> = Record<never, never>, const R extends Requirements & { readonly [I in keyof R]: OneScheme<R[I]>; } = never, U extends Readonly<Record<string, AnyPort>> = Record<never, never>, const PS extends readonly unknown[] = []>(api: HttpAuth<A>, options: GraphqlOptions<A, Units, R, U, PS>) => Provider<HttpHandler, ConfigInvalid, HttpConfig | HttpUnit | Observers | Scope | SchemePortsOf<R> | UnitsNeedsOf<Units> | EnvReading<never, "GRAPHQL_DEVELOPER_TOOLS">> & {
|
|
78
85
|
authenticators: readonly import("@btravstack/http-server/internal").SchemeProviders<A>[];
|
|
79
86
|
};
|
|
80
87
|
//#endregion
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createGraphQLError, createYoga, maskError } from "graphql-yoga";
|
|
2
|
+
import { Config, Env } from "@btravstack/config";
|
|
2
3
|
import { Observers, observe } from "@btravstack/core";
|
|
3
4
|
import { Provider } from "@btravstack/di";
|
|
4
5
|
import { HttpConfig, HttpHandler, HttpUnit, forkUnit, principalOf, resolveScheme, schemeDeps, schemeServices } from "@btravstack/http-server/internal";
|
|
@@ -32,6 +33,10 @@ const dispose = Symbol("graphql.dispose");
|
|
|
32
33
|
/** What failed: GraphQL locates a resolver's error by wrapping it, and a log line walks `cause`, not `originalError`. */
|
|
33
34
|
const causeOf = (error) => typeof error === "object" && error !== null && "originalError" in error && error.originalError ? error.originalError : error;
|
|
34
35
|
const pathOf = (error) => typeof error === "object" && error !== null && "path" in error && Array.isArray(error.path) ? error.path.join(".") : void 0;
|
|
36
|
+
const noIntrospection = (context) => ({ Field: (node) => {
|
|
37
|
+
if (node.name.value === "__schema" || node.name.value === "__type") context.reportError(createGraphQLError(`GraphQL introspection is not allowed, but the query contained ${node.name.value}`, { nodes: [node] }));
|
|
38
|
+
} });
|
|
39
|
+
const withoutIntrospection = { onValidate: ({ addValidationRule }) => addValidationRule(noIntrospection) };
|
|
35
40
|
const graphql = (api, options) => {
|
|
36
41
|
const mount = options.prefix ?? "/graphql";
|
|
37
42
|
let end = mount.length;
|
|
@@ -39,14 +44,16 @@ const graphql = (api, options) => {
|
|
|
39
44
|
const prefix = mount.slice(0, end);
|
|
40
45
|
const requirements = options.requires;
|
|
41
46
|
const schemes = [...new Set(requirements?.flatMap((requirement) => Object.keys(requirement)) ?? [])];
|
|
47
|
+
const developerToolsSchema = Config.object({ developerTools: Config.pinned(options.developerTools, Config.boolean("GRAPHQL_DEVELOPER_TOOLS", { default: false })) });
|
|
42
48
|
const typed = Provider.member(HttpHandler)({
|
|
43
49
|
inject: {
|
|
50
|
+
env: Env,
|
|
44
51
|
config: HttpConfig,
|
|
45
52
|
units: HttpUnit,
|
|
46
53
|
observers: Observers,
|
|
47
54
|
...schemeDeps(schemes)
|
|
48
55
|
},
|
|
49
|
-
|
|
56
|
+
make: (services) => Config.parse("GraphqlConfig", developerToolsSchema)(services.env).map(({ developerTools }) => {
|
|
50
57
|
const config = services.config;
|
|
51
58
|
const units = services.units;
|
|
52
59
|
const observers = services.observers;
|
|
@@ -58,9 +65,13 @@ const graphql = (api, options) => {
|
|
|
58
65
|
schema: options.schema,
|
|
59
66
|
graphqlEndpoint: prefix,
|
|
60
67
|
cors: options.cors ?? (config.corsOrigin === "" ? false : { origin: config.corsOrigin.split(",").map((origin) => origin.trim()) }),
|
|
61
|
-
plugins: [
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
plugins: [
|
|
69
|
+
{ onRequest: ({ serverContext, fetchAPI, endResponse }) => {
|
|
70
|
+
if (serverContext.refusalStatus !== void 0) endResponse(new fetchAPI.Response(null, { status: serverContext.refusalStatus }));
|
|
71
|
+
} },
|
|
72
|
+
...developerTools ? [] : [withoutIntrospection],
|
|
73
|
+
...options.plugins ?? []
|
|
74
|
+
],
|
|
64
75
|
maskedErrors: { maskError: (error, message, isDev) => {
|
|
65
76
|
const masked = maskError(error, message, isDev);
|
|
66
77
|
if (masked !== error) observe(observers, {
|
|
@@ -76,7 +87,7 @@ const graphql = (api, options) => {
|
|
|
76
87
|
return masked;
|
|
77
88
|
} },
|
|
78
89
|
logging: false,
|
|
79
|
-
graphiql:
|
|
90
|
+
graphiql: developerTools,
|
|
80
91
|
landingPage: false,
|
|
81
92
|
multipart: false,
|
|
82
93
|
maxRequestBodySize: config.bodyLimit === 0 ? false : config.bodyLimit,
|
|
@@ -121,7 +132,7 @@ const graphql = (api, options) => {
|
|
|
121
132
|
},
|
|
122
133
|
[dispose]: () => yoga.dispose()
|
|
123
134
|
};
|
|
124
|
-
},
|
|
135
|
+
}),
|
|
125
136
|
onStop: (answerer) => answerer[dispose]()
|
|
126
137
|
});
|
|
127
138
|
return Object.assign(typed, { authenticators: api.providers });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@btravstack/graphql-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "The GraphQL Yoga answerer for @btravstack/http-server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"graphql",
|
|
@@ -41,10 +41,11 @@
|
|
|
41
41
|
"./package.json": "./package.json"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@btravstack/
|
|
45
|
-
"@btravstack/
|
|
46
|
-
"@btravstack/
|
|
47
|
-
"@btravstack/
|
|
44
|
+
"@btravstack/config": "0.28.0",
|
|
45
|
+
"@btravstack/contract": "0.28.0",
|
|
46
|
+
"@btravstack/core": "0.28.0",
|
|
47
|
+
"@btravstack/di": "0.28.0",
|
|
48
|
+
"@btravstack/http-server": "0.28.0",
|
|
48
49
|
"@btravstack/tsconfig": "0.4.0",
|
|
49
50
|
"@types/node": "26.6.4",
|
|
50
51
|
"@unthrown/vitest": "5.12.0",
|
|
@@ -58,10 +59,11 @@
|
|
|
58
59
|
"vitest": "5.0.3"
|
|
59
60
|
},
|
|
60
61
|
"peerDependencies": {
|
|
61
|
-
"@btravstack/
|
|
62
|
-
"@btravstack/
|
|
63
|
-
"@btravstack/
|
|
64
|
-
"@btravstack/
|
|
62
|
+
"@btravstack/config": "^0.28.0",
|
|
63
|
+
"@btravstack/contract": "^0.28.0",
|
|
64
|
+
"@btravstack/core": "^0.28.0",
|
|
65
|
+
"@btravstack/di": "^0.28.0",
|
|
66
|
+
"@btravstack/http-server": "^0.28.0",
|
|
65
67
|
"graphql": "^16.0.0 || ^17.0.0",
|
|
66
68
|
"graphql-yoga": "^5.24.1",
|
|
67
69
|
"unthrown": "^5.7.0"
|