@effect-gql/web 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 +7 -0
- package/dist/handler.d.ts +56 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +46 -0
- package/dist/handler.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/sse.d.ts +100 -0
- package/dist/sse.d.ts.map +1 -0
- package/dist/sse.js +146 -0
- package/dist/sse.js.map +1 -0
- package/package.json +46 -0
- package/src/handler.ts +63 -0
- package/src/index.ts +4 -0
- package/src/sse.ts +193 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2025 Nick Fisher
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Context, Layer } from "effect";
|
|
2
|
+
import { HttpRouter } from "@effect/platform";
|
|
3
|
+
/**
|
|
4
|
+
* Result of creating a web handler
|
|
5
|
+
*/
|
|
6
|
+
export interface WebHandler {
|
|
7
|
+
/**
|
|
8
|
+
* Handle a web standard Request and return a Response.
|
|
9
|
+
* This is the main entry point for Cloudflare Workers, Deno, and other WASM runtimes.
|
|
10
|
+
*/
|
|
11
|
+
readonly handler: (request: Request, context?: Context.Context<never>) => Promise<Response>;
|
|
12
|
+
/**
|
|
13
|
+
* Dispose of the handler and clean up resources.
|
|
14
|
+
* Call this when shutting down the worker.
|
|
15
|
+
*/
|
|
16
|
+
readonly dispose: () => Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Create a web standard Request/Response handler from an HttpRouter.
|
|
20
|
+
*
|
|
21
|
+
* This is designed for Cloudflare Workers, Deno, and other WASM-based runtimes
|
|
22
|
+
* that use the Web standard fetch API.
|
|
23
|
+
*
|
|
24
|
+
* @param router - The HttpRouter to handle (typically from makeGraphQLRouter or toRouter)
|
|
25
|
+
* @param layer - Layer providing any services required by the router
|
|
26
|
+
* @returns A handler object with handler() and dispose() methods
|
|
27
|
+
*
|
|
28
|
+
* @example Cloudflare Workers
|
|
29
|
+
* ```typescript
|
|
30
|
+
* import { makeGraphQLRouter } from "@effect-gql/core"
|
|
31
|
+
* import { toHandler } from "@effect-gql/web"
|
|
32
|
+
* import { Layer } from "effect"
|
|
33
|
+
*
|
|
34
|
+
* const router = makeGraphQLRouter(schema, Layer.empty, { graphiql: true })
|
|
35
|
+
* const { handler } = toHandler(router, Layer.empty)
|
|
36
|
+
*
|
|
37
|
+
* export default {
|
|
38
|
+
* async fetch(request: Request) {
|
|
39
|
+
* return await handler(request)
|
|
40
|
+
* }
|
|
41
|
+
* }
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @example Deno
|
|
45
|
+
* ```typescript
|
|
46
|
+
* import { makeGraphQLRouter } from "@effect-gql/core"
|
|
47
|
+
* import { toHandler } from "@effect-gql/web"
|
|
48
|
+
*
|
|
49
|
+
* const router = makeGraphQLRouter(schema, Layer.empty, { graphiql: true })
|
|
50
|
+
* const { handler } = toHandler(router, Layer.empty)
|
|
51
|
+
*
|
|
52
|
+
* Deno.serve((request) => handler(request))
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare const toHandler: <E, R, RE>(router: HttpRouter.HttpRouter<E, R>, layer: Layer.Layer<R, RE>) => WebHandler;
|
|
56
|
+
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AACvC,OAAO,EAAW,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAEtD;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAE3F;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACtC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,SAAS,GAAI,CAAC,EAAE,CAAC,EAAE,EAAE,EAChC,QAAQ,UAAU,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EACnC,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KACxB,UAEF,CAAA"}
|
package/dist/handler.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toHandler = void 0;
|
|
4
|
+
const platform_1 = require("@effect/platform");
|
|
5
|
+
/**
|
|
6
|
+
* Create a web standard Request/Response handler from an HttpRouter.
|
|
7
|
+
*
|
|
8
|
+
* This is designed for Cloudflare Workers, Deno, and other WASM-based runtimes
|
|
9
|
+
* that use the Web standard fetch API.
|
|
10
|
+
*
|
|
11
|
+
* @param router - The HttpRouter to handle (typically from makeGraphQLRouter or toRouter)
|
|
12
|
+
* @param layer - Layer providing any services required by the router
|
|
13
|
+
* @returns A handler object with handler() and dispose() methods
|
|
14
|
+
*
|
|
15
|
+
* @example Cloudflare Workers
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { makeGraphQLRouter } from "@effect-gql/core"
|
|
18
|
+
* import { toHandler } from "@effect-gql/web"
|
|
19
|
+
* import { Layer } from "effect"
|
|
20
|
+
*
|
|
21
|
+
* const router = makeGraphQLRouter(schema, Layer.empty, { graphiql: true })
|
|
22
|
+
* const { handler } = toHandler(router, Layer.empty)
|
|
23
|
+
*
|
|
24
|
+
* export default {
|
|
25
|
+
* async fetch(request: Request) {
|
|
26
|
+
* return await handler(request)
|
|
27
|
+
* }
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @example Deno
|
|
32
|
+
* ```typescript
|
|
33
|
+
* import { makeGraphQLRouter } from "@effect-gql/core"
|
|
34
|
+
* import { toHandler } from "@effect-gql/web"
|
|
35
|
+
*
|
|
36
|
+
* const router = makeGraphQLRouter(schema, Layer.empty, { graphiql: true })
|
|
37
|
+
* const { handler } = toHandler(router, Layer.empty)
|
|
38
|
+
*
|
|
39
|
+
* Deno.serve((request) => handler(request))
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
const toHandler = (router, layer) => {
|
|
43
|
+
return platform_1.HttpApp.toWebHandlerLayer(router, layer);
|
|
44
|
+
};
|
|
45
|
+
exports.toHandler = toHandler;
|
|
46
|
+
//# sourceMappingURL=handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":";;;AACA,+CAAsD;AAmBtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACI,MAAM,SAAS,GAAG,CACvB,MAAmC,EACnC,KAAyB,EACb,EAAE;IACd,OAAO,kBAAO,CAAC,iBAAiB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;AACjD,CAAC,CAAA;AALY,QAAA,SAAS,aAKrB"}
|
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,SAAS,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAA;AAGtD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,KAAK,aAAa,EAAE,MAAM,OAAO,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSSEHandlers = exports.createSSEHandler = exports.toHandler = void 0;
|
|
4
|
+
var handler_1 = require("./handler");
|
|
5
|
+
Object.defineProperty(exports, "toHandler", { enumerable: true, get: function () { return handler_1.toHandler; } });
|
|
6
|
+
// SSE (Server-Sent Events) subscription support
|
|
7
|
+
var sse_1 = require("./sse");
|
|
8
|
+
Object.defineProperty(exports, "createSSEHandler", { enumerable: true, get: function () { return sse_1.createSSEHandler; } });
|
|
9
|
+
Object.defineProperty(exports, "createSSEHandlers", { enumerable: true, get: function () { return sse_1.createSSEHandlers; } });
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,qCAAsD;AAA7C,oGAAA,SAAS,OAAA;AAElB,gDAAgD;AAChD,6BAA+E;AAAtE,uGAAA,gBAAgB,OAAA;AAAE,wGAAA,iBAAiB,OAAA"}
|
package/dist/sse.d.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Layer } from "effect";
|
|
2
|
+
import { GraphQLSchema } from "graphql";
|
|
3
|
+
import { type GraphQLSSEOptions } from "@effect-gql/core";
|
|
4
|
+
/**
|
|
5
|
+
* Options for Web SSE handler
|
|
6
|
+
*/
|
|
7
|
+
export interface WebSSEOptions<R> extends GraphQLSSEOptions<R> {
|
|
8
|
+
/**
|
|
9
|
+
* Path for SSE connections.
|
|
10
|
+
* @default "/graphql/stream"
|
|
11
|
+
*/
|
|
12
|
+
readonly path?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Create an SSE handler for web standard environments.
|
|
16
|
+
*
|
|
17
|
+
* This handler is designed for Cloudflare Workers, Deno, and other runtimes
|
|
18
|
+
* that use the Web standard fetch API. It returns a streaming Response for
|
|
19
|
+
* SSE subscription requests.
|
|
20
|
+
*
|
|
21
|
+
* @param schema - The GraphQL schema with subscription definitions
|
|
22
|
+
* @param layer - Effect layer providing services required by resolvers
|
|
23
|
+
* @param options - Optional lifecycle hooks and configuration
|
|
24
|
+
* @returns A function that handles SSE requests and returns a Response
|
|
25
|
+
*
|
|
26
|
+
* @example Cloudflare Workers
|
|
27
|
+
* ```typescript
|
|
28
|
+
* import { toHandler } from "@effect-gql/web"
|
|
29
|
+
* import { createSSEHandler } from "@effect-gql/web"
|
|
30
|
+
*
|
|
31
|
+
* const graphqlHandler = toHandler(router, Layer.empty)
|
|
32
|
+
* const sseHandler = createSSEHandler(schema, Layer.empty)
|
|
33
|
+
*
|
|
34
|
+
* export default {
|
|
35
|
+
* async fetch(request: Request) {
|
|
36
|
+
* const url = new URL(request.url)
|
|
37
|
+
*
|
|
38
|
+
* // Handle SSE subscriptions
|
|
39
|
+
* if (url.pathname === "/graphql/stream" && request.method === "POST") {
|
|
40
|
+
* return await sseHandler(request)
|
|
41
|
+
* }
|
|
42
|
+
*
|
|
43
|
+
* // Handle regular GraphQL requests
|
|
44
|
+
* return await graphqlHandler.handler(request)
|
|
45
|
+
* }
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @example Deno
|
|
50
|
+
* ```typescript
|
|
51
|
+
* import { toHandler, createSSEHandler } from "@effect-gql/web"
|
|
52
|
+
*
|
|
53
|
+
* const graphqlHandler = toHandler(router, Layer.empty)
|
|
54
|
+
* const sseHandler = createSSEHandler(schema, Layer.empty)
|
|
55
|
+
*
|
|
56
|
+
* Deno.serve((request) => {
|
|
57
|
+
* const url = new URL(request.url)
|
|
58
|
+
*
|
|
59
|
+
* if (url.pathname === "/graphql/stream" && request.method === "POST") {
|
|
60
|
+
* return sseHandler(request)
|
|
61
|
+
* }
|
|
62
|
+
*
|
|
63
|
+
* return graphqlHandler.handler(request)
|
|
64
|
+
* })
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare const createSSEHandler: <R>(schema: GraphQLSchema, layer: Layer.Layer<R>, options?: WebSSEOptions<R>) => ((request: Request) => Promise<Response>);
|
|
68
|
+
/**
|
|
69
|
+
* Create SSE handlers with path matching for web standard environments.
|
|
70
|
+
*
|
|
71
|
+
* This returns an object with methods to check if a request should be
|
|
72
|
+
* handled as SSE and to handle it.
|
|
73
|
+
*
|
|
74
|
+
* @param schema - The GraphQL schema with subscription definitions
|
|
75
|
+
* @param layer - Effect layer providing services required by resolvers
|
|
76
|
+
* @param options - Optional lifecycle hooks and configuration
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```typescript
|
|
80
|
+
* const sse = createSSEHandlers(schema, Layer.empty)
|
|
81
|
+
*
|
|
82
|
+
* export default {
|
|
83
|
+
* async fetch(request: Request) {
|
|
84
|
+
* if (sse.shouldHandle(request)) {
|
|
85
|
+
* return sse.handle(request)
|
|
86
|
+
* }
|
|
87
|
+
* // Handle other requests...
|
|
88
|
+
* }
|
|
89
|
+
* }
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export declare const createSSEHandlers: <R>(schema: GraphQLSchema, layer: Layer.Layer<R>, options?: WebSSEOptions<R>) => {
|
|
93
|
+
/** Path this SSE handler responds to */
|
|
94
|
+
readonly path: string;
|
|
95
|
+
/** Check if a request should be handled as SSE */
|
|
96
|
+
shouldHandle: (request: Request) => boolean;
|
|
97
|
+
/** Handle an SSE request */
|
|
98
|
+
handle: (request: Request) => Promise<Response>;
|
|
99
|
+
};
|
|
100
|
+
//# sourceMappingURL=sse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../src/sse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,EAAU,MAAM,QAAQ,CAAA;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACvC,OAAO,EAIL,KAAK,iBAAiB,EAEvB,MAAM,kBAAkB,CAAA;AAEzB;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC,CAAE,SAAQ,iBAAiB,CAAC,CAAC,CAAC;IAC5D;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,eAAO,MAAM,gBAAgB,GAAI,CAAC,EAChC,QAAQ,aAAa,EACrB,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EACrB,UAAU,aAAa,CAAC,CAAC,CAAC,KACzB,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAgE1C,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,iBAAiB,GAAI,CAAC,EACjC,QAAQ,aAAa,EACrB,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EACrB,UAAU,aAAa,CAAC,CAAC,CAAC,KACzB;IACD,wCAAwC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,kDAAkD;IAClD,YAAY,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAA;IAC3C,4BAA4B;IAC5B,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAchD,CAAA"}
|
package/dist/sse.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSSEHandlers = exports.createSSEHandler = void 0;
|
|
4
|
+
const effect_1 = require("effect");
|
|
5
|
+
const core_1 = require("@effect-gql/core");
|
|
6
|
+
/**
|
|
7
|
+
* Create an SSE handler for web standard environments.
|
|
8
|
+
*
|
|
9
|
+
* This handler is designed for Cloudflare Workers, Deno, and other runtimes
|
|
10
|
+
* that use the Web standard fetch API. It returns a streaming Response for
|
|
11
|
+
* SSE subscription requests.
|
|
12
|
+
*
|
|
13
|
+
* @param schema - The GraphQL schema with subscription definitions
|
|
14
|
+
* @param layer - Effect layer providing services required by resolvers
|
|
15
|
+
* @param options - Optional lifecycle hooks and configuration
|
|
16
|
+
* @returns A function that handles SSE requests and returns a Response
|
|
17
|
+
*
|
|
18
|
+
* @example Cloudflare Workers
|
|
19
|
+
* ```typescript
|
|
20
|
+
* import { toHandler } from "@effect-gql/web"
|
|
21
|
+
* import { createSSEHandler } from "@effect-gql/web"
|
|
22
|
+
*
|
|
23
|
+
* const graphqlHandler = toHandler(router, Layer.empty)
|
|
24
|
+
* const sseHandler = createSSEHandler(schema, Layer.empty)
|
|
25
|
+
*
|
|
26
|
+
* export default {
|
|
27
|
+
* async fetch(request: Request) {
|
|
28
|
+
* const url = new URL(request.url)
|
|
29
|
+
*
|
|
30
|
+
* // Handle SSE subscriptions
|
|
31
|
+
* if (url.pathname === "/graphql/stream" && request.method === "POST") {
|
|
32
|
+
* return await sseHandler(request)
|
|
33
|
+
* }
|
|
34
|
+
*
|
|
35
|
+
* // Handle regular GraphQL requests
|
|
36
|
+
* return await graphqlHandler.handler(request)
|
|
37
|
+
* }
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @example Deno
|
|
42
|
+
* ```typescript
|
|
43
|
+
* import { toHandler, createSSEHandler } from "@effect-gql/web"
|
|
44
|
+
*
|
|
45
|
+
* const graphqlHandler = toHandler(router, Layer.empty)
|
|
46
|
+
* const sseHandler = createSSEHandler(schema, Layer.empty)
|
|
47
|
+
*
|
|
48
|
+
* Deno.serve((request) => {
|
|
49
|
+
* const url = new URL(request.url)
|
|
50
|
+
*
|
|
51
|
+
* if (url.pathname === "/graphql/stream" && request.method === "POST") {
|
|
52
|
+
* return sseHandler(request)
|
|
53
|
+
* }
|
|
54
|
+
*
|
|
55
|
+
* return graphqlHandler.handler(request)
|
|
56
|
+
* })
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
const createSSEHandler = (schema, layer, options) => {
|
|
60
|
+
const sseHandler = (0, core_1.makeGraphQLSSEHandler)(schema, layer, options);
|
|
61
|
+
return async (request) => {
|
|
62
|
+
// Check Accept header for SSE support
|
|
63
|
+
const accept = request.headers.get("accept") ?? "";
|
|
64
|
+
if (!accept.includes("text/event-stream") && !accept.includes("*/*")) {
|
|
65
|
+
return new Response(JSON.stringify({
|
|
66
|
+
errors: [{ message: "Client must accept text/event-stream" }],
|
|
67
|
+
}), { status: 406, headers: { "Content-Type": "application/json" } });
|
|
68
|
+
}
|
|
69
|
+
// Read and parse the request body
|
|
70
|
+
let subscriptionRequest;
|
|
71
|
+
try {
|
|
72
|
+
const body = (await request.json());
|
|
73
|
+
if (typeof body.query !== "string") {
|
|
74
|
+
throw new Error("Missing query");
|
|
75
|
+
}
|
|
76
|
+
subscriptionRequest = {
|
|
77
|
+
query: body.query,
|
|
78
|
+
variables: body.variables,
|
|
79
|
+
operationName: body.operationName,
|
|
80
|
+
extensions: body.extensions,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return new Response(JSON.stringify({
|
|
85
|
+
errors: [{ message: "Invalid GraphQL request body" }],
|
|
86
|
+
}), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
87
|
+
}
|
|
88
|
+
// Get the event stream
|
|
89
|
+
const eventStream = sseHandler(subscriptionRequest, request.headers);
|
|
90
|
+
// Create a ReadableStream from the Effect Stream
|
|
91
|
+
const readableStream = new ReadableStream({
|
|
92
|
+
async start(controller) {
|
|
93
|
+
const encoder = new TextEncoder();
|
|
94
|
+
await effect_1.Effect.runPromise(effect_1.Stream.runForEach(eventStream, (event) => effect_1.Effect.sync(() => {
|
|
95
|
+
const message = (0, core_1.formatSSEMessage)(event);
|
|
96
|
+
controller.enqueue(encoder.encode(message));
|
|
97
|
+
})).pipe(effect_1.Effect.catchAll((error) => effect_1.Effect.logWarning("SSE stream error", error)), effect_1.Effect.ensuring(effect_1.Effect.sync(() => controller.close()))));
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
return new Response(readableStream, {
|
|
101
|
+
status: 200,
|
|
102
|
+
headers: core_1.SSE_HEADERS,
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
exports.createSSEHandler = createSSEHandler;
|
|
107
|
+
/**
|
|
108
|
+
* Create SSE handlers with path matching for web standard environments.
|
|
109
|
+
*
|
|
110
|
+
* This returns an object with methods to check if a request should be
|
|
111
|
+
* handled as SSE and to handle it.
|
|
112
|
+
*
|
|
113
|
+
* @param schema - The GraphQL schema with subscription definitions
|
|
114
|
+
* @param layer - Effect layer providing services required by resolvers
|
|
115
|
+
* @param options - Optional lifecycle hooks and configuration
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```typescript
|
|
119
|
+
* const sse = createSSEHandlers(schema, Layer.empty)
|
|
120
|
+
*
|
|
121
|
+
* export default {
|
|
122
|
+
* async fetch(request: Request) {
|
|
123
|
+
* if (sse.shouldHandle(request)) {
|
|
124
|
+
* return sse.handle(request)
|
|
125
|
+
* }
|
|
126
|
+
* // Handle other requests...
|
|
127
|
+
* }
|
|
128
|
+
* }
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
const createSSEHandlers = (schema, layer, options) => {
|
|
132
|
+
const path = options?.path ?? "/graphql/stream";
|
|
133
|
+
const handler = (0, exports.createSSEHandler)(schema, layer, options);
|
|
134
|
+
return {
|
|
135
|
+
path,
|
|
136
|
+
shouldHandle: (request) => {
|
|
137
|
+
if (request.method !== "POST")
|
|
138
|
+
return false;
|
|
139
|
+
const url = new URL(request.url);
|
|
140
|
+
return url.pathname === path;
|
|
141
|
+
},
|
|
142
|
+
handle: handler,
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
exports.createSSEHandlers = createSSEHandlers;
|
|
146
|
+
//# sourceMappingURL=sse.js.map
|
package/dist/sse.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.js","sourceRoot":"","sources":["../src/sse.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@effect-gql/web",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Web standard handler for @effect-gql/core - works with Cloudflare Workers, Deno, and other WASM runtimes",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"src"
|
|
16
|
+
],
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"@effect-gql/core": "^0.1.0",
|
|
19
|
+
"@effect/platform": "^0.94.0",
|
|
20
|
+
"effect": "^3.19.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@effect-gql/core": "*",
|
|
24
|
+
"@effect/platform": "^0.94.0",
|
|
25
|
+
"effect": "^3.19.13",
|
|
26
|
+
"graphql": "^16.0.0"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"effect",
|
|
30
|
+
"graphql",
|
|
31
|
+
"cloudflare",
|
|
32
|
+
"workers",
|
|
33
|
+
"wasm",
|
|
34
|
+
"web",
|
|
35
|
+
"deno"
|
|
36
|
+
],
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc",
|
|
40
|
+
"dev": "tsc --watch",
|
|
41
|
+
"clean": "rm -rf dist",
|
|
42
|
+
"test": "vitest run",
|
|
43
|
+
"test:unit": "vitest run test/unit",
|
|
44
|
+
"test:watch": "vitest"
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/handler.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Context, Layer } from "effect"
|
|
2
|
+
import { HttpApp, HttpRouter } from "@effect/platform"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Result of creating a web handler
|
|
6
|
+
*/
|
|
7
|
+
export interface WebHandler {
|
|
8
|
+
/**
|
|
9
|
+
* Handle a web standard Request and return a Response.
|
|
10
|
+
* This is the main entry point for Cloudflare Workers, Deno, and other WASM runtimes.
|
|
11
|
+
*/
|
|
12
|
+
readonly handler: (request: Request, context?: Context.Context<never>) => Promise<Response>
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Dispose of the handler and clean up resources.
|
|
16
|
+
* Call this when shutting down the worker.
|
|
17
|
+
*/
|
|
18
|
+
readonly dispose: () => Promise<void>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create a web standard Request/Response handler from an HttpRouter.
|
|
23
|
+
*
|
|
24
|
+
* This is designed for Cloudflare Workers, Deno, and other WASM-based runtimes
|
|
25
|
+
* that use the Web standard fetch API.
|
|
26
|
+
*
|
|
27
|
+
* @param router - The HttpRouter to handle (typically from makeGraphQLRouter or toRouter)
|
|
28
|
+
* @param layer - Layer providing any services required by the router
|
|
29
|
+
* @returns A handler object with handler() and dispose() methods
|
|
30
|
+
*
|
|
31
|
+
* @example Cloudflare Workers
|
|
32
|
+
* ```typescript
|
|
33
|
+
* import { makeGraphQLRouter } from "@effect-gql/core"
|
|
34
|
+
* import { toHandler } from "@effect-gql/web"
|
|
35
|
+
* import { Layer } from "effect"
|
|
36
|
+
*
|
|
37
|
+
* const router = makeGraphQLRouter(schema, Layer.empty, { graphiql: true })
|
|
38
|
+
* const { handler } = toHandler(router, Layer.empty)
|
|
39
|
+
*
|
|
40
|
+
* export default {
|
|
41
|
+
* async fetch(request: Request) {
|
|
42
|
+
* return await handler(request)
|
|
43
|
+
* }
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @example Deno
|
|
48
|
+
* ```typescript
|
|
49
|
+
* import { makeGraphQLRouter } from "@effect-gql/core"
|
|
50
|
+
* import { toHandler } from "@effect-gql/web"
|
|
51
|
+
*
|
|
52
|
+
* const router = makeGraphQLRouter(schema, Layer.empty, { graphiql: true })
|
|
53
|
+
* const { handler } = toHandler(router, Layer.empty)
|
|
54
|
+
*
|
|
55
|
+
* Deno.serve((request) => handler(request))
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export const toHandler = <E, R, RE>(
|
|
59
|
+
router: HttpRouter.HttpRouter<E, R>,
|
|
60
|
+
layer: Layer.Layer<R, RE>
|
|
61
|
+
): WebHandler => {
|
|
62
|
+
return HttpApp.toWebHandlerLayer(router, layer)
|
|
63
|
+
}
|
package/src/index.ts
ADDED
package/src/sse.ts
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { Effect, Layer, Stream } from "effect"
|
|
2
|
+
import { GraphQLSchema } from "graphql"
|
|
3
|
+
import {
|
|
4
|
+
makeGraphQLSSEHandler,
|
|
5
|
+
formatSSEMessage,
|
|
6
|
+
SSE_HEADERS,
|
|
7
|
+
type GraphQLSSEOptions,
|
|
8
|
+
type SSESubscriptionRequest,
|
|
9
|
+
} from "@effect-gql/core"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Options for Web SSE handler
|
|
13
|
+
*/
|
|
14
|
+
export interface WebSSEOptions<R> extends GraphQLSSEOptions<R> {
|
|
15
|
+
/**
|
|
16
|
+
* Path for SSE connections.
|
|
17
|
+
* @default "/graphql/stream"
|
|
18
|
+
*/
|
|
19
|
+
readonly path?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Create an SSE handler for web standard environments.
|
|
24
|
+
*
|
|
25
|
+
* This handler is designed for Cloudflare Workers, Deno, and other runtimes
|
|
26
|
+
* that use the Web standard fetch API. It returns a streaming Response for
|
|
27
|
+
* SSE subscription requests.
|
|
28
|
+
*
|
|
29
|
+
* @param schema - The GraphQL schema with subscription definitions
|
|
30
|
+
* @param layer - Effect layer providing services required by resolvers
|
|
31
|
+
* @param options - Optional lifecycle hooks and configuration
|
|
32
|
+
* @returns A function that handles SSE requests and returns a Response
|
|
33
|
+
*
|
|
34
|
+
* @example Cloudflare Workers
|
|
35
|
+
* ```typescript
|
|
36
|
+
* import { toHandler } from "@effect-gql/web"
|
|
37
|
+
* import { createSSEHandler } from "@effect-gql/web"
|
|
38
|
+
*
|
|
39
|
+
* const graphqlHandler = toHandler(router, Layer.empty)
|
|
40
|
+
* const sseHandler = createSSEHandler(schema, Layer.empty)
|
|
41
|
+
*
|
|
42
|
+
* export default {
|
|
43
|
+
* async fetch(request: Request) {
|
|
44
|
+
* const url = new URL(request.url)
|
|
45
|
+
*
|
|
46
|
+
* // Handle SSE subscriptions
|
|
47
|
+
* if (url.pathname === "/graphql/stream" && request.method === "POST") {
|
|
48
|
+
* return await sseHandler(request)
|
|
49
|
+
* }
|
|
50
|
+
*
|
|
51
|
+
* // Handle regular GraphQL requests
|
|
52
|
+
* return await graphqlHandler.handler(request)
|
|
53
|
+
* }
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* @example Deno
|
|
58
|
+
* ```typescript
|
|
59
|
+
* import { toHandler, createSSEHandler } from "@effect-gql/web"
|
|
60
|
+
*
|
|
61
|
+
* const graphqlHandler = toHandler(router, Layer.empty)
|
|
62
|
+
* const sseHandler = createSSEHandler(schema, Layer.empty)
|
|
63
|
+
*
|
|
64
|
+
* Deno.serve((request) => {
|
|
65
|
+
* const url = new URL(request.url)
|
|
66
|
+
*
|
|
67
|
+
* if (url.pathname === "/graphql/stream" && request.method === "POST") {
|
|
68
|
+
* return sseHandler(request)
|
|
69
|
+
* }
|
|
70
|
+
*
|
|
71
|
+
* return graphqlHandler.handler(request)
|
|
72
|
+
* })
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export const createSSEHandler = <R>(
|
|
76
|
+
schema: GraphQLSchema,
|
|
77
|
+
layer: Layer.Layer<R>,
|
|
78
|
+
options?: WebSSEOptions<R>
|
|
79
|
+
): ((request: Request) => Promise<Response>) => {
|
|
80
|
+
const sseHandler = makeGraphQLSSEHandler(schema, layer, options)
|
|
81
|
+
|
|
82
|
+
return async (request: Request): Promise<Response> => {
|
|
83
|
+
// Check Accept header for SSE support
|
|
84
|
+
const accept = request.headers.get("accept") ?? ""
|
|
85
|
+
if (!accept.includes("text/event-stream") && !accept.includes("*/*")) {
|
|
86
|
+
return new Response(
|
|
87
|
+
JSON.stringify({
|
|
88
|
+
errors: [{ message: "Client must accept text/event-stream" }],
|
|
89
|
+
}),
|
|
90
|
+
{ status: 406, headers: { "Content-Type": "application/json" } }
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Read and parse the request body
|
|
95
|
+
let subscriptionRequest: SSESubscriptionRequest
|
|
96
|
+
try {
|
|
97
|
+
const body = (await request.json()) as Record<string, unknown>
|
|
98
|
+
if (typeof body.query !== "string") {
|
|
99
|
+
throw new Error("Missing query")
|
|
100
|
+
}
|
|
101
|
+
subscriptionRequest = {
|
|
102
|
+
query: body.query,
|
|
103
|
+
variables: body.variables as Record<string, unknown> | undefined,
|
|
104
|
+
operationName: body.operationName as string | undefined,
|
|
105
|
+
extensions: body.extensions as Record<string, unknown> | undefined,
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
return new Response(
|
|
109
|
+
JSON.stringify({
|
|
110
|
+
errors: [{ message: "Invalid GraphQL request body" }],
|
|
111
|
+
}),
|
|
112
|
+
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Get the event stream
|
|
117
|
+
const eventStream = sseHandler(subscriptionRequest, request.headers)
|
|
118
|
+
|
|
119
|
+
// Create a ReadableStream from the Effect Stream
|
|
120
|
+
const readableStream = new ReadableStream({
|
|
121
|
+
async start(controller) {
|
|
122
|
+
const encoder = new TextEncoder()
|
|
123
|
+
|
|
124
|
+
await Effect.runPromise(
|
|
125
|
+
Stream.runForEach(eventStream, (event) =>
|
|
126
|
+
Effect.sync(() => {
|
|
127
|
+
const message = formatSSEMessage(event)
|
|
128
|
+
controller.enqueue(encoder.encode(message))
|
|
129
|
+
})
|
|
130
|
+
).pipe(
|
|
131
|
+
Effect.catchAll((error) => Effect.logWarning("SSE stream error", error)),
|
|
132
|
+
Effect.ensuring(Effect.sync(() => controller.close()))
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
},
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
return new Response(readableStream, {
|
|
139
|
+
status: 200,
|
|
140
|
+
headers: SSE_HEADERS,
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Create SSE handlers with path matching for web standard environments.
|
|
147
|
+
*
|
|
148
|
+
* This returns an object with methods to check if a request should be
|
|
149
|
+
* handled as SSE and to handle it.
|
|
150
|
+
*
|
|
151
|
+
* @param schema - The GraphQL schema with subscription definitions
|
|
152
|
+
* @param layer - Effect layer providing services required by resolvers
|
|
153
|
+
* @param options - Optional lifecycle hooks and configuration
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```typescript
|
|
157
|
+
* const sse = createSSEHandlers(schema, Layer.empty)
|
|
158
|
+
*
|
|
159
|
+
* export default {
|
|
160
|
+
* async fetch(request: Request) {
|
|
161
|
+
* if (sse.shouldHandle(request)) {
|
|
162
|
+
* return sse.handle(request)
|
|
163
|
+
* }
|
|
164
|
+
* // Handle other requests...
|
|
165
|
+
* }
|
|
166
|
+
* }
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
export const createSSEHandlers = <R>(
|
|
170
|
+
schema: GraphQLSchema,
|
|
171
|
+
layer: Layer.Layer<R>,
|
|
172
|
+
options?: WebSSEOptions<R>
|
|
173
|
+
): {
|
|
174
|
+
/** Path this SSE handler responds to */
|
|
175
|
+
readonly path: string
|
|
176
|
+
/** Check if a request should be handled as SSE */
|
|
177
|
+
shouldHandle: (request: Request) => boolean
|
|
178
|
+
/** Handle an SSE request */
|
|
179
|
+
handle: (request: Request) => Promise<Response>
|
|
180
|
+
} => {
|
|
181
|
+
const path = options?.path ?? "/graphql/stream"
|
|
182
|
+
const handler = createSSEHandler(schema, layer, options)
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
path,
|
|
186
|
+
shouldHandle: (request: Request) => {
|
|
187
|
+
if (request.method !== "POST") return false
|
|
188
|
+
const url = new URL(request.url)
|
|
189
|
+
return url.pathname === path
|
|
190
|
+
},
|
|
191
|
+
handle: handler,
|
|
192
|
+
}
|
|
193
|
+
}
|