effect-start 0.20.0 → 0.21.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.
@@ -1,88 +0,0 @@
1
- import * as HttpApp from "@effect/platform/HttpApp"
2
- import * as HttpServer from "@effect/platform/HttpServer"
3
- import * as HttpServerError from "@effect/platform/HttpServerError"
4
- import * as HttpServerRequest from "@effect/platform/HttpServerRequest"
5
- import type * as Bun from "bun"
6
- import * as Effect from "effect/Effect"
7
- import * as FiberSet from "effect/FiberSet"
8
- import type * as Scope from "effect/Scope"
9
- import * as BunServer from "./BunServer.ts"
10
- import * as BunServerRequest from "./BunServerRequest.ts"
11
-
12
- /**
13
- * From times when we used @effect/platform
14
- * Not used any more internally. Kept for the future,
15
- * in case someone will need it for whatever reason. [2026]
16
- */
17
- export const make: Effect.Effect<
18
- HttpServer.HttpServer,
19
- never,
20
- Scope.Scope | BunServer.BunServer
21
- > = Effect.gen(function*() {
22
- const bunServer = yield* BunServer.BunServer
23
-
24
- return HttpServer.make({
25
- address: {
26
- _tag: "TcpAddress",
27
- port: bunServer.server.port!,
28
- hostname: bunServer.server.hostname!,
29
- },
30
- serve(httpApp, middleware) {
31
- return Effect.gen(function*() {
32
- const runFork = yield* FiberSet.makeRuntime<never>()
33
- const runtime = yield* Effect.runtime<never>()
34
- const app = HttpApp.toHandled(
35
- httpApp,
36
- (request, response) =>
37
- Effect.sync(() => {
38
- ;(request as BunServerRequest.ServerRequestImpl).resolve(
39
- BunServerRequest.makeResponse(request, response, runtime),
40
- )
41
- }),
42
- middleware,
43
- )
44
-
45
- function handler(
46
- request: Request,
47
- server: Bun.Server<BunServerRequest.WebSocketContext>,
48
- ) {
49
- return new Promise<Response>((resolve, _reject) => {
50
- const fiber = runFork(Effect.provideService(
51
- app,
52
- HttpServerRequest.HttpServerRequest,
53
- new BunServerRequest.ServerRequestImpl(
54
- request,
55
- resolve,
56
- removeHost(request.url),
57
- server,
58
- ),
59
- ))
60
- request.signal.addEventListener("abort", () => {
61
- runFork(
62
- fiber.interruptAsFork(HttpServerError.clientAbortFiberId),
63
- )
64
- }, { once: true })
65
- })
66
- }
67
-
68
- yield* Effect.acquireRelease(
69
- Effect.sync(() => {
70
- bunServer.pushHandler(handler)
71
- }),
72
- () =>
73
- Effect.sync(() => {
74
- bunServer.popHandler()
75
- }),
76
- )
77
- })
78
- },
79
- })
80
- })
81
-
82
- const removeHost = (url: string) => {
83
- if (url[0] === "/") {
84
- return url
85
- }
86
- const index = url.indexOf("/", url.indexOf("//") + 2)
87
- return index === -1 ? "/" : url.slice(index)
88
- }
@@ -1,396 +0,0 @@
1
- import * as Cookies from "@effect/platform/Cookies"
2
- import type * as FileSystem from "@effect/platform/FileSystem"
3
- import * as Headers from "@effect/platform/Headers"
4
- import * as HttpApp from "@effect/platform/HttpApp"
5
- import * as HttpIncomingMessage from "@effect/platform/HttpIncomingMessage"
6
- import type { HttpMethod } from "@effect/platform/HttpMethod"
7
- import * as HttpServerError from "@effect/platform/HttpServerError"
8
- import * as HttpServerRequest from "@effect/platform/HttpServerRequest"
9
- import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
10
- import type * as Multipart from "@effect/platform/Multipart"
11
- import type * as Path from "@effect/platform/Path"
12
- import * as Socket from "@effect/platform/Socket"
13
- import * as UrlParams from "@effect/platform/UrlParams"
14
- import type {
15
- Server as BunServerInstance,
16
- ServerWebSocket,
17
- } from "bun"
18
- import * as Deferred from "effect/Deferred"
19
- import * as Effect from "effect/Effect"
20
- import * as FiberSet from "effect/FiberSet"
21
- import * as Inspectable from "effect/Inspectable"
22
- import * as Option from "effect/Option"
23
- import type { ReadonlyRecord } from "effect/Record"
24
- import * as Runtime from "effect/Runtime"
25
- import type * as Scope from "effect/Scope"
26
- import * as Stream from "effect/Stream"
27
-
28
- export interface WebSocketContext {
29
- readonly deferred: Deferred.Deferred<ServerWebSocket<WebSocketContext>>
30
- readonly closeDeferred: Deferred.Deferred<void, Socket.SocketError>
31
- readonly buffer: Array<Uint8Array | string>
32
- run: (_: Uint8Array | string) => void
33
- }
34
-
35
- export class ServerRequestImpl extends Inspectable.Class
36
- implements HttpServerRequest.HttpServerRequest
37
- {
38
- readonly [HttpServerRequest.TypeId]: HttpServerRequest.TypeId
39
- readonly [HttpIncomingMessage.TypeId]: HttpIncomingMessage.TypeId
40
- readonly source: Request
41
- resolve: (response: Response) => void
42
- readonly url: string
43
- private bunServer: BunServerInstance<WebSocketContext>
44
- headersOverride?: Headers.Headers
45
- private remoteAddressOverride?: string
46
-
47
- constructor(
48
- source: Request,
49
- resolve: (response: Response) => void,
50
- url: string,
51
- bunServer: BunServerInstance<WebSocketContext>,
52
- headersOverride?: Headers.Headers,
53
- remoteAddressOverride?: string,
54
- ) {
55
- super()
56
- this[HttpServerRequest.TypeId] = HttpServerRequest.TypeId
57
- this[HttpIncomingMessage.TypeId] = HttpIncomingMessage.TypeId
58
- this.source = source
59
- this.resolve = resolve
60
- this.url = url
61
- this.bunServer = bunServer
62
- this.headersOverride = headersOverride
63
- this.remoteAddressOverride = remoteAddressOverride
64
- }
65
-
66
- toJSON(): unknown {
67
- return HttpIncomingMessage.inspect(this, {
68
- _id: "@effect/platform/HttpServerRequest",
69
- method: this.method,
70
- url: this.originalUrl,
71
- })
72
- }
73
-
74
- modify(
75
- options: {
76
- readonly url?: string | undefined
77
- readonly headers?: Headers.Headers | undefined
78
- readonly remoteAddress?: string | undefined
79
- },
80
- ) {
81
- return new ServerRequestImpl(
82
- this.source,
83
- this.resolve,
84
- options.url ?? this.url,
85
- this.bunServer,
86
- options.headers ?? this.headersOverride,
87
- options.remoteAddress ?? this.remoteAddressOverride,
88
- )
89
- }
90
-
91
- get method(): HttpMethod {
92
- return this.source.method.toUpperCase() as HttpMethod
93
- }
94
-
95
- get originalUrl() {
96
- return this.source.url
97
- }
98
-
99
- get remoteAddress(): Option.Option<string> {
100
- return this.remoteAddressOverride
101
- ? Option.some(this.remoteAddressOverride)
102
- : Option.fromNullable(this.bunServer.requestIP(this.source)?.address)
103
- }
104
-
105
- get headers(): Headers.Headers {
106
- this.headersOverride ??= Headers.fromInput(this.source.headers)
107
- return this.headersOverride
108
- }
109
-
110
- private cachedCookies: ReadonlyRecord<string, string> | undefined
111
- get cookies() {
112
- if (this.cachedCookies) {
113
- return this.cachedCookies
114
- }
115
- return this.cachedCookies = Cookies.parseHeader(this.headers.cookie ?? "")
116
- }
117
-
118
- get stream(): Stream.Stream<Uint8Array, HttpServerError.RequestError> {
119
- return this.source.body
120
- ? Stream.fromReadableStream(
121
- () => this.source.body as ReadableStream<Uint8Array>,
122
- (cause) =>
123
- new HttpServerError.RequestError({
124
- request: this,
125
- reason: "Decode",
126
- cause,
127
- }),
128
- )
129
- : Stream.fail(
130
- new HttpServerError.RequestError({
131
- request: this,
132
- reason: "Decode",
133
- description: "can not create stream from empty body",
134
- }),
135
- )
136
- }
137
-
138
- private textEffect:
139
- | Effect.Effect<string, HttpServerError.RequestError>
140
- | undefined
141
-
142
- get text(): Effect.Effect<string, HttpServerError.RequestError> {
143
- if (this.textEffect) {
144
- return this.textEffect
145
- }
146
- this.textEffect = Effect.runSync(Effect.cached(
147
- Effect.tryPromise({
148
- try: () => this.source.text(),
149
- catch: (cause) =>
150
- new HttpServerError.RequestError({
151
- request: this,
152
- reason: "Decode",
153
- cause,
154
- }),
155
- }),
156
- ))
157
- return this.textEffect
158
- }
159
-
160
- get json(): Effect.Effect<unknown, HttpServerError.RequestError> {
161
- return Effect.tryMap(this.text, {
162
- try: (_) => JSON.parse(_) as unknown,
163
- catch: (cause) =>
164
- new HttpServerError.RequestError({
165
- request: this,
166
- reason: "Decode",
167
- cause,
168
- }),
169
- })
170
- }
171
-
172
- get urlParamsBody(): Effect.Effect<
173
- UrlParams.UrlParams,
174
- HttpServerError.RequestError
175
- > {
176
- return Effect.flatMap(this.text, (_) =>
177
- Effect.try({
178
- try: () => UrlParams.fromInput(new URLSearchParams(_)),
179
- catch: (cause) =>
180
- new HttpServerError.RequestError({
181
- request: this,
182
- reason: "Decode",
183
- cause,
184
- }),
185
- }))
186
- }
187
-
188
- private multipartEffect:
189
- | Effect.Effect<
190
- Multipart.Persisted,
191
- Multipart.MultipartError,
192
- Scope.Scope | FileSystem.FileSystem | Path.Path
193
- >
194
- | undefined
195
-
196
- get multipart(): Effect.Effect<
197
- Multipart.Persisted,
198
- Multipart.MultipartError,
199
- Scope.Scope | FileSystem.FileSystem | Path.Path
200
- > {
201
- if (this.multipartEffect) {
202
- return this.multipartEffect
203
- }
204
- this.multipartEffect = Effect.runSync(Effect.cached(
205
- Effect.die("Multipart not implemented"),
206
- ))
207
- return this.multipartEffect
208
- }
209
-
210
- get multipartStream(): Stream.Stream<
211
- Multipart.Part,
212
- Multipart.MultipartError
213
- > {
214
- return Stream.die("Multipart stream not implemented")
215
- }
216
-
217
- private arrayBufferEffect:
218
- | Effect.Effect<ArrayBuffer, HttpServerError.RequestError>
219
- | undefined
220
- get arrayBuffer(): Effect.Effect<ArrayBuffer, HttpServerError.RequestError> {
221
- if (this.arrayBufferEffect) {
222
- return this.arrayBufferEffect
223
- }
224
- this.arrayBufferEffect = Effect.runSync(Effect.cached(
225
- Effect.tryPromise({
226
- try: () => this.source.arrayBuffer(),
227
- catch: (cause) =>
228
- new HttpServerError.RequestError({
229
- request: this,
230
- reason: "Decode",
231
- cause,
232
- }),
233
- }),
234
- ))
235
- return this.arrayBufferEffect
236
- }
237
-
238
- get upgrade(): Effect.Effect<Socket.Socket, HttpServerError.RequestError> {
239
- return Effect.flatMap(
240
- Effect.all([
241
- Deferred.make<ServerWebSocket<WebSocketContext>>(),
242
- Deferred.make<void, Socket.SocketError>(),
243
- Effect.makeSemaphore(1),
244
- ]),
245
- ([deferred, closeDeferred, semaphore]) =>
246
- Effect.async<Socket.Socket, HttpServerError.RequestError>((resume) => {
247
- const success = this.bunServer.upgrade(
248
- this.source,
249
- {
250
- data: {
251
- deferred,
252
- closeDeferred,
253
- buffer: [],
254
- run: wsDefaultRun,
255
- },
256
- },
257
- )
258
- if (!success) {
259
- resume(Effect.fail(
260
- new HttpServerError.RequestError({
261
- request: this,
262
- reason: "Decode",
263
- description: "Not an upgradeable ServerRequest",
264
- }),
265
- ))
266
- return
267
- }
268
- resume(Effect.map(Deferred.await(deferred), (ws) => {
269
- const write = (chunk: Uint8Array | string | Socket.CloseEvent) =>
270
- Effect.sync(() => {
271
- if (typeof chunk === "string") {
272
- ws.sendText(chunk)
273
- } else if (Socket.isCloseEvent(chunk)) {
274
- ws.close(chunk.code, chunk.reason)
275
- } else {
276
- ws.sendBinary(chunk)
277
- }
278
-
279
- return true
280
- })
281
- const writer = Effect.succeed(write)
282
- const runRaw = Effect.fnUntraced(
283
- function*<RR, EE, _>(
284
- handler: (
285
- _: Uint8Array | string,
286
- ) => Effect.Effect<_, EE, RR> | void,
287
- opts?: { readonly onOpen?: Effect.Effect<void> | undefined },
288
- ) {
289
- const set = yield* FiberSet.make<unknown, EE>()
290
- const run = yield* FiberSet.runtime(set)<RR>()
291
- function runRawInner(data: Uint8Array | string) {
292
- const result = handler(data)
293
- if (Effect.isEffect(result)) {
294
- run(result)
295
- }
296
- }
297
- ws.data.run = runRawInner
298
- ws.data.buffer.forEach(runRawInner)
299
- ws.data.buffer.length = 0
300
- if (opts?.onOpen) yield* opts.onOpen
301
- return yield* FiberSet.join(set)
302
- },
303
- Effect.scoped,
304
- Effect.onExit((exit) => {
305
- ws.close(exit._tag === "Success" ? 1000 : 1011)
306
- return Effect.void
307
- }),
308
- Effect.raceFirst(Deferred.await(closeDeferred)),
309
- semaphore.withPermits(1),
310
- )
311
-
312
- const encoder = new TextEncoder()
313
- const run = <RR, EE, _>(
314
- handler: (_: Uint8Array) => Effect.Effect<_, EE, RR> | void,
315
- opts?: {
316
- readonly onOpen?: Effect.Effect<void> | undefined
317
- },
318
- ) =>
319
- runRaw(
320
- (data) =>
321
- typeof data === "string"
322
- ? handler(encoder.encode(data))
323
- : handler(data),
324
- opts,
325
- )
326
-
327
- return Socket.Socket.of({
328
- [Socket.TypeId]: Socket.TypeId,
329
- run,
330
- runRaw,
331
- writer,
332
- })
333
- }))
334
- }),
335
- )
336
- }
337
- }
338
-
339
- function wsDefaultRun(this: WebSocketContext, _: Uint8Array | string) {
340
- this.buffer.push(_)
341
- }
342
-
343
- export function makeResponse(
344
- request: HttpServerRequest.HttpServerRequest,
345
- response: HttpServerResponse.HttpServerResponse,
346
- runtime: Runtime.Runtime<never>,
347
- ): Response {
348
- const fields: {
349
- headers: globalThis.Headers
350
- status?: number
351
- statusText?: string
352
- } = {
353
- headers: new globalThis.Headers(response.headers),
354
- status: response.status,
355
- }
356
-
357
- if (!Cookies.isEmpty(response.cookies)) {
358
- for (const header of Cookies.toSetCookieHeaders(response.cookies)) {
359
- fields.headers.append("set-cookie", header)
360
- }
361
- }
362
-
363
- if (response.statusText !== undefined) {
364
- fields.statusText = response.statusText
365
- }
366
-
367
- if (request.method === "HEAD") {
368
- return new Response(undefined, fields)
369
- }
370
- const ejectedResponse = HttpApp.unsafeEjectStreamScope(response)
371
- const body = ejectedResponse.body
372
- switch (body._tag) {
373
- case "Empty": {
374
- return new Response(undefined, fields)
375
- }
376
- case "Uint8Array":
377
- case "Raw": {
378
- if (body.body instanceof Response) {
379
- for (const [key, value] of fields.headers.entries()) {
380
- body.body.headers.set(key, value)
381
- }
382
- return body.body
383
- }
384
- return new Response(body.body as BodyInit, fields)
385
- }
386
- case "FormData": {
387
- return new Response(body.formData as FormData, fields)
388
- }
389
- case "Stream": {
390
- return new Response(
391
- Stream.toReadableStreamRuntime(body.stream, runtime),
392
- fields,
393
- )
394
- }
395
- }
396
- }