@effect-gql/bun 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 +100 -0
- package/index.cjs +291 -0
- package/index.cjs.map +1 -0
- package/index.d.cts +235 -0
- package/index.d.ts +235 -0
- package/index.js +287 -0
- package/index.js.map +1 -0
- package/package.json +14 -32
- package/dist/index.d.ts +0 -4
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -14
- package/dist/index.js.map +0 -1
- package/dist/serve.d.ts +0 -73
- package/dist/serve.d.ts.map +0 -1
- package/dist/serve.js +0 -145
- package/dist/serve.js.map +0 -1
- package/dist/sse.d.ts +0 -89
- package/dist/sse.d.ts.map +0 -1
- package/dist/sse.js +0 -135
- package/dist/sse.js.map +0 -1
- package/dist/ws.d.ts +0 -78
- package/dist/ws.d.ts.map +0 -1
- package/dist/ws.js +0 -160
- package/dist/ws.js.map +0 -1
- package/src/index.ts +0 -7
- package/src/serve.ts +0 -182
- package/src/sse.ts +0 -182
- package/src/ws.ts +0 -247
package/index.js
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { Effect, Queue, Deferred, Stream, Layer } from 'effect';
|
|
2
|
+
import { makeGraphQLWSHandler, WebSocketError, makeGraphQLSSEHandler, formatSSEMessage, SSE_HEADERS } from '@effect-gql/core';
|
|
3
|
+
import { HttpServer, HttpApp } from '@effect/platform';
|
|
4
|
+
import { BunHttpServer, BunRuntime } from '@effect/platform-bun';
|
|
5
|
+
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/ws.ts
|
|
17
|
+
var ws_exports = {};
|
|
18
|
+
__export(ws_exports, {
|
|
19
|
+
createBunWSHandlers: () => createBunWSHandlers,
|
|
20
|
+
toBunEffectWebSocket: () => toBunEffectWebSocket
|
|
21
|
+
});
|
|
22
|
+
var createBunWSHandlers, toBunEffectWebSocket;
|
|
23
|
+
var init_ws = __esm({
|
|
24
|
+
"src/ws.ts"() {
|
|
25
|
+
createBunWSHandlers = (schema, layer, options) => {
|
|
26
|
+
const path = options?.path ?? "/graphql";
|
|
27
|
+
const handler = makeGraphQLWSHandler(schema, layer, options);
|
|
28
|
+
const activeHandlers = /* @__PURE__ */ new Map();
|
|
29
|
+
const upgrade = (request, server) => {
|
|
30
|
+
const url = new URL(request.url);
|
|
31
|
+
if (url.pathname !== path) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const upgradeHeader = request.headers.get("upgrade");
|
|
35
|
+
if (upgradeHeader?.toLowerCase() !== "websocket") {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
const protocol = request.headers.get("sec-websocket-protocol");
|
|
39
|
+
if (!protocol?.includes("graphql-transport-ws")) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
const success = server.upgrade(request, {
|
|
43
|
+
data: {}
|
|
44
|
+
// Will be populated in open handler
|
|
45
|
+
});
|
|
46
|
+
return success;
|
|
47
|
+
};
|
|
48
|
+
const websocket = {
|
|
49
|
+
open: (ws) => {
|
|
50
|
+
const setupEffect = Effect.gen(function* () {
|
|
51
|
+
const messageQueue = yield* Queue.unbounded();
|
|
52
|
+
const closedDeferred = yield* Deferred.make();
|
|
53
|
+
const effectSocket = {
|
|
54
|
+
protocol: ws.data?.effectSocket?.protocol || "graphql-transport-ws",
|
|
55
|
+
send: (data) => Effect.try({
|
|
56
|
+
try: () => {
|
|
57
|
+
ws.send(data);
|
|
58
|
+
},
|
|
59
|
+
catch: (error) => new WebSocketError({ cause: error })
|
|
60
|
+
}),
|
|
61
|
+
close: (code, reason) => Effect.sync(() => {
|
|
62
|
+
ws.close(code ?? 1e3, reason ?? "");
|
|
63
|
+
}),
|
|
64
|
+
messages: Stream.fromQueue(messageQueue).pipe(Stream.catchAll(() => Stream.empty)),
|
|
65
|
+
closed: Deferred.await(closedDeferred)
|
|
66
|
+
};
|
|
67
|
+
ws.data = {
|
|
68
|
+
messageQueue,
|
|
69
|
+
closedDeferred,
|
|
70
|
+
effectSocket
|
|
71
|
+
};
|
|
72
|
+
return effectSocket;
|
|
73
|
+
});
|
|
74
|
+
const handlerPromise = Effect.runPromise(
|
|
75
|
+
setupEffect.pipe(
|
|
76
|
+
Effect.flatMap((effectSocket) => handler(effectSocket)),
|
|
77
|
+
Effect.catchAllCause(() => Effect.void)
|
|
78
|
+
)
|
|
79
|
+
);
|
|
80
|
+
activeHandlers.set(ws, handlerPromise);
|
|
81
|
+
},
|
|
82
|
+
message: (ws, message) => {
|
|
83
|
+
const data = ws.data;
|
|
84
|
+
if (data?.messageQueue) {
|
|
85
|
+
const messageStr = typeof message === "string" ? message : message.toString();
|
|
86
|
+
Effect.runPromise(Queue.offer(data.messageQueue, messageStr)).catch(() => {
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
close: (ws, code, reason) => {
|
|
91
|
+
const data = ws.data;
|
|
92
|
+
if (data) {
|
|
93
|
+
Effect.runPromise(
|
|
94
|
+
Effect.all([
|
|
95
|
+
Queue.shutdown(data.messageQueue),
|
|
96
|
+
Deferred.succeed(data.closedDeferred, { code, reason })
|
|
97
|
+
])
|
|
98
|
+
).catch(() => {
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
activeHandlers.delete(ws);
|
|
102
|
+
},
|
|
103
|
+
error: (ws, error) => {
|
|
104
|
+
const data = ws.data;
|
|
105
|
+
if (data) {
|
|
106
|
+
Effect.runPromise(
|
|
107
|
+
Deferred.fail(data.closedDeferred, new WebSocketError({ cause: error }))
|
|
108
|
+
).catch(() => {
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
return { upgrade, websocket };
|
|
114
|
+
};
|
|
115
|
+
toBunEffectWebSocket = (ws) => Effect.gen(function* () {
|
|
116
|
+
const messageQueue = yield* Queue.unbounded();
|
|
117
|
+
const closedDeferred = yield* Deferred.make();
|
|
118
|
+
const effectSocket = {
|
|
119
|
+
protocol: "graphql-transport-ws",
|
|
120
|
+
send: (data) => Effect.try({
|
|
121
|
+
try: () => {
|
|
122
|
+
ws.send(data);
|
|
123
|
+
},
|
|
124
|
+
catch: (error) => new WebSocketError({ cause: error })
|
|
125
|
+
}),
|
|
126
|
+
close: (code, reason) => Effect.sync(() => {
|
|
127
|
+
ws.close(code ?? 1e3, reason ?? "");
|
|
128
|
+
}),
|
|
129
|
+
messages: Stream.fromQueue(messageQueue).pipe(Stream.catchAll(() => Stream.empty)),
|
|
130
|
+
closed: Deferred.await(closedDeferred)
|
|
131
|
+
};
|
|
132
|
+
ws.data = {
|
|
133
|
+
messageQueue,
|
|
134
|
+
closedDeferred,
|
|
135
|
+
effectSocket
|
|
136
|
+
};
|
|
137
|
+
return effectSocket;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
var serve = (router, layer, options = {}) => {
|
|
142
|
+
const { port = 4e3, host = "0.0.0.0", onStart, subscriptions } = options;
|
|
143
|
+
if (subscriptions) {
|
|
144
|
+
serveWithSubscriptions(router, layer, port, host, subscriptions, onStart);
|
|
145
|
+
} else {
|
|
146
|
+
const app = router.pipe(
|
|
147
|
+
Effect.catchAllCause((cause) => Effect.die(cause)),
|
|
148
|
+
HttpServer.serve()
|
|
149
|
+
);
|
|
150
|
+
const serverLayer = BunHttpServer.layer({ port });
|
|
151
|
+
const fullLayer = Layer.merge(serverLayer, layer);
|
|
152
|
+
if (onStart) {
|
|
153
|
+
onStart(`http://${host === "0.0.0.0" ? "localhost" : host}:${port}`);
|
|
154
|
+
}
|
|
155
|
+
BunRuntime.runMain(Layer.launch(Layer.provide(app, fullLayer)));
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
function serveWithSubscriptions(router, layer, port, host, subscriptions, onStart) {
|
|
159
|
+
const importWs = Effect.tryPromise({
|
|
160
|
+
try: () => Promise.resolve().then(() => (init_ws(), ws_exports)),
|
|
161
|
+
catch: (error) => error
|
|
162
|
+
});
|
|
163
|
+
Effect.runPromise(
|
|
164
|
+
importWs.pipe(
|
|
165
|
+
Effect.catchAll(
|
|
166
|
+
(error) => Effect.logError("Failed to load WebSocket support", error).pipe(
|
|
167
|
+
Effect.andThen(Effect.sync(() => process.exit(1))),
|
|
168
|
+
Effect.andThen(Effect.fail(error))
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
).then(({ createBunWSHandlers: createBunWSHandlers2 }) => {
|
|
173
|
+
const { handler } = HttpApp.toWebHandlerLayer(router, layer);
|
|
174
|
+
const { upgrade, websocket } = createBunWSHandlers2(
|
|
175
|
+
subscriptions.schema,
|
|
176
|
+
layer,
|
|
177
|
+
{
|
|
178
|
+
path: subscriptions.path,
|
|
179
|
+
complexity: subscriptions.complexity,
|
|
180
|
+
fieldComplexities: subscriptions.fieldComplexities,
|
|
181
|
+
onConnect: subscriptions.onConnect,
|
|
182
|
+
onDisconnect: subscriptions.onDisconnect,
|
|
183
|
+
onSubscribe: subscriptions.onSubscribe,
|
|
184
|
+
onComplete: subscriptions.onComplete,
|
|
185
|
+
onError: subscriptions.onError
|
|
186
|
+
}
|
|
187
|
+
);
|
|
188
|
+
const server = Bun.serve({
|
|
189
|
+
port,
|
|
190
|
+
hostname: host,
|
|
191
|
+
fetch: async (request, server2) => {
|
|
192
|
+
if (upgrade(request, server2)) {
|
|
193
|
+
return new Response(null, { status: 101 });
|
|
194
|
+
}
|
|
195
|
+
return handler(request);
|
|
196
|
+
},
|
|
197
|
+
websocket
|
|
198
|
+
});
|
|
199
|
+
if (onStart) {
|
|
200
|
+
onStart(`http://${host === "0.0.0.0" ? "localhost" : host}:${port}`);
|
|
201
|
+
}
|
|
202
|
+
process.on("SIGINT", () => {
|
|
203
|
+
server.stop();
|
|
204
|
+
process.exit(0);
|
|
205
|
+
});
|
|
206
|
+
process.on("SIGTERM", () => {
|
|
207
|
+
server.stop();
|
|
208
|
+
process.exit(0);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// src/index.ts
|
|
214
|
+
init_ws();
|
|
215
|
+
var createBunSSEHandler = (schema, layer, options) => {
|
|
216
|
+
const sseHandler = makeGraphQLSSEHandler(schema, layer, options);
|
|
217
|
+
return async (request) => {
|
|
218
|
+
const accept = request.headers.get("accept") ?? "";
|
|
219
|
+
if (!accept.includes("text/event-stream") && !accept.includes("*/*")) {
|
|
220
|
+
return new Response(
|
|
221
|
+
JSON.stringify({
|
|
222
|
+
errors: [{ message: "Client must accept text/event-stream" }]
|
|
223
|
+
}),
|
|
224
|
+
{ status: 406, headers: { "Content-Type": "application/json" } }
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
let subscriptionRequest;
|
|
228
|
+
try {
|
|
229
|
+
const body = await request.json();
|
|
230
|
+
if (typeof body.query !== "string") {
|
|
231
|
+
throw new Error("Missing query");
|
|
232
|
+
}
|
|
233
|
+
subscriptionRequest = {
|
|
234
|
+
query: body.query,
|
|
235
|
+
variables: body.variables,
|
|
236
|
+
operationName: body.operationName,
|
|
237
|
+
extensions: body.extensions
|
|
238
|
+
};
|
|
239
|
+
} catch {
|
|
240
|
+
return new Response(
|
|
241
|
+
JSON.stringify({
|
|
242
|
+
errors: [{ message: "Invalid GraphQL request body" }]
|
|
243
|
+
}),
|
|
244
|
+
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
const eventStream = sseHandler(subscriptionRequest, request.headers);
|
|
248
|
+
const readableStream = new ReadableStream({
|
|
249
|
+
async start(controller) {
|
|
250
|
+
const encoder = new TextEncoder();
|
|
251
|
+
await Effect.runPromise(
|
|
252
|
+
Stream.runForEach(
|
|
253
|
+
eventStream,
|
|
254
|
+
(event) => Effect.sync(() => {
|
|
255
|
+
const message = formatSSEMessage(event);
|
|
256
|
+
controller.enqueue(encoder.encode(message));
|
|
257
|
+
})
|
|
258
|
+
).pipe(
|
|
259
|
+
Effect.catchAll((error) => Effect.logWarning("SSE stream error", error)),
|
|
260
|
+
Effect.ensuring(Effect.sync(() => controller.close()))
|
|
261
|
+
)
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
return new Response(readableStream, {
|
|
266
|
+
status: 200,
|
|
267
|
+
headers: SSE_HEADERS
|
|
268
|
+
});
|
|
269
|
+
};
|
|
270
|
+
};
|
|
271
|
+
var createBunSSEHandlers = (schema, layer, options) => {
|
|
272
|
+
const path = options?.path ?? "/graphql/stream";
|
|
273
|
+
const handler = createBunSSEHandler(schema, layer, options);
|
|
274
|
+
return {
|
|
275
|
+
path,
|
|
276
|
+
shouldHandle: (request) => {
|
|
277
|
+
if (request.method !== "POST") return false;
|
|
278
|
+
const url = new URL(request.url);
|
|
279
|
+
return url.pathname === path;
|
|
280
|
+
},
|
|
281
|
+
handle: handler
|
|
282
|
+
};
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
export { createBunSSEHandler, createBunSSEHandlers, createBunWSHandlers, serve, toBunEffectWebSocket };
|
|
286
|
+
//# sourceMappingURL=index.js.map
|
|
287
|
+
//# sourceMappingURL=index.js.map
|
package/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ws.ts","../src/serve.ts","../src/index.ts","../src/sse.ts"],"names":["Effect","Layer","createBunWSHandlers","server","Stream"],"mappings":"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","file":"index.js","sourcesContent":["import { Effect, Stream, Queue, Deferred, Layer } from \"effect\"\nimport { GraphQLSchema } from \"graphql\"\nimport {\n makeGraphQLWSHandler,\n type EffectWebSocket,\n type GraphQLWSOptions,\n WebSocketError,\n type CloseEvent,\n} from \"@effect-gql/core\"\nimport type { Server, ServerWebSocket } from \"bun\"\n\n/**\n * Data attached to each WebSocket connection\n */\ninterface WebSocketData {\n messageQueue: Queue.Queue<string>\n closedDeferred: Deferred.Deferred<CloseEvent, WebSocketError>\n effectSocket: EffectWebSocket\n}\n\n/**\n * Options for Bun WebSocket server\n */\nexport interface BunWSOptions<R> extends GraphQLWSOptions<R> {\n /**\n * Path for WebSocket connections.\n * @default \"/graphql\"\n */\n readonly path?: string\n}\n\n/**\n * Create WebSocket handlers for Bun.serve().\n *\n * Bun has built-in WebSocket support that's configured as part of Bun.serve().\n * This function returns the handlers needed to integrate GraphQL subscriptions.\n *\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 containing upgrade check and WebSocket handlers\n *\n * @example\n * ```typescript\n * const { upgrade, websocket } = createBunWSHandlers(schema, serviceLayer)\n *\n * Bun.serve({\n * port: 4000,\n * fetch(req, server) {\n * // Try WebSocket upgrade first\n * if (upgrade(req, server)) {\n * return // Upgraded to WebSocket\n * }\n * // Handle HTTP requests...\n * },\n * websocket,\n * })\n * ```\n */\nexport const createBunWSHandlers = <R>(\n schema: GraphQLSchema,\n layer: Layer.Layer<R>,\n options?: BunWSOptions<R>\n): {\n /**\n * Check if request should upgrade to WebSocket and perform upgrade.\n * Returns true if upgraded, false otherwise.\n */\n upgrade: (request: Request, server: Server<WebSocketData>) => boolean\n /**\n * WebSocket event handlers for Bun.serve()\n */\n websocket: {\n open: (ws: ServerWebSocket<WebSocketData>) => void\n message: (ws: ServerWebSocket<WebSocketData>, message: string | Buffer) => void\n close: (ws: ServerWebSocket<WebSocketData>, code: number, reason: string) => void\n error: (ws: ServerWebSocket<WebSocketData>, error: Error) => void\n }\n} => {\n const path = options?.path ?? \"/graphql\"\n const handler = makeGraphQLWSHandler(schema, layer, options)\n\n // Track active connection handlers for cleanup\n const activeHandlers = new Map<ServerWebSocket<WebSocketData>, Promise<void>>()\n\n const upgrade = (request: Request, server: Server<WebSocketData>): boolean => {\n const url = new URL(request.url)\n\n // Check if this is a WebSocket upgrade request for the GraphQL path\n if (url.pathname !== path) {\n return false\n }\n\n const upgradeHeader = request.headers.get(\"upgrade\")\n if (upgradeHeader?.toLowerCase() !== \"websocket\") {\n return false\n }\n\n // Check for correct subprotocol\n const protocol = request.headers.get(\"sec-websocket-protocol\")\n if (!protocol?.includes(\"graphql-transport-ws\")) {\n return false\n }\n\n // Perform upgrade - data will be set in open handler\n const success = server.upgrade(request, {\n data: {} as WebSocketData, // Will be populated in open handler\n })\n\n return success\n }\n\n const websocket = {\n open: (ws: ServerWebSocket<WebSocketData>) => {\n // Create Effect-based socket wrapper\n const setupEffect = Effect.gen(function* () {\n const messageQueue = yield* Queue.unbounded<string>()\n const closedDeferred = yield* Deferred.make<CloseEvent, WebSocketError>()\n\n const effectSocket: EffectWebSocket = {\n protocol: ws.data?.effectSocket?.protocol || \"graphql-transport-ws\",\n\n send: (data: string) =>\n Effect.try({\n try: () => {\n ws.send(data)\n },\n catch: (error) => new WebSocketError({ cause: error }),\n }),\n\n close: (code?: number, reason?: string) =>\n Effect.sync(() => {\n ws.close(code ?? 1000, reason ?? \"\")\n }),\n\n messages: Stream.fromQueue(messageQueue).pipe(Stream.catchAll(() => Stream.empty)),\n\n closed: Deferred.await(closedDeferred),\n }\n\n // Store in WebSocket data\n ws.data = {\n messageQueue,\n closedDeferred,\n effectSocket,\n }\n\n return effectSocket\n })\n\n // Run setup and handler\n const handlerPromise = Effect.runPromise(\n setupEffect.pipe(\n Effect.flatMap((effectSocket) => handler(effectSocket)),\n Effect.catchAllCause(() => Effect.void)\n )\n )\n\n activeHandlers.set(ws, handlerPromise)\n },\n\n message: (ws: ServerWebSocket<WebSocketData>, message: string | Buffer) => {\n const data = ws.data as WebSocketData | undefined\n if (data?.messageQueue) {\n const messageStr = typeof message === \"string\" ? message : message.toString()\n Effect.runPromise(Queue.offer(data.messageQueue, messageStr)).catch(() => {\n // Queue might be shutdown\n })\n }\n },\n\n close: (ws: ServerWebSocket<WebSocketData>, code: number, reason: string) => {\n const data = ws.data as WebSocketData | undefined\n if (data) {\n Effect.runPromise(\n Effect.all([\n Queue.shutdown(data.messageQueue),\n Deferred.succeed(data.closedDeferred, { code, reason }),\n ])\n ).catch(() => {\n // Already completed\n })\n }\n activeHandlers.delete(ws)\n },\n\n error: (ws: ServerWebSocket<WebSocketData>, error: Error) => {\n const data = ws.data as WebSocketData | undefined\n if (data) {\n Effect.runPromise(\n Deferred.fail(data.closedDeferred, new WebSocketError({ cause: error }))\n ).catch(() => {\n // Already completed\n })\n }\n },\n }\n\n return { upgrade, websocket }\n}\n\n/**\n * Convert a Bun ServerWebSocket to an EffectWebSocket.\n *\n * This is a lower-level utility for custom WebSocket handling.\n * Most users should use createBunWSHandlers() instead.\n *\n * @param ws - The Bun ServerWebSocket instance\n * @returns An EffectWebSocket that can be used with makeGraphQLWSHandler\n */\nexport const toBunEffectWebSocket = (\n ws: ServerWebSocket<WebSocketData>\n): Effect.Effect<EffectWebSocket, never, never> =>\n Effect.gen(function* () {\n const messageQueue = yield* Queue.unbounded<string>()\n const closedDeferred = yield* Deferred.make<CloseEvent, WebSocketError>()\n\n const effectSocket: EffectWebSocket = {\n protocol: \"graphql-transport-ws\",\n\n send: (data: string) =>\n Effect.try({\n try: () => {\n ws.send(data)\n },\n catch: (error) => new WebSocketError({ cause: error }),\n }),\n\n close: (code?: number, reason?: string) =>\n Effect.sync(() => {\n ws.close(code ?? 1000, reason ?? \"\")\n }),\n\n messages: Stream.fromQueue(messageQueue).pipe(Stream.catchAll(() => Stream.empty)),\n\n closed: Deferred.await(closedDeferred),\n }\n\n // Store in WebSocket data for event handlers\n ws.data = {\n messageQueue,\n closedDeferred,\n effectSocket,\n }\n\n return effectSocket\n })\n","import { Effect, Layer } from \"effect\"\nimport { HttpApp, HttpRouter, HttpServer } from \"@effect/platform\"\nimport { BunHttpServer, BunRuntime } from \"@effect/platform-bun\"\nimport type { GraphQLSchema } from \"graphql\"\nimport type { GraphQLWSOptions } from \"@effect-gql/core\"\n\n/**\n * Configuration for WebSocket subscriptions\n */\nexport interface SubscriptionsConfig<R> extends GraphQLWSOptions<R> {\n /**\n * The GraphQL schema (required for subscriptions).\n * Must be the same schema used to create the router.\n */\n readonly schema: GraphQLSchema\n /**\n * Path for WebSocket connections.\n * @default \"/graphql\"\n */\n readonly path?: string\n}\n\n/**\n * Options for the Bun GraphQL server\n */\nexport interface ServeOptions<R = never> {\n /** Port to listen on (default: 4000) */\n readonly port?: number\n /** Hostname to bind to (default: \"0.0.0.0\") */\n readonly host?: string\n /** Callback when server starts */\n readonly onStart?: (url: string) => void\n /**\n * Enable WebSocket subscriptions.\n * When provided, the server will handle WebSocket upgrade requests\n * for GraphQL subscriptions using the graphql-ws protocol.\n */\n readonly subscriptions?: SubscriptionsConfig<R>\n}\n\n/**\n * Start a Bun HTTP server with the given router.\n *\n * This is the main entry point for running a GraphQL server on Bun.\n * It handles all the Effect runtime setup and server lifecycle.\n *\n * @param router - The HttpRouter to serve (typically from makeGraphQLRouter or toRouter)\n * @param layer - Layer providing the router's service dependencies\n * @param options - Server configuration options\n *\n * @example\n * ```typescript\n * import { makeGraphQLRouter } from \"@effect-gql/core\"\n * import { serve } from \"@effect-gql/bun\"\n *\n * const schema = GraphQLSchemaBuilder.empty\n * .query(\"hello\", { type: S.String, resolve: () => Effect.succeed(\"world\") })\n * .buildSchema()\n *\n * const router = makeGraphQLRouter(schema, Layer.empty, { graphiql: true })\n *\n * // Without subscriptions\n * serve(router, serviceLayer, {\n * port: 4000,\n * onStart: (url) => console.log(`Server running at ${url}`)\n * })\n *\n * // With subscriptions\n * serve(router, serviceLayer, {\n * port: 4000,\n * subscriptions: { schema },\n * onStart: (url) => console.log(`Server running at ${url}`)\n * })\n * ```\n */\nexport const serve = <E, R, RE>(\n router: HttpRouter.HttpRouter<E, R>,\n layer: Layer.Layer<R, RE>,\n options: ServeOptions<R> = {}\n): void => {\n const { port = 4000, host = \"0.0.0.0\", onStart, subscriptions } = options\n\n if (subscriptions) {\n // With WebSocket subscriptions - use Bun.serve() directly\n serveWithSubscriptions(router, layer, port, host, subscriptions, onStart)\n } else {\n // Without subscriptions - use the standard Effect approach\n const app = router.pipe(\n Effect.catchAllCause((cause) => Effect.die(cause)),\n HttpServer.serve()\n )\n\n const serverLayer = BunHttpServer.layer({ port })\n const fullLayer = Layer.merge(serverLayer, layer)\n\n if (onStart) {\n onStart(`http://${host === \"0.0.0.0\" ? \"localhost\" : host}:${port}`)\n }\n\n BunRuntime.runMain(Layer.launch(Layer.provide(app, fullLayer)))\n }\n}\n\n/**\n * Internal implementation for serving with WebSocket subscriptions.\n * Uses Bun.serve() directly to enable WebSocket support.\n */\nfunction serveWithSubscriptions<E, R, RE>(\n router: HttpRouter.HttpRouter<E, R>,\n layer: Layer.Layer<R, RE>,\n port: number,\n host: string,\n subscriptions: SubscriptionsConfig<R>,\n onStart?: (url: string) => void\n): void {\n // Dynamically import ws module to keep it optional\n const importWs = Effect.tryPromise({\n try: () => import(\"./ws\"),\n catch: (error) => error as Error,\n })\n\n Effect.runPromise(\n importWs.pipe(\n Effect.catchAll((error) =>\n Effect.logError(\"Failed to load WebSocket support\", error).pipe(\n Effect.andThen(Effect.sync(() => process.exit(1))),\n Effect.andThen(Effect.fail(error))\n )\n )\n )\n ).then(({ createBunWSHandlers }) => {\n // Create the web handler from the Effect router\n const { handler } = HttpApp.toWebHandlerLayer(router, layer)\n\n // Create WebSocket handlers\n const { upgrade, websocket } = createBunWSHandlers(\n subscriptions.schema,\n layer as Layer.Layer<R>,\n {\n path: subscriptions.path,\n complexity: subscriptions.complexity,\n fieldComplexities: subscriptions.fieldComplexities,\n onConnect: subscriptions.onConnect,\n onDisconnect: subscriptions.onDisconnect,\n onSubscribe: subscriptions.onSubscribe,\n onComplete: subscriptions.onComplete,\n onError: subscriptions.onError,\n }\n )\n\n // Start Bun server with WebSocket support\n const server = Bun.serve({\n port,\n hostname: host,\n fetch: async (request, server) => {\n // Try WebSocket upgrade first\n if (upgrade(request, server)) {\n return new Response(null, { status: 101 })\n }\n\n // Handle HTTP requests\n return handler(request)\n },\n websocket,\n })\n\n if (onStart) {\n onStart(`http://${host === \"0.0.0.0\" ? \"localhost\" : host}:${port}`)\n }\n\n // Handle shutdown\n process.on(\"SIGINT\", () => {\n server.stop()\n process.exit(0)\n })\n\n process.on(\"SIGTERM\", () => {\n server.stop()\n process.exit(0)\n })\n })\n}\n","export { serve, type ServeOptions, type SubscriptionsConfig } from \"./serve\"\n\n// WebSocket subscription support\nexport { createBunWSHandlers, toBunEffectWebSocket, type BunWSOptions } from \"./ws\"\n\n// SSE (Server-Sent Events) subscription support\nexport { createBunSSEHandler, createBunSSEHandlers, type BunSSEOptions } from \"./sse\"\n","import { Effect, Layer, Stream } from \"effect\"\nimport { GraphQLSchema } from \"graphql\"\nimport {\n makeGraphQLSSEHandler,\n formatSSEMessage,\n SSE_HEADERS,\n type GraphQLSSEOptions,\n type SSESubscriptionRequest,\n} from \"@effect-gql/core\"\n\n/**\n * Options for Bun SSE handler\n */\nexport interface BunSSEOptions<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 SSE handler for Bun.serve().\n *\n * This function creates a handler that returns a streaming Response for SSE\n * subscription requests. It's designed to integrate with Bun.serve()'s fetch handler.\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 A function that handles SSE requests and returns a Response\n *\n * @example\n * ```typescript\n * const sseHandler = createBunSSEHandler(schema, serviceLayer, {\n * path: \"/graphql/stream\",\n * })\n *\n * Bun.serve({\n * port: 4000,\n * fetch(req, server) {\n * const url = new URL(req.url)\n *\n * // Handle SSE subscriptions\n * if (url.pathname === \"/graphql/stream\" && req.method === \"POST\") {\n * return sseHandler(req)\n * }\n *\n * // Handle other requests...\n * },\n * })\n * ```\n */\nexport const createBunSSEHandler = <R>(\n schema: GraphQLSchema,\n layer: Layer.Layer<R>,\n options?: BunSSEOptions<R>\n): ((request: Request) => Promise<Response>) => {\n const sseHandler = makeGraphQLSSEHandler(schema, layer, options)\n\n return async (request: Request): Promise<Response> => {\n // Check Accept header for SSE support\n const accept = request.headers.get(\"accept\") ?? \"\"\n if (!accept.includes(\"text/event-stream\") && !accept.includes(\"*/*\")) {\n return new Response(\n JSON.stringify({\n errors: [{ message: \"Client must accept text/event-stream\" }],\n }),\n { status: 406, headers: { \"Content-Type\": \"application/json\" } }\n )\n }\n\n // Read and parse the request body\n let subscriptionRequest: SSESubscriptionRequest\n try {\n const body = (await request.json()) 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 return new Response(\n JSON.stringify({\n errors: [{ message: \"Invalid GraphQL request body\" }],\n }),\n { status: 400, headers: { \"Content-Type\": \"application/json\" } }\n )\n }\n\n // Get the event stream\n const eventStream = sseHandler(subscriptionRequest, request.headers)\n\n // Create a ReadableStream from the Effect Stream\n const readableStream = new ReadableStream({\n async start(controller) {\n const encoder = new TextEncoder()\n\n await Effect.runPromise(\n Stream.runForEach(eventStream, (event) =>\n Effect.sync(() => {\n const message = formatSSEMessage(event)\n controller.enqueue(encoder.encode(message))\n })\n ).pipe(\n Effect.catchAll((error) => Effect.logWarning(\"SSE stream error\", error)),\n Effect.ensuring(Effect.sync(() => controller.close()))\n )\n )\n },\n })\n\n return new Response(readableStream, {\n status: 200,\n headers: SSE_HEADERS,\n })\n }\n}\n\n/**\n * Create SSE handlers that integrate with Bun.serve().\n *\n * This returns an object with methods to check if a request should be\n * handled as SSE and to handle it.\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 *\n * @example\n * ```typescript\n * const { upgrade: wsUpgrade, websocket } = createBunWSHandlers(schema, layer)\n * const sse = createBunSSEHandlers(schema, layer)\n *\n * Bun.serve({\n * port: 4000,\n * fetch(req, server) {\n * // Try WebSocket upgrade first\n * if (wsUpgrade(req, server)) {\n * return\n * }\n *\n * // Try SSE subscriptions\n * if (sse.shouldHandle(req)) {\n * return sse.handle(req)\n * }\n *\n * // Handle other requests...\n * },\n * websocket,\n * })\n * ```\n */\nexport const createBunSSEHandlers = <R>(\n schema: GraphQLSchema,\n layer: Layer.Layer<R>,\n options?: BunSSEOptions<R>\n): {\n /** Path this SSE handler responds to */\n readonly path: string\n /** Check if a request should be handled as SSE */\n shouldHandle: (request: Request) => boolean\n /** Handle an SSE request */\n handle: (request: Request) => Promise<Response>\n} => {\n const path = options?.path ?? \"/graphql/stream\"\n const handler = createBunSSEHandler(schema, layer, options)\n\n return {\n path,\n shouldHandle: (request: Request) => {\n if (request.method !== \"POST\") return false\n const url = new URL(request.url)\n return url.pathname === path\n },\n handle: handler,\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,37 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect-gql/bun",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Bun HTTP server integration for @effect-gql/core",
|
|
5
|
-
"
|
|
6
|
-
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://github.com/nrf110/effect-gql"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"main": "./index.cjs",
|
|
10
|
+
"module": "dist/index.js",
|
|
11
|
+
"types": "./index.d.ts",
|
|
7
12
|
"exports": {
|
|
8
13
|
".": {
|
|
9
|
-
"types": "./
|
|
10
|
-
"
|
|
14
|
+
"types": "./index.d.ts",
|
|
15
|
+
"import": "./index.js",
|
|
16
|
+
"require": "./index.cjs"
|
|
11
17
|
}
|
|
12
18
|
},
|
|
13
|
-
"files": [
|
|
14
|
-
"dist",
|
|
15
|
-
"src"
|
|
16
|
-
],
|
|
17
19
|
"peerDependencies": {
|
|
18
|
-
"@effect-gql/core": "^
|
|
20
|
+
"@effect-gql/core": "^1.0.0",
|
|
19
21
|
"@effect/platform": "^0.94.0",
|
|
20
22
|
"@effect/platform-bun": "^0.87.0",
|
|
21
23
|
"effect": "^3.19.0",
|
|
22
24
|
"graphql": "^16.0.0"
|
|
23
25
|
},
|
|
24
|
-
"devDependencies": {
|
|
25
|
-
"@effect-gql/core": "*",
|
|
26
|
-
"@effect/platform": "^0.94.0",
|
|
27
|
-
"@effect/platform-bun": "^0.87.0",
|
|
28
|
-
"@types/bun": "latest",
|
|
29
|
-
"effect": "^3.19.13",
|
|
30
|
-
"graphql": "^16.0.0",
|
|
31
|
-
"graphql-ws": "^6.0.6",
|
|
32
|
-
"ws": "^8.18.0",
|
|
33
|
-
"@types/ws": "^8.5.0"
|
|
34
|
-
},
|
|
35
26
|
"keywords": [
|
|
36
27
|
"effect",
|
|
37
28
|
"graphql",
|
|
@@ -39,14 +30,5 @@
|
|
|
39
30
|
"http",
|
|
40
31
|
"server"
|
|
41
32
|
],
|
|
42
|
-
"license": "MIT"
|
|
43
|
-
|
|
44
|
-
"build": "tsc",
|
|
45
|
-
"dev": "tsc --watch",
|
|
46
|
-
"clean": "rm -rf dist",
|
|
47
|
-
"test": "vitest run",
|
|
48
|
-
"test:unit": "vitest run test/unit",
|
|
49
|
-
"test:integration": "vitest run test/integration",
|
|
50
|
-
"test:watch": "vitest"
|
|
51
|
-
}
|
|
52
|
-
}
|
|
33
|
+
"license": "MIT"
|
|
34
|
+
}
|
package/dist/index.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export { serve, type ServeOptions, type SubscriptionsConfig } from "./serve";
|
|
2
|
-
export { createBunWSHandlers, toBunEffectWebSocket, type BunWSOptions } from "./ws";
|
|
3
|
-
export { createBunSSEHandler, createBunSSEHandlers, type BunSSEOptions } from "./sse";
|
|
4
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAG5E,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,KAAK,YAAY,EAAE,MAAM,MAAM,CAAA;AAGnF,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,KAAK,aAAa,EAAE,MAAM,OAAO,CAAA"}
|
package/dist/index.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createBunSSEHandlers = exports.createBunSSEHandler = exports.toBunEffectWebSocket = exports.createBunWSHandlers = exports.serve = void 0;
|
|
4
|
-
var serve_1 = require("./serve");
|
|
5
|
-
Object.defineProperty(exports, "serve", { enumerable: true, get: function () { return serve_1.serve; } });
|
|
6
|
-
// WebSocket subscription support
|
|
7
|
-
var ws_1 = require("./ws");
|
|
8
|
-
Object.defineProperty(exports, "createBunWSHandlers", { enumerable: true, get: function () { return ws_1.createBunWSHandlers; } });
|
|
9
|
-
Object.defineProperty(exports, "toBunEffectWebSocket", { enumerable: true, get: function () { return ws_1.toBunEffectWebSocket; } });
|
|
10
|
-
// SSE (Server-Sent Events) subscription support
|
|
11
|
-
var sse_1 = require("./sse");
|
|
12
|
-
Object.defineProperty(exports, "createBunSSEHandler", { enumerable: true, get: function () { return sse_1.createBunSSEHandler; } });
|
|
13
|
-
Object.defineProperty(exports, "createBunSSEHandlers", { enumerable: true, get: function () { return sse_1.createBunSSEHandlers; } });
|
|
14
|
-
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,iCAA4E;AAAnE,8FAAA,KAAK,OAAA;AAEd,iCAAiC;AACjC,2BAAmF;AAA1E,yGAAA,mBAAmB,OAAA;AAAE,0GAAA,oBAAoB,OAAA;AAElD,gDAAgD;AAChD,6BAAqF;AAA5E,0GAAA,mBAAmB,OAAA;AAAE,2GAAA,oBAAoB,OAAA"}
|
package/dist/serve.d.ts
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { Layer } from "effect";
|
|
2
|
-
import { HttpRouter } from "@effect/platform";
|
|
3
|
-
import type { GraphQLSchema } from "graphql";
|
|
4
|
-
import type { GraphQLWSOptions } from "@effect-gql/core";
|
|
5
|
-
/**
|
|
6
|
-
* Configuration for WebSocket subscriptions
|
|
7
|
-
*/
|
|
8
|
-
export interface SubscriptionsConfig<R> extends GraphQLWSOptions<R> {
|
|
9
|
-
/**
|
|
10
|
-
* The GraphQL schema (required for subscriptions).
|
|
11
|
-
* Must be the same schema used to create the router.
|
|
12
|
-
*/
|
|
13
|
-
readonly schema: GraphQLSchema;
|
|
14
|
-
/**
|
|
15
|
-
* Path for WebSocket connections.
|
|
16
|
-
* @default "/graphql"
|
|
17
|
-
*/
|
|
18
|
-
readonly path?: string;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Options for the Bun GraphQL server
|
|
22
|
-
*/
|
|
23
|
-
export interface ServeOptions<R = never> {
|
|
24
|
-
/** Port to listen on (default: 4000) */
|
|
25
|
-
readonly port?: number;
|
|
26
|
-
/** Hostname to bind to (default: "0.0.0.0") */
|
|
27
|
-
readonly host?: string;
|
|
28
|
-
/** Callback when server starts */
|
|
29
|
-
readonly onStart?: (url: string) => void;
|
|
30
|
-
/**
|
|
31
|
-
* Enable WebSocket subscriptions.
|
|
32
|
-
* When provided, the server will handle WebSocket upgrade requests
|
|
33
|
-
* for GraphQL subscriptions using the graphql-ws protocol.
|
|
34
|
-
*/
|
|
35
|
-
readonly subscriptions?: SubscriptionsConfig<R>;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Start a Bun HTTP server with the given router.
|
|
39
|
-
*
|
|
40
|
-
* This is the main entry point for running a GraphQL server on Bun.
|
|
41
|
-
* It handles all the Effect runtime setup and server lifecycle.
|
|
42
|
-
*
|
|
43
|
-
* @param router - The HttpRouter to serve (typically from makeGraphQLRouter or toRouter)
|
|
44
|
-
* @param layer - Layer providing the router's service dependencies
|
|
45
|
-
* @param options - Server configuration options
|
|
46
|
-
*
|
|
47
|
-
* @example
|
|
48
|
-
* ```typescript
|
|
49
|
-
* import { makeGraphQLRouter } from "@effect-gql/core"
|
|
50
|
-
* import { serve } from "@effect-gql/bun"
|
|
51
|
-
*
|
|
52
|
-
* const schema = GraphQLSchemaBuilder.empty
|
|
53
|
-
* .query("hello", { type: S.String, resolve: () => Effect.succeed("world") })
|
|
54
|
-
* .buildSchema()
|
|
55
|
-
*
|
|
56
|
-
* const router = makeGraphQLRouter(schema, Layer.empty, { graphiql: true })
|
|
57
|
-
*
|
|
58
|
-
* // Without subscriptions
|
|
59
|
-
* serve(router, serviceLayer, {
|
|
60
|
-
* port: 4000,
|
|
61
|
-
* onStart: (url) => console.log(`Server running at ${url}`)
|
|
62
|
-
* })
|
|
63
|
-
*
|
|
64
|
-
* // With subscriptions
|
|
65
|
-
* serve(router, serviceLayer, {
|
|
66
|
-
* port: 4000,
|
|
67
|
-
* subscriptions: { schema },
|
|
68
|
-
* onStart: (url) => console.log(`Server running at ${url}`)
|
|
69
|
-
* })
|
|
70
|
-
* ```
|
|
71
|
-
*/
|
|
72
|
-
export declare const serve: <E, R, RE>(router: HttpRouter.HttpRouter<E, R>, layer: Layer.Layer<R, RE>, options?: ServeOptions<R>) => void;
|
|
73
|
-
//# sourceMappingURL=serve.d.ts.map
|
package/dist/serve.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,EAAE,MAAM,QAAQ,CAAA;AACtC,OAAO,EAAW,UAAU,EAAc,MAAM,kBAAkB,CAAA;AAElE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAExD;;GAEG;AACH,MAAM,WAAW,mBAAmB,CAAC,CAAC,CAAE,SAAQ,gBAAgB,CAAC,CAAC,CAAC;IACjE;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;IAC9B;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,KAAK;IACrC,wCAAwC;IACxC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,kCAAkC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IACxC;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAA;CAChD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,KAAK,GAAI,CAAC,EAAE,CAAC,EAAE,EAAE,EAC5B,QAAQ,UAAU,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EACnC,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EACzB,UAAS,YAAY,CAAC,CAAC,CAAM,KAC5B,IAsBF,CAAA"}
|
package/dist/serve.js
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.serve = void 0;
|
|
37
|
-
const effect_1 = require("effect");
|
|
38
|
-
const platform_1 = require("@effect/platform");
|
|
39
|
-
const platform_bun_1 = require("@effect/platform-bun");
|
|
40
|
-
/**
|
|
41
|
-
* Start a Bun HTTP server with the given router.
|
|
42
|
-
*
|
|
43
|
-
* This is the main entry point for running a GraphQL server on Bun.
|
|
44
|
-
* It handles all the Effect runtime setup and server lifecycle.
|
|
45
|
-
*
|
|
46
|
-
* @param router - The HttpRouter to serve (typically from makeGraphQLRouter or toRouter)
|
|
47
|
-
* @param layer - Layer providing the router's service dependencies
|
|
48
|
-
* @param options - Server configuration options
|
|
49
|
-
*
|
|
50
|
-
* @example
|
|
51
|
-
* ```typescript
|
|
52
|
-
* import { makeGraphQLRouter } from "@effect-gql/core"
|
|
53
|
-
* import { serve } from "@effect-gql/bun"
|
|
54
|
-
*
|
|
55
|
-
* const schema = GraphQLSchemaBuilder.empty
|
|
56
|
-
* .query("hello", { type: S.String, resolve: () => Effect.succeed("world") })
|
|
57
|
-
* .buildSchema()
|
|
58
|
-
*
|
|
59
|
-
* const router = makeGraphQLRouter(schema, Layer.empty, { graphiql: true })
|
|
60
|
-
*
|
|
61
|
-
* // Without subscriptions
|
|
62
|
-
* serve(router, serviceLayer, {
|
|
63
|
-
* port: 4000,
|
|
64
|
-
* onStart: (url) => console.log(`Server running at ${url}`)
|
|
65
|
-
* })
|
|
66
|
-
*
|
|
67
|
-
* // With subscriptions
|
|
68
|
-
* serve(router, serviceLayer, {
|
|
69
|
-
* port: 4000,
|
|
70
|
-
* subscriptions: { schema },
|
|
71
|
-
* onStart: (url) => console.log(`Server running at ${url}`)
|
|
72
|
-
* })
|
|
73
|
-
* ```
|
|
74
|
-
*/
|
|
75
|
-
const serve = (router, layer, options = {}) => {
|
|
76
|
-
const { port = 4000, host = "0.0.0.0", onStart, subscriptions } = options;
|
|
77
|
-
if (subscriptions) {
|
|
78
|
-
// With WebSocket subscriptions - use Bun.serve() directly
|
|
79
|
-
serveWithSubscriptions(router, layer, port, host, subscriptions, onStart);
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
// Without subscriptions - use the standard Effect approach
|
|
83
|
-
const app = router.pipe(effect_1.Effect.catchAllCause((cause) => effect_1.Effect.die(cause)), platform_1.HttpServer.serve());
|
|
84
|
-
const serverLayer = platform_bun_1.BunHttpServer.layer({ port });
|
|
85
|
-
const fullLayer = effect_1.Layer.merge(serverLayer, layer);
|
|
86
|
-
if (onStart) {
|
|
87
|
-
onStart(`http://${host === "0.0.0.0" ? "localhost" : host}:${port}`);
|
|
88
|
-
}
|
|
89
|
-
platform_bun_1.BunRuntime.runMain(effect_1.Layer.launch(effect_1.Layer.provide(app, fullLayer)));
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
exports.serve = serve;
|
|
93
|
-
/**
|
|
94
|
-
* Internal implementation for serving with WebSocket subscriptions.
|
|
95
|
-
* Uses Bun.serve() directly to enable WebSocket support.
|
|
96
|
-
*/
|
|
97
|
-
function serveWithSubscriptions(router, layer, port, host, subscriptions, onStart) {
|
|
98
|
-
// Dynamically import ws module to keep it optional
|
|
99
|
-
const importWs = effect_1.Effect.tryPromise({
|
|
100
|
-
try: () => Promise.resolve().then(() => __importStar(require("./ws"))),
|
|
101
|
-
catch: (error) => error,
|
|
102
|
-
});
|
|
103
|
-
effect_1.Effect.runPromise(importWs.pipe(effect_1.Effect.catchAll((error) => effect_1.Effect.logError("Failed to load WebSocket support", error).pipe(effect_1.Effect.andThen(effect_1.Effect.sync(() => process.exit(1))), effect_1.Effect.andThen(effect_1.Effect.fail(error)))))).then(({ createBunWSHandlers }) => {
|
|
104
|
-
// Create the web handler from the Effect router
|
|
105
|
-
const { handler } = platform_1.HttpApp.toWebHandlerLayer(router, layer);
|
|
106
|
-
// Create WebSocket handlers
|
|
107
|
-
const { upgrade, websocket } = createBunWSHandlers(subscriptions.schema, layer, {
|
|
108
|
-
path: subscriptions.path,
|
|
109
|
-
complexity: subscriptions.complexity,
|
|
110
|
-
fieldComplexities: subscriptions.fieldComplexities,
|
|
111
|
-
onConnect: subscriptions.onConnect,
|
|
112
|
-
onDisconnect: subscriptions.onDisconnect,
|
|
113
|
-
onSubscribe: subscriptions.onSubscribe,
|
|
114
|
-
onComplete: subscriptions.onComplete,
|
|
115
|
-
onError: subscriptions.onError,
|
|
116
|
-
});
|
|
117
|
-
// Start Bun server with WebSocket support
|
|
118
|
-
const server = Bun.serve({
|
|
119
|
-
port,
|
|
120
|
-
hostname: host,
|
|
121
|
-
fetch: async (request, server) => {
|
|
122
|
-
// Try WebSocket upgrade first
|
|
123
|
-
if (upgrade(request, server)) {
|
|
124
|
-
return new Response(null, { status: 101 });
|
|
125
|
-
}
|
|
126
|
-
// Handle HTTP requests
|
|
127
|
-
return handler(request);
|
|
128
|
-
},
|
|
129
|
-
websocket,
|
|
130
|
-
});
|
|
131
|
-
if (onStart) {
|
|
132
|
-
onStart(`http://${host === "0.0.0.0" ? "localhost" : host}:${port}`);
|
|
133
|
-
}
|
|
134
|
-
// Handle shutdown
|
|
135
|
-
process.on("SIGINT", () => {
|
|
136
|
-
server.stop();
|
|
137
|
-
process.exit(0);
|
|
138
|
-
});
|
|
139
|
-
process.on("SIGTERM", () => {
|
|
140
|
-
server.stop();
|
|
141
|
-
process.exit(0);
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
//# sourceMappingURL=serve.js.map
|