@effect/platform 0.28.3 → 0.29.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.
- package/dist/cjs/Http/FormData.js +21 -26
- package/dist/cjs/Http/FormData.js.map +1 -1
- package/dist/cjs/Http/ServerRequest.js +3 -3
- package/dist/cjs/Http/ServerRequest.js.map +1 -1
- package/dist/cjs/Terminal.js.map +1 -1
- package/dist/cjs/internal/http/formData.js +212 -31
- package/dist/cjs/internal/http/formData.js.map +1 -1
- package/dist/cjs/internal/http/serverRequest.js +4 -4
- package/dist/cjs/internal/http/serverRequest.js.map +1 -1
- package/dist/cjs/internal/worker.js +6 -5
- package/dist/cjs/internal/worker.js.map +1 -1
- package/dist/dts/Http/FormData.d.ts +46 -35
- package/dist/dts/Http/FormData.d.ts.map +1 -1
- package/dist/dts/Http/ServerRequest.d.ts +3 -3
- package/dist/dts/Http/ServerRequest.d.ts.map +1 -1
- package/dist/dts/Terminal.d.ts +4 -0
- package/dist/dts/Terminal.d.ts.map +1 -1
- package/dist/dts/WorkerRunner.d.ts +1 -1
- package/dist/dts/WorkerRunner.d.ts.map +1 -1
- package/dist/esm/Http/FormData.js +20 -25
- package/dist/esm/Http/FormData.js.map +1 -1
- package/dist/esm/Http/ServerRequest.js +1 -1
- package/dist/esm/Http/ServerRequest.js.map +1 -1
- package/dist/esm/Terminal.js.map +1 -1
- package/dist/esm/internal/http/formData.js +207 -29
- package/dist/esm/internal/http/formData.js.map +1 -1
- package/dist/esm/internal/http/serverRequest.js +3 -3
- package/dist/esm/internal/http/serverRequest.js.map +1 -1
- package/dist/esm/internal/worker.js +2 -1
- package/dist/esm/internal/worker.js.map +1 -1
- package/package.json +2 -1
- package/src/Http/FormData.ts +62 -41
- package/src/Http/ServerRequest.ts +9 -5
- package/src/Terminal.ts +4 -0
- package/src/WorkerRunner.ts +1 -1
- package/src/internal/http/formData.ts +320 -62
- package/src/internal/http/serverRequest.ts +4 -10
- package/src/internal/worker.ts +2 -1
package/src/Terminal.ts
CHANGED
|
@@ -16,6 +16,10 @@ import * as InternalTerminal from "./internal/terminal.js"
|
|
|
16
16
|
* @category models
|
|
17
17
|
*/
|
|
18
18
|
export interface Terminal {
|
|
19
|
+
/**
|
|
20
|
+
* The number of columns available on the platform's terminal interface.
|
|
21
|
+
*/
|
|
22
|
+
readonly columns: number
|
|
19
23
|
/**
|
|
20
24
|
* Reads a single input event from the default standard input.
|
|
21
25
|
*/
|
package/src/WorkerRunner.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @since 1.0.0
|
|
3
3
|
*/
|
|
4
|
-
import type { Effect } from "effect"
|
|
5
4
|
import type * as Context from "effect/Context"
|
|
5
|
+
import type * as Effect from "effect/Effect"
|
|
6
6
|
import type * as Fiber from "effect/Fiber"
|
|
7
7
|
import type * as Queue from "effect/Queue"
|
|
8
8
|
import type * as Scope from "effect/Scope"
|
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import type * as ParseResult from "@effect/schema/ParseResult"
|
|
2
2
|
import * as Schema from "@effect/schema/Schema"
|
|
3
|
+
import * as Cause from "effect/Cause"
|
|
4
|
+
import * as Channel from "effect/Channel"
|
|
5
|
+
import type * as AsyncInput from "effect/ChannelSingleProducerAsyncInput"
|
|
3
6
|
import * as Chunk from "effect/Chunk"
|
|
4
7
|
import * as Data from "effect/Data"
|
|
5
8
|
import * as Effect from "effect/Effect"
|
|
6
9
|
import * as FiberRef from "effect/FiberRef"
|
|
7
|
-
import { dual, pipe } from "effect/Function"
|
|
10
|
+
import { dual, flow, pipe } from "effect/Function"
|
|
8
11
|
import { globalValue } from "effect/GlobalValue"
|
|
9
12
|
import * as Option from "effect/Option"
|
|
10
13
|
import * as Predicate from "effect/Predicate"
|
|
11
|
-
import * as
|
|
14
|
+
import * as Queue from "effect/Queue"
|
|
15
|
+
import type * as Scope from "effect/Scope"
|
|
16
|
+
import * as Stream from "effect/Stream"
|
|
17
|
+
import * as MP from "multipasta"
|
|
12
18
|
import * as FileSystem from "../../FileSystem.js"
|
|
13
19
|
import type * as FormData from "../../Http/FormData.js"
|
|
20
|
+
import * as IncomingMessage from "../../Http/IncomingMessage.js"
|
|
21
|
+
import * as Path from "../../Path.js"
|
|
14
22
|
|
|
15
23
|
/** @internal */
|
|
16
24
|
export const TypeId: FormData.TypeId = Symbol.for("@effect/platform/Http/FormData") as FormData.TypeId
|
|
@@ -29,6 +37,10 @@ export const FormDataError = (reason: FormData.FormDataError["reason"], error: u
|
|
|
29
37
|
error
|
|
30
38
|
})
|
|
31
39
|
|
|
40
|
+
/** @internal */
|
|
41
|
+
export const isField = (u: unknown): u is FormData.Field =>
|
|
42
|
+
Predicate.hasProperty(u, TypeId) && Predicate.isTagged(u, "Field")
|
|
43
|
+
|
|
32
44
|
/** @internal */
|
|
33
45
|
export const maxParts: FiberRef.FiberRef<Option.Option<number>> = globalValue(
|
|
34
46
|
"@effect/platform/Http/FormData/maxParts",
|
|
@@ -53,30 +65,6 @@ export const withMaxFieldSize = dual<
|
|
|
53
65
|
<R, E, A>(effect: Effect.Effect<R, E, A>, size: FileSystem.SizeInput) => Effect.Effect<R, E, A>
|
|
54
66
|
>(2, (effect, size) => Effect.locally(effect, maxFieldSize, FileSystem.Size(size)))
|
|
55
67
|
|
|
56
|
-
/** @internal */
|
|
57
|
-
export const maxFields: FiberRef.FiberRef<Option.Option<number>> = globalValue(
|
|
58
|
-
"@effect/platform/Http/FormData/maxFields",
|
|
59
|
-
() => FiberRef.unsafeMake(Option.none<number>())
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
/** @internal */
|
|
63
|
-
export const withMaxFields = dual<
|
|
64
|
-
(count: Option.Option<number>) => <R, E, A>(effect: Effect.Effect<R, E, A>) => Effect.Effect<R, E, A>,
|
|
65
|
-
<R, E, A>(effect: Effect.Effect<R, E, A>, count: Option.Option<number>) => Effect.Effect<R, E, A>
|
|
66
|
-
>(2, (effect, count) => Effect.locally(effect, maxFields, count))
|
|
67
|
-
|
|
68
|
-
/** @internal */
|
|
69
|
-
export const maxFiles: FiberRef.FiberRef<Option.Option<number>> = globalValue(
|
|
70
|
-
"@effect/platform/Http/FormData/maxFiles",
|
|
71
|
-
() => FiberRef.unsafeMake(Option.none<number>())
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
/** @internal */
|
|
75
|
-
export const withMaxFiles = dual<
|
|
76
|
-
(count: Option.Option<number>) => <R, E, A>(effect: Effect.Effect<R, E, A>) => Effect.Effect<R, E, A>,
|
|
77
|
-
<R, E, A>(effect: Effect.Effect<R, E, A>, count: Option.Option<number>) => Effect.Effect<R, E, A>
|
|
78
|
-
>(2, (effect, count) => Effect.locally(effect, maxFiles, count))
|
|
79
|
-
|
|
80
68
|
/** @internal */
|
|
81
69
|
export const maxFileSize: FiberRef.FiberRef<Option.Option<FileSystem.Size>> = globalValue(
|
|
82
70
|
"@effect/platform/Http/FormData/maxFileSize",
|
|
@@ -102,49 +90,32 @@ export const withFieldMimeTypes = dual<
|
|
|
102
90
|
>(2, (effect, mimeTypes) => Effect.locally(effect, fieldMimeTypes, Chunk.fromIterable(mimeTypes)))
|
|
103
91
|
|
|
104
92
|
/** @internal */
|
|
105
|
-
export const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (Array.isArray(existing)) {
|
|
115
|
-
existing.push(value)
|
|
116
|
-
} else {
|
|
117
|
-
acc[key] = [value]
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return acc
|
|
121
|
-
}
|
|
122
|
-
)
|
|
123
|
-
/** @internal */
|
|
124
|
-
export const filesSchema: Schema.Schema<ReadonlyArray<File>, ReadonlyArray<File>> = Schema.array(
|
|
125
|
-
pipe(
|
|
126
|
-
Schema.instanceOf(Blob),
|
|
127
|
-
Schema.filter(
|
|
128
|
-
(blob): blob is File => "name" in blob
|
|
93
|
+
export const filesSchema: Schema.Schema<ReadonlyArray<FormData.PersistedFile>, ReadonlyArray<FormData.PersistedFile>> =
|
|
94
|
+
Schema
|
|
95
|
+
.array(
|
|
96
|
+
pipe(
|
|
97
|
+
Schema.object,
|
|
98
|
+
Schema.filter(
|
|
99
|
+
(file): file is FormData.PersistedFile => TypeId in file && "_tag" in file && file._tag === "PersistedFile"
|
|
100
|
+
)
|
|
101
|
+
) as any as Schema.Schema<FormData.PersistedFile, FormData.PersistedFile>
|
|
129
102
|
)
|
|
130
|
-
) as any as Schema.Schema<File, File>
|
|
131
|
-
)
|
|
132
103
|
|
|
133
104
|
/** @internal */
|
|
134
|
-
export const
|
|
105
|
+
export const schemaPersisted = <I extends FormData.PersistedFormData, A>(
|
|
135
106
|
schema: Schema.Schema<I, A>
|
|
136
107
|
) => {
|
|
137
108
|
const parse = Schema.parse(schema)
|
|
138
|
-
return (formData:
|
|
109
|
+
return (formData: FormData.PersistedFormData) => parse(formData)
|
|
139
110
|
}
|
|
140
111
|
|
|
141
112
|
/** @internal */
|
|
142
113
|
export const schemaJson = <I, A>(schema: Schema.Schema<I, A>): {
|
|
143
114
|
(
|
|
144
115
|
field: string
|
|
145
|
-
): (formData:
|
|
116
|
+
): (formData: FormData.PersistedFormData) => Effect.Effect<never, FormData.FormDataError | ParseResult.ParseError, A>
|
|
146
117
|
(
|
|
147
|
-
formData:
|
|
118
|
+
formData: FormData.PersistedFormData,
|
|
148
119
|
field: string
|
|
149
120
|
): Effect.Effect<never, FormData.FormDataError | ParseResult.ParseError, A>
|
|
150
121
|
} => {
|
|
@@ -152,22 +123,309 @@ export const schemaJson = <I, A>(schema: Schema.Schema<I, A>): {
|
|
|
152
123
|
return dual<
|
|
153
124
|
(
|
|
154
125
|
field: string
|
|
155
|
-
) => (
|
|
126
|
+
) => (
|
|
127
|
+
formData: FormData.PersistedFormData
|
|
128
|
+
) => Effect.Effect<never, FormData.FormDataError | ParseResult.ParseError, A>,
|
|
156
129
|
(
|
|
157
|
-
formData:
|
|
130
|
+
formData: FormData.PersistedFormData,
|
|
158
131
|
field: string
|
|
159
132
|
) => Effect.Effect<never, FormData.FormDataError | ParseResult.ParseError, A>
|
|
160
133
|
>(2, (formData, field) =>
|
|
161
134
|
pipe(
|
|
162
|
-
Effect.succeed(formData
|
|
135
|
+
Effect.succeed(formData[field]),
|
|
163
136
|
Effect.filterOrFail(
|
|
164
|
-
|
|
165
|
-
() => FormDataError("Parse", `schemaJson:
|
|
137
|
+
isField,
|
|
138
|
+
() => FormDataError("Parse", `schemaJson: was not a field`)
|
|
166
139
|
),
|
|
167
140
|
Effect.tryMap({
|
|
168
|
-
try: (field) => JSON.parse(field
|
|
141
|
+
try: (field) => JSON.parse(field.value),
|
|
169
142
|
catch: (error) => FormDataError("Parse", `schemaJson: field was not valid json: ${error}`)
|
|
170
143
|
}),
|
|
171
144
|
Effect.flatMap(parse)
|
|
172
145
|
))
|
|
173
146
|
}
|
|
147
|
+
|
|
148
|
+
/** @internal */
|
|
149
|
+
export const makeConfig = (
|
|
150
|
+
headers: Record<string, string>
|
|
151
|
+
): Effect.Effect<never, never, MP.BaseConfig> =>
|
|
152
|
+
Effect.map(
|
|
153
|
+
Effect.all({
|
|
154
|
+
maxParts: Effect.map(FiberRef.get(maxParts), Option.getOrUndefined),
|
|
155
|
+
maxFieldSize: Effect.map(FiberRef.get(maxFieldSize), Number),
|
|
156
|
+
maxPartSize: Effect.map(FiberRef.get(maxFileSize), flow(Option.map(Number), Option.getOrUndefined)),
|
|
157
|
+
maxTotalSize: Effect.map(
|
|
158
|
+
FiberRef.get(IncomingMessage.maxBodySize),
|
|
159
|
+
flow(Option.map(Number), Option.getOrUndefined)
|
|
160
|
+
),
|
|
161
|
+
isFile: Effect.map(FiberRef.get(fieldMimeTypes), (mimeTypes) => {
|
|
162
|
+
if (mimeTypes.length === 0) {
|
|
163
|
+
return undefined
|
|
164
|
+
}
|
|
165
|
+
return (info: MP.PartInfo): boolean =>
|
|
166
|
+
Chunk.some(mimeTypes, (_) => info.contentType.includes(_)) || MP.defaultIsFile(info)
|
|
167
|
+
})
|
|
168
|
+
}),
|
|
169
|
+
(_) => ({ ..._, headers })
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
/** @internal */
|
|
173
|
+
export const makeChannel = <IE>(
|
|
174
|
+
headers: Record<string, string>,
|
|
175
|
+
bufferSize = 16
|
|
176
|
+
): Channel.Channel<
|
|
177
|
+
never,
|
|
178
|
+
IE,
|
|
179
|
+
Chunk.Chunk<Uint8Array>,
|
|
180
|
+
unknown,
|
|
181
|
+
FormData.FormDataError | IE,
|
|
182
|
+
Chunk.Chunk<FormData.Part>,
|
|
183
|
+
unknown
|
|
184
|
+
> =>
|
|
185
|
+
Channel.acquireUseRelease(
|
|
186
|
+
Effect.all([
|
|
187
|
+
makeConfig(headers),
|
|
188
|
+
Queue.bounded<Chunk.Chunk<Uint8Array> | null>(bufferSize)
|
|
189
|
+
]),
|
|
190
|
+
([config, queue]) => makeFromQueue(config, queue),
|
|
191
|
+
([, queue]) => Queue.shutdown(queue)
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
const makeFromQueue = <IE>(
|
|
195
|
+
config: MP.BaseConfig,
|
|
196
|
+
queue: Queue.Queue<Chunk.Chunk<Uint8Array> | null>
|
|
197
|
+
): Channel.Channel<
|
|
198
|
+
never,
|
|
199
|
+
IE,
|
|
200
|
+
Chunk.Chunk<Uint8Array>,
|
|
201
|
+
unknown,
|
|
202
|
+
IE | FormData.FormDataError,
|
|
203
|
+
Chunk.Chunk<FormData.Part>,
|
|
204
|
+
unknown
|
|
205
|
+
> =>
|
|
206
|
+
Channel.suspend(() => {
|
|
207
|
+
let error = Option.none<Cause.Cause<IE | FormData.FormDataError>>()
|
|
208
|
+
let partsBuffer: Array<FormData.Part> = []
|
|
209
|
+
let partsFinished = false
|
|
210
|
+
|
|
211
|
+
const input: AsyncInput.AsyncInputProducer<IE, Chunk.Chunk<Uint8Array>, unknown> = {
|
|
212
|
+
awaitRead: () => Effect.unit,
|
|
213
|
+
emit(element) {
|
|
214
|
+
return Queue.offer(queue, element)
|
|
215
|
+
},
|
|
216
|
+
error(cause) {
|
|
217
|
+
error = Option.some(cause)
|
|
218
|
+
return Queue.offer(queue, null)
|
|
219
|
+
},
|
|
220
|
+
done(_value) {
|
|
221
|
+
return Queue.offer(queue, null)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const parser = MP.make({
|
|
226
|
+
...config,
|
|
227
|
+
onField(info, value) {
|
|
228
|
+
partsBuffer.push(new FieldImpl(info.name, info.contentType, MP.decodeField(info, value)))
|
|
229
|
+
},
|
|
230
|
+
onFile(info) {
|
|
231
|
+
let chunks: Array<Uint8Array> = []
|
|
232
|
+
let finished = false
|
|
233
|
+
const take: Channel.Channel<never, unknown, unknown, unknown, never, Chunk.Chunk<Uint8Array>, void> = Channel
|
|
234
|
+
.suspend(() => {
|
|
235
|
+
if (finished) {
|
|
236
|
+
return Channel.unit
|
|
237
|
+
} else if (chunks.length === 0) {
|
|
238
|
+
return Channel.zipRight(pump, take)
|
|
239
|
+
}
|
|
240
|
+
const chunk = Chunk.unsafeFromArray(chunks)
|
|
241
|
+
chunks = []
|
|
242
|
+
return Channel.zipRight(
|
|
243
|
+
Channel.write(chunk),
|
|
244
|
+
Channel.zipRight(pump, take)
|
|
245
|
+
)
|
|
246
|
+
})
|
|
247
|
+
partsBuffer.push(new FileImpl(info, take))
|
|
248
|
+
return function(chunk) {
|
|
249
|
+
if (chunk === null) {
|
|
250
|
+
finished = true
|
|
251
|
+
} else {
|
|
252
|
+
chunks.push(chunk)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
onError(error_) {
|
|
257
|
+
error = Option.some(Cause.fail(convertError(error_)))
|
|
258
|
+
},
|
|
259
|
+
onDone() {
|
|
260
|
+
partsFinished = true
|
|
261
|
+
}
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
const pump = Channel.flatMap(
|
|
265
|
+
Queue.take(queue),
|
|
266
|
+
(chunk) =>
|
|
267
|
+
Channel.sync(() => {
|
|
268
|
+
if (chunk === null) {
|
|
269
|
+
parser.end()
|
|
270
|
+
} else {
|
|
271
|
+
Chunk.forEach(chunk, function(buf) {
|
|
272
|
+
parser.write(buf)
|
|
273
|
+
})
|
|
274
|
+
}
|
|
275
|
+
})
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
const takeParts = Channel.zipRight(
|
|
279
|
+
pump,
|
|
280
|
+
Channel.suspend(() => {
|
|
281
|
+
if (partsBuffer.length === 0) {
|
|
282
|
+
return Channel.unit
|
|
283
|
+
}
|
|
284
|
+
const parts = Chunk.unsafeFromArray(partsBuffer)
|
|
285
|
+
partsBuffer = []
|
|
286
|
+
return Channel.write(parts)
|
|
287
|
+
})
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
const partsChannel: Channel.Channel<
|
|
291
|
+
never,
|
|
292
|
+
unknown,
|
|
293
|
+
unknown,
|
|
294
|
+
unknown,
|
|
295
|
+
IE | FormData.FormDataError,
|
|
296
|
+
Chunk.Chunk<FormData.Part>,
|
|
297
|
+
void
|
|
298
|
+
> = Channel.suspend(() => {
|
|
299
|
+
if (error._tag === "Some") {
|
|
300
|
+
return Channel.failCause(error.value)
|
|
301
|
+
} else if (partsFinished) {
|
|
302
|
+
return Channel.unit
|
|
303
|
+
}
|
|
304
|
+
return Channel.zipRight(takeParts, partsChannel)
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
return Channel.embedInput(partsChannel, input)
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
function convertError(error: MP.MultipartError): FormData.FormDataError {
|
|
311
|
+
switch (error._tag) {
|
|
312
|
+
case "ReachedLimit": {
|
|
313
|
+
switch (error.limit) {
|
|
314
|
+
case "MaxParts": {
|
|
315
|
+
return FormDataError("TooManyParts", error)
|
|
316
|
+
}
|
|
317
|
+
case "MaxFieldSize": {
|
|
318
|
+
return FormDataError("FieldTooLarge", error)
|
|
319
|
+
}
|
|
320
|
+
case "MaxPartSize": {
|
|
321
|
+
return FormDataError("FileTooLarge", error)
|
|
322
|
+
}
|
|
323
|
+
case "MaxTotalSize": {
|
|
324
|
+
return FormDataError("BodyTooLarge", error)
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
default: {
|
|
329
|
+
return FormDataError("Parse", error)
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
class FieldImpl implements FormData.Field {
|
|
335
|
+
readonly [TypeId]: FormData.TypeId
|
|
336
|
+
readonly _tag = "Field"
|
|
337
|
+
|
|
338
|
+
constructor(
|
|
339
|
+
readonly key: string,
|
|
340
|
+
readonly contentType: string,
|
|
341
|
+
readonly value: string
|
|
342
|
+
) {
|
|
343
|
+
this[TypeId] = TypeId
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
class FileImpl implements FormData.File {
|
|
348
|
+
readonly _tag = "File"
|
|
349
|
+
readonly [TypeId]: FormData.TypeId
|
|
350
|
+
readonly key: string
|
|
351
|
+
readonly name: string
|
|
352
|
+
readonly contentType: string
|
|
353
|
+
readonly content: Stream.Stream<never, FormData.FormDataError, Uint8Array>
|
|
354
|
+
|
|
355
|
+
constructor(
|
|
356
|
+
info: MP.PartInfo,
|
|
357
|
+
channel: Channel.Channel<never, unknown, unknown, unknown, never, Chunk.Chunk<Uint8Array>, void>
|
|
358
|
+
) {
|
|
359
|
+
this[TypeId] = TypeId
|
|
360
|
+
this.key = info.name
|
|
361
|
+
this.name = info.filename ?? info.name
|
|
362
|
+
this.contentType = info.contentType
|
|
363
|
+
this.content = Stream.fromChannel(channel)
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const defaultWriteFile = (path: string, file: FormData.File) =>
|
|
368
|
+
Effect.flatMap(
|
|
369
|
+
FileSystem.FileSystem,
|
|
370
|
+
(fs) =>
|
|
371
|
+
Effect.mapError(
|
|
372
|
+
Stream.run(file.content, fs.sink(path)),
|
|
373
|
+
(error) => FormDataError("InternalError", error)
|
|
374
|
+
)
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
/** @internal */
|
|
378
|
+
export const formData = (
|
|
379
|
+
stream: Stream.Stream<never, FormData.FormDataError, FormData.Part>,
|
|
380
|
+
writeFile = defaultWriteFile
|
|
381
|
+
): Effect.Effect<FileSystem.FileSystem | Path.Path | Scope.Scope, FormData.FormDataError, FormData.PersistedFormData> =>
|
|
382
|
+
pipe(
|
|
383
|
+
Effect.Do,
|
|
384
|
+
Effect.bind("fs", () => FileSystem.FileSystem),
|
|
385
|
+
Effect.bind("path", () => Path.Path),
|
|
386
|
+
Effect.bind("dir", ({ fs }) => fs.makeTempDirectoryScoped()),
|
|
387
|
+
Effect.flatMap(({ dir, path: path_ }) =>
|
|
388
|
+
Stream.runFoldEffect(
|
|
389
|
+
stream,
|
|
390
|
+
Object.create(null) as Record<string, Array<FormData.PersistedFile> | string>,
|
|
391
|
+
(formData, part) => {
|
|
392
|
+
if (part._tag === "Field") {
|
|
393
|
+
formData[part.key] = part.value
|
|
394
|
+
return Effect.succeed(formData)
|
|
395
|
+
}
|
|
396
|
+
const file = part
|
|
397
|
+
const path = path_.join(dir, path_.basename(file.name).slice(-128))
|
|
398
|
+
if (!Array.isArray(formData[part.key])) {
|
|
399
|
+
formData[part.key] = []
|
|
400
|
+
}
|
|
401
|
+
;(formData[part.key] as Array<FormData.PersistedFile>).push(
|
|
402
|
+
new PersistedFileImpl(
|
|
403
|
+
file.key,
|
|
404
|
+
file.name,
|
|
405
|
+
file.contentType,
|
|
406
|
+
path
|
|
407
|
+
)
|
|
408
|
+
)
|
|
409
|
+
return Effect.as(writeFile(path, file), formData)
|
|
410
|
+
}
|
|
411
|
+
)
|
|
412
|
+
),
|
|
413
|
+
Effect.catchTags({
|
|
414
|
+
SystemError: (err) => Effect.fail(FormDataError("InternalError", err)),
|
|
415
|
+
BadArgument: (err) => Effect.fail(FormDataError("InternalError", err))
|
|
416
|
+
})
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
class PersistedFileImpl implements FormData.PersistedFile {
|
|
420
|
+
readonly [TypeId]: FormData.TypeId
|
|
421
|
+
readonly _tag = "PersistedFile"
|
|
422
|
+
|
|
423
|
+
constructor(
|
|
424
|
+
readonly key: string,
|
|
425
|
+
readonly name: string,
|
|
426
|
+
readonly contentType: string,
|
|
427
|
+
readonly path: string
|
|
428
|
+
) {
|
|
429
|
+
this[TypeId] = TypeId
|
|
430
|
+
}
|
|
431
|
+
}
|
|
@@ -13,10 +13,7 @@ export const TypeId: ServerRequest.TypeId = Symbol.for("@effect/platform/Http/Se
|
|
|
13
13
|
export const serverRequestTag = Context.Tag<ServerRequest.ServerRequest>(TypeId)
|
|
14
14
|
|
|
15
15
|
/** @internal */
|
|
16
|
-
export const
|
|
17
|
-
Effect.flatMap(serverRequestTag, (request) => request.formData),
|
|
18
|
-
FormData.toRecord
|
|
19
|
-
)
|
|
16
|
+
export const persistedFormData = Effect.flatMap(serverRequestTag, (request) => request.formData)
|
|
20
17
|
|
|
21
18
|
/** @internal */
|
|
22
19
|
export const schemaHeaders = <I extends Readonly<Record<string, string>>, A>(schema: Schema.Schema<I, A>) => {
|
|
@@ -37,14 +34,11 @@ export const schemaBodyUrlParams = <I extends Readonly<Record<string, string>>,
|
|
|
37
34
|
}
|
|
38
35
|
|
|
39
36
|
/** @internal */
|
|
40
|
-
export const schemaFormData = <I extends
|
|
37
|
+
export const schemaFormData = <I extends FormData.PersistedFormData, A>(
|
|
41
38
|
schema: Schema.Schema<I, A>
|
|
42
39
|
) => {
|
|
43
|
-
const parse = FormData.
|
|
44
|
-
return Effect.flatMap(
|
|
45
|
-
Effect.flatMap(serverRequestTag, (request) => request.formData),
|
|
46
|
-
parse
|
|
47
|
-
)
|
|
40
|
+
const parse = FormData.schemaPersisted(schema)
|
|
41
|
+
return Effect.flatMap(persistedFormData, parse)
|
|
48
42
|
}
|
|
49
43
|
|
|
50
44
|
/** @internal */
|
package/src/internal/worker.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Cause from "effect/Cause"
|
|
2
2
|
import * as Channel from "effect/Channel"
|
|
3
|
+
import * as Chunk from "effect/Chunk"
|
|
3
4
|
import * as Context from "effect/Context"
|
|
4
5
|
import * as Deferred from "effect/Deferred"
|
|
5
6
|
import * as Effect from "effect/Effect"
|