@effect/platform 0.47.1 → 0.48.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 (48) hide show
  1. package/dist/cjs/Http/IncomingMessage.js +1 -1
  2. package/dist/cjs/Http/IncomingMessage.js.map +1 -1
  3. package/dist/cjs/Http/Multipart.js +21 -6
  4. package/dist/cjs/Http/Multipart.js.map +1 -1
  5. package/dist/cjs/Socket.js +104 -22
  6. package/dist/cjs/Socket.js.map +1 -1
  7. package/dist/cjs/Transferable.js +1 -1
  8. package/dist/cjs/Transferable.js.map +1 -1
  9. package/dist/cjs/WorkerError.js.map +1 -1
  10. package/dist/cjs/internal/http/multipart.js +15 -11
  11. package/dist/cjs/internal/http/multipart.js.map +1 -1
  12. package/dist/dts/Http/Multipart.d.ts +21 -6
  13. package/dist/dts/Http/Multipart.d.ts.map +1 -1
  14. package/dist/dts/Http/ServerRequest.d.ts +3 -3
  15. package/dist/dts/Http/ServerRequest.d.ts.map +1 -1
  16. package/dist/dts/Runtime.d.ts +4 -1
  17. package/dist/dts/Runtime.d.ts.map +1 -1
  18. package/dist/dts/Socket.d.ts +89 -13
  19. package/dist/dts/Socket.d.ts.map +1 -1
  20. package/dist/dts/Worker.d.ts +6 -6
  21. package/dist/dts/Worker.d.ts.map +1 -1
  22. package/dist/dts/WorkerError.d.ts +10 -5
  23. package/dist/dts/WorkerError.d.ts.map +1 -1
  24. package/dist/dts/WorkerRunner.d.ts +1 -1
  25. package/dist/dts/WorkerRunner.d.ts.map +1 -1
  26. package/dist/esm/Http/IncomingMessage.js +1 -1
  27. package/dist/esm/Http/IncomingMessage.js.map +1 -1
  28. package/dist/esm/Http/Multipart.js +20 -5
  29. package/dist/esm/Http/Multipart.js.map +1 -1
  30. package/dist/esm/Socket.js +98 -20
  31. package/dist/esm/Socket.js.map +1 -1
  32. package/dist/esm/Transferable.js +1 -1
  33. package/dist/esm/Transferable.js.map +1 -1
  34. package/dist/esm/WorkerError.js.map +1 -1
  35. package/dist/esm/internal/http/multipart.js +10 -9
  36. package/dist/esm/internal/http/multipart.js.map +1 -1
  37. package/package.json +3 -3
  38. package/src/Http/IncomingMessage.ts +1 -1
  39. package/src/Http/Multipart.ts +25 -7
  40. package/src/Http/ServerRequest.ts +5 -5
  41. package/src/Runtime.ts +4 -1
  42. package/src/Socket.ts +179 -39
  43. package/src/Transferable.ts +1 -1
  44. package/src/Worker.ts +6 -6
  45. package/src/WorkerError.ts +9 -7
  46. package/src/WorkerRunner.ts +8 -8
  47. package/src/internal/http/multipart.ts +16 -12
  48. package/src/internal/http/serverRequest.ts +2 -2
package/src/Socket.ts CHANGED
@@ -5,27 +5,30 @@ import * as Cause from "effect/Cause"
5
5
  import * as Channel from "effect/Channel"
6
6
  import * as Chunk from "effect/Chunk"
7
7
  import * as Context from "effect/Context"
8
- import * as Data from "effect/Data"
8
+ import type { DurationInput } from "effect/Duration"
9
9
  import * as Effect from "effect/Effect"
10
10
  import * as Exit from "effect/Exit"
11
+ import * as Fiber from "effect/Fiber"
11
12
  import * as FiberSet from "effect/FiberSet"
12
13
  import * as Layer from "effect/Layer"
14
+ import * as Predicate from "effect/Predicate"
13
15
  import * as Queue from "effect/Queue"
14
16
  import * as Scope from "effect/Scope"
15
17
  import type * as AsyncProducer from "effect/SingleProducerAsyncInput"
16
18
  import IsoWebSocket from "isomorphic-ws"
19
+ import { RefailError, TypeIdError } from "./Error.js"
17
20
 
18
21
  /**
19
22
  * @since 1.0.0
20
23
  * @category type ids
21
24
  */
22
- export const SocketTypeId = Symbol.for("@effect/platform/Socket")
25
+ export const TypeId = Symbol.for("@effect/platform/Socket")
23
26
 
24
27
  /**
25
28
  * @since 1.0.0
26
29
  * @category type ids
27
30
  */
28
- export type SocketTypeId = typeof SocketTypeId
31
+ export type TypeId = typeof TypeId
29
32
 
30
33
  /**
31
34
  * @since 1.0.0
@@ -40,26 +43,117 @@ export const Socket: Context.Tag<Socket, Socket> = Context.GenericTag<Socket>(
40
43
  * @category models
41
44
  */
42
45
  export interface Socket {
43
- readonly [SocketTypeId]: SocketTypeId
46
+ readonly [TypeId]: TypeId
44
47
  readonly run: <R, E, _>(
45
48
  handler: (_: Uint8Array) => Effect.Effect<_, E, R>
46
49
  ) => Effect.Effect<void, SocketError | E, R>
47
- readonly writer: Effect.Effect<(chunk: Uint8Array) => Effect.Effect<void>, never, Scope.Scope>
50
+ readonly writer: Effect.Effect<(chunk: Uint8Array | CloseEvent) => Effect.Effect<void>, never, Scope.Scope>
48
51
  }
49
52
 
53
+ /**
54
+ * @since 1.0.0
55
+ * @category type ids
56
+ */
57
+ export const CloseEventTypeId = Symbol.for("@effect/platform/Socket/CloseEvent")
58
+
59
+ /**
60
+ * @since 1.0.0
61
+ * @category type ids
62
+ */
63
+ export type CloseEventTypeId = typeof CloseEventTypeId
64
+
65
+ /**
66
+ * @since 1.0.0
67
+ * @category models
68
+ */
69
+ export class CloseEvent {
70
+ /**
71
+ * @since 1.0.0
72
+ */
73
+ readonly [CloseEventTypeId]: CloseEventTypeId
74
+ constructor(readonly code = 1000, readonly reason?: string) {
75
+ this[CloseEventTypeId] = CloseEventTypeId
76
+ }
77
+ /**
78
+ * @since 1.0.0
79
+ */
80
+ toString() {
81
+ return this.reason ? `${this.code}: ${this.reason}` : `${this.code}`
82
+ }
83
+ }
84
+
85
+ /**
86
+ * @since 1.0.0
87
+ * @category refinements
88
+ */
89
+ export const isCloseEvent = (u: unknown): u is CloseEvent => Predicate.hasProperty(u, CloseEventTypeId)
90
+
91
+ /**
92
+ * @since 1.0.0
93
+ * @category type ids
94
+ */
95
+ export const SocketErrorTypeId = Symbol.for("@effect/platform/Socket/SocketError")
96
+
97
+ /**
98
+ * @since 1.0.0
99
+ * @category type ids
100
+ */
101
+ export type SocketErrorTypeId = typeof SocketErrorTypeId
102
+
103
+ /**
104
+ * @since 1.0.0
105
+ * @category refinements
106
+ */
107
+ export const isSocketError = (u: unknown): u is SocketError => Predicate.hasProperty(u, SocketErrorTypeId)
108
+
50
109
  /**
51
110
  * @since 1.0.0
52
111
  * @category errors
53
112
  */
54
- export class SocketError extends Data.TaggedError("SocketError")<{
55
- readonly reason: "Write" | "Read" | "Open" | "Close"
56
- readonly error: unknown
113
+ export type SocketError = SocketGenericError | SocketCloseError
114
+
115
+ /**
116
+ * @since 1.0.0
117
+ * @category errors
118
+ */
119
+ export class SocketGenericError extends RefailError(SocketErrorTypeId, "SocketError")<{
120
+ readonly reason: "Write" | "Read" | "Open" | "OpenTimeout"
121
+ }> {
122
+ get message() {
123
+ return `${this.reason}: ${super.message}`
124
+ }
125
+ }
126
+
127
+ /**
128
+ * @since 1.0.0
129
+ * @category errors
130
+ */
131
+ export class SocketCloseError extends TypeIdError(SocketErrorTypeId, "SocketError")<{
132
+ readonly reason: "Close"
133
+ readonly code: number
134
+ readonly closeReason?: string
57
135
  }> {
58
136
  /**
59
137
  * @since 1.0.0
60
138
  */
61
- toString(): string {
62
- return `SocketError: ${this.reason} - ${this.error}`
139
+ static is(u: unknown): u is SocketCloseError {
140
+ return isSocketError(u) && u.reason === "Close"
141
+ }
142
+
143
+ /**
144
+ * @since 1.0.0
145
+ */
146
+ static isClean(isClean: (code: number) => boolean) {
147
+ return function(u: unknown): u is SocketCloseError {
148
+ return SocketCloseError.is(u) && isClean(u.code)
149
+ }
150
+ }
151
+
152
+ get message() {
153
+ if (this.closeReason) {
154
+ return `${this.reason}: ${this.code}: ${this.closeReason}`
155
+ }
156
+ return `${this.reason}: ${this.code}`
63
157
  }
64
158
  }
65
159
 
@@ -69,14 +163,21 @@ export class SocketError extends Data.TaggedError("SocketError")<{
69
163
  */
70
164
  export const toChannel = <IE>(
71
165
  self: Socket
72
- ): Channel.Channel<Chunk.Chunk<Uint8Array>, Chunk.Chunk<Uint8Array>, SocketError | IE, IE, void, unknown> =>
166
+ ): Channel.Channel<
167
+ Chunk.Chunk<Uint8Array>,
168
+ Chunk.Chunk<Uint8Array | CloseEvent>,
169
+ SocketError | IE,
170
+ IE,
171
+ void,
172
+ unknown
173
+ > =>
73
174
  Channel.unwrap(
74
175
  Effect.gen(function*(_) {
75
176
  const writeScope = yield* _(Scope.make())
76
177
  const write = yield* _(Scope.extend(self.writer, writeScope))
77
178
  const exitQueue = yield* _(Queue.unbounded<Exit.Exit<Chunk.Chunk<Uint8Array>, SocketError | IE>>())
78
179
 
79
- const input: AsyncProducer.AsyncInputProducer<IE, Chunk.Chunk<Uint8Array>, unknown> = {
180
+ const input: AsyncProducer.AsyncInputProducer<IE, Chunk.Chunk<Uint8Array | CloseEvent>, unknown> = {
80
181
  awaitRead: () => Effect.unit,
81
182
  emit(chunk) {
82
183
  return Effect.catchAllCause(
@@ -123,8 +224,14 @@ export const toChannel = <IE>(
123
224
  export const toChannelWith = <IE = never>() =>
124
225
  (
125
226
  self: Socket
126
- ): Channel.Channel<Chunk.Chunk<Uint8Array>, Chunk.Chunk<Uint8Array>, SocketError | IE, IE, void, unknown> =>
127
- toChannel(self)
227
+ ): Channel.Channel<
228
+ Chunk.Chunk<Uint8Array>,
229
+ Chunk.Chunk<Uint8Array | CloseEvent>,
230
+ SocketError | IE,
231
+ IE,
232
+ void,
233
+ unknown
234
+ > => toChannel(self)
128
235
 
129
236
  /**
130
237
  * @since 1.0.0
@@ -132,7 +239,7 @@ export const toChannelWith = <IE = never>() =>
132
239
  */
133
240
  export const makeChannel = <IE = never>(): Channel.Channel<
134
241
  Chunk.Chunk<Uint8Array>,
135
- Chunk.Chunk<Uint8Array>,
242
+ Chunk.Chunk<Uint8Array | CloseEvent>,
136
243
  SocketError | IE,
137
244
  IE,
138
245
  void,
@@ -167,6 +274,7 @@ export const WebSocket: Context.Tag<WebSocket, globalThis.WebSocket> = Context.G
167
274
  */
168
275
  export const makeWebSocket = (url: string | Effect.Effect<string>, options?: {
169
276
  readonly closeCodeIsError?: (code: number) => boolean
277
+ readonly openTimeout?: DurationInput
170
278
  }): Effect.Effect<Socket> =>
171
279
  fromWebSocket(
172
280
  Effect.acquireRelease(
@@ -190,11 +298,12 @@ export const fromWebSocket = (
190
298
  acquire: Effect.Effect<globalThis.WebSocket, SocketError, Scope.Scope>,
191
299
  options?: {
192
300
  readonly closeCodeIsError?: (code: number) => boolean
301
+ readonly openTimeout?: DurationInput
193
302
  }
194
303
  ): Effect.Effect<Socket> =>
195
304
  Effect.gen(function*(_) {
196
305
  const closeCodeIsError = options?.closeCodeIsError ?? defaultCloseCodeIsError
197
- const sendQueue = yield* _(Queue.unbounded<Uint8Array>())
306
+ const sendQueue = yield* _(Queue.unbounded<Uint8Array | CloseEvent>())
198
307
 
199
308
  const run = <R, E, _>(handler: (_: Uint8Array) => Effect.Effect<_, E, R>) =>
200
309
  Effect.gen(function*(_) {
@@ -219,23 +328,38 @@ export const fromWebSocket = (
219
328
  }
220
329
 
221
330
  if (ws.readyState !== IsoWebSocket.OPEN) {
222
- yield* _(Effect.async<void, SocketError, never>((resume) => {
223
- ws.onopen = () => {
224
- resume(Effect.unit)
225
- }
226
- ws.onerror = (error_) => {
227
- resume(Effect.fail(new SocketError({ reason: "Open", error: (error_ as any).message })))
228
- }
229
- }))
331
+ yield* _(
332
+ Effect.async<void, SocketError, never>((resume) => {
333
+ ws.onopen = () => {
334
+ resume(Effect.unit)
335
+ }
336
+ ws.onerror = (error_) => {
337
+ resume(Effect.fail(new SocketGenericError({ reason: "Open", error: (error_ as any).message })))
338
+ }
339
+ }),
340
+ Effect.timeoutFail({
341
+ duration: options?.openTimeout ?? 10000,
342
+ onTimeout: () => new SocketGenericError({ reason: "OpenTimeout", error: "timeout waiting for \"open\"" })
343
+ })
344
+ )
230
345
  }
231
346
 
232
- yield* _(
347
+ const writeFiber = yield* _(
233
348
  Queue.take(sendQueue),
234
349
  Effect.tap((chunk) =>
235
- Effect.try({
236
- try: () => ws.send(chunk),
237
- catch: (error) => Effect.fail(new SocketError({ reason: "Write", error: (error as any).message }))
238
- })
350
+ isCloseEvent(chunk) ?
351
+ Effect.failSync(() => {
352
+ ws.close(chunk.code, chunk.reason)
353
+ return new SocketCloseError({
354
+ reason: "Close",
355
+ code: chunk.code,
356
+ closeReason: chunk.reason
357
+ })
358
+ }) :
359
+ Effect.try({
360
+ try: () => ws.send(chunk),
361
+ catch: (error) => new SocketGenericError({ reason: "Write", error: (error as any).message })
362
+ })
239
363
  ),
240
364
  Effect.forever,
241
365
  Effect.fork
@@ -244,25 +368,34 @@ export const fromWebSocket = (
244
368
  yield* _(
245
369
  Effect.async<void, SocketError, never>((resume) => {
246
370
  ws.onclose = (event) => {
247
- if (closeCodeIsError(event.code)) {
248
- resume(Effect.fail(new SocketError({ reason: "Close", error: event })))
249
- } else {
250
- resume(Effect.unit)
251
- }
371
+ resume(
372
+ Effect.fail(
373
+ new SocketCloseError({
374
+ reason: "Close",
375
+ code: event.code,
376
+ closeReason: event.reason
377
+ })
378
+ )
379
+ )
252
380
  }
253
381
  ws.onerror = (error) => {
254
- resume(Effect.fail(new SocketError({ reason: "Read", error: (error as any).message })))
382
+ resume(Effect.fail(new SocketGenericError({ reason: "Read", error: (error as any).message })))
255
383
  }
256
384
  }),
257
- Effect.raceFirst(FiberSet.join(fiberSet))
385
+ Effect.raceFirst(FiberSet.join(fiberSet)),
386
+ Effect.raceFirst(Fiber.join(writeFiber)),
387
+ Effect.catchIf(
388
+ SocketCloseError.isClean((_) => !closeCodeIsError(_)),
389
+ (_) => Effect.unit
390
+ )
258
391
  )
259
392
  }).pipe(Effect.scoped)
260
393
 
261
- const write = (chunk: Uint8Array) => Queue.offer(sendQueue, chunk)
394
+ const write = (chunk: Uint8Array | CloseEvent) => Queue.offer(sendQueue, chunk)
262
395
  const writer = Effect.succeed(write)
263
396
 
264
397
  return Socket.of({
265
- [SocketTypeId]: SocketTypeId,
398
+ [TypeId]: TypeId,
266
399
  run,
267
400
  writer
268
401
  })
@@ -277,7 +410,14 @@ export const makeWebSocketChannel = <IE = never>(
277
410
  options?: {
278
411
  readonly closeCodeIsError?: (code: number) => boolean
279
412
  }
280
- ): Channel.Channel<Chunk.Chunk<Uint8Array>, Chunk.Chunk<Uint8Array>, SocketError | IE, IE, void, unknown> =>
413
+ ): Channel.Channel<
414
+ Chunk.Chunk<Uint8Array>,
415
+ Chunk.Chunk<Uint8Array | CloseEvent>,
416
+ SocketError | IE,
417
+ IE,
418
+ void,
419
+ unknown
420
+ > =>
281
421
  Channel.unwrapScoped(
282
422
  Effect.map(makeWebSocket(url, options), toChannelWith<IE>())
283
423
  )
@@ -91,7 +91,7 @@ export const schema: {
91
91
  f: (_: I) => Iterable<globalThis.Transferable>
92
92
  ) =>
93
93
  Schema.transformOrFail(
94
- Schema.from(self),
94
+ Schema.encodedSchema(self),
95
95
  self,
96
96
  ParseResult.succeed,
97
97
  (i) => Effect.as(addAll(f(i)), i)
package/src/Worker.ts CHANGED
@@ -134,7 +134,7 @@ export declare namespace Worker {
134
134
  | readonly [id: number, end: 1]
135
135
  | readonly [id: number, end: 1, ReadonlyArray<O>]
136
136
  | readonly [id: number, error: 2, E]
137
- | readonly [id: number, defect: 3, Schema.CauseFrom<WorkerErrorFrom>]
137
+ | readonly [id: number, defect: 3, Schema.CauseEncoded<WorkerErrorFrom>]
138
138
  }
139
139
 
140
140
  /**
@@ -246,12 +246,12 @@ export interface SerializedWorker<I extends Schema.TaggedRequest.Any> {
246
246
  readonly id: number
247
247
  readonly execute: <Req extends I>(
248
248
  message: Req
249
- ) => Req extends Serializable.WithResult<infer R, infer _IE, infer E, infer _IA, infer A>
249
+ ) => Req extends Serializable.WithResult<infer A, infer _I, infer E, infer _EI, infer R>
250
250
  ? Stream.Stream<A, E | WorkerError | ParseResult.ParseError, R>
251
251
  : never
252
252
  readonly executeEffect: <Req extends I>(
253
253
  message: Req
254
- ) => Req extends Serializable.WithResult<infer R, infer _IE, infer E, infer _IA, infer A>
254
+ ) => Req extends Serializable.WithResult<infer A, infer _I, infer E, infer _EI, infer R>
255
255
  ? Effect.Effect<A, E | WorkerError | ParseResult.ParseError, R>
256
256
  : never
257
257
  }
@@ -290,17 +290,17 @@ export interface SerializedWorkerPool<I extends Schema.TaggedRequest.Any> {
290
290
  readonly backing: Pool.Pool<SerializedWorker<I>, WorkerError>
291
291
  readonly broadcast: <Req extends I>(
292
292
  message: Req
293
- ) => Req extends Serializable.WithResult<infer R, infer _IE, infer E, infer _IA, infer _A>
293
+ ) => Req extends Serializable.WithResult<infer _A, infer _I, infer E, infer _EI, infer R>
294
294
  ? Effect.Effect<void, E | WorkerError | ParseResult.ParseError, R>
295
295
  : never
296
296
  readonly execute: <Req extends I>(
297
297
  message: Req
298
- ) => Req extends Serializable.WithResult<infer R, infer _IE, infer E, infer _IA, infer A>
298
+ ) => Req extends Serializable.WithResult<infer A, infer _I, infer E, infer _EI, infer R>
299
299
  ? Stream.Stream<A, E | WorkerError | ParseResult.ParseError, R>
300
300
  : never
301
301
  readonly executeEffect: <Req extends I>(
302
302
  message: Req
303
- ) => Req extends Serializable.WithResult<infer R, infer _IE, infer E, infer _IA, infer A>
303
+ ) => Req extends Serializable.WithResult<infer A, infer _I, infer E, infer _EI, infer R>
304
304
  ? Effect.Effect<A, E | WorkerError | ParseResult.ParseError, R>
305
305
  : never
306
306
  }
@@ -55,22 +55,24 @@ export class WorkerError extends Schema.TaggedError<WorkerError>()("WorkerError"
55
55
  */
56
56
  static readonly Cause: Schema.Schema<
57
57
  Cause.Cause<WorkerError>,
58
- Schema.CauseFrom<WorkerErrorFrom>
58
+ Schema.CauseEncoded<WorkerErrorFrom>
59
59
  > = Schema.cause({ defect: causeDefectPretty, error: this })
60
60
 
61
61
  /**
62
62
  * @since 1.0.0
63
63
  */
64
- static readonly encodeCause: (a: Cause.Cause<WorkerError>) => Schema.CauseFrom<WorkerErrorFrom> = Schema.encodeSync(
65
- this.Cause
66
- )
64
+ static readonly encodeCause: (a: Cause.Cause<WorkerError>) => Schema.CauseEncoded<WorkerErrorFrom> = Schema
65
+ .encodeSync(
66
+ this.Cause
67
+ )
67
68
 
68
69
  /**
69
70
  * @since 1.0.0
70
71
  */
71
- static readonly decodeCause: (u: Schema.CauseFrom<WorkerErrorFrom>) => Cause.Cause<WorkerError> = Schema.decodeSync(
72
- this.Cause
73
- )
72
+ static readonly decodeCause: (u: Schema.CauseEncoded<WorkerErrorFrom>) => Cause.Cause<WorkerError> = Schema
73
+ .decodeSync(
74
+ this.Cause
75
+ )
74
76
 
75
77
  /**
76
78
  * @since 1.0.0
@@ -121,19 +121,19 @@ export declare namespace SerializedRunner {
121
121
  A,
122
122
  { readonly _tag: K }
123
123
  > extends Serializable.SerializableWithResult<
124
- infer _RS,
125
- infer _IS,
126
124
  infer S,
127
- infer _RR,
128
- infer _IE,
125
+ infer _SI,
126
+ infer _SR,
127
+ infer A,
128
+ infer _AI,
129
129
  infer E,
130
- infer _IO,
131
- infer O
130
+ infer _EI,
131
+ infer _RR
132
132
  > ? (
133
133
  _: S
134
134
  ) =>
135
- | Stream.Stream<O, E, any>
136
- | Effect.Effect<O, E, any>
135
+ | Stream.Stream<A, E, any>
136
+ | Effect.Effect<A, E, any>
137
137
  | Layer.Layer<any, E, any>
138
138
  | Layer.Layer<never, E, any>
139
139
  : never
@@ -23,6 +23,19 @@ import * as Path from "../../Path.js"
23
23
  /** @internal */
24
24
  export const TypeId: Multipart.TypeId = Symbol.for("@effect/platform/Http/Multipart") as Multipart.TypeId
25
25
 
26
+ /** @internal */
27
+ export const isPart = (u: unknown): u is Multipart.Part => Predicate.hasProperty(u, TypeId)
28
+
29
+ /** @internal */
30
+ export const isField = (u: unknown): u is Multipart.Field => isPart(u) && u._tag === "Field"
31
+
32
+ /** @internal */
33
+ export const isFile = (u: unknown): u is Multipart.File => isPart(u) && u._tag === "File"
34
+
35
+ /** @internal */
36
+ export const isPersistedFile = (u: unknown): u is Multipart.PersistedFile =>
37
+ Predicate.hasProperty(u, TypeId) && Predicate.isTagged(u, "PersistedFile")
38
+
26
39
  /** @internal */
27
40
  export const ErrorTypeId: Multipart.ErrorTypeId = Symbol.for(
28
41
  "@effect/platform/Http/Multipart/MultipartError"
@@ -37,10 +50,6 @@ export const MultipartError = (reason: Multipart.MultipartError["reason"], error
37
50
  error
38
51
  })
39
52
 
40
- /** @internal */
41
- export const isField = (u: unknown): u is Multipart.Field =>
42
- Predicate.hasProperty(u, TypeId) && Predicate.isTagged(u, "Field")
43
-
44
53
  /** @internal */
45
54
  export const maxParts: FiberRef.FiberRef<Option.Option<number>> = globalValue(
46
55
  "@effect/platform/Http/Multipart/maxParts",
@@ -89,20 +98,15 @@ export const withFieldMimeTypes = dual<
89
98
  <R, E, A>(effect: Effect.Effect<A, E, R>, mimeTypes: ReadonlyArray<string>) => Effect.Effect<A, E, R>
90
99
  >(2, (effect, mimeTypes) => Effect.locally(effect, fieldMimeTypes, Chunk.fromIterable(mimeTypes)))
91
100
 
92
- const fileSchema: Schema.Schema<Multipart.PersistedFile> = Schema.struct({
93
- [TypeId]: Schema.uniqueSymbol(TypeId),
94
- _tag: Schema.literal("PersistedFile"),
95
- key: Schema.string,
96
- name: Schema.string,
97
- contentType: Schema.string,
98
- path: Schema.string
101
+ const fileSchema: Schema.Schema<Multipart.PersistedFile> = Schema.declare(isPersistedFile, {
102
+ identifier: "PersistedFile"
99
103
  })
100
104
 
101
105
  /** @internal */
102
106
  export const filesSchema: Schema.Schema<ReadonlyArray<Multipart.PersistedFile>> = Schema.array(fileSchema)
103
107
 
104
108
  /** @internal */
105
- export const schemaPersisted = <R, I extends Multipart.Persisted, A>(
109
+ export const schemaPersisted = <R, I extends Partial<Multipart.Persisted>, A>(
106
110
  schema: Schema.Schema<A, I, R>
107
111
  ) => {
108
112
  const parse = Schema.decodeUnknown(schema)
@@ -48,7 +48,7 @@ const isMultipart = (request: ServerRequest.ServerRequest) =>
48
48
  request.headers["content-type"]?.toLowerCase().includes("multipart/form-data")
49
49
 
50
50
  /** @internal */
51
- export const schemaBodyForm = <R, I extends Multipart.Persisted, A>(
51
+ export const schemaBodyForm = <R, I extends Partial<Multipart.Persisted>, A>(
52
52
  schema: Schema.Schema<A, I, R>
53
53
  ) => {
54
54
  const parseMultipart = Multipart.schemaPersisted(schema)
@@ -74,7 +74,7 @@ export const schemaBodyUrlParams = <R, I extends Readonly<Record<string, string>
74
74
  }
75
75
 
76
76
  /** @internal */
77
- export const schemaBodyMultipart = <R, I extends Multipart.Persisted, A>(
77
+ export const schemaBodyMultipart = <R, I extends Partial<Multipart.Persisted>, A>(
78
78
  schema: Schema.Schema<A, I, R>
79
79
  ) => {
80
80
  const parse = Multipart.schemaPersisted(schema)