@effect/platform-bun 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/BunContext.d.ts +15 -0
  2. package/BunContext.d.ts.map +1 -0
  3. package/BunContext.js +23 -0
  4. package/BunContext.js.map +1 -0
  5. package/Command.d.ts +111 -0
  6. package/Command.d.ts.map +1 -0
  7. package/Command.js +103 -0
  8. package/Command.js.map +1 -0
  9. package/CommandExecutor.d.ts +41 -0
  10. package/CommandExecutor.d.ts.map +1 -0
  11. package/CommandExecutor.js +19 -0
  12. package/CommandExecutor.js.map +1 -0
  13. package/Effectify.d.ts +19 -0
  14. package/Effectify.d.ts.map +1 -0
  15. package/Effectify.js +13 -0
  16. package/Effectify.js.map +1 -0
  17. package/Error.d.ts +30 -0
  18. package/Error.d.ts.map +1 -0
  19. package/Error.js +25 -0
  20. package/Error.js.map +1 -0
  21. package/FileSystem.d.ts +71 -0
  22. package/FileSystem.d.ts.map +1 -0
  23. package/FileSystem.js +25 -0
  24. package/FileSystem.js.map +1 -0
  25. package/Http/Server.d.ts +34 -0
  26. package/Http/Server.d.ts.map +1 -0
  27. package/Http/Server.js +49 -0
  28. package/Http/Server.js.map +1 -0
  29. package/Http/ServerResponse.d.ts +23 -0
  30. package/Http/ServerResponse.d.ts.map +1 -0
  31. package/Http/ServerResponse.js +38 -0
  32. package/Http/ServerResponse.js.map +1 -0
  33. package/HttpClient.d.ts +61 -0
  34. package/HttpClient.d.ts.map +1 -0
  35. package/HttpClient.js +23 -0
  36. package/HttpClient.js.map +1 -0
  37. package/HttpServer.d.ts +101 -0
  38. package/HttpServer.d.ts.map +1 -0
  39. package/HttpServer.js +33 -0
  40. package/HttpServer.js.map +1 -0
  41. package/KeyValueStore.d.ts +13 -0
  42. package/KeyValueStore.d.ts.map +1 -0
  43. package/KeyValueStore.js +28 -0
  44. package/KeyValueStore.js.map +1 -0
  45. package/LICENSE +21 -0
  46. package/Path.d.ts +25 -0
  47. package/Path.d.ts.map +1 -0
  48. package/Path.js +31 -0
  49. package/Path.js.map +1 -0
  50. package/README.md +3 -0
  51. package/Runtime.d.ts +26 -0
  52. package/Runtime.d.ts.map +1 -0
  53. package/Runtime.js +19 -0
  54. package/Runtime.js.map +1 -0
  55. package/Sink.d.ts +16 -0
  56. package/Sink.d.ts.map +1 -0
  57. package/Sink.js +13 -0
  58. package/Sink.js.map +1 -0
  59. package/Stream.d.ts +26 -0
  60. package/Stream.d.ts.map +1 -0
  61. package/Stream.js +25 -0
  62. package/Stream.js.map +1 -0
  63. package/internal/http/server.d.ts +2 -0
  64. package/internal/http/server.d.ts.map +1 -0
  65. package/internal/http/server.js +203 -0
  66. package/internal/http/server.js.map +1 -0
  67. package/internal/http/serverResponse.d.ts +2 -0
  68. package/internal/http/serverResponse.d.ts.map +1 -0
  69. package/internal/http/serverResponse.js +52 -0
  70. package/internal/http/serverResponse.js.map +1 -0
  71. package/mjs/BunContext.mjs +14 -0
  72. package/mjs/BunContext.mjs.map +1 -0
  73. package/mjs/Command.mjs +85 -0
  74. package/mjs/Command.mjs.map +1 -0
  75. package/mjs/CommandExecutor.mjs +15 -0
  76. package/mjs/CommandExecutor.mjs.map +1 -0
  77. package/mjs/Effectify.mjs +10 -0
  78. package/mjs/Effectify.mjs.map +1 -0
  79. package/mjs/Error.mjs +20 -0
  80. package/mjs/Error.mjs.map +1 -0
  81. package/mjs/FileSystem.mjs +20 -0
  82. package/mjs/FileSystem.mjs.map +1 -0
  83. package/mjs/Http/Server.mjs +21 -0
  84. package/mjs/Http/Server.mjs.map +1 -0
  85. package/mjs/Http/ServerResponse.mjs +13 -0
  86. package/mjs/Http/ServerResponse.mjs.map +1 -0
  87. package/mjs/HttpClient.mjs +61 -0
  88. package/mjs/HttpClient.mjs.map +1 -0
  89. package/mjs/HttpServer.mjs +101 -0
  90. package/mjs/HttpServer.mjs.map +1 -0
  91. package/mjs/KeyValueStore.mjs +13 -0
  92. package/mjs/KeyValueStore.mjs.map +1 -0
  93. package/mjs/Path.mjs +25 -0
  94. package/mjs/Path.mjs.map +1 -0
  95. package/mjs/Runtime.mjs +15 -0
  96. package/mjs/Runtime.mjs.map +1 -0
  97. package/mjs/Sink.mjs +10 -0
  98. package/mjs/Sink.mjs.map +1 -0
  99. package/mjs/Stream.mjs +20 -0
  100. package/mjs/Stream.mjs.map +1 -0
  101. package/mjs/internal/http/server.mjs +192 -0
  102. package/mjs/internal/http/server.mjs.map +1 -0
  103. package/mjs/internal/http/serverResponse.mjs +42 -0
  104. package/mjs/internal/http/serverResponse.mjs.map +1 -0
  105. package/package.json +33 -0
  106. package/src/BunContext.ts +24 -0
  107. package/src/Command.ts +114 -0
  108. package/src/CommandExecutor.ts +44 -0
  109. package/src/Effectify.ts +22 -0
  110. package/src/Error.ts +31 -0
  111. package/src/FileSystem.ts +74 -0
  112. package/src/Http/Server.ts +43 -0
  113. package/src/Http/ServerResponse.ts +33 -0
  114. package/src/HttpClient.ts +62 -0
  115. package/src/HttpServer.ts +102 -0
  116. package/src/KeyValueStore.ts +15 -0
  117. package/src/Path.ts +26 -0
  118. package/src/Runtime.ts +29 -0
  119. package/src/Sink.ts +19 -0
  120. package/src/Stream.ts +29 -0
  121. package/src/internal/http/server.ts +263 -0
  122. package/src/internal/http/serverResponse.ts +52 -0
  123. package/tsconfig.build.json +12 -0
  124. package/tsconfig.examples.json +12 -0
  125. package/tsconfig.json +11 -0
  126. package/tsconfig.test.json +17 -0
@@ -0,0 +1,263 @@
1
+ import { pipe } from "@effect/data/Function"
2
+ import * as Config from "@effect/io/Config"
3
+ import * as Effect from "@effect/io/Effect"
4
+ import * as Layer from "@effect/io/Layer"
5
+ import * as Runtime from "@effect/io/Runtime"
6
+ import type * as Scope from "@effect/io/Scope"
7
+ import * as Etag from "@effect/platform-node/Http/Etag"
8
+ import * as FormData from "@effect/platform-node/Http/FormData"
9
+ import type * as FileSystem from "@effect/platform/FileSystem"
10
+ import type * as App from "@effect/platform/Http/App"
11
+ import * as Headers from "@effect/platform/Http/Headers"
12
+ import * as IncomingMessage from "@effect/platform/Http/IncomingMessage"
13
+ import type { Method } from "@effect/platform/Http/Method"
14
+ import * as Server from "@effect/platform/Http/Server"
15
+ import * as Error from "@effect/platform/Http/ServerError"
16
+ import * as ServerRequest from "@effect/platform/Http/ServerRequest"
17
+ import type * as ServerResponse from "@effect/platform/Http/ServerResponse"
18
+ import * as UrlParams from "@effect/platform/Http/UrlParams"
19
+ import type * as Path from "@effect/platform/Path"
20
+ import * as Stream from "@effect/stream/Stream"
21
+ import type { ServeOptions, Server as BunServer } from "bun"
22
+ import { Readable } from "node:stream"
23
+
24
+ /** @internal */
25
+ export const make = (
26
+ options: Omit<ServeOptions, "fetch" | "error">
27
+ ): Effect.Effect<Scope.Scope, never, Server.Server> =>
28
+ Effect.gen(function*(_) {
29
+ const handlerStack: Array<(request: Request, server: BunServer) => Response | Promise<Response>> = [
30
+ function(_request, _server) {
31
+ return new Response("not found", { status: 404 })
32
+ }
33
+ ]
34
+ const server = Bun.serve({
35
+ ...options,
36
+ fetch: handlerStack[0]
37
+ })
38
+
39
+ yield* _(Effect.addFinalizer(() =>
40
+ Effect.sync(() => {
41
+ server.stop()
42
+ })
43
+ ))
44
+
45
+ return Server.make({
46
+ address: { _tag: "TcpAddress", port: server.port, hostname: server.hostname },
47
+ serve(httpApp, middleware) {
48
+ const app: App.HttpApp<never, unknown, Response> = Effect.flatMap(
49
+ middleware ? middleware(httpApp) : httpApp,
50
+ respond
51
+ )
52
+ return pipe(
53
+ Effect.runtime<never>(),
54
+ Effect.flatMap((runtime) =>
55
+ Effect.async<never, Error.ServeError, never>(() => {
56
+ const runPromise = Runtime.runPromise(runtime)
57
+ function handler(request: Request, _server: BunServer) {
58
+ return runPromise(Effect.provideService(
59
+ app,
60
+ ServerRequest.ServerRequest,
61
+ new ServerRequestImpl(request)
62
+ ))
63
+ }
64
+ handlerStack.push(handler)
65
+ server.reload({ fetch: handler } as ServeOptions)
66
+ })
67
+ ),
68
+ Effect.acquireRelease(() =>
69
+ Effect.sync(() => {
70
+ handlerStack.pop()
71
+ server.reload({ fetch: handlerStack[handlerStack.length - 1] } as ServeOptions)
72
+ })
73
+ )
74
+ )
75
+ }
76
+ })
77
+ })
78
+
79
+ const respond = (response: ServerResponse.ServerResponse) =>
80
+ Effect.sync((): Response => {
81
+ const body = response.body
82
+ switch (body._tag) {
83
+ case "Empty": {
84
+ return new Response(undefined, {
85
+ status: response.status,
86
+ statusText: response.statusText,
87
+ headers: response.headers
88
+ })
89
+ }
90
+ case "Uint8Array":
91
+ case "Raw": {
92
+ return new Response(body.body as any, {
93
+ status: response.status,
94
+ statusText: response.statusText,
95
+ headers: response.headers
96
+ })
97
+ }
98
+ case "FormData": {
99
+ return new Response(body.formData, {
100
+ status: response.status,
101
+ statusText: response.statusText,
102
+ headers: response.headers
103
+ })
104
+ }
105
+ case "Stream": {
106
+ return new Response(Stream.toReadableStream(body.stream), {
107
+ status: response.status,
108
+ statusText: response.statusText,
109
+ headers: response.headers
110
+ })
111
+ }
112
+ }
113
+ })
114
+
115
+ /** @internal */
116
+ export const layer = (
117
+ options: Omit<ServeOptions, "fetch" | "error">
118
+ ) =>
119
+ Layer.merge(
120
+ Layer.scoped(Server.Server, make(options)),
121
+ Etag.layer
122
+ )
123
+
124
+ /** @internal */
125
+ export const layerConfig = (
126
+ options: Config.Config.Wrap<Omit<ServeOptions, "fetch" | "error">>
127
+ ) =>
128
+ Layer.merge(
129
+ Layer.scoped(Server.Server, Effect.flatMap(Effect.config(Config.unwrap(options)), make)),
130
+ Etag.layer
131
+ )
132
+
133
+ class ServerRequestImpl implements ServerRequest.ServerRequest {
134
+ readonly [ServerRequest.TypeId]: ServerRequest.TypeId
135
+ readonly [IncomingMessage.TypeId]: IncomingMessage.TypeId
136
+ constructor(
137
+ readonly source: Request,
138
+ readonly url = source.url,
139
+ public headersOverride?: Headers.Headers
140
+ ) {
141
+ this[ServerRequest.TypeId] = ServerRequest.TypeId
142
+ this[IncomingMessage.TypeId] = IncomingMessage.TypeId
143
+ }
144
+ get method(): Method {
145
+ return this.source.method as Method
146
+ }
147
+ get originalUrl() {
148
+ return this.source.url
149
+ }
150
+ get headers(): Headers.Headers {
151
+ this.headersOverride ??= Headers.fromInput(this.source.headers)
152
+ return this.headersOverride
153
+ }
154
+ setUrl(url: string): ServerRequest.ServerRequest {
155
+ return new ServerRequestImpl(this.source, url)
156
+ }
157
+ replaceHeaders(headers: Headers.Headers): ServerRequest.ServerRequest {
158
+ return new ServerRequestImpl(this.source, this.url, headers)
159
+ }
160
+
161
+ get stream(): Stream.Stream<never, Error.RequestError, Uint8Array> {
162
+ return this.source.body
163
+ ? Stream.fromReadableStream(() => this.source.body!, (_) =>
164
+ Error.RequestError({
165
+ request: this,
166
+ reason: "Decode",
167
+ error: _
168
+ }))
169
+ : Stream.fail(Error.RequestError({
170
+ request: this,
171
+ reason: "Decode",
172
+ error: "can not create stream from empty body"
173
+ }))
174
+ }
175
+
176
+ private textEffect: Effect.Effect<never, Error.RequestError, string> | undefined
177
+ get text(): Effect.Effect<never, Error.RequestError, string> {
178
+ if (this.textEffect) {
179
+ return this.textEffect
180
+ }
181
+ this.textEffect = Effect.runSync(Effect.cached(
182
+ Effect.tryPromise({
183
+ try: () => this.source.text(),
184
+ catch: (error) =>
185
+ Error.RequestError({
186
+ request: this,
187
+ reason: "Decode",
188
+ error
189
+ })
190
+ })
191
+ ))
192
+ return this.textEffect
193
+ }
194
+
195
+ get json(): Effect.Effect<never, Error.RequestError, unknown> {
196
+ return Effect.tryMap(this.text, {
197
+ try: (_) => JSON.parse(_) as unknown,
198
+ catch: (error) =>
199
+ Error.RequestError({
200
+ request: this,
201
+ reason: "Decode",
202
+ error
203
+ })
204
+ })
205
+ }
206
+
207
+ get urlParamsBody(): Effect.Effect<never, Error.RequestError, UrlParams.UrlParams> {
208
+ return Effect.flatMap(this.text, (_) =>
209
+ Effect.try({
210
+ try: () => UrlParams.fromInput(new URLSearchParams(_)),
211
+ catch: (error) =>
212
+ Error.RequestError({
213
+ request: this,
214
+ reason: "Decode",
215
+ error
216
+ })
217
+ }))
218
+ }
219
+
220
+ private formDataEffect:
221
+ | Effect.Effect<
222
+ Scope.Scope | FileSystem.FileSystem | Path.Path,
223
+ FormData.FormDataError,
224
+ globalThis.FormData
225
+ >
226
+ | undefined
227
+ get formData(): Effect.Effect<
228
+ Scope.Scope | FileSystem.FileSystem | Path.Path,
229
+ FormData.FormDataError,
230
+ globalThis.FormData
231
+ > {
232
+ if (this.formDataEffect) {
233
+ return this.formDataEffect
234
+ }
235
+ this.formDataEffect = Effect.runSync(Effect.cached(
236
+ FormData.formData(Readable.fromWeb(this.source.body!), this.headers)
237
+ ))
238
+ return this.formDataEffect
239
+ }
240
+
241
+ get formDataStream(): Stream.Stream<never, FormData.FormDataError, FormData.Part> {
242
+ return FormData.stream(Readable.fromWeb(this.source.body!), this.headers)
243
+ }
244
+
245
+ private arrayBufferEffect: Effect.Effect<never, Error.RequestError, ArrayBuffer> | undefined
246
+ get arrayBuffer(): Effect.Effect<never, Error.RequestError, ArrayBuffer> {
247
+ if (this.arrayBuffer) {
248
+ return this.arrayBuffer
249
+ }
250
+ this.arrayBufferEffect = Effect.runSync(Effect.cached(
251
+ Effect.tryPromise({
252
+ try: () => this.source.arrayBuffer(),
253
+ catch: (error) =>
254
+ Error.RequestError({
255
+ request: this,
256
+ reason: "Decode",
257
+ error
258
+ })
259
+ })
260
+ ))
261
+ return this.arrayBufferEffect
262
+ }
263
+ }
@@ -0,0 +1,52 @@
1
+ import { pipe } from "@effect/data/Function"
2
+ import * as Effect from "@effect/io/Effect"
3
+ import type * as PlatformError from "@effect/platform/Error"
4
+ import * as FileSystem from "@effect/platform/FileSystem"
5
+ import type * as Body from "@effect/platform/Http/Body"
6
+ import * as Etag from "@effect/platform/Http/Etag"
7
+ import * as ServerResponse from "@effect/platform/Http/ServerResponse"
8
+
9
+ /** @internal */
10
+ export const file = (
11
+ path: string,
12
+ options?: ServerResponse.Options.WithContentType & FileSystem.StreamOptions
13
+ ): Effect.Effect<FileSystem.FileSystem | Etag.Generator, PlatformError.PlatformError, ServerResponse.ServerResponse> =>
14
+ pipe(
15
+ Effect.bindTo(Effect.flatMap(FileSystem.FileSystem, (fs) => fs.stat(path)), "info"),
16
+ Effect.bind("etag", ({ info }) =>
17
+ Effect.flatMap(
18
+ Etag.Generator,
19
+ (generator) => generator.fromFileInfo(info)
20
+ )),
21
+ Effect.map(({ etag, info }) => {
22
+ const headers: Record<string, string> = {
23
+ ...(options?.headers ?? {}),
24
+ etag: Etag.toString(etag)
25
+ }
26
+ if (info.mtime._tag === "Some") {
27
+ headers["last-modified"] = info.mtime.value.toUTCString()
28
+ }
29
+ let file = Bun.file(path)
30
+ if (options?.bytesToRead !== undefined || options?.offset !== undefined) {
31
+ const start = Number(options?.offset ?? 0)
32
+ const end = options?.bytesToRead !== undefined ? start + Number(options.bytesToRead) : undefined
33
+ file = file.slice(start, end)
34
+ }
35
+ return ServerResponse.raw(file, { ...options, headers })
36
+ })
37
+ )
38
+
39
+ /** @internal */
40
+ export const fileWeb = (
41
+ file: Body.Body.FileLike,
42
+ options?: ServerResponse.Options.WithContent
43
+ ): Effect.Effect<Etag.Generator, never, ServerResponse.ServerResponse> =>
44
+ Effect.flatMap(Etag.Generator, (generator) =>
45
+ Effect.map(generator.fromFileWeb(file), (etag) => {
46
+ const headers: Record<string, string> = {
47
+ ...(options?.headers ?? {}),
48
+ etag: Etag.toString(etag),
49
+ "last-modified": new Date(file.lastModified).toUTCString()
50
+ }
51
+ return ServerResponse.raw(file, { ...options, headers })
52
+ }))
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "build/esm",
5
+ "declarationDir": "build/dts",
6
+ "tsBuildInfoFile": "build/tsbuildinfo/esm.tsbuildinfo",
7
+ "rootDir": "src",
8
+ "types": ["bun-types"]
9
+ },
10
+ "include": ["src/**/*.ts"],
11
+ "references": [{ "path": "../platform" }, { "path": "../platform-node" }]
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "tsBuildInfoFile": "build/tsbuildinfo/examples.tsbuildinfo",
5
+ "rootDir": "examples",
6
+ "module": "CommonJS",
7
+ "outDir": "build/examples",
8
+ "types": ["bun-types"]
9
+ },
10
+ "include": ["examples/**/*.ts"],
11
+ "references": [{ "path": "./tsconfig.build.json" }]
12
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "tsBuildInfoFile": "build/tsbuildinfo/tsconfig.tsbuildinfo"
5
+ },
6
+ "references": [
7
+ { "path": "./tsconfig.build.json" },
8
+ { "path": "./tsconfig.test.json" },
9
+ { "path": "./tsconfig.examples.json" }
10
+ ]
11
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "build/test",
5
+ "declarationDir": "build/test-dts",
6
+ "tsBuildInfoFile": "build/tsbuildinfo/test.tsbuildinfo",
7
+ "rootDir": "test",
8
+ "noEmit": true,
9
+ "types": ["vitest/globals"]
10
+ },
11
+ "include": ["test/**/*.ts"],
12
+ "references": [
13
+ {
14
+ "path": "./tsconfig.build.json"
15
+ }
16
+ ]
17
+ }