@effect/platform 0.13.2 → 0.13.4

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 (81) hide show
  1. package/Http/Body.d.ts +8 -1
  2. package/Http/Body.d.ts.map +1 -1
  3. package/Http/Body.js +7 -1
  4. package/Http/Body.js.map +1 -1
  5. package/Http/Client.d.ts +11 -0
  6. package/Http/Client.d.ts.map +1 -1
  7. package/Http/Client.js +7 -1
  8. package/Http/Client.js.map +1 -1
  9. package/Http/Etag.d.ts +56 -0
  10. package/Http/Etag.d.ts.map +1 -0
  11. package/Http/Etag.js +28 -0
  12. package/Http/Etag.js.map +1 -0
  13. package/Http/Middleware.d.ts +0 -8
  14. package/Http/Middleware.d.ts.map +1 -1
  15. package/Http/Middleware.js +2 -8
  16. package/Http/Middleware.js.map +1 -1
  17. package/Http/Router.d.ts +90 -0
  18. package/Http/Router.d.ts.map +1 -1
  19. package/Http/Router.js +43 -1
  20. package/Http/Router.js.map +1 -1
  21. package/Http/ServerResponse.d.ts +2 -1
  22. package/Http/ServerResponse.d.ts.map +1 -1
  23. package/Http/ServerResponse.js.map +1 -1
  24. package/internal/http/body.js +6 -2
  25. package/internal/http/body.js.map +1 -1
  26. package/internal/http/client.d.ts.map +1 -1
  27. package/internal/http/client.js +6 -2
  28. package/internal/http/client.js.map +1 -1
  29. package/internal/http/etag.d.ts +2 -0
  30. package/internal/http/etag.d.ts.map +1 -0
  31. package/internal/http/etag.js +26 -0
  32. package/internal/http/etag.js.map +1 -0
  33. package/internal/http/middleware.js +2 -5
  34. package/internal/http/middleware.js.map +1 -1
  35. package/internal/http/router.d.ts +3 -1
  36. package/internal/http/router.d.ts.map +1 -1
  37. package/internal/http/router.js +22 -2
  38. package/internal/http/router.js.map +1 -1
  39. package/internal/http/server.js +1 -1
  40. package/internal/http/server.js.map +1 -1
  41. package/internal/http/serverResponse.js +20 -1
  42. package/internal/http/serverResponse.js.map +1 -1
  43. package/mjs/Http/Body.mjs +5 -0
  44. package/mjs/Http/Body.mjs.map +1 -1
  45. package/mjs/Http/Client.mjs +5 -0
  46. package/mjs/Http/Client.mjs.map +1 -1
  47. package/mjs/Http/Etag.mjs +17 -0
  48. package/mjs/Http/Etag.mjs.map +1 -0
  49. package/mjs/Http/Middleware.mjs +0 -5
  50. package/mjs/Http/Middleware.mjs.map +1 -1
  51. package/mjs/Http/Router.mjs +35 -0
  52. package/mjs/Http/Router.mjs.map +1 -1
  53. package/mjs/Http/ServerResponse.mjs.map +1 -1
  54. package/mjs/internal/http/body.mjs +4 -1
  55. package/mjs/internal/http/body.mjs.map +1 -1
  56. package/mjs/internal/http/client.mjs +4 -1
  57. package/mjs/internal/http/client.mjs.map +1 -1
  58. package/mjs/internal/http/etag.mjs +15 -0
  59. package/mjs/internal/http/etag.mjs.map +1 -0
  60. package/mjs/internal/http/middleware.mjs +2 -4
  61. package/mjs/internal/http/middleware.mjs.map +1 -1
  62. package/mjs/internal/http/router.mjs +14 -1
  63. package/mjs/internal/http/router.mjs.map +1 -1
  64. package/mjs/internal/http/server.mjs +1 -1
  65. package/mjs/internal/http/server.mjs.map +1 -1
  66. package/mjs/internal/http/serverResponse.mjs +20 -1
  67. package/mjs/internal/http/serverResponse.mjs.map +1 -1
  68. package/package.json +2 -1
  69. package/src/Http/Body.ts +16 -1
  70. package/src/Http/Client.ts +13 -0
  71. package/src/Http/Etag.ts +64 -0
  72. package/src/Http/Middleware.ts +0 -14
  73. package/src/Http/Router.ts +148 -0
  74. package/src/Http/ServerResponse.ts +2 -1
  75. package/src/internal/http/body.ts +22 -3
  76. package/src/internal/http/client.ts +5 -1
  77. package/src/internal/http/etag.ts +20 -0
  78. package/src/internal/http/middleware.ts +2 -16
  79. package/src/internal/http/router.ts +129 -1
  80. package/src/internal/http/server.ts +1 -1
  81. package/src/internal/http/serverResponse.ts +32 -10
@@ -4,6 +4,7 @@
4
4
  import type * as Chunk from "@effect/data/Chunk"
5
5
  import type * as Context from "@effect/data/Context"
6
6
  import type * as Option from "@effect/data/Option"
7
+ import type * as Cause from "@effect/io/Cause"
7
8
  import type * as Effect from "@effect/io/Effect"
8
9
  import type * as App from "@effect/platform/Http/App"
9
10
  import type * as Method from "@effect/platform/Http/Method"
@@ -340,3 +341,150 @@ export const options: {
340
341
  handler: Route.Handler<R1, E1>
341
342
  ): Router<R | Exclude<R1, RouteContext>, E | E1>
342
343
  } = internal.options
344
+
345
+ /**
346
+ * @since 1.0.0
347
+ * @category combinators
348
+ */
349
+ export const use = internal.use
350
+
351
+ /**
352
+ * @since 1.0.0
353
+ * @category combinators
354
+ */
355
+ export const catchAll: {
356
+ <E, R2, E2>(f: (e: E) => Route.Handler<R2, E2>): <R>(
357
+ self: Router<R, E>
358
+ ) => Router<R2 | R, E2>
359
+ <R, E, R2, E2>(
360
+ self: Router<R, E>,
361
+ f: (e: E) => Route.Handler<R2, E2>
362
+ ): Router<R | R2, E2>
363
+ } = internal.catchAll
364
+
365
+ /**
366
+ * @since 1.0.0
367
+ * @category combinators
368
+ */
369
+ export const catchAllCause: {
370
+ <E, R2, E2>(f: (e: Cause.Cause<E>) => Route.Handler<R2, E2>): <R>(
371
+ self: Router<R, E>
372
+ ) => Router<R2 | R, E2>
373
+ <R, E, R2, E2>(
374
+ self: Router<R, E>,
375
+ f: (e: Cause.Cause<E>) => Route.Handler<R2, E2>
376
+ ): Router<R | R2, E2>
377
+ } = internal.catchAllCause
378
+
379
+ /**
380
+ * @since 1.0.0
381
+ * @category combinators
382
+ */
383
+ export const catchTag: {
384
+ <K extends E extends { _tag: string } ? E["_tag"] : never, E, R1, E1>(
385
+ k: K,
386
+ f: (e: Extract<E, { _tag: K }>) => Route.Handler<R1, E1>
387
+ ): <R>(self: Router<R, E>) => Router<R1 | R, E1 | Exclude<E, { _tag: K }>>
388
+ <R, E, K extends E extends { _tag: string } ? E["_tag"] : never, R1, E1>(
389
+ self: Router<R, E>,
390
+ k: K,
391
+ f: (e: Extract<E, { _tag: K }>) => Route.Handler<R1, E1>
392
+ ): Router<R | R1, E1 | Exclude<E, { _tag: K }>>
393
+ } = internal.catchTag
394
+
395
+ /**
396
+ * @since 1.0.0
397
+ * @category combinators
398
+ */
399
+ export const catchTags: {
400
+ <
401
+ E,
402
+ Cases extends E extends { _tag: string } ? {
403
+ [K in E["_tag"]]+?:
404
+ | ((error: Extract<E, { _tag: K }>) => Route.Handler<any, any>)
405
+ | undefined
406
+ }
407
+ : {}
408
+ >(
409
+ cases: Cases
410
+ ): <R>(self: Router<R, E>) => Router<
411
+ | R
412
+ | {
413
+ [K in keyof Cases]: Cases[K] extends (
414
+ ...args: Array<any>
415
+ ) => Effect.Effect<infer R, any, any> ? R
416
+ : never
417
+ }[keyof Cases],
418
+ | Exclude<E, { _tag: keyof Cases }>
419
+ | {
420
+ [K in keyof Cases]: Cases[K] extends (
421
+ ...args: Array<any>
422
+ ) => Effect.Effect<any, infer E, any> ? E
423
+ : never
424
+ }[keyof Cases]
425
+ >
426
+ <
427
+ R,
428
+ E,
429
+ Cases extends E extends { _tag: string } ? {
430
+ [K in E["_tag"]]+?:
431
+ | ((error: Extract<E, { _tag: K }>) => Route.Handler<any, any>)
432
+ | undefined
433
+ }
434
+ : {}
435
+ >(
436
+ self: Router<R, E>,
437
+ cases: Cases
438
+ ): Router<
439
+ | R
440
+ | {
441
+ [K in keyof Cases]: Cases[K] extends (
442
+ ...args: Array<any>
443
+ ) => Effect.Effect<infer R, any, any> ? R
444
+ : never
445
+ }[keyof Cases],
446
+ | Exclude<E, { _tag: keyof Cases }>
447
+ | {
448
+ [K in keyof Cases]: Cases[K] extends (
449
+ ...args: Array<any>
450
+ ) => Effect.Effect<any, infer E, any> ? E
451
+ : never
452
+ }[keyof Cases]
453
+ >
454
+ } = internal.catchTags
455
+
456
+ /**
457
+ * @since 1.0.0
458
+ * @category combinators
459
+ */
460
+ export const provideService: {
461
+ <T extends Context.Tag<any, any>>(tag: T, service: Context.Tag.Service<T>): <
462
+ R,
463
+ E
464
+ >(
465
+ self: Router<R, E>
466
+ ) => Router<Exclude<R, Context.Tag.Identifier<T>>, E>
467
+ <R, E, T extends Context.Tag<any, any>>(
468
+ self: Router<R, E>,
469
+ tag: T,
470
+ service: Context.Tag.Service<T>
471
+ ): Router<Exclude<R, Context.Tag.Identifier<T>>, E>
472
+ } = internal.provideService
473
+
474
+ /**
475
+ * @since 1.0.0
476
+ * @category combinators
477
+ */
478
+ export const provideServiceEffect: {
479
+ <T extends Context.Tag<any, any>, R1, E1>(
480
+ tag: T,
481
+ effect: Effect.Effect<R1, E1, Context.Tag.Service<T>>
482
+ ): <R, E>(
483
+ self: Router<R, E>
484
+ ) => Router<R1 | Exclude<R, Context.Tag.Identifier<T>>, E1 | E>
485
+ <R, E, T extends Context.Tag<any, any>, R1, E1>(
486
+ self: Router<R, E>,
487
+ tag: T,
488
+ effect: Effect.Effect<R1, E1, Context.Tag.Service<T>>
489
+ ): Router<R1 | Exclude<R, Context.Tag.Identifier<T>>, E | E1>
490
+ } = internal.provideServiceEffect
@@ -6,6 +6,7 @@ import type * as Effect from "@effect/io/Effect"
6
6
  import type * as PlatformError from "@effect/platform/Error"
7
7
  import type * as FileSystem from "@effect/platform/FileSystem"
8
8
  import type * as Body from "@effect/platform/Http/Body"
9
+ import type * as Etag from "@effect/platform/Http/Etag"
9
10
  import type * as Headers from "@effect/platform/Http/Headers"
10
11
  import type * as Error from "@effect/platform/Http/ServerError"
11
12
  import type * as ServerRequest from "@effect/platform/Http/ServerRequest"
@@ -172,7 +173,7 @@ export const stream: (body: Stream.Stream<never, unknown, Uint8Array>, options?:
172
173
  export const file: (
173
174
  path: string,
174
175
  options?: Options & FileSystem.StreamOptions
175
- ) => Effect.Effect<FileSystem.FileSystem, PlatformError.PlatformError, ServerResponse> = internal.file
176
+ ) => Effect.Effect<FileSystem.FileSystem | Etag.Generator, PlatformError.PlatformError, ServerResponse> = internal.file
176
177
 
177
178
  /**
178
179
  * @since 1.0.0
@@ -4,6 +4,7 @@ import * as FileSystem from "@effect/platform/FileSystem"
4
4
  import type * as Body from "@effect/platform/Http/Body"
5
5
  import * as Schema from "@effect/schema/Schema"
6
6
  import type * as Stream_ from "@effect/stream/Stream"
7
+ import * as Mime from "mime/lite"
7
8
 
8
9
  /** @internal */
9
10
  export const TypeId: Body.TypeId = Symbol.for(
@@ -100,9 +101,27 @@ export const file = (
100
101
  Effect.flatMap(
101
102
  FileSystem.FileSystem,
102
103
  (fs) =>
103
- Effect.map(
104
- fs.stat(path),
105
- (stat) => stream(fs.stream(path, options), options?.contentType, Number(stat.size))
104
+ Effect.map(fs.stat(path), (info) =>
105
+ stream(
106
+ fs.stream(path, options),
107
+ options?.contentType ?? Mime.getType(path) ?? undefined,
108
+ Number(info.size)
109
+ ))
110
+ )
111
+
112
+ /** @internal */
113
+ export const fileInfo = (
114
+ path: string,
115
+ info: FileSystem.File.Info,
116
+ options?: FileSystem.StreamOptions & { readonly contentType?: string }
117
+ ): Effect.Effect<FileSystem.FileSystem, PlatformError.PlatformError, Body.Stream> =>
118
+ Effect.map(
119
+ FileSystem.FileSystem,
120
+ (fs) =>
121
+ stream(
122
+ fs.stream(path, options),
123
+ options?.contentType ?? Mime.getType(path) ?? undefined,
124
+ Number(info.size)
106
125
  )
107
126
  )
108
127
 
@@ -21,9 +21,13 @@ import * as Schema from "@effect/schema/Schema"
21
21
  import * as Stream from "@effect/stream/Stream"
22
22
 
23
23
  /** @internal */
24
- export const tag = Context.Tag<Client.Client.Default>("@effect/platform/Http/Client")
24
+ export const TypeId: Client.TypeId = Symbol.for("@effect/platform/Http/Client") as Client.TypeId
25
+
26
+ /** @internal */
27
+ export const tag = Context.Tag<Client.Client.Default>(TypeId)
25
28
 
26
29
  const clientProto = {
30
+ [TypeId]: TypeId,
27
31
  pipe() {
28
32
  return pipeArguments(this, arguments)
29
33
  }
@@ -0,0 +1,20 @@
1
+ import * as Context from "@effect/data/Context"
2
+ import type * as Etag from "@effect/platform/Http/Etag"
3
+
4
+ /** @internal */
5
+ export const GeneratorTypeId: Etag.GeneratorTypeId = Symbol.for(
6
+ "@effect/platform/Http/Etag/Generator"
7
+ ) as Etag.GeneratorTypeId
8
+
9
+ /** @internal */
10
+ export const tag = Context.Tag<Etag.Generator>(GeneratorTypeId)
11
+
12
+ /** @internal */
13
+ export const toString = (self: Etag.Etag): string => {
14
+ switch (self._tag) {
15
+ case "Weak":
16
+ return `W/"${self.value}"`
17
+ case "Strong":
18
+ return `"${self.value}"`
19
+ }
20
+ }
@@ -1,6 +1,5 @@
1
- import { dual } from "@effect/data/Function"
1
+ import { flow } from "@effect/data/Function"
2
2
  import * as Effect from "@effect/io/Effect"
3
- import type * as App from "@effect/platform/Http/App"
4
3
  import * as Headers from "@effect/platform/Http/Headers"
5
4
  import type * as Middleware from "@effect/platform/Http/Middleware"
6
5
  import * as ServerRequest from "@effect/platform/Http/ServerRequest"
@@ -62,17 +61,4 @@ export const xForwardedHeaders = make((httpApp) =>
62
61
  )
63
62
 
64
63
  /** @internal */
65
- export const compose = dual<
66
- <B extends App.Default<any, any>, C extends App.Default<any, any>>(
67
- that: (b: B) => C
68
- ) => <A extends App.Default<any, any>>(
69
- self: (a: A) => B
70
- ) => (a: A) => C,
71
- <A extends App.Default<any, any>, B extends App.Default<any, any>, C extends App.Default<any, any>>(
72
- self: (a: A) => B,
73
- that: (b: B) => C
74
- ) => (a: A) => C
75
- >(2, (self, that) => (inApp) => that(self(inApp)))
76
-
77
- /** @internal */
78
- export const loggerTracer = compose(tracer, logger)
64
+ export const loggerTracer = flow(tracer, logger)
@@ -5,6 +5,7 @@ import { dual } from "@effect/data/Function"
5
5
  import * as Hash from "@effect/data/Hash"
6
6
  import * as Option from "@effect/data/Option"
7
7
  import { pipeArguments } from "@effect/data/Pipeable"
8
+ import type * as Cause from "@effect/io/Cause"
8
9
  import * as Effect from "@effect/io/Effect"
9
10
  import type * as App from "@effect/platform/Http/App"
10
11
  import type * as Method from "@effect/platform/Http/Method"
@@ -98,7 +99,7 @@ const toHttpApp = <R, E>(
98
99
  const mounts = Chunk.toReadonlyArray(self.mounts)
99
100
  const mountsLen = mounts.length
100
101
  Chunk.forEach(self.routes, (route) => {
101
- function fn(_: any, __: any) {
102
+ function fn() {
102
103
  return route
103
104
  }
104
105
  if (route.method === "*") {
@@ -302,3 +303,130 @@ export const head = route("HEAD")
302
303
 
303
304
  /** @internal */
304
305
  export const options = route("OPTIONS")
306
+
307
+ /** @internal */
308
+ export const use = dual<
309
+ <R, E, R1, E1>(
310
+ f: (self: Router.Route.Handler<R, E>) => Router.Route.Handler<R1, E1>
311
+ ) => (self: Router.Router<R, E>) => Router.Router<R1, E1>,
312
+ <R, E, R1, E1>(
313
+ self: Router.Router<R, E>,
314
+ f: (self: Router.Route.Handler<R, E>) => Router.Route.Handler<R1, E1>
315
+ ) => Router.Router<R1, E1>
316
+ >(2, (self, f) =>
317
+ new RouterImpl(
318
+ Chunk.map(
319
+ self.routes,
320
+ (route) => new RouteImpl(route.method, route.path, f(route.handler), route.prefix)
321
+ ),
322
+ Chunk.map(
323
+ self.mounts,
324
+ ([path, app]) => [path, f(app as any)]
325
+ )
326
+ ))
327
+
328
+ /** @internal */
329
+ export const catchAll = dual<
330
+ <E, R2, E2>(
331
+ f: (e: E) => Router.Route.Handler<R2, E2>
332
+ ) => <R>(self: Router.Router<R, E>) => Router.Router<R2 | R, E2>,
333
+ <R, E, R2, E2>(
334
+ self: Router.Router<R, E>,
335
+ f: (e: E) => Router.Route.Handler<R2, E2>
336
+ ) => Router.Router<R2 | R, E2>
337
+ >(2, (self, f) => use(self, Effect.catchAll(f)))
338
+
339
+ /** @internal */
340
+ export const catchAllCause = dual<
341
+ <E, R2, E2>(
342
+ f: (e: Cause.Cause<E>) => Router.Route.Handler<R2, E2>
343
+ ) => <R>(self: Router.Router<R, E>) => Router.Router<R2 | R, E2>,
344
+ <R, E, R2, E2>(
345
+ self: Router.Router<R, E>,
346
+ f: (e: Cause.Cause<E>) => Router.Route.Handler<R2, E2>
347
+ ) => Router.Router<R2 | R, E2>
348
+ >(2, (self, f) => use(self, Effect.catchAllCause(f)))
349
+
350
+ /** @internal */
351
+ export const catchTag = dual<
352
+ <K extends (E extends { _tag: string } ? E["_tag"] : never), E, R1, E1>(
353
+ k: K,
354
+ f: (e: Extract<E, { _tag: K }>) => Router.Route.Handler<R1, E1>
355
+ ) => <R>(self: Router.Router<R, E>) => Router.Router<R | R1, Exclude<E, { _tag: K }> | E1>,
356
+ <R, E, K extends (E extends { _tag: string } ? E["_tag"] : never), R1, E1>(
357
+ self: Router.Router<R, E>,
358
+ k: K,
359
+ f: (e: Extract<E, { _tag: K }>) => Router.Route.Handler<R1, E1>
360
+ ) => Router.Router<R | R1, Exclude<E, { _tag: K }> | E1>
361
+ >(3, (self, k, f) => use(self, Effect.catchTag(k, f)))
362
+
363
+ /** @internal */
364
+ export const catchTags: {
365
+ <
366
+ E,
367
+ Cases extends (E extends { _tag: string } ? {
368
+ [K in E["_tag"]]+?: (error: Extract<E, { _tag: K }>) => Router.Route.Handler<any, any>
369
+ } :
370
+ {})
371
+ >(
372
+ cases: Cases
373
+ ): <R>(self: Router.Router<R, E>) => Router.Router<
374
+ | R
375
+ | {
376
+ [K in keyof Cases]: Cases[K] extends ((...args: Array<any>) => Effect.Effect<infer R, any, any>) ? R : never
377
+ }[keyof Cases],
378
+ | Exclude<E, { _tag: keyof Cases }>
379
+ | {
380
+ [K in keyof Cases]: Cases[K] extends ((...args: Array<any>) => Effect.Effect<any, infer E, any>) ? E : never
381
+ }[keyof Cases]
382
+ >
383
+ <
384
+ R,
385
+ E,
386
+ Cases extends (E extends { _tag: string } ? {
387
+ [K in E["_tag"]]+?: (error: Extract<E, { _tag: K }>) => Router.Route.Handler<any, any>
388
+ } :
389
+ {})
390
+ >(
391
+ self: Router.Router<R, E>,
392
+ cases: Cases
393
+ ): Router.Router<
394
+ | R
395
+ | {
396
+ [K in keyof Cases]: Cases[K] extends ((...args: Array<any>) => Effect.Effect<infer R, any, any>) ? R : never
397
+ }[keyof Cases],
398
+ | Exclude<E, { _tag: keyof Cases }>
399
+ | {
400
+ [K in keyof Cases]: Cases[K] extends ((...args: Array<any>) => Effect.Effect<any, infer E, any>) ? E : never
401
+ }[keyof Cases]
402
+ >
403
+ } = dual(2, (self: Router.Router<any, any>, cases: {}) => use(self, Effect.catchTags(cases)))
404
+
405
+ export const provideService = dual<
406
+ <T extends Context.Tag<any, any>>(
407
+ tag: T,
408
+ service: Context.Tag.Service<T>
409
+ ) => <R, E>(self: Router.Router<R, E>) => Router.Router<Exclude<R, Context.Tag.Identifier<T>>, E>,
410
+ <R, E, T extends Context.Tag<any, any>>(
411
+ self: Router.Router<R, E>,
412
+ tag: T,
413
+ service: Context.Tag.Service<T>
414
+ ) => Router.Router<Exclude<R, Context.Tag.Identifier<T>>, E>
415
+ >(3, (self, tag, service) => use(self, Effect.provideService(tag, service)))
416
+
417
+ /* @internal */
418
+ export const provideServiceEffect = dual<
419
+ <T extends Context.Tag<any, any>, R1, E1>(
420
+ tag: T,
421
+ effect: Effect.Effect<R1, E1, Context.Tag.Service<T>>
422
+ ) => <R, E>(self: Router.Router<R, E>) => Router.Router<R1 | Exclude<R, Context.Tag.Identifier<T>>, E | E1>,
423
+ <R, E, T extends Context.Tag<any, any>, R1, E1>(
424
+ self: Router.Router<R, E>,
425
+ tag: T,
426
+ effect: Effect.Effect<R1, E1, Context.Tag.Service<T>>
427
+ ) => Router.Router<R1 | Exclude<R, Context.Tag.Identifier<T>>, E | E1>
428
+ >(3, <R, E, T extends Context.Tag<any, any>, R1, E1>(
429
+ self: Router.Router<R, E>,
430
+ tag: T,
431
+ effect: Effect.Effect<R1, E1, Context.Tag.Service<T>>
432
+ ) => use(self, Effect.provideServiceEffect(tag, effect)))
@@ -12,7 +12,7 @@ import type * as ServerRequest from "@effect/platform/Http/ServerRequest"
12
12
  export const TypeId: Server.TypeId = Symbol.for("@effect/platform/Http/Server") as Server.TypeId
13
13
 
14
14
  /** @internal */
15
- export const serverTag = Context.Tag<Server.Server>("@effect/platform/Http/Server")
15
+ export const serverTag = Context.Tag<Server.Server>(TypeId)
16
16
 
17
17
  const serverProto = {
18
18
  [TypeId]: TypeId
@@ -1,9 +1,10 @@
1
- import { dual } from "@effect/data/Function"
1
+ import { dual, pipe } from "@effect/data/Function"
2
2
  import { pipeArguments } from "@effect/data/Pipeable"
3
3
  import * as Effect from "@effect/io/Effect"
4
4
  import type * as PlatformError from "@effect/platform/Error"
5
- import type * as FileSystem from "@effect/platform/FileSystem"
5
+ import * as FileSystem from "@effect/platform/FileSystem"
6
6
  import type * as Body from "@effect/platform/Http/Body"
7
+ import * as Etag from "@effect/platform/Http/Etag"
7
8
  import * as Headers from "@effect/platform/Http/Headers"
8
9
  import type * as Error from "@effect/platform/Http/ServerError"
9
10
  import * as ServerRequest from "@effect/platform/Http/ServerRequest"
@@ -141,14 +142,35 @@ export const schemaJson = <I, A>(
141
142
  export const file = (
142
143
  path: string,
143
144
  options?: ServerResponse.Options & FileSystem.StreamOptions
144
- ): Effect.Effect<FileSystem.FileSystem, PlatformError.PlatformError, ServerResponse.ServerResponse> =>
145
- Effect.map(internalBody.file(path, options), (body) =>
146
- new ServerResponseImpl(
147
- options?.status ?? 200,
148
- options?.statusText,
149
- options?.headers ?? Headers.empty,
150
- body
151
- ))
145
+ ): Effect.Effect<
146
+ FileSystem.FileSystem | Etag.Generator,
147
+ PlatformError.PlatformError,
148
+ ServerResponse.ServerResponse
149
+ > =>
150
+ pipe(
151
+ Effect.bindTo(Effect.flatMap(FileSystem.FileSystem, (fs) => fs.stat(path)), "info"),
152
+ Effect.bind("etag", ({ info }) =>
153
+ Effect.flatMap(
154
+ Etag.Generator,
155
+ (generator) => generator.fromFileInfo(info)
156
+ )),
157
+ Effect.bind("body", ({ info }) => internalBody.fileInfo(path, info, options)),
158
+ Effect.map(({ body, etag, info }) => {
159
+ const headers: Record<string, string> = {
160
+ ...(options?.headers ?? {}),
161
+ etag: Etag.toString(etag)
162
+ }
163
+ if (info.mtime._tag === "Some") {
164
+ headers["last-modified"] = info.mtime.value.toUTCString()
165
+ }
166
+ return new ServerResponseImpl(
167
+ options?.status ?? 200,
168
+ options?.statusText,
169
+ headers,
170
+ body
171
+ )
172
+ })
173
+ )
152
174
 
153
175
  /** @internal */
154
176
  export const urlParams = (