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.
@@ -0,0 +1,403 @@
1
+ /*
2
+ * Adapted from @effect/platform
3
+ */
4
+ import * as Brand from "effect/Brand"
5
+ import * as Channel from "effect/Channel"
6
+ import * as Chunk from "effect/Chunk"
7
+ import * as Context from "effect/Context"
8
+ import * as Data from "effect/Data"
9
+ import * as Effect from "effect/Effect"
10
+ import * as Function from "effect/Function"
11
+ import * as Option from "effect/Option"
12
+ import * as Sink from "effect/Sink"
13
+ import type * as Scope from "effect/Scope"
14
+ import * as Stream from "effect/Stream"
15
+ import * as PlatformError from "./PlatformError.ts"
16
+
17
+ export interface FileSystem {
18
+ readonly access: (
19
+ path: string,
20
+ options?: AccessFileOptions,
21
+ ) => Effect.Effect<void, PlatformError.PlatformError>
22
+ readonly copy: (
23
+ fromPath: string,
24
+ toPath: string,
25
+ options?: CopyOptions,
26
+ ) => Effect.Effect<void, PlatformError.PlatformError>
27
+ readonly copyFile: (
28
+ fromPath: string,
29
+ toPath: string,
30
+ ) => Effect.Effect<void, PlatformError.PlatformError>
31
+ readonly chmod: (
32
+ path: string,
33
+ mode: number,
34
+ ) => Effect.Effect<void, PlatformError.PlatformError>
35
+ readonly chown: (
36
+ path: string,
37
+ uid: number,
38
+ gid: number,
39
+ ) => Effect.Effect<void, PlatformError.PlatformError>
40
+ readonly exists: (
41
+ path: string,
42
+ ) => Effect.Effect<boolean, PlatformError.PlatformError>
43
+ readonly link: (
44
+ fromPath: string,
45
+ toPath: string,
46
+ ) => Effect.Effect<void, PlatformError.PlatformError>
47
+ readonly makeDirectory: (
48
+ path: string,
49
+ options?: MakeDirectoryOptions,
50
+ ) => Effect.Effect<void, PlatformError.PlatformError>
51
+ readonly makeTempDirectory: (
52
+ options?: MakeTempDirectoryOptions,
53
+ ) => Effect.Effect<string, PlatformError.PlatformError>
54
+ readonly makeTempDirectoryScoped: (
55
+ options?: MakeTempDirectoryOptions,
56
+ ) => Effect.Effect<string, PlatformError.PlatformError, Scope.Scope>
57
+ readonly makeTempFile: (
58
+ options?: MakeTempFileOptions,
59
+ ) => Effect.Effect<string, PlatformError.PlatformError>
60
+ readonly makeTempFileScoped: (
61
+ options?: MakeTempFileOptions,
62
+ ) => Effect.Effect<string, PlatformError.PlatformError, Scope.Scope>
63
+ readonly open: (
64
+ path: string,
65
+ options?: OpenFileOptions,
66
+ ) => Effect.Effect<File, PlatformError.PlatformError, Scope.Scope>
67
+ readonly readDirectory: (
68
+ path: string,
69
+ options?: ReadDirectoryOptions,
70
+ ) => Effect.Effect<Array<string>, PlatformError.PlatformError>
71
+ readonly readFile: (
72
+ path: string,
73
+ ) => Effect.Effect<Uint8Array, PlatformError.PlatformError>
74
+ readonly readFileString: (
75
+ path: string,
76
+ encoding?: string,
77
+ ) => Effect.Effect<string, PlatformError.PlatformError>
78
+ readonly readLink: (
79
+ path: string,
80
+ ) => Effect.Effect<string, PlatformError.PlatformError>
81
+ readonly realPath: (
82
+ path: string,
83
+ ) => Effect.Effect<string, PlatformError.PlatformError>
84
+ readonly remove: (
85
+ path: string,
86
+ options?: RemoveOptions,
87
+ ) => Effect.Effect<void, PlatformError.PlatformError>
88
+ readonly rename: (
89
+ oldPath: string,
90
+ newPath: string,
91
+ ) => Effect.Effect<void, PlatformError.PlatformError>
92
+ readonly sink: (
93
+ path: string,
94
+ options?: SinkOptions,
95
+ ) => Sink.Sink<void, Uint8Array, never, PlatformError.PlatformError>
96
+ readonly stat: (
97
+ path: string,
98
+ ) => Effect.Effect<File.Info, PlatformError.PlatformError>
99
+ readonly stream: (
100
+ path: string,
101
+ options?: StreamOptions,
102
+ ) => Stream.Stream<Uint8Array, PlatformError.PlatformError>
103
+ readonly symlink: (
104
+ fromPath: string,
105
+ toPath: string,
106
+ ) => Effect.Effect<void, PlatformError.PlatformError>
107
+ readonly truncate: (
108
+ path: string,
109
+ length?: SizeInput,
110
+ ) => Effect.Effect<void, PlatformError.PlatformError>
111
+ readonly utimes: (
112
+ path: string,
113
+ atime: Date | number,
114
+ mtime: Date | number,
115
+ ) => Effect.Effect<void, PlatformError.PlatformError>
116
+ readonly watch: (
117
+ path: string,
118
+ options?: WatchOptions,
119
+ ) => Stream.Stream<WatchEvent, PlatformError.PlatformError>
120
+ readonly writeFile: (
121
+ path: string,
122
+ data: Uint8Array,
123
+ options?: WriteFileOptions,
124
+ ) => Effect.Effect<void, PlatformError.PlatformError>
125
+ readonly writeFileString: (
126
+ path: string,
127
+ data: string,
128
+ options?: WriteFileStringOptions,
129
+ ) => Effect.Effect<void, PlatformError.PlatformError>
130
+ }
131
+
132
+ export const FileSystem: Context.Tag<FileSystem, FileSystem> = Context.GenericTag<FileSystem>(
133
+ "@effect/platform/FileSystem",
134
+ )
135
+
136
+ export type Size = Brand.Branded<bigint, "Size">
137
+
138
+ export type SizeInput = bigint | number | Size
139
+
140
+ export const Size = (bytes: SizeInput): Size =>
141
+ typeof bytes === "bigint" ? bytes as Size : BigInt(bytes) as Size
142
+
143
+ export type OpenFlag =
144
+ | "r"
145
+ | "r+"
146
+ | "w"
147
+ | "wx"
148
+ | "w+"
149
+ | "wx+"
150
+ | "a"
151
+ | "ax"
152
+ | "a+"
153
+ | "ax+"
154
+
155
+ export type SeekMode = "start" | "current"
156
+
157
+ export interface AccessFileOptions {
158
+ readonly ok?: boolean
159
+ readonly readable?: boolean
160
+ readonly writable?: boolean
161
+ }
162
+
163
+ export interface MakeDirectoryOptions {
164
+ readonly recursive?: boolean
165
+ readonly mode?: number
166
+ }
167
+
168
+ export interface CopyOptions {
169
+ readonly overwrite?: boolean
170
+ readonly preserveTimestamps?: boolean
171
+ }
172
+
173
+ export interface MakeTempDirectoryOptions {
174
+ readonly directory?: string
175
+ readonly prefix?: string
176
+ }
177
+
178
+ export interface MakeTempFileOptions {
179
+ readonly directory?: string
180
+ readonly prefix?: string
181
+ readonly suffix?: string
182
+ }
183
+
184
+ export interface OpenFileOptions {
185
+ readonly flag?: OpenFlag
186
+ readonly mode?: number
187
+ }
188
+
189
+ export interface ReadDirectoryOptions {
190
+ readonly recursive?: boolean
191
+ }
192
+
193
+ export interface RemoveOptions {
194
+ readonly recursive?: boolean
195
+ readonly force?: boolean
196
+ }
197
+
198
+ export interface SinkOptions extends OpenFileOptions {}
199
+
200
+ export interface StreamOptions {
201
+ readonly bufferSize?: number
202
+ readonly bytesToRead?: SizeInput
203
+ readonly chunkSize?: SizeInput
204
+ readonly offset?: SizeInput
205
+ }
206
+
207
+ export interface WriteFileOptions {
208
+ readonly flag?: OpenFlag
209
+ readonly mode?: number
210
+ }
211
+
212
+ export interface WriteFileStringOptions {
213
+ readonly flag?: OpenFlag
214
+ readonly mode?: number
215
+ }
216
+
217
+ export interface WatchOptions {
218
+ readonly recursive?: boolean
219
+ }
220
+
221
+ export const FileTypeId: unique symbol = Symbol.for("@effect/platform/FileSystem/File")
222
+
223
+ export type FileTypeId = typeof FileTypeId
224
+
225
+ export interface File {
226
+ readonly [FileTypeId]: FileTypeId
227
+ readonly fd: File.Descriptor
228
+ readonly stat: Effect.Effect<File.Info, PlatformError.PlatformError>
229
+ readonly seek: (offset: SizeInput, from: SeekMode) => Effect.Effect<void>
230
+ readonly sync: Effect.Effect<void, PlatformError.PlatformError>
231
+ readonly read: (buffer: Uint8Array) => Effect.Effect<Size, PlatformError.PlatformError>
232
+ readonly readAlloc: (size: SizeInput) => Effect.Effect<Option.Option<Uint8Array>, PlatformError.PlatformError>
233
+ readonly truncate: (length?: SizeInput) => Effect.Effect<void, PlatformError.PlatformError>
234
+ readonly write: (buffer: Uint8Array) => Effect.Effect<Size, PlatformError.PlatformError>
235
+ readonly writeAll: (buffer: Uint8Array) => Effect.Effect<void, PlatformError.PlatformError>
236
+ }
237
+
238
+ export declare namespace File {
239
+ export type Descriptor = Brand.Branded<number, "FileDescriptor">
240
+
241
+ export type Type =
242
+ | "File"
243
+ | "Directory"
244
+ | "SymbolicLink"
245
+ | "BlockDevice"
246
+ | "CharacterDevice"
247
+ | "FIFO"
248
+ | "Socket"
249
+ | "Unknown"
250
+
251
+ export interface Info {
252
+ readonly type: Type
253
+ readonly mtime: Option.Option<Date>
254
+ readonly atime: Option.Option<Date>
255
+ readonly birthtime: Option.Option<Date>
256
+ readonly dev: number
257
+ readonly ino: Option.Option<number>
258
+ readonly mode: number
259
+ readonly nlink: Option.Option<number>
260
+ readonly uid: Option.Option<number>
261
+ readonly gid: Option.Option<number>
262
+ readonly rdev: Option.Option<number>
263
+ readonly size: Size
264
+ readonly blksize: Option.Option<Size>
265
+ readonly blocks: Option.Option<number>
266
+ }
267
+ }
268
+
269
+ export const FileDescriptor = Brand.nominal<File.Descriptor>()
270
+
271
+ export type WatchEvent = WatchEvent.Create | WatchEvent.Update | WatchEvent.Remove
272
+
273
+ export declare namespace WatchEvent {
274
+ export interface Create {
275
+ readonly _tag: "Create"
276
+ readonly path: string
277
+ }
278
+
279
+ export interface Update {
280
+ readonly _tag: "Update"
281
+ readonly path: string
282
+ }
283
+
284
+ export interface Remove {
285
+ readonly _tag: "Remove"
286
+ readonly path: string
287
+ }
288
+ }
289
+
290
+ export const WatchEventCreate: Data.Case.Constructor<WatchEvent.Create, "_tag"> = Data.tagged<WatchEvent.Create>(
291
+ "Create",
292
+ )
293
+
294
+ export const WatchEventUpdate: Data.Case.Constructor<WatchEvent.Update, "_tag"> = Data.tagged<WatchEvent.Update>(
295
+ "Update",
296
+ )
297
+
298
+ export const WatchEventRemove: Data.Case.Constructor<WatchEvent.Remove, "_tag"> = Data.tagged<WatchEvent.Remove>(
299
+ "Remove",
300
+ )
301
+
302
+ export class WatchBackend extends Context.Tag("@effect/platform/FileSystem/WatchBackend")<
303
+ WatchBackend,
304
+ {
305
+ readonly register: (
306
+ path: string,
307
+ stat: File.Info,
308
+ options?: WatchOptions,
309
+ ) => Option.Option<Stream.Stream<WatchEvent, PlatformError.PlatformError>>
310
+ }
311
+ >() {}
312
+
313
+ export const make = (
314
+ impl: Omit<FileSystem, "exists" | "readFileString" | "stream" | "sink" | "writeFileString">,
315
+ ): FileSystem => {
316
+ return FileSystem.of({
317
+ ...impl,
318
+ exists: (path) =>
319
+ Function.pipe(
320
+ impl.access(path),
321
+ Effect.as(true),
322
+ Effect.catchTag("SystemError", (e) => e.reason === "NotFound" ? Effect.succeed(false) : Effect.fail(e)),
323
+ ),
324
+ readFileString: (path, encoding) =>
325
+ Effect.tryMap(impl.readFile(path), {
326
+ try: (_) => new TextDecoder(encoding).decode(_),
327
+ catch: (cause) =>
328
+ new PlatformError.BadArgument({
329
+ module: "FileSystem",
330
+ method: "readFileString",
331
+ description: "invalid encoding",
332
+ cause,
333
+ }),
334
+ }),
335
+ stream: (path, options) =>
336
+ Function.pipe(
337
+ impl.open(path, { flag: "r" }),
338
+ options?.offset
339
+ ? Effect.tap((file) => file.seek(options.offset!, "start"))
340
+ : Function.identity,
341
+ Effect.map((file) => fileStream(file, options)),
342
+ Stream.unwrapScoped,
343
+ ),
344
+ sink: (path, options) =>
345
+ Function.pipe(
346
+ impl.open(path, { flag: "w", ...options }),
347
+ Effect.map((file) => Sink.forEach((_: Uint8Array) => file.writeAll(_))),
348
+ Sink.unwrapScoped,
349
+ ),
350
+ writeFileString: (path, data, options) =>
351
+ Effect.flatMap(
352
+ Effect.try({
353
+ try: () => new TextEncoder().encode(data),
354
+ catch: (cause) =>
355
+ new PlatformError.BadArgument({
356
+ module: "FileSystem",
357
+ method: "writeFileString",
358
+ description: "could not encode string",
359
+ cause,
360
+ }),
361
+ }),
362
+ (_) => impl.writeFile(path, _, options),
363
+ ),
364
+ })
365
+ }
366
+
367
+ const fileStream = (file: File, {
368
+ bufferSize = 16,
369
+ bytesToRead: bytesToRead_,
370
+ chunkSize: chunkSize_ = Size(64 * 1024),
371
+ }: StreamOptions = {}) => {
372
+ const bytesToRead = bytesToRead_ !== undefined ? Size(bytesToRead_) : undefined
373
+ const chunkSize = Size(chunkSize_)
374
+
375
+ function loop(
376
+ totalBytesRead: bigint,
377
+ ): Channel.Channel<Chunk.Chunk<Uint8Array>, unknown, PlatformError.PlatformError, unknown, void, unknown> {
378
+ if (bytesToRead !== undefined && bytesToRead <= totalBytesRead) {
379
+ return Channel.void
380
+ }
381
+
382
+ const toRead = bytesToRead !== undefined && (bytesToRead - totalBytesRead) < chunkSize
383
+ ? bytesToRead - totalBytesRead
384
+ : chunkSize
385
+
386
+ return Channel.flatMap(
387
+ file.readAlloc(toRead),
388
+ Option.match({
389
+ onNone: () => Channel.void,
390
+ onSome: (buf) =>
391
+ Channel.flatMap(
392
+ Channel.write(Chunk.of(buf)),
393
+ () => loop(totalBytesRead + BigInt(buf.length)),
394
+ ),
395
+ }),
396
+ )
397
+ }
398
+
399
+ return Stream.bufferChunks(
400
+ Stream.fromChannel(loop(BigInt(0))),
401
+ { capacity: bufferSize },
402
+ )
403
+ }
@@ -7,9 +7,9 @@ import * as Predicate from "effect/Predicate"
7
7
  import * as Schema from "effect/Schema"
8
8
  import type * as Types from "effect/Types"
9
9
 
10
- import { TypeId as TypeId_ } from "@effect/platform/Error"
11
-
12
- export const TypeId: typeof TypeId_ = TypeId_
10
+ export const TypeId: unique symbol = Symbol.for(
11
+ "@effect/platform/Error/PlatformError",
12
+ )
13
13
 
14
14
  export type TypeId = typeof TypeId
15
15
 
package/src/Socket.ts ADDED
@@ -0,0 +1,48 @@
1
+ /*
2
+ * Adapted from @effect/platform
3
+ */
4
+ import * as Predicate from "effect/Predicate"
5
+ import * as PlatformError from "./PlatformError.ts"
6
+
7
+ export const SocketErrorTypeId: unique symbol = Symbol.for("@effect/platform/Socket/SocketError")
8
+
9
+ export type SocketErrorTypeId = typeof SocketErrorTypeId
10
+
11
+ export const isSocketError = (u: unknown): u is SocketError =>
12
+ Predicate.hasProperty(u, SocketErrorTypeId)
13
+
14
+ export type SocketError = SocketGenericError | SocketCloseError
15
+
16
+ export class SocketGenericError extends PlatformError.TypeIdError(SocketErrorTypeId, "SocketError")<{
17
+ readonly reason: "Write" | "Read" | "Open" | "OpenTimeout"
18
+ readonly cause: unknown
19
+ }> {
20
+ get message() {
21
+ return `An error occurred during ${this.reason}`
22
+ }
23
+ }
24
+
25
+ export class SocketCloseError extends PlatformError.TypeIdError(SocketErrorTypeId, "SocketError")<{
26
+ readonly reason: "Close"
27
+ readonly code: number
28
+ readonly closeReason?: string | undefined
29
+ }> {
30
+ static is(u: unknown): u is SocketCloseError {
31
+ return isSocketError(u) && u.reason === "Close"
32
+ }
33
+
34
+ static isClean(isClean: (code: number) => boolean) {
35
+ return function(u: unknown): u is SocketCloseError {
36
+ return SocketCloseError.is(u) && isClean(u.code)
37
+ }
38
+ }
39
+
40
+ get message() {
41
+ if (this.closeReason) {
42
+ return `${this.reason}: ${this.code}: ${this.closeReason}`
43
+ }
44
+ return `${this.reason}: ${this.code}`
45
+ }
46
+ }
47
+
48
+ export const defaultCloseCodeIsError = (code: number) => code !== 1000 && code !== 1006
package/src/Start.ts CHANGED
@@ -1,4 +1,4 @@
1
- import * as FileSystem from "@effect/platform/FileSystem"
1
+ import * as FileSystem from "./FileSystem.ts"
2
2
  import * as Context from "effect/Context"
3
3
  import * as Effect from "effect/Effect"
4
4
  import * as Function from "effect/Function"
@@ -84,7 +84,6 @@ export function serve<
84
84
  BunServer.withLogAddress,
85
85
  Layer.provide(appLayer),
86
86
  Layer.provide(NodeFileSystem.layer),
87
- Layer.provide(BunServer.layer()),
88
87
  ) as Layer.Layer<BunServer.BunServer, never, never>
89
88
 
90
89
  return Function.pipe(
@@ -1,4 +1,4 @@
1
- import * as Socket from "@effect/platform/Socket"
1
+ import * as Socket from "../Socket.ts"
2
2
  import * as Bun from "bun"
3
3
  import * as Config from "effect/Config"
4
4
  import * as Context from "effect/Context"
@@ -9,6 +9,7 @@ import * as Layer from "effect/Layer"
9
9
  import * as Option from "effect/Option"
10
10
  import * as Runtime from "effect/Runtime"
11
11
  import type * as Scope from "effect/Scope"
12
+ import * as NOs from "node:os"
12
13
  import * as PathPattern from "../PathPattern.ts"
13
14
  import * as PlataformRuntime from "../PlatformRuntime.ts"
14
15
  import * as Route from "../Route.ts"
@@ -16,11 +17,16 @@ import * as RouteHttp from "../RouteHttp.ts"
16
17
  import * as RouteMount from "../RouteMount.ts"
17
18
  import * as RouteTree from "../RouteTree.ts"
18
19
  import * as BunRoute from "./BunRoute.ts"
19
- import * as BunServerRequest from "./BunServerRequest.ts"
20
+ export interface WebSocketContext {
21
+ readonly deferred: Deferred.Deferred<Bun.ServerWebSocket<WebSocketContext>>
22
+ readonly closeDeferred: Deferred.Deferred<void, Socket.SocketError>
23
+ readonly buffer: Array<Uint8Array | string>
24
+ run: (_: Uint8Array | string) => void
25
+ }
20
26
 
21
27
  type FetchHandler = (
22
28
  request: Request,
23
- server: Bun.Server<BunServerRequest.WebSocketContext>,
29
+ server: Bun.Server<WebSocketContext>,
24
30
  ) => Response | Promise<Response>
25
31
 
26
32
  /**
@@ -38,7 +44,7 @@ interface BunServeOptions {
38
44
  }
39
45
 
40
46
  export type BunServer = {
41
- readonly server: Bun.Server<BunServerRequest.WebSocketContext>
47
+ readonly server: Bun.Server<WebSocketContext>
42
48
  readonly pushHandler: (fetch: FetchHandler) => void
43
49
  readonly popHandler: () => void
44
50
  }
@@ -66,8 +72,10 @@ export const make = (
66
72
  : Effect.succeed(3000)
67
73
  }),
68
74
  )
75
+ const hostFlag = process.argv.includes("--host")
69
76
  const hostname = yield* Config.string("HOSTNAME").pipe(
70
- Effect.catchTag("ConfigError", () => Effect.succeed(undefined)),
77
+ Effect.catchTag("ConfigError", () =>
78
+ Effect.succeed(hostFlag ? "0.0.0.0" : undefined)),
71
79
  )
72
80
 
73
81
  const handlerStack: Array<FetchHandler> = [
@@ -76,35 +84,43 @@ export const make = (
76
84
  },
77
85
  ]
78
86
 
79
- const service = BunServer.of({
80
- // During the construction we need to create a service imlpementation
81
- // first so we can provide it in the runtime that will be used in web
82
- // handlers. After we create the runtime, we set it below so it's always
83
- // available at runtime.
84
- // An alternative approach would be to use Bun.Server.reload but I prefer
85
- // to avoid it since it's badly documented and has bunch of bugs.
86
- server: undefined as any,
87
- pushHandler(fetch) {
88
- handlerStack.push(fetch)
89
- reload()
90
- },
91
- popHandler() {
92
- handlerStack.pop()
93
- reload()
94
- },
95
- })
87
+ const service = BunServer
88
+ .of({
89
+ // During the construction we need to create a service imlpementation
90
+ // first so we can provide it in the runtime that will be used in web
91
+ // handlers. After we create the runtime, we set it below so it's always
92
+ // available at runtime.
93
+ // An alternative approach would be to use Bun.Server.reload but I prefer
94
+ // to avoid it since it's badly documented and has bunch of bugs.
95
+ server: undefined as any,
96
+ pushHandler(fetch) {
97
+ handlerStack
98
+ .push(fetch)
99
+ reload()
100
+ },
101
+ popHandler() {
102
+ handlerStack
103
+ .pop()
104
+ reload()
105
+ },
106
+ })
96
107
 
97
- const runtime = yield* Effect.runtime().pipe(
98
- Effect.andThen(Runtime.provideService(BunServer, service)),
99
- )
108
+ const runtime = yield* Effect
109
+ .runtime()
110
+ .pipe(
111
+ Effect
112
+ .andThen(Runtime
113
+ .provideService(BunServer, service)),
114
+ )
100
115
 
101
116
  let currentRoutes: BunRoute.BunRoutes = routes
102
117
  ? yield* walkBunRoutes(runtime, routes)
103
118
  : {}
104
119
 
105
- const websocket: Bun.WebSocketHandler<BunServerRequest.WebSocketContext> = {
120
+ const websocket: Bun.WebSocketHandler<WebSocketContext> = {
106
121
  open(ws) {
107
- Deferred.unsafeDone(ws.data.deferred, Exit.succeed(ws))
122
+ Deferred
123
+ .unsafeDone(ws.data.deferred, Exit.succeed(ws))
108
124
  },
109
125
  message(ws, message) {
110
126
  ws.data.run(message)
@@ -183,11 +199,14 @@ export const withLogAddress = <A, E, R>(
183
199
  Layer
184
200
  .effectDiscard(
185
201
  BunServer.pipe(
186
- Effect.andThen(server =>
187
- Effect.log(
188
- `Listening on ${server.server.hostname}:${server.server.port}`,
189
- )
190
- ),
202
+ Effect.andThen(server => {
203
+ const { hostname, port } = server.server
204
+ const addr = hostname === "0.0.0.0"
205
+ ? getLocalIp()
206
+ : "localhost"
207
+
208
+ return Effect.log(`Listening on http://${addr}:${port}`)
209
+ }),
191
210
  ),
192
211
  )
193
212
  .pipe(
@@ -226,3 +245,10 @@ function walkBunRoutes(
226
245
  return bunRoutes
227
246
  })
228
247
  }
248
+
249
+ function getLocalIp(): string | undefined {
250
+ return Object.values(NOs.networkInterfaces())
251
+ .flatMap(addresses => addresses ?? [])
252
+ .find(addr => addr.family === "IPv4" && !addr.internal)
253
+ ?.address
254
+ }
@@ -1,4 +1,4 @@
1
- import * as FileSystem from "@effect/platform/FileSystem"
1
+ import * as FileSystem from "../FileSystem.ts"
2
2
  import * as Array from "effect/Array"
3
3
  import * as Effect from "effect/Effect"
4
4
  import * as Function from "effect/Function"
@@ -1,15 +1,8 @@
1
- import {
2
- Cookies,
3
- HttpApp,
4
- HttpServerResponse,
5
- } from "@effect/platform"
6
- import {
7
- Effect,
8
- pipe,
9
- } from "effect"
1
+ import * as Cookies from "../Cookies.ts"
10
2
  import * as Config from "effect/Config"
11
3
  import * as Context from "effect/Context"
12
4
  import * as Data from "effect/Data"
5
+ import * as Effect from "effect/Effect"
13
6
  import * as Layer from "effect/Layer"
14
7
 
15
8
  type CookieValue =
@@ -79,8 +72,7 @@ export function layer(options: { secret: string }) {
79
72
  export function layerConfig(name = "SECRET_KEY_BASE") {
80
73
  return Effect
81
74
  .gen(function*() {
82
- const secret = yield* pipe(
83
- Config.nonEmptyString(name),
75
+ const secret = yield* Config.nonEmptyString(name).pipe(
84
76
  Effect.flatMap((value) => {
85
77
  return (value.length < 40)
86
78
  ? Effect.fail(new Error("ba"))
@@ -426,26 +418,3 @@ function deriveKey(
426
418
  return key
427
419
  })
428
420
  }
429
-
430
- // TODO something si wrong with return type
431
- export function handleError<E>(
432
- app: HttpApp.Default<E | EncryptedCookiesError>,
433
- ) {
434
- return Effect.gen(function*() {
435
- const res = yield* app.pipe(
436
- Effect.catchTag("EncryptedCookiesError", (error) => {
437
- return HttpServerResponse.empty()
438
- }),
439
- )
440
-
441
- return res
442
- })
443
- }
444
-
445
- function generateFriendlyKey(bits = 128) {
446
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
447
- const length = Math.ceil(bits / Math.log2(chars.length))
448
- const bytes = crypto.getRandomValues(new Uint8Array(length))
449
-
450
- return Array.from(bytes, b => chars[b % chars.length]).join("")
451
- }
@@ -1,2 +1 @@
1
1
  export * as EncryptedCookies from "./EncryptedCookies.ts"
2
- export * as SseHttpResponse from "./SseHttpResponse.ts"