@effect-gql/express 0.1.0 → 1.0.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 ADDED
@@ -0,0 +1,100 @@
1
+ # Effect GraphQL
2
+
3
+ A GraphQL framework for Effect-TS that brings full type safety, composability, and functional programming to your GraphQL servers.
4
+
5
+ > **Note:** This is an experimental prototype exploring the integration between Effect Schema, Effect's service system, and GraphQL.
6
+
7
+ ## Features
8
+
9
+ - **Type-Safe End-to-End** - Define schemas once with Effect Schema, get TypeScript types and GraphQL types automatically
10
+ - **Effect-Powered Resolvers** - Resolvers are Effect programs with built-in error handling and service injection
11
+ - **Immutable Builder** - Fluent, pipe-able API for composing schemas from reusable parts
12
+ - **Service Integration** - Use Effect's Layer system for dependency injection
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @effect-gql/core effect graphql
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ```typescript
23
+ import { Effect, Layer } from "effect"
24
+ import * as S from "effect/Schema"
25
+ import { GraphQLSchemaBuilder, execute } from "@effect-gql/core"
26
+
27
+ // Define your schema with Effect Schema
28
+ const UserSchema = S.Struct({
29
+ id: S.String,
30
+ name: S.String,
31
+ email: S.String,
32
+ })
33
+
34
+ // Build your GraphQL schema
35
+ const schema = GraphQLSchemaBuilder.empty
36
+ .objectType({ name: "User", schema: UserSchema })
37
+ .query("users", {
38
+ type: S.Array(UserSchema),
39
+ resolve: () => Effect.succeed([
40
+ { id: "1", name: "Alice", email: "alice@example.com" },
41
+ { id: "2", name: "Bob", email: "bob@example.com" },
42
+ ]),
43
+ })
44
+ .query("user", {
45
+ type: UserSchema,
46
+ args: S.Struct({ id: S.String }),
47
+ resolve: (args) => Effect.succeed({
48
+ id: args.id,
49
+ name: "Alice",
50
+ email: "alice@example.com",
51
+ }),
52
+ })
53
+ .buildSchema()
54
+
55
+ // Execute a query
56
+ const result = await Effect.runPromise(
57
+ execute(schema, Layer.empty)(`
58
+ query {
59
+ users { id name email }
60
+ }
61
+ `)
62
+ )
63
+ ```
64
+
65
+ ## Documentation
66
+
67
+ For full documentation, guides, and API reference, visit the [documentation site](https://nrf110.github.io/effect-gql/).
68
+
69
+ ## Development
70
+
71
+ ```bash
72
+ # Install dependencies
73
+ npm install
74
+
75
+ # Build the project
76
+ npm run build
77
+
78
+ # Run tests
79
+ npm test
80
+
81
+ # Development mode with watch
82
+ npm run dev
83
+ ```
84
+
85
+ ## Contributing
86
+
87
+ Contributions are welcome! Here's how you can help:
88
+
89
+ 1. **Report bugs** - Open an issue describing the problem and steps to reproduce
90
+ 2. **Suggest features** - Open an issue describing your idea
91
+ 3. **Submit PRs** - Fork the repo, make your changes, and open a pull request
92
+
93
+ Please ensure your code:
94
+ - Passes all existing tests (`npm test`)
95
+ - Includes tests for new functionality
96
+ - Follows the existing code style
97
+
98
+ ## License
99
+
100
+ MIT
package/index.cjs ADDED
@@ -0,0 +1,252 @@
1
+ 'use strict';
2
+
3
+ var platform = require('@effect/platform');
4
+ var effect = require('effect');
5
+ var ws = require('ws');
6
+ var core = require('@effect-gql/core');
7
+
8
+ // src/middleware.ts
9
+
10
+ // src/http-utils.ts
11
+ var toWebHeaders = (nodeHeaders) => {
12
+ const headers = new Headers();
13
+ for (const [key, value] of Object.entries(nodeHeaders)) {
14
+ if (value) {
15
+ if (Array.isArray(value)) {
16
+ value.forEach((v) => headers.append(key, v));
17
+ } else {
18
+ headers.set(key, value);
19
+ }
20
+ }
21
+ }
22
+ return headers;
23
+ };
24
+
25
+ // src/middleware.ts
26
+ var toMiddleware = (router, layer) => {
27
+ const { handler } = platform.HttpApp.toWebHandlerLayer(router, layer);
28
+ return async (req, res, next) => {
29
+ try {
30
+ const baseUrl = `${req.protocol}://${req.hostname}`;
31
+ const url = new URL(req.originalUrl || "/", baseUrl).href;
32
+ const headers = toWebHeaders(req.headers);
33
+ const webRequest = new Request(url, {
34
+ method: req.method,
35
+ headers,
36
+ body: ["GET", "HEAD"].includes(req.method) ? void 0 : JSON.stringify(req.body)
37
+ });
38
+ const webResponse = await handler(webRequest);
39
+ res.status(webResponse.status);
40
+ webResponse.headers.forEach((value, key) => {
41
+ res.setHeader(key, value);
42
+ });
43
+ const body = await webResponse.text();
44
+ res.send(body);
45
+ } catch (error) {
46
+ next(error);
47
+ }
48
+ };
49
+ };
50
+ var attachWebSocket = (server, schema, layer, options) => {
51
+ const wss = new ws.WebSocketServer({ noServer: true });
52
+ const path = options?.path ?? "/graphql";
53
+ const handler = core.makeGraphQLWSHandler(schema, layer, options);
54
+ const activeConnections = /* @__PURE__ */ new Set();
55
+ wss.on("connection", (ws) => {
56
+ activeConnections.add(ws);
57
+ const effectSocket = core.toEffectWebSocketFromWs(ws);
58
+ effect.Effect.runPromise(
59
+ handler(effectSocket).pipe(
60
+ effect.Effect.catchAll((error) => effect.Effect.logError("GraphQL WebSocket handler error", error))
61
+ )
62
+ ).finally(() => {
63
+ activeConnections.delete(ws);
64
+ });
65
+ });
66
+ const handleUpgrade = (request, socket, head) => {
67
+ const url = new URL(request.url ?? "/", `http://${request.headers.host}`);
68
+ if (url.pathname !== path) {
69
+ socket.destroy();
70
+ return;
71
+ }
72
+ const protocol = request.headers["sec-websocket-protocol"];
73
+ if (!protocol?.includes("graphql-transport-ws")) {
74
+ socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
75
+ socket.destroy();
76
+ return;
77
+ }
78
+ wss.handleUpgrade(request, socket, head, (ws) => {
79
+ wss.emit("connection", ws, request);
80
+ });
81
+ };
82
+ server.on("upgrade", (request, socket, head) => {
83
+ handleUpgrade(request, socket, head);
84
+ });
85
+ const close = async () => {
86
+ for (const ws of activeConnections) {
87
+ ws.close(1001, "Server shutting down");
88
+ }
89
+ activeConnections.clear();
90
+ return new Promise((resolve, reject) => {
91
+ wss.close((error) => {
92
+ if (error) reject(error);
93
+ else resolve();
94
+ });
95
+ });
96
+ };
97
+ return { close };
98
+ };
99
+ var sseMiddleware = (schema, layer, options) => {
100
+ const path = options?.path ?? "/graphql/stream";
101
+ const sseHandler = core.makeGraphQLSSEHandler(schema, layer, options);
102
+ return async (req, res, next) => {
103
+ if (req.path !== path) {
104
+ next();
105
+ return;
106
+ }
107
+ if (req.method !== "POST") {
108
+ next();
109
+ return;
110
+ }
111
+ const accept = req.headers.accept ?? "";
112
+ if (!accept.includes("text/event-stream") && !accept.includes("*/*")) {
113
+ res.status(406).json({
114
+ errors: [{ message: "Client must accept text/event-stream" }]
115
+ });
116
+ return;
117
+ }
118
+ let subscriptionRequest;
119
+ try {
120
+ const body = req.body;
121
+ if (typeof body.query !== "string") {
122
+ throw new Error("Missing query");
123
+ }
124
+ subscriptionRequest = {
125
+ query: body.query,
126
+ variables: body.variables,
127
+ operationName: body.operationName,
128
+ extensions: body.extensions
129
+ };
130
+ } catch {
131
+ res.status(400).json({
132
+ errors: [{ message: "Invalid GraphQL request body" }]
133
+ });
134
+ return;
135
+ }
136
+ const headers = toWebHeaders(req.headers);
137
+ res.writeHead(200, core.SSE_HEADERS);
138
+ const eventStream = sseHandler(subscriptionRequest, headers);
139
+ const streamEffect = effect.Effect.gen(function* () {
140
+ const clientDisconnected = yield* effect.Deferred.make();
141
+ req.on("close", () => {
142
+ effect.Effect.runPromise(effect.Deferred.succeed(clientDisconnected, void 0)).catch(() => {
143
+ });
144
+ });
145
+ req.on("error", (error) => {
146
+ effect.Effect.runPromise(effect.Deferred.fail(clientDisconnected, new core.SSEError({ cause: error }))).catch(
147
+ () => {
148
+ }
149
+ );
150
+ });
151
+ const runStream = effect.Stream.runForEach(
152
+ eventStream,
153
+ (event) => effect.Effect.async((resume) => {
154
+ const message = core.formatSSEMessage(event);
155
+ res.write(message, (error) => {
156
+ if (error) {
157
+ resume(effect.Effect.fail(new core.SSEError({ cause: error })));
158
+ } else {
159
+ resume(effect.Effect.succeed(void 0));
160
+ }
161
+ });
162
+ })
163
+ );
164
+ yield* effect.Effect.race(
165
+ runStream.pipe(effect.Effect.catchAll((error) => effect.Effect.logWarning("SSE stream error", error))),
166
+ effect.Deferred.await(clientDisconnected)
167
+ );
168
+ });
169
+ await effect.Effect.runPromise(
170
+ streamEffect.pipe(
171
+ effect.Effect.ensuring(effect.Effect.sync(() => res.end())),
172
+ effect.Effect.catchAll(() => effect.Effect.void)
173
+ )
174
+ );
175
+ };
176
+ };
177
+ var createSSEHandler = (schema, layer, options) => {
178
+ const sseHandler = core.makeGraphQLSSEHandler(schema, layer, options);
179
+ return async (req, res, _next) => {
180
+ const accept = req.headers.accept ?? "";
181
+ if (!accept.includes("text/event-stream") && !accept.includes("*/*")) {
182
+ res.status(406).json({
183
+ errors: [{ message: "Client must accept text/event-stream" }]
184
+ });
185
+ return;
186
+ }
187
+ let subscriptionRequest;
188
+ try {
189
+ const body = req.body;
190
+ if (typeof body.query !== "string") {
191
+ throw new Error("Missing query");
192
+ }
193
+ subscriptionRequest = {
194
+ query: body.query,
195
+ variables: body.variables,
196
+ operationName: body.operationName,
197
+ extensions: body.extensions
198
+ };
199
+ } catch {
200
+ res.status(400).json({
201
+ errors: [{ message: "Invalid GraphQL request body" }]
202
+ });
203
+ return;
204
+ }
205
+ const headers = toWebHeaders(req.headers);
206
+ res.writeHead(200, core.SSE_HEADERS);
207
+ const eventStream = sseHandler(subscriptionRequest, headers);
208
+ const streamEffect = effect.Effect.gen(function* () {
209
+ const clientDisconnected = yield* effect.Deferred.make();
210
+ req.on("close", () => {
211
+ effect.Effect.runPromise(effect.Deferred.succeed(clientDisconnected, void 0)).catch(() => {
212
+ });
213
+ });
214
+ req.on("error", (error) => {
215
+ effect.Effect.runPromise(effect.Deferred.fail(clientDisconnected, new core.SSEError({ cause: error }))).catch(
216
+ () => {
217
+ }
218
+ );
219
+ });
220
+ const runStream = effect.Stream.runForEach(
221
+ eventStream,
222
+ (event) => effect.Effect.async((resume) => {
223
+ const message = core.formatSSEMessage(event);
224
+ res.write(message, (error) => {
225
+ if (error) {
226
+ resume(effect.Effect.fail(new core.SSEError({ cause: error })));
227
+ } else {
228
+ resume(effect.Effect.succeed(void 0));
229
+ }
230
+ });
231
+ })
232
+ );
233
+ yield* effect.Effect.race(
234
+ runStream.pipe(effect.Effect.catchAll((error) => effect.Effect.logWarning("SSE stream error", error))),
235
+ effect.Deferred.await(clientDisconnected)
236
+ );
237
+ });
238
+ await effect.Effect.runPromise(
239
+ streamEffect.pipe(
240
+ effect.Effect.ensuring(effect.Effect.sync(() => res.end())),
241
+ effect.Effect.catchAll(() => effect.Effect.void)
242
+ )
243
+ );
244
+ };
245
+ };
246
+
247
+ exports.attachWebSocket = attachWebSocket;
248
+ exports.createSSEHandler = createSSEHandler;
249
+ exports.sseMiddleware = sseMiddleware;
250
+ exports.toMiddleware = toMiddleware;
251
+ //# sourceMappingURL=index.cjs.map
252
+ //# sourceMappingURL=index.cjs.map
package/index.cjs.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/http-utils.ts","../src/middleware.ts","../src/ws.ts","../src/sse.ts"],"names":["HttpApp","WebSocketServer","makeGraphQLWSHandler","toEffectWebSocketFromWs","Effect","makeGraphQLSSEHandler","SSE_HEADERS","Deferred","SSEError","Stream","formatSSEMessage"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { IncomingHttpHeaders } from \"node:http\"\n\n/**\n * Convert Node.js/Express IncomingHttpHeaders to web standard Headers.\n *\n * This handles the difference between Node.js headers (which can be\n * string | string[] | undefined) and web Headers (which are always strings).\n *\n * @param nodeHeaders - Headers from req.headers\n * @returns A web standard Headers object\n */\nexport const toWebHeaders = (nodeHeaders: IncomingHttpHeaders): Headers => {\n const headers = new Headers()\n for (const [key, value] of Object.entries(nodeHeaders)) {\n if (value) {\n if (Array.isArray(value)) {\n value.forEach((v) => headers.append(key, v))\n } else {\n headers.set(key, value)\n }\n }\n }\n return headers\n}\n","import { Layer } from \"effect\"\nimport { HttpApp, HttpRouter } from \"@effect/platform\"\nimport type { Request, Response, NextFunction, RequestHandler } from \"express\"\nimport { toWebHeaders } from \"./http-utils\"\n\n/**\n * Convert an HttpRouter to Express middleware.\n *\n * This creates Express-compatible middleware that can be mounted on any Express app.\n * The middleware converts Express requests to web standard Requests, processes them\n * through the Effect router, and writes the response back to Express.\n *\n * @param router - The HttpRouter to convert (typically from makeGraphQLRouter or toRouter)\n * @param layer - Layer providing any services required by the router\n * @returns Express middleware function\n *\n * @example\n * ```typescript\n * import express from \"express\"\n * import { makeGraphQLRouter } from \"@effect-gql/core\"\n * import { toMiddleware } from \"@effect-gql/express\"\n * import { Layer } from \"effect\"\n *\n * const router = makeGraphQLRouter(schema, Layer.empty, { graphiql: true })\n *\n * const app = express()\n * app.use(toMiddleware(router, Layer.empty))\n * app.listen(4000, () => console.log(\"Server running on http://localhost:4000\"))\n * ```\n */\nexport const toMiddleware = <E, R, RE>(\n router: HttpRouter.HttpRouter<E, R>,\n layer: Layer.Layer<R, RE>\n): RequestHandler => {\n const { handler } = HttpApp.toWebHandlerLayer(router, layer)\n\n return async (req: Request, res: Response, next: NextFunction) => {\n try {\n // Convert Express request to web standard Request\n // Use URL constructor for safe URL parsing (avoids Host header injection)\n const baseUrl = `${req.protocol}://${req.hostname}`\n const url = new URL(req.originalUrl || \"/\", baseUrl).href\n const headers = toWebHeaders(req.headers)\n\n const webRequest = new Request(url, {\n method: req.method,\n headers,\n body: [\"GET\", \"HEAD\"].includes(req.method) ? undefined : JSON.stringify(req.body),\n })\n\n // Process through Effect handler\n const webResponse = await handler(webRequest)\n\n // Write response back to Express\n res.status(webResponse.status)\n webResponse.headers.forEach((value, key) => {\n res.setHeader(key, value)\n })\n const body = await webResponse.text()\n res.send(body)\n } catch (error) {\n next(error)\n }\n }\n}\n","import { Effect, Layer } from \"effect\"\nimport type { Server } from \"node:http\"\nimport type { IncomingMessage } from \"node:http\"\nimport type { Duplex } from \"node:stream\"\nimport { WebSocket, WebSocketServer } from \"ws\"\nimport { GraphQLSchema } from \"graphql\"\nimport {\n makeGraphQLWSHandler,\n toEffectWebSocketFromWs,\n type GraphQLWSOptions,\n} from \"@effect-gql/core\"\n\n/**\n * Options for Express WebSocket server\n */\nexport interface ExpressWSOptions<R> extends GraphQLWSOptions<R> {\n /**\n * Path for WebSocket connections.\n * @default \"/graphql\"\n */\n readonly path?: string\n}\n\n/**\n * Attach WebSocket subscription support to an Express HTTP server.\n *\n * Since Express middleware doesn't own the HTTP server, this function\n * must be called separately with the HTTP server instance to enable\n * WebSocket subscriptions.\n *\n * @param server - The HTTP server running the Express app\n * @param schema - The GraphQL schema with subscription definitions\n * @param layer - Effect layer providing services required by resolvers\n * @param options - Optional configuration and lifecycle hooks\n * @returns Object with cleanup function\n *\n * @example\n * ```typescript\n * import express from \"express\"\n * import { createServer } from \"node:http\"\n * import { toMiddleware, attachWebSocket } from \"@effect-gql/express\"\n * import { makeGraphQLRouter, GraphQLSchemaBuilder } from \"@effect-gql/core\"\n * import { Layer, Effect, Stream } from \"effect\"\n * import * as S from \"effect/Schema\"\n *\n * // Build schema with subscriptions\n * const schema = GraphQLSchemaBuilder.empty\n * .query(\"hello\", { type: S.String, resolve: () => Effect.succeed(\"world\") })\n * .subscription(\"counter\", {\n * type: S.Int,\n * subscribe: () => Effect.succeed(Stream.fromIterable([1, 2, 3]))\n * })\n * .buildSchema()\n *\n * // Create Express app with middleware\n * const app = express()\n * app.use(express.json())\n * const router = makeGraphQLRouter(schema, Layer.empty, { graphiql: true })\n * app.use(toMiddleware(router, Layer.empty))\n *\n * // Create HTTP server and attach WebSocket support\n * const server = createServer(app)\n * const ws = attachWebSocket(server, schema, Layer.empty, {\n * path: \"/graphql\"\n * })\n *\n * server.listen(4000, () => {\n * console.log(\"Server running on http://localhost:4000\")\n * console.log(\"WebSocket subscriptions available at ws://localhost:4000/graphql\")\n * })\n *\n * // Cleanup on shutdown\n * process.on(\"SIGINT\", async () => {\n * await ws.close()\n * server.close()\n * })\n * ```\n */\nexport const attachWebSocket = <R>(\n server: Server,\n schema: GraphQLSchema,\n layer: Layer.Layer<R>,\n options?: ExpressWSOptions<R>\n): { close: () => Promise<void> } => {\n const wss = new WebSocketServer({ noServer: true })\n const path = options?.path ?? \"/graphql\"\n\n // Create the handler from core\n const handler = makeGraphQLWSHandler(schema, layer, options)\n\n // Track active connections for cleanup\n const activeConnections = new Set<WebSocket>()\n\n wss.on(\"connection\", (ws) => {\n activeConnections.add(ws)\n\n const effectSocket = toEffectWebSocketFromWs(ws)\n\n // Run the handler\n Effect.runPromise(\n handler(effectSocket).pipe(\n Effect.catchAll((error) => Effect.logError(\"GraphQL WebSocket handler error\", error))\n )\n ).finally(() => {\n activeConnections.delete(ws)\n })\n })\n\n const handleUpgrade = (request: IncomingMessage, socket: Duplex, head: Buffer) => {\n // Check if this is the GraphQL WebSocket path\n const url = new URL(request.url ?? \"/\", `http://${request.headers.host}`)\n if (url.pathname !== path) {\n socket.destroy()\n return\n }\n\n // Check for correct WebSocket subprotocol\n const protocol = request.headers[\"sec-websocket-protocol\"]\n if (!protocol?.includes(\"graphql-transport-ws\")) {\n socket.write(\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\")\n socket.destroy()\n return\n }\n\n wss.handleUpgrade(request, socket, head, (ws) => {\n wss.emit(\"connection\", ws, request)\n })\n }\n\n // Attach upgrade handler to server\n server.on(\"upgrade\", (request, socket, head) => {\n handleUpgrade(request, socket as Duplex, head)\n })\n\n const close = async () => {\n // Close all active connections\n for (const ws of activeConnections) {\n ws.close(1001, \"Server shutting down\")\n }\n activeConnections.clear()\n\n // Close the WebSocket server\n return new Promise<void>((resolve, reject) => {\n wss.close((error) => {\n if (error) reject(error)\n else resolve()\n })\n })\n }\n\n return { close }\n}\n","import { Effect, Layer, Stream, Deferred } from \"effect\"\nimport type { Request, Response, NextFunction, RequestHandler } from \"express\"\nimport { GraphQLSchema } from \"graphql\"\nimport {\n makeGraphQLSSEHandler,\n formatSSEMessage,\n SSE_HEADERS,\n type GraphQLSSEOptions,\n type SSESubscriptionRequest,\n SSEError,\n} from \"@effect-gql/core\"\nimport { toWebHeaders } from \"./http-utils\"\n\n/**\n * Options for Express SSE middleware\n */\nexport interface ExpressSSEOptions<R> extends GraphQLSSEOptions<R> {\n /**\n * Path for SSE connections.\n * @default \"/graphql/stream\"\n */\n readonly path?: string\n}\n\n/**\n * Create an Express middleware for SSE subscriptions.\n *\n * This middleware handles POST requests to the configured path and streams\n * GraphQL subscription events as Server-Sent Events.\n *\n * @param schema - The GraphQL schema with subscription definitions\n * @param layer - Effect layer providing services required by resolvers\n * @param options - Optional lifecycle hooks and configuration\n * @returns An Express middleware function\n *\n * @example\n * ```typescript\n * import express from \"express\"\n * import { createServer } from \"node:http\"\n * import { toMiddleware, sseMiddleware, attachWebSocket } from \"@effect-gql/express\"\n * import { makeGraphQLRouter } from \"@effect-gql/core\"\n *\n * const app = express()\n * app.use(express.json())\n *\n * // Regular GraphQL endpoint\n * const router = makeGraphQLRouter(schema, Layer.empty, { graphiql: true })\n * app.use(toMiddleware(router, Layer.empty))\n *\n * // SSE subscriptions endpoint\n * app.use(sseMiddleware(schema, Layer.empty, {\n * path: \"/graphql/stream\",\n * onConnect: (request, headers) => Effect.gen(function* () {\n * const token = headers.get(\"authorization\")\n * const user = yield* AuthService.validateToken(token)\n * return { user }\n * }),\n * }))\n *\n * const server = createServer(app)\n *\n * // Optional: Also attach WebSocket subscriptions\n * attachWebSocket(server, schema, Layer.empty)\n *\n * server.listen(4000)\n * ```\n */\nexport const sseMiddleware = <R>(\n schema: GraphQLSchema,\n layer: Layer.Layer<R>,\n options?: ExpressSSEOptions<R>\n): RequestHandler => {\n const path = options?.path ?? \"/graphql/stream\"\n const sseHandler = makeGraphQLSSEHandler(schema, layer, options)\n\n return async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n // Check if this request is for our path\n if (req.path !== path) {\n next()\n return\n }\n\n // Only handle POST requests\n if (req.method !== \"POST\") {\n next()\n return\n }\n\n // Check Accept header for SSE support\n const accept = req.headers.accept ?? \"\"\n if (!accept.includes(\"text/event-stream\") && !accept.includes(\"*/*\")) {\n res.status(406).json({\n errors: [{ message: \"Client must accept text/event-stream\" }],\n })\n return\n }\n\n // Parse the GraphQL request from the body\n let subscriptionRequest: SSESubscriptionRequest\n try {\n const body = req.body as Record<string, unknown>\n if (typeof body.query !== \"string\") {\n throw new Error(\"Missing query\")\n }\n subscriptionRequest = {\n query: body.query,\n variables: body.variables as Record<string, unknown> | undefined,\n operationName: body.operationName as string | undefined,\n extensions: body.extensions as Record<string, unknown> | undefined,\n }\n } catch {\n res.status(400).json({\n errors: [{ message: \"Invalid GraphQL request body\" }],\n })\n return\n }\n\n // Convert Express headers to web Headers\n const headers = toWebHeaders(req.headers)\n\n // Set SSE headers\n res.writeHead(200, SSE_HEADERS)\n\n // Get the event stream\n const eventStream = sseHandler(subscriptionRequest, headers)\n\n // Create the streaming effect\n const streamEffect = Effect.gen(function* () {\n // Track client disconnection\n const clientDisconnected = yield* Deferred.make<void, SSEError>()\n\n req.on(\"close\", () => {\n Effect.runPromise(Deferred.succeed(clientDisconnected, undefined)).catch(() => {})\n })\n\n req.on(\"error\", (error) => {\n Effect.runPromise(Deferred.fail(clientDisconnected, new SSEError({ cause: error }))).catch(\n () => {}\n )\n })\n\n // Stream events to the client\n const runStream = Stream.runForEach(eventStream, (event) =>\n Effect.async<void, SSEError>((resume) => {\n const message = formatSSEMessage(event)\n res.write(message, (error) => {\n if (error) {\n resume(Effect.fail(new SSEError({ cause: error })))\n } else {\n resume(Effect.succeed(undefined))\n }\n })\n })\n )\n\n // Race between stream completion and client disconnection\n yield* Effect.race(\n runStream.pipe(Effect.catchAll((error) => Effect.logWarning(\"SSE stream error\", error))),\n Deferred.await(clientDisconnected)\n )\n })\n\n await Effect.runPromise(\n streamEffect.pipe(\n Effect.ensuring(Effect.sync(() => res.end())),\n Effect.catchAll(() => Effect.void)\n )\n )\n }\n}\n\n/**\n * Create a standalone Express route handler for SSE subscriptions.\n *\n * Use this if you want more control over routing than the middleware provides.\n *\n * @param schema - The GraphQL schema with subscription definitions\n * @param layer - Effect layer providing services required by resolvers\n * @param options - Optional lifecycle hooks and configuration\n * @returns An Express request handler\n *\n * @example\n * ```typescript\n * import express from \"express\"\n * import { createSSEHandler } from \"@effect-gql/express\"\n *\n * const app = express()\n * app.use(express.json())\n *\n * const sseHandler = createSSEHandler(schema, Layer.empty)\n * app.post(\"/graphql/stream\", sseHandler)\n *\n * app.listen(4000)\n * ```\n */\nexport const createSSEHandler = <R>(\n schema: GraphQLSchema,\n layer: Layer.Layer<R>,\n options?: Omit<ExpressSSEOptions<R>, \"path\">\n): RequestHandler => {\n const sseHandler = makeGraphQLSSEHandler(schema, layer, options)\n\n return async (req: Request, res: Response, _next: NextFunction): Promise<void> => {\n // Check Accept header for SSE support\n const accept = req.headers.accept ?? \"\"\n if (!accept.includes(\"text/event-stream\") && !accept.includes(\"*/*\")) {\n res.status(406).json({\n errors: [{ message: \"Client must accept text/event-stream\" }],\n })\n return\n }\n\n // Parse the GraphQL request from the body\n let subscriptionRequest: SSESubscriptionRequest\n try {\n const body = req.body as Record<string, unknown>\n if (typeof body.query !== \"string\") {\n throw new Error(\"Missing query\")\n }\n subscriptionRequest = {\n query: body.query,\n variables: body.variables as Record<string, unknown> | undefined,\n operationName: body.operationName as string | undefined,\n extensions: body.extensions as Record<string, unknown> | undefined,\n }\n } catch {\n res.status(400).json({\n errors: [{ message: \"Invalid GraphQL request body\" }],\n })\n return\n }\n\n // Convert Express headers to web Headers\n const headers = toWebHeaders(req.headers)\n\n // Set SSE headers\n res.writeHead(200, SSE_HEADERS)\n\n // Get the event stream\n const eventStream = sseHandler(subscriptionRequest, headers)\n\n // Create the streaming effect\n const streamEffect = Effect.gen(function* () {\n // Track client disconnection\n const clientDisconnected = yield* Deferred.make<void, SSEError>()\n\n req.on(\"close\", () => {\n Effect.runPromise(Deferred.succeed(clientDisconnected, undefined)).catch(() => {})\n })\n\n req.on(\"error\", (error) => {\n Effect.runPromise(Deferred.fail(clientDisconnected, new SSEError({ cause: error }))).catch(\n () => {}\n )\n })\n\n // Stream events to the client\n const runStream = Stream.runForEach(eventStream, (event) =>\n Effect.async<void, SSEError>((resume) => {\n const message = formatSSEMessage(event)\n res.write(message, (error) => {\n if (error) {\n resume(Effect.fail(new SSEError({ cause: error })))\n } else {\n resume(Effect.succeed(undefined))\n }\n })\n })\n )\n\n // Race between stream completion and client disconnection\n yield* Effect.race(\n runStream.pipe(Effect.catchAll((error) => Effect.logWarning(\"SSE stream error\", error))),\n Deferred.await(clientDisconnected)\n )\n })\n\n await Effect.runPromise(\n streamEffect.pipe(\n Effect.ensuring(Effect.sync(() => res.end())),\n Effect.catchAll(() => Effect.void)\n )\n )\n }\n}\n"]}
package/index.d.cts ADDED
@@ -0,0 +1,184 @@
1
+ import { Layer } from 'effect';
2
+ import { HttpRouter } from '@effect/platform';
3
+ import { RequestHandler } from 'express';
4
+ import { Server } from 'node:http';
5
+ import { GraphQLSchema } from 'graphql';
6
+ import { GraphQLWSOptions, GraphQLSSEOptions } from '@effect-gql/core';
7
+
8
+ /**
9
+ * Convert an HttpRouter to Express middleware.
10
+ *
11
+ * This creates Express-compatible middleware that can be mounted on any Express app.
12
+ * The middleware converts Express requests to web standard Requests, processes them
13
+ * through the Effect router, and writes the response back to Express.
14
+ *
15
+ * @param router - The HttpRouter to convert (typically from makeGraphQLRouter or toRouter)
16
+ * @param layer - Layer providing any services required by the router
17
+ * @returns Express middleware function
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * import express from "express"
22
+ * import { makeGraphQLRouter } from "@effect-gql/core"
23
+ * import { toMiddleware } from "@effect-gql/express"
24
+ * import { Layer } from "effect"
25
+ *
26
+ * const router = makeGraphQLRouter(schema, Layer.empty, { graphiql: true })
27
+ *
28
+ * const app = express()
29
+ * app.use(toMiddleware(router, Layer.empty))
30
+ * app.listen(4000, () => console.log("Server running on http://localhost:4000"))
31
+ * ```
32
+ */
33
+ declare const toMiddleware: <E, R, RE>(router: HttpRouter.HttpRouter<E, R>, layer: Layer.Layer<R, RE>) => RequestHandler;
34
+
35
+ /**
36
+ * Options for Express WebSocket server
37
+ */
38
+ interface ExpressWSOptions<R> extends GraphQLWSOptions<R> {
39
+ /**
40
+ * Path for WebSocket connections.
41
+ * @default "/graphql"
42
+ */
43
+ readonly path?: string;
44
+ }
45
+ /**
46
+ * Attach WebSocket subscription support to an Express HTTP server.
47
+ *
48
+ * Since Express middleware doesn't own the HTTP server, this function
49
+ * must be called separately with the HTTP server instance to enable
50
+ * WebSocket subscriptions.
51
+ *
52
+ * @param server - The HTTP server running the Express app
53
+ * @param schema - The GraphQL schema with subscription definitions
54
+ * @param layer - Effect layer providing services required by resolvers
55
+ * @param options - Optional configuration and lifecycle hooks
56
+ * @returns Object with cleanup function
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * import express from "express"
61
+ * import { createServer } from "node:http"
62
+ * import { toMiddleware, attachWebSocket } from "@effect-gql/express"
63
+ * import { makeGraphQLRouter, GraphQLSchemaBuilder } from "@effect-gql/core"
64
+ * import { Layer, Effect, Stream } from "effect"
65
+ * import * as S from "effect/Schema"
66
+ *
67
+ * // Build schema with subscriptions
68
+ * const schema = GraphQLSchemaBuilder.empty
69
+ * .query("hello", { type: S.String, resolve: () => Effect.succeed("world") })
70
+ * .subscription("counter", {
71
+ * type: S.Int,
72
+ * subscribe: () => Effect.succeed(Stream.fromIterable([1, 2, 3]))
73
+ * })
74
+ * .buildSchema()
75
+ *
76
+ * // Create Express app with middleware
77
+ * const app = express()
78
+ * app.use(express.json())
79
+ * const router = makeGraphQLRouter(schema, Layer.empty, { graphiql: true })
80
+ * app.use(toMiddleware(router, Layer.empty))
81
+ *
82
+ * // Create HTTP server and attach WebSocket support
83
+ * const server = createServer(app)
84
+ * const ws = attachWebSocket(server, schema, Layer.empty, {
85
+ * path: "/graphql"
86
+ * })
87
+ *
88
+ * server.listen(4000, () => {
89
+ * console.log("Server running on http://localhost:4000")
90
+ * console.log("WebSocket subscriptions available at ws://localhost:4000/graphql")
91
+ * })
92
+ *
93
+ * // Cleanup on shutdown
94
+ * process.on("SIGINT", async () => {
95
+ * await ws.close()
96
+ * server.close()
97
+ * })
98
+ * ```
99
+ */
100
+ declare const attachWebSocket: <R>(server: Server, schema: GraphQLSchema, layer: Layer.Layer<R>, options?: ExpressWSOptions<R>) => {
101
+ close: () => Promise<void>;
102
+ };
103
+
104
+ /**
105
+ * Options for Express SSE middleware
106
+ */
107
+ interface ExpressSSEOptions<R> extends GraphQLSSEOptions<R> {
108
+ /**
109
+ * Path for SSE connections.
110
+ * @default "/graphql/stream"
111
+ */
112
+ readonly path?: string;
113
+ }
114
+ /**
115
+ * Create an Express middleware for SSE subscriptions.
116
+ *
117
+ * This middleware handles POST requests to the configured path and streams
118
+ * GraphQL subscription events as Server-Sent Events.
119
+ *
120
+ * @param schema - The GraphQL schema with subscription definitions
121
+ * @param layer - Effect layer providing services required by resolvers
122
+ * @param options - Optional lifecycle hooks and configuration
123
+ * @returns An Express middleware function
124
+ *
125
+ * @example
126
+ * ```typescript
127
+ * import express from "express"
128
+ * import { createServer } from "node:http"
129
+ * import { toMiddleware, sseMiddleware, attachWebSocket } from "@effect-gql/express"
130
+ * import { makeGraphQLRouter } from "@effect-gql/core"
131
+ *
132
+ * const app = express()
133
+ * app.use(express.json())
134
+ *
135
+ * // Regular GraphQL endpoint
136
+ * const router = makeGraphQLRouter(schema, Layer.empty, { graphiql: true })
137
+ * app.use(toMiddleware(router, Layer.empty))
138
+ *
139
+ * // SSE subscriptions endpoint
140
+ * app.use(sseMiddleware(schema, Layer.empty, {
141
+ * path: "/graphql/stream",
142
+ * onConnect: (request, headers) => Effect.gen(function* () {
143
+ * const token = headers.get("authorization")
144
+ * const user = yield* AuthService.validateToken(token)
145
+ * return { user }
146
+ * }),
147
+ * }))
148
+ *
149
+ * const server = createServer(app)
150
+ *
151
+ * // Optional: Also attach WebSocket subscriptions
152
+ * attachWebSocket(server, schema, Layer.empty)
153
+ *
154
+ * server.listen(4000)
155
+ * ```
156
+ */
157
+ declare const sseMiddleware: <R>(schema: GraphQLSchema, layer: Layer.Layer<R>, options?: ExpressSSEOptions<R>) => RequestHandler;
158
+ /**
159
+ * Create a standalone Express route handler for SSE subscriptions.
160
+ *
161
+ * Use this if you want more control over routing than the middleware provides.
162
+ *
163
+ * @param schema - The GraphQL schema with subscription definitions
164
+ * @param layer - Effect layer providing services required by resolvers
165
+ * @param options - Optional lifecycle hooks and configuration
166
+ * @returns An Express request handler
167
+ *
168
+ * @example
169
+ * ```typescript
170
+ * import express from "express"
171
+ * import { createSSEHandler } from "@effect-gql/express"
172
+ *
173
+ * const app = express()
174
+ * app.use(express.json())
175
+ *
176
+ * const sseHandler = createSSEHandler(schema, Layer.empty)
177
+ * app.post("/graphql/stream", sseHandler)
178
+ *
179
+ * app.listen(4000)
180
+ * ```
181
+ */
182
+ declare const createSSEHandler: <R>(schema: GraphQLSchema, layer: Layer.Layer<R>, options?: Omit<ExpressSSEOptions<R>, "path">) => RequestHandler;
183
+
184
+ export { type ExpressSSEOptions, type ExpressWSOptions, attachWebSocket, createSSEHandler, sseMiddleware, toMiddleware };