@nxara/olio-http 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +223 -2
- package/dist/core.d.ts +66 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +96 -0
- package/dist/core.js.map +1 -0
- package/dist/fetch.d.ts +7 -0
- package/dist/fetch.d.ts.map +1 -0
- package/dist/fetch.js +32 -0
- package/dist/fetch.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/node.d.ts +8 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +31 -0
- package/dist/node.js.map +1 -0
- package/package.json +53 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nxara contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,224 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @nxara/olio-http
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Serve an olio app over HTTP, on any framework: GraphQL over HTTP ([graphql-http](https://github.com/graphql/graphql-http), the reference implementation), with `context`, error masking and per-operation cleanup. Nothing more: subscriptions are yours to serve, with the libraries you choose (below).
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
bun add @nxara/olio-http graphql@16
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
`graphql` 16 is a peer dependency (not 17, which is about 4× slower on small queries); graphql-http is its only third-party dependency.
|
|
10
|
+
|
|
11
|
+
## Adapters
|
|
12
|
+
|
|
13
|
+
| Import | For |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| `@nxara/olio-http/fetch` | Anything that speaks the Fetch API: Hono, Elysia, Bun.serve, Deno, Cloudflare Workers, Next.js route handlers, SvelteKit, Remix |
|
|
16
|
+
| `@nxara/olio-http/node` | Node's `IncomingMessage` / `ServerResponse`: `node:http`, Express, Connect, Koa (`ctx.req`, `ctx.res`), Fastify (`request.raw`, `reply.raw`) |
|
|
17
|
+
|
|
18
|
+
Your `context` hook receives the framework's request: a Fetch `Request`, or a Node `IncomingMessage`. Once your app registers a `ctx` type (`interface Olio { ctx: … }`), the hook is required: without it graphql-http passes no context, and every resolver reading `ctx` would fail at runtime. So `fetchHandler(app)` is then a compile error (`Expected 2 arguments`), as is an options object without `context`.
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { fetchHandler } from "@nxara/olio-http/fetch";
|
|
22
|
+
|
|
23
|
+
const graphql = fetchHandler(app, {
|
|
24
|
+
context: (request) => createContext(request.headers.get("authorization")),
|
|
25
|
+
onOperationEnd: (ctx) => ctx.di.dispose()
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
hono.on(["GET", "POST"], "/graphql", (c) => graphql(c.req.raw)); // Hono
|
|
29
|
+
Bun.serve({ fetch: graphql }); // Bun
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { nodeHandler } from "@nxara/olio-http/node";
|
|
34
|
+
|
|
35
|
+
const graphql = nodeHandler(app, {
|
|
36
|
+
context: (req) => createContext(req.headers.authorization)
|
|
37
|
+
});
|
|
38
|
+
expressApp.all("/graphql", (req, res) => graphql(req, res)); // or: createServer(graphql)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Subscriptions
|
|
42
|
+
|
|
43
|
+
Not included, the way Apollo Server leaves them out: install and run the reference libraries yourself, at the versions you want, next to the HTTP handler.
|
|
44
|
+
|
|
45
|
+
- [graphql-ws](https://github.com/enisdenjo/graphql-ws): subscriptions over WebSocket (`useServer` on a `ws` server, `makeHandler` on Bun).
|
|
46
|
+
- [graphql-sse](https://github.com/enisdenjo/graphql-sse): subscriptions as server-sent events, on the same URL (route `Accept: text/event-stream` to its handler).
|
|
47
|
+
|
|
48
|
+
Pass them `app.schema`; `errorMasker` hides their unexpected errors the way the HTTP handler does, and `validateOperation` applies its rules. Both, with a context (and DI scope) per subscription, disposed when it ends:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import type { SubscriptionContext } from "@nxara/olio";
|
|
52
|
+
import { errorMasker, validateOperation } from "@nxara/olio-http";
|
|
53
|
+
import { createHandler } from "graphql-sse/lib/use/fetch";
|
|
54
|
+
import { useServer } from "graphql-ws/use/ws";
|
|
55
|
+
|
|
56
|
+
const mask = errorMasker<SubscriptionContext>();
|
|
57
|
+
|
|
58
|
+
// graphql-sse: route requests with `Accept: text/event-stream` here, GET and
|
|
59
|
+
// POST (what @nxara/olio-client sends; single-connection mode also needs PUT and DELETE).
|
|
60
|
+
export const sseHandler = createHandler({
|
|
61
|
+
schema: app.schema,
|
|
62
|
+
context: (req) =>
|
|
63
|
+
createContext(req.raw.headers.get("authorization")) as never,
|
|
64
|
+
onSubscribe: (_req, params) => {
|
|
65
|
+
const errors = validateOperation(app.schema, params, validationRules);
|
|
66
|
+
return errors.length ? { data: null, errors: [...errors] } : undefined; // needs `data`
|
|
67
|
+
},
|
|
68
|
+
onNext: (ctx, _req, result) =>
|
|
69
|
+
mask.result(result, ctx as unknown as SubscriptionContext),
|
|
70
|
+
onComplete: (ctx) => (ctx as unknown as SubscriptionContext).di.dispose()
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// graphql-ws, on a `ws` server: `context` runs once per subscription, so the
|
|
74
|
+
// scope is per operation; keep it by connection and id to end it.
|
|
75
|
+
const live = new WeakMap<object, Map<string, SubscriptionContext>>();
|
|
76
|
+
useServer(
|
|
77
|
+
{
|
|
78
|
+
schema: app.schema,
|
|
79
|
+
context: (ctx, id) => {
|
|
80
|
+
// A browser can't set WebSocket headers: @nxara/olio-client sends them here.
|
|
81
|
+
const headers = ctx.connectionParams?.headers as
|
|
82
|
+
| Record<string, string>
|
|
83
|
+
| undefined;
|
|
84
|
+
const value = createContext(headers?.authorization);
|
|
85
|
+
live.set(ctx, (live.get(ctx) ?? new Map()).set(id, value));
|
|
86
|
+
return value as never;
|
|
87
|
+
},
|
|
88
|
+
onSubscribe: (_ctx, _id, payload) => {
|
|
89
|
+
const errors = validateOperation(app.schema, payload, validationRules);
|
|
90
|
+
return errors.length ? errors : undefined;
|
|
91
|
+
},
|
|
92
|
+
onNext: (ctx, id, _payload, _args, result) =>
|
|
93
|
+
mask.result(result, live.get(ctx)?.get(id)),
|
|
94
|
+
onError: (ctx, id, _payload, errors) =>
|
|
95
|
+
errors.map((e) => mask.error(e, live.get(ctx)?.get(id)).toJSON()),
|
|
96
|
+
// Also runs when the socket closes mid-subscription (see the note below).
|
|
97
|
+
onComplete: async (ctx, id) => {
|
|
98
|
+
const value = live.get(ctx)?.get(id);
|
|
99
|
+
live.get(ctx)?.delete(id);
|
|
100
|
+
await value?.di.dispose();
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
new WebSocketServer({ server, path: "/graphql" })
|
|
104
|
+
);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
When a socket closes, graphql-ws ends each live subscription and waits for it before `onComplete`. A resolver that's an `async *` waiting inside `for await` can only end at its next event, so until one arrives, nothing is cleaned up. Return the event source's own iterator instead (olio's README, [Subscriptions](../olio/README.md#subscriptions)).
|
|
108
|
+
|
|
109
|
+
With `@hono/node-server`, `serve()` is typed to return HTTP/2 servers too: pass `server as Server` (from `node:http`) to `WebSocketServer`. [`apps/example-server/src/subscriptions.ts`](../../apps/example-server/src/subscriptions.ts) is all of this, running. For tests, `testClient` from `@nxara/olio-client/test` runs these same options and handler in memory, so export them (the example exports `wsOptions` and `sseHandler`).
|
|
110
|
+
|
|
111
|
+
## Refusing a bad token
|
|
112
|
+
|
|
113
|
+
Don't throw from `context`. A throw there refuses the whole request before any field runs, so a client still holding an expired token can't even call `login` to get a new one. Build a context without a user instead, and refuse in middleware; public fields skip it with `@bypass` (olio's README, [Skipping middleware](../olio/README.md#skipping-middleware-bypass)):
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
fetchHandler(app, {
|
|
117
|
+
// An invalid or expired token gives `userId: undefined`, not an error.
|
|
118
|
+
context: (request) => ({
|
|
119
|
+
userId: verify(request.headers.get("authorization"))
|
|
120
|
+
})
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const requireAuth =
|
|
124
|
+
(): MiddlewareFn =>
|
|
125
|
+
({ ctx, bypass }, next) => {
|
|
126
|
+
if (!ctx.userId && !bypass.has("auth")) throw unauthenticated();
|
|
127
|
+
return next();
|
|
128
|
+
};
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
It's also the only way that gives clients the same `UNAUTHENTICATED` result, at once, on every transport. Over subscriptions, a `context` that throws is a server error to both libraries: graphql-ws closes the socket with 4500 (the error's code is lost), graphql-sse's handler throws (your framework answers 500). Refusing the socket in graphql-ws's `onConnect` closes it with 4403, which graphql-ws's client retries by default ([@nxara/olio-client](../client/README.md#subscriptions)).
|
|
132
|
+
|
|
133
|
+
## Validation rules
|
|
134
|
+
|
|
135
|
+
`validationRules` run with graphql's own, before any resolver: a cost or depth limit, say. They're added to graphql's rules, never in their place. Pass a list, or a function of the operation (`{ variables, operationName }`) for rules that need its variables, like [graphql-query-complexity](https://github.com/slicknode/graphql-query-complexity) pricing a page by its size:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
import {
|
|
139
|
+
createComplexityRule,
|
|
140
|
+
fieldExtensionsEstimator,
|
|
141
|
+
simpleEstimator
|
|
142
|
+
} from "graphql-query-complexity";
|
|
143
|
+
|
|
144
|
+
fetchHandler(app, {
|
|
145
|
+
context,
|
|
146
|
+
validationRules: ({ variables }) => [
|
|
147
|
+
createComplexityRule({
|
|
148
|
+
maximumComplexity: 1000,
|
|
149
|
+
variables,
|
|
150
|
+
estimators: [
|
|
151
|
+
fieldExtensionsEstimator(),
|
|
152
|
+
simpleEstimator({ defaultComplexity: 1 })
|
|
153
|
+
]
|
|
154
|
+
})
|
|
155
|
+
]
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`fieldExtensionsEstimator` reads each field's `complexity` extension, which olio sets from the resolver's `{ resolve, extensions }`. An operation over the limit gets the rule's error, and nothing runs.
|
|
160
|
+
|
|
161
|
+
Subscription servers take the same rules through `validateOperation(schema, operation, rules)` in their `onSubscribe`; it returns the errors (none when the operation is valid):
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
import { validateOperation } from "@nxara/olio-http";
|
|
165
|
+
|
|
166
|
+
// graphql-ws
|
|
167
|
+
onSubscribe: (_ctx, _id, payload) => {
|
|
168
|
+
const errors = validateOperation(app.schema, payload, validationRules);
|
|
169
|
+
return errors.length ? errors : undefined;
|
|
170
|
+
};
|
|
171
|
+
// graphql-sse: a result needs `data` to be read as one
|
|
172
|
+
onSubscribe: (_req, params) => {
|
|
173
|
+
const errors = validateOperation(app.schema, params, validationRules);
|
|
174
|
+
return errors.length ? { data: null, errors: [...errors] } : undefined;
|
|
175
|
+
};
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Errors
|
|
179
|
+
|
|
180
|
+
Only `GraphQLError`s reach clients. Anything else that's thrown, such as a failing database or a bug, is replaced by `"Unexpected error."` and passed to `onUnexpectedError` (default: `console.error`), so it isn't lost. That includes a `context` hook that throws: a `GraphQLError` from it (say, `BAD_REQUEST` for a malformed header) reaches the client as a normal GraphQL error with status 200; anything else is a 500 with "Unexpected error.". It refuses the whole request, so a bad token belongs in middleware instead ([Refusing a bad token](#refusing-a-bad-token)).
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
throw new GraphQLError("Forbidden", { extensions: { code: "FORBIDDEN" } }); // the client sees this
|
|
184
|
+
throw new Error("connection refused"); // the client sees "Unexpected error."
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`onUnexpectedError(error, { ctx, request })` gets what the error happened in, so logs can tie it to a request: `ctx` is the operation's context (`undefined` when the `context` hook itself failed), `request` the adapter's (a Fetch `Request`, or Node's `IncomingMessage`).
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
fetchHandler(app, {
|
|
191
|
+
context,
|
|
192
|
+
onUnexpectedError: (error, { ctx, request }) =>
|
|
193
|
+
log.error({ error, requestId: ctx?.requestId, url: request.url })
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`errorMasker(options?)`, from `@nxara/olio-http`, is the same masking for your own subscription servers. It takes the handlers' `maskErrors` and `onUnexpectedError` (both optional), and gives the functions below. Each takes the operation's context as a last argument, which `onUnexpectedError` receives as `ctx` (`request` is `undefined` there); type it with `errorMasker<SubscriptionContext>()`:
|
|
198
|
+
|
|
199
|
+
| | |
|
|
200
|
+
| --- | --- |
|
|
201
|
+
| `mask.result(result, ctx?)` | The result with its errors masked, any shape: graphql's `ExecutionResult`, or what graphql-sse's and graphql-ws's `onNext` hand you |
|
|
202
|
+
| `mask.error(error, ctx?)` | One error, or any caught value: a `GraphQLError` passes (unless it wraps an unexpected error), anything else becomes "Unexpected error." and is reported |
|
|
203
|
+
| `mask.report(error, ctx?)` | Sends an error to `onUnexpectedError` |
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import { errorMasker } from "@nxara/olio-http";
|
|
207
|
+
|
|
208
|
+
const mask = errorMasker<SubscriptionContext>();
|
|
209
|
+
createHandler({
|
|
210
|
+
schema: app.schema,
|
|
211
|
+
onNext: (ctx, _req, result) => mask.result(result, ctx)
|
|
212
|
+
}); // graphql-sse
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Options
|
|
216
|
+
|
|
217
|
+
| Option | Default | |
|
|
218
|
+
| --- | --- | --- |
|
|
219
|
+
| `context` | none | Builds `ctx` from the request. Required once the app registers a `ctx` type |
|
|
220
|
+
| `validationRules` | none | Rules run with graphql's own before execution: a list, or `({ variables, operationName }) => rules`. See [Validation rules](#validation-rules) |
|
|
221
|
+
| `onOperationEnd` | none | Called once per operation, after the response is built. Release per-operation resources here, e.g. `(ctx) => ctx.di.dispose()`. A failure in it goes to `onUnexpectedError` |
|
|
222
|
+
| `maskErrors` | `true` | Hide errors that aren't `GraphQLError`s. Turn off only while developing |
|
|
223
|
+
| `onUnexpectedError` | `console.error` of the error | `(error, { ctx, request }) => void`: receives each hidden error, with the context and request it happened in (`ctx` is `undefined` when the `context` hook failed) |
|
|
224
|
+
| graphql-http's own | its defaults | `execute`, `parse`, `validate`, `getOperationAST`, `rootValue`, `formatError` (runs after masking) and `parseRequestParams` (e.g. persisted queries: map an id to its stored query) go to [graphql-http](https://github.com/graphql/graphql-http) as they are. Not `onSubscribe`: operation args returned from it would skip `context` and every validation rule |
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Context } from "@nxara/olio-core";
|
|
2
|
+
import { GraphQLError, type GraphQLSchema, type ValidationRule } from "graphql";
|
|
3
|
+
import type { Response as HttpResponse } from "graphql-http";
|
|
4
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
5
|
+
export interface Servable {
|
|
6
|
+
readonly schema: GraphQLSchema;
|
|
7
|
+
}
|
|
8
|
+
export interface ErrorInfo<Request = undefined, Ctx = Context> {
|
|
9
|
+
readonly ctx: Ctx | undefined;
|
|
10
|
+
readonly request: Request;
|
|
11
|
+
}
|
|
12
|
+
export interface ErrorOptions<Request = undefined, Ctx = Context> {
|
|
13
|
+
maskErrors?: boolean;
|
|
14
|
+
onUnexpectedError?: (error: unknown, info: ErrorInfo<Request, Ctx>) => void;
|
|
15
|
+
}
|
|
16
|
+
type ContextHook<Request> = (request: Request) => MaybePromise<Context>;
|
|
17
|
+
type ContextOption<Request> = undefined extends Context ? {
|
|
18
|
+
context?: ContextHook<Request>;
|
|
19
|
+
} : {
|
|
20
|
+
context: ContextHook<Request>;
|
|
21
|
+
};
|
|
22
|
+
export interface OperationInfo {
|
|
23
|
+
readonly variables: Readonly<Record<string, unknown>>;
|
|
24
|
+
readonly operationName: string | null | undefined;
|
|
25
|
+
}
|
|
26
|
+
export type ValidationRules = readonly ValidationRule[] | ((operation: OperationInfo) => readonly ValidationRule[]);
|
|
27
|
+
export type GraphQLHttpOptions<Options> = Omit<Options, "schema" | "context" | "validationRules" | "onOperation" | "onSubscribe">;
|
|
28
|
+
export type HttpOptions<Request, Passed = unknown> = ErrorOptions<Request> & ContextOption<Request> & Passed & {
|
|
29
|
+
validationRules?: ValidationRules;
|
|
30
|
+
onOperationEnd?: (ctx: Context) => MaybePromise<void>;
|
|
31
|
+
};
|
|
32
|
+
export type OptionsArgs<Request, Passed = unknown> = undefined extends Context ? [options?: HttpOptions<Request, Passed>] : [options: HttpOptions<Request, Passed>];
|
|
33
|
+
export interface ErrorMasker<Ctx = Context> {
|
|
34
|
+
error: (error: unknown, ctx?: Ctx) => GraphQLError;
|
|
35
|
+
result: <R extends {
|
|
36
|
+
readonly errors?: readonly GraphQLError[];
|
|
37
|
+
}>(result: R, ctx?: Ctx) => R;
|
|
38
|
+
report: (error: unknown, ctx?: Ctx) => void;
|
|
39
|
+
}
|
|
40
|
+
export interface Masking<Info> {
|
|
41
|
+
error: (error: unknown, info: Info) => GraphQLError;
|
|
42
|
+
result: <R extends {
|
|
43
|
+
readonly errors?: readonly GraphQLError[];
|
|
44
|
+
}>(result: R, info: Info) => R;
|
|
45
|
+
report: (error: unknown, info: Info) => void;
|
|
46
|
+
}
|
|
47
|
+
export declare function masking<Info>(options: {
|
|
48
|
+
maskErrors?: boolean;
|
|
49
|
+
onUnexpectedError?: (error: unknown, info: Info) => void;
|
|
50
|
+
}): Masking<Info>;
|
|
51
|
+
export declare function errorMasker<Ctx = Context>(options?: ErrorOptions<undefined, Ctx>): ErrorMasker<Ctx>;
|
|
52
|
+
export declare function contextFailure<Request>(mask: Masking<ErrorInfo<Request>>, error: unknown, request: Request): HttpResponse;
|
|
53
|
+
interface Operation {
|
|
54
|
+
readonly variables?: Readonly<Record<string, unknown>> | null;
|
|
55
|
+
readonly operationName?: string | null;
|
|
56
|
+
}
|
|
57
|
+
export declare function validateOperation(schema: GraphQLSchema, operation: Operation & {
|
|
58
|
+
readonly query: string;
|
|
59
|
+
}, rules: ValidationRules): readonly GraphQLError[];
|
|
60
|
+
export declare const handlerRules: (rules: ValidationRules | undefined) => ((req: unknown, args: {
|
|
61
|
+
readonly variableValues?: Readonly<Record<string, unknown>> | null;
|
|
62
|
+
readonly operationName?: string | null;
|
|
63
|
+
}) => readonly ValidationRule[]) | undefined;
|
|
64
|
+
export declare function operationEnd<Request>(mask: Masking<ErrorInfo<Request>>, hook: ((ctx: Context) => MaybePromise<void>) | undefined): (ctx: Context | undefined, request: Request) => Promise<void>;
|
|
65
|
+
export {};
|
|
66
|
+
//# sourceMappingURL=core.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
|
package/dist/core.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { GraphQLError, parse, specifiedRules, validate } from "graphql";
|
|
2
|
+
export function masking(options) {
|
|
3
|
+
const { maskErrors = true, onUnexpectedError = (error) => console.error(error) } = options;
|
|
4
|
+
const error = (thrown, info) => {
|
|
5
|
+
if (!(thrown instanceof GraphQLError)) {
|
|
6
|
+
onUnexpectedError(thrown, info);
|
|
7
|
+
return new GraphQLError(maskErrors || !(thrown instanceof Error)
|
|
8
|
+
? "Unexpected error."
|
|
9
|
+
: thrown.message);
|
|
10
|
+
}
|
|
11
|
+
const cause = thrown.originalError;
|
|
12
|
+
if (!cause || cause instanceof GraphQLError)
|
|
13
|
+
return thrown;
|
|
14
|
+
onUnexpectedError(cause, info);
|
|
15
|
+
return maskErrors
|
|
16
|
+
? new GraphQLError("Unexpected error.", {
|
|
17
|
+
nodes: thrown.nodes,
|
|
18
|
+
path: thrown.path
|
|
19
|
+
})
|
|
20
|
+
: thrown;
|
|
21
|
+
};
|
|
22
|
+
return {
|
|
23
|
+
error,
|
|
24
|
+
result: (result, info) => result.errors
|
|
25
|
+
? { ...result, errors: result.errors.map((e) => error(e, info)) }
|
|
26
|
+
: result,
|
|
27
|
+
report: onUnexpectedError
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function errorMasker(options = {}) {
|
|
31
|
+
const mask = masking(options);
|
|
32
|
+
const info = (ctx) => ({
|
|
33
|
+
ctx,
|
|
34
|
+
request: undefined
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
error: (error, ctx) => mask.error(error, info(ctx)),
|
|
38
|
+
result: (result, ctx) => mask.result(result, info(ctx)),
|
|
39
|
+
report: (error, ctx) => mask.report(error, info(ctx))
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function contextFailure(mask, error, request) {
|
|
43
|
+
const deliberate = error instanceof GraphQLError;
|
|
44
|
+
const graphqlError = mask.error(error, { ctx: undefined, request });
|
|
45
|
+
return [
|
|
46
|
+
JSON.stringify({ errors: [graphqlError.toJSON()] }),
|
|
47
|
+
{
|
|
48
|
+
status: deliberate ? 200 : 500,
|
|
49
|
+
statusText: deliberate ? "OK" : "Internal Server Error",
|
|
50
|
+
headers: { "content-type": "application/json; charset=utf-8" }
|
|
51
|
+
}
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
function ruleSet(rules, operation) {
|
|
55
|
+
const custom = typeof rules === "function"
|
|
56
|
+
? rules({
|
|
57
|
+
variables: operation.variables ?? {},
|
|
58
|
+
operationName: operation.operationName
|
|
59
|
+
})
|
|
60
|
+
: rules;
|
|
61
|
+
return [...specifiedRules, ...custom];
|
|
62
|
+
}
|
|
63
|
+
export function validateOperation(schema, operation, rules) {
|
|
64
|
+
let document;
|
|
65
|
+
try {
|
|
66
|
+
document = parse(operation.query);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
return [error];
|
|
70
|
+
}
|
|
71
|
+
return validate(schema, document, ruleSet(rules, operation));
|
|
72
|
+
}
|
|
73
|
+
export const handlerRules = (rules) => rules &&
|
|
74
|
+
((_req, args) => ruleSet(rules, {
|
|
75
|
+
variables: args.variableValues,
|
|
76
|
+
operationName: args.operationName
|
|
77
|
+
}));
|
|
78
|
+
export function operationEnd(mask, hook) {
|
|
79
|
+
const ended = new WeakSet();
|
|
80
|
+
return async (ctx, request) => {
|
|
81
|
+
if (!hook || ctx === undefined)
|
|
82
|
+
return;
|
|
83
|
+
if (typeof ctx === "object" && ctx !== null) {
|
|
84
|
+
if (ended.has(ctx))
|
|
85
|
+
return;
|
|
86
|
+
ended.add(ctx);
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
await hook(ctx);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
mask.report(error, { ctx, request });
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=core.js.map
|
package/dist/core.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
|
package/dist/fetch.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { OperationContext } from "graphql-http";
|
|
2
|
+
import { type HandlerOptions } from "graphql-http/lib/use/fetch";
|
|
3
|
+
import { type GraphQLHttpOptions, type OptionsArgs, type Servable } from "./core.js";
|
|
4
|
+
type Passed = GraphQLHttpOptions<HandlerOptions<OperationContext>>;
|
|
5
|
+
export declare function fetchHandler(app: Servable, ...[options]: OptionsArgs<Request, Passed>): (request: Request) => Promise<Response>;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=fetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../src/fetch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAEhF,OAAO,EAML,KAAK,kBAAkB,EAEvB,KAAK,WAAW,EAChB,KAAK,QAAQ,EACd,MAAM,WAAW,CAAC;AAEnB,KAAK,MAAM,GAAG,kBAAkB,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC,CAAC;AAGnE,wBAAgB,YAAY,CAC1B,GAAG,EAAE,QAAQ,EACb,GAAG,CAAC,OAA4C,CAAC,EAAE,WAAW,CAC5D,OAAO,EACP,MAAM,CACP,GACA,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CA6BzC"}
|
package/dist/fetch.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createHandler } from "graphql-http/lib/use/fetch";
|
|
2
|
+
import { contextFailure, masking, operationEnd, handlerRules } from "./core.js";
|
|
3
|
+
export function fetchHandler(app, ...[options = {}]) {
|
|
4
|
+
const mask = masking(options);
|
|
5
|
+
const { context } = options;
|
|
6
|
+
const end = operationEnd(mask, options.onOperationEnd);
|
|
7
|
+
const contexts = new WeakMap();
|
|
8
|
+
const http = createHandler({
|
|
9
|
+
...options,
|
|
10
|
+
schema: app.schema,
|
|
11
|
+
context: context
|
|
12
|
+
? async (req) => {
|
|
13
|
+
try {
|
|
14
|
+
const ctx = await context(req.raw);
|
|
15
|
+
contexts.set(req.raw, ctx);
|
|
16
|
+
return ctx;
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
return contextFailure(mask, error, req.raw);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
: undefined,
|
|
23
|
+
validationRules: handlerRules(options.validationRules),
|
|
24
|
+
onOperation: (req, _args, result) => mask.result(result, { ctx: contexts.get(req.raw), request: req.raw })
|
|
25
|
+
});
|
|
26
|
+
return async (request) => {
|
|
27
|
+
const response = await http(request);
|
|
28
|
+
await end(contexts.get(request), request);
|
|
29
|
+
return response;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch.js","sourceRoot":"","sources":["../src/fetch.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAuB,MAAM,4BAA4B,CAAC;AAEhF,OAAO,EACL,cAAc,EACd,OAAO,EACP,YAAY,EACZ,YAAY,EAMb,MAAM,WAAW,CAAC;AAKnB,MAAM,UAAU,YAAY,CAC1B,GAAa,EACb,GAAG,CAAC,OAAO,GAAG,EAAkC,CAG/C;IAED,MAAM,IAAI,GAAG,OAAO,CAAqB,OAAO,CAAC,CAAC;IAClD,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAC5B,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;IAEvD,MAAM,QAAQ,GAAG,IAAI,OAAO,EAAoB,CAAC;IACjD,MAAM,IAAI,GAAG,aAAa,CAAC;QACzB,GAAG,OAAO;QACV,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,OAAO,EAAE,OAAO;YACd,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;gBACZ,IAAI,CAAC;oBACH,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;oBACnC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;oBAC3B,OAAO,GAAY,CAAC;gBACtB,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,OAAO,cAAc,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;gBAC9C,CAAC;YACH,CAAC;YACH,CAAC,CAAC,SAAS;QACb,eAAe,EAAE,YAAY,CAAC,OAAO,CAAC,eAAe,CAAC;QACtD,WAAW,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAClC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC;KACxE,CAAC,CAAC;IACH,OAAO,KAAK,EAAE,OAAO,EAAE,EAAE;QACvB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC;QACrC,MAAM,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;QAC1C,OAAO,QAAQ,CAAC;IAClB,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC3D,YAAY,EACV,SAAS,EACT,WAAW,EACX,YAAY,EACZ,WAAW,EACX,aAAa,EACb,WAAW,EACX,QAAQ,EACR,eAAe,EAChB,MAAM,WAAW,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/node.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import type { OperationContext } from "graphql-http";
|
|
3
|
+
import { type HandlerOptions } from "graphql-http/lib/use/http";
|
|
4
|
+
import { type GraphQLHttpOptions, type OptionsArgs, type Servable } from "./core.js";
|
|
5
|
+
type Passed = GraphQLHttpOptions<HandlerOptions<OperationContext>>;
|
|
6
|
+
export declare function nodeHandler(app: Servable, ...[options]: OptionsArgs<IncomingMessage, Passed>): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE/E,OAAO,EAML,KAAK,kBAAkB,EAEvB,KAAK,WAAW,EAChB,KAAK,QAAQ,EACd,MAAM,WAAW,CAAC;AAEnB,KAAK,MAAM,GAAG,kBAAkB,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC,CAAC;AAGnE,wBAAgB,WAAW,CACzB,GAAG,EAAE,QAAQ,EACb,GAAG,CAAC,OAAoD,CAAC,EAAE,WAAW,CACpE,eAAe,EACf,MAAM,CACP,GACA,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CA4B9D"}
|
package/dist/node.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createHandler } from "graphql-http/lib/use/http";
|
|
2
|
+
import { contextFailure, masking, operationEnd, handlerRules } from "./core.js";
|
|
3
|
+
export function nodeHandler(app, ...[options = {}]) {
|
|
4
|
+
const mask = masking(options);
|
|
5
|
+
const { context } = options;
|
|
6
|
+
const end = operationEnd(mask, options.onOperationEnd);
|
|
7
|
+
const contexts = new WeakMap();
|
|
8
|
+
const http = createHandler({
|
|
9
|
+
...options,
|
|
10
|
+
schema: app.schema,
|
|
11
|
+
context: context
|
|
12
|
+
? async (req) => {
|
|
13
|
+
try {
|
|
14
|
+
const ctx = await context(req.raw);
|
|
15
|
+
contexts.set(req.raw, ctx);
|
|
16
|
+
return ctx;
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
return contextFailure(mask, error, req.raw);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
: undefined,
|
|
23
|
+
validationRules: handlerRules(options.validationRules),
|
|
24
|
+
onOperation: (req, _args, result) => mask.result(result, { ctx: contexts.get(req.raw), request: req.raw })
|
|
25
|
+
});
|
|
26
|
+
return async (req, res) => {
|
|
27
|
+
await http(req, res);
|
|
28
|
+
await end(contexts.get(req), req);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=node.js.map
|
package/dist/node.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAuB,MAAM,2BAA2B,CAAC;AAE/E,OAAO,EACL,cAAc,EACd,OAAO,EACP,YAAY,EACZ,YAAY,EAMb,MAAM,WAAW,CAAC;AAKnB,MAAM,UAAU,WAAW,CACzB,GAAa,EACb,GAAG,CAAC,OAAO,GAAG,EAA0C,CAGvD;IAED,MAAM,IAAI,GAAG,OAAO,CAA6B,OAAO,CAAC,CAAC;IAC1D,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAC5B,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;IAEvD,MAAM,QAAQ,GAAG,IAAI,OAAO,EAA4B,CAAC;IACzD,MAAM,IAAI,GAAG,aAAa,CAAC;QACzB,GAAG,OAAO;QACV,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,OAAO,EAAE,OAAO;YACd,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;gBACZ,IAAI,CAAC;oBACH,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;oBACnC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;oBAC3B,OAAO,GAAY,CAAC;gBACtB,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,OAAO,cAAc,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;gBAC9C,CAAC;YACH,CAAC;YACH,CAAC,CAAC,SAAS;QACb,eAAe,EAAE,YAAY,CAAC,OAAO,CAAC,eAAe,CAAC;QACtD,WAAW,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAClC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC;KACxE,CAAC,CAAC;IACH,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACxB,MAAM,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACrB,MAAM,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;IACpC,CAAC,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,55 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxara/olio-http",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Serve an olio app over HTTP, on any framework: GraphQL over HTTP with context, error masking and per-operation cleanup.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"graphql",
|
|
7
|
+
"graphql-http",
|
|
8
|
+
"http",
|
|
9
|
+
"nxara"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/nxara-hub/olio.git",
|
|
15
|
+
"directory": "packages/http"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"default": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./fetch": {
|
|
28
|
+
"types": "./dist/fetch.d.ts",
|
|
29
|
+
"default": "./dist/fetch.js"
|
|
30
|
+
},
|
|
31
|
+
"./node": {
|
|
32
|
+
"types": "./dist/node.d.ts",
|
|
33
|
+
"default": "./dist/node.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc",
|
|
41
|
+
"test": "bun test",
|
|
42
|
+
"check-types": "tsc -p tsconfig.test.json"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@nxara/olio-core": "0.1.0",
|
|
46
|
+
"graphql-http": "^1.23.1"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@nxara/olio": "0.1.0",
|
|
50
|
+
"graphql": "^16.14.2"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"graphql": "^16.13.0"
|
|
54
|
+
}
|
|
55
|
+
}
|