@toa.io/extensions.exposition 1.0.0-alpha.40 → 1.0.0-alpha.42
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/documentation/octets.md +24 -23
- package/features/octets.entries.feature +0 -1
- package/features/octets.workflows.feature +184 -19
- package/features/steps/components/octets.tester/manifest.toa.yaml +5 -1
- package/features/steps/components/octets.tester/operations/authority.js +7 -0
- package/features/steps/components/octets.tester/operations/baz.js +1 -2
- package/features/steps/components/octets.tester/operations/diversify.js +3 -1
- package/features/steps/components/octets.tester/operations/foo.js +2 -2
- package/features/steps/components/octets.tester/operations/yex.js +16 -0
- package/features/steps/components/octets.tester/operations/yield.js +13 -0
- package/package.json +7 -7
- package/source/HTTP/messages.test.ts +39 -2
- package/source/HTTP/messages.ts +6 -2
- package/source/directives/octets/workflows/Execution.ts +52 -8
- package/source/directives/octets/workflows/Workflow.ts +2 -1
- package/transpiled/HTTP/messages.d.ts +1 -0
- package/transpiled/HTTP/messages.js +8 -2
- package/transpiled/HTTP/messages.js.map +1 -1
- package/transpiled/directives/octets/workflows/Execution.d.ts +5 -1
- package/transpiled/directives/octets/workflows/Execution.js +38 -9
- package/transpiled/directives/octets/workflows/Execution.js.map +1 -1
- package/transpiled/directives/octets/workflows/Workflow.js +2 -1
- package/transpiled/directives/octets/workflows/Workflow.js.map +1 -1
- package/transpiled/tsconfig.tsbuildinfo +1 -1
package/documentation/octets.md
CHANGED
|
@@ -20,7 +20,7 @@ Stores the content of the request body into a storage, under the request path wi
|
|
|
20
20
|
specified `content-type`.
|
|
21
21
|
|
|
22
22
|
If request's `content-type` is not acceptable, or if the request body does not pass
|
|
23
|
-
the [validation](/extensions/storages/readme.md#async-putpath-string-stream-readable-
|
|
23
|
+
the [validation](/extensions/storages/readme.md#async-putpath-string-stream-readable-options-options-maybeentry),
|
|
24
24
|
the request is rejected with a `415 Unsupported Media Type` response.
|
|
25
25
|
|
|
26
26
|
The value of the directive is `null` or an object with the following properties:
|
|
@@ -83,7 +83,8 @@ is [multipart](protocol.md#multipart-types).
|
|
|
83
83
|
The first part represents the created Entry, which is sent immediately after the BLOB is stored,
|
|
84
84
|
while subsequent parts are results from the workflow endpoints, sent as soon as they are available.
|
|
85
85
|
|
|
86
|
-
In case a workflow endpoint returns an `Error`, the error part is sent,
|
|
86
|
+
In case a workflow endpoint returns an `Error`, the error part is sent,
|
|
87
|
+
and the response is closed.
|
|
87
88
|
Error's properties are added to the error part, among with the `step` identifier.
|
|
88
89
|
|
|
89
90
|
```
|
|
@@ -91,16 +92,29 @@ Error's properties are added to the error part, among with the `step` identifier
|
|
|
91
92
|
content-type: multipart/yaml; boundary=cut
|
|
92
93
|
|
|
93
94
|
--cut
|
|
95
|
+
|
|
94
96
|
id: eecd837c
|
|
95
97
|
type: image/jpeg
|
|
96
98
|
created: 1698004822358
|
|
99
|
+
|
|
97
100
|
--cut
|
|
98
|
-
|
|
101
|
+
|
|
102
|
+
step: optimize
|
|
103
|
+
status: completed
|
|
104
|
+
|
|
99
105
|
--cut
|
|
106
|
+
|
|
107
|
+
step: resize
|
|
100
108
|
error:
|
|
101
|
-
step: resize
|
|
102
109
|
code: TOO_SMALL
|
|
103
110
|
message: Image is too small
|
|
111
|
+
status: completed
|
|
112
|
+
|
|
113
|
+
--cut
|
|
114
|
+
|
|
115
|
+
step: analyze
|
|
116
|
+
status: exception
|
|
117
|
+
|
|
104
118
|
--cut--
|
|
105
119
|
```
|
|
106
120
|
|
|
@@ -193,22 +207,6 @@ the entry is deleted.
|
|
|
193
207
|
|
|
194
208
|
The error returned by the workflow prevents the deletion of the entry.
|
|
195
209
|
|
|
196
|
-
## `octets:permute`
|
|
197
|
-
|
|
198
|
-
Performs
|
|
199
|
-
a [permutation](/extensions/storages/readme.md#async-permutepath-string-ids-string-maybevoid) on the
|
|
200
|
-
entries
|
|
201
|
-
under the request path.
|
|
202
|
-
|
|
203
|
-
```yaml
|
|
204
|
-
/images:
|
|
205
|
-
octets:context: images
|
|
206
|
-
PUT:
|
|
207
|
-
octets:permute: ~
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
The request body must be a list of entry identifiers.
|
|
211
|
-
|
|
212
210
|
## `octets:workflow`
|
|
213
211
|
|
|
214
212
|
Execute a [workflow](#workflows) on the entry under the request path.
|
|
@@ -227,14 +225,16 @@ A workflow is a list of endpoints to be called.
|
|
|
227
225
|
The following input will be passed to each endpoint:
|
|
228
226
|
|
|
229
227
|
```yaml
|
|
228
|
+
authority: string
|
|
230
229
|
storage: string
|
|
231
230
|
path: string
|
|
232
231
|
entry: Entry
|
|
233
232
|
parameters: Record<string, string> # route parameters
|
|
234
233
|
```
|
|
235
234
|
|
|
236
|
-
|
|
237
|
-
|
|
235
|
+
- [Storages](/extensions/storages/readme.md)
|
|
236
|
+
- [Authorities](authorities.md)
|
|
237
|
+
- Example [workflow step processor](../features/steps/components/octets.tester)
|
|
238
238
|
|
|
239
239
|
A _workflow unit_ is an object with keys referencing the workflow step identifier, and an endpoint
|
|
240
240
|
as value.
|
|
@@ -258,4 +258,5 @@ octets:store:
|
|
|
258
258
|
analyze: images.analyze # executed in parallel with `resize`
|
|
259
259
|
```
|
|
260
260
|
|
|
261
|
-
If one of the workflow units returns an error,
|
|
261
|
+
If one of the workflow units returns or throws an error,
|
|
262
|
+
the execution of the workflow is interrupted.
|
|
@@ -24,7 +24,7 @@ Feature: Octets storage workflows
|
|
|
24
24
|
"""
|
|
25
25
|
POST / HTTP/1.1
|
|
26
26
|
host: nex.toa.io
|
|
27
|
-
accept: application/yaml
|
|
27
|
+
accept: application/yaml, multipart/yaml
|
|
28
28
|
content-type: application/octet-stream
|
|
29
29
|
"""
|
|
30
30
|
Then the following reply is sent:
|
|
@@ -33,18 +33,29 @@ Feature: Octets storage workflows
|
|
|
33
33
|
content-type: multipart/yaml; boundary=cut
|
|
34
34
|
|
|
35
35
|
--cut
|
|
36
|
+
|
|
36
37
|
id: 10cf16b458f759e0d617f2f3d83599ff
|
|
37
38
|
type: application/octet-stream
|
|
38
39
|
size: 8169
|
|
39
40
|
--cut
|
|
40
|
-
|
|
41
|
+
|
|
42
|
+
step: add-foo
|
|
43
|
+
status: completed
|
|
41
44
|
--cut
|
|
42
|
-
|
|
45
|
+
|
|
46
|
+
step: add-bar
|
|
47
|
+
output:
|
|
43
48
|
bar: baz
|
|
49
|
+
status: completed
|
|
44
50
|
--cut
|
|
45
|
-
|
|
51
|
+
|
|
52
|
+
step: add-baz
|
|
53
|
+
status: completed
|
|
46
54
|
--cut
|
|
47
|
-
|
|
55
|
+
|
|
56
|
+
step: diversify
|
|
57
|
+
output: hello
|
|
58
|
+
status: completed
|
|
48
59
|
--cut--
|
|
49
60
|
"""
|
|
50
61
|
When the following request is received:
|
|
@@ -88,15 +99,15 @@ Feature: Octets storage workflows
|
|
|
88
99
|
POST:
|
|
89
100
|
octets:store:
|
|
90
101
|
workflow:
|
|
91
|
-
add-foo: octets.tester.foo
|
|
92
|
-
add-bar: octets.tester.err
|
|
93
|
-
add-baz: octets.tester.baz
|
|
102
|
+
- add-foo: octets.tester.foo
|
|
103
|
+
- add-bar: octets.tester.err
|
|
104
|
+
- add-baz: octets.tester.baz
|
|
94
105
|
"""
|
|
95
106
|
When the stream of `lenna.ascii` is received with the following headers:
|
|
96
107
|
"""
|
|
97
108
|
POST / HTTP/1.1
|
|
98
109
|
host: nex.toa.io
|
|
99
|
-
accept: application/yaml
|
|
110
|
+
accept: application/yaml, multipart/yaml
|
|
100
111
|
content-type: application/octet-stream
|
|
101
112
|
"""
|
|
102
113
|
Then the following reply is sent:
|
|
@@ -109,12 +120,16 @@ Feature: Octets storage workflows
|
|
|
109
120
|
type: application/octet-stream
|
|
110
121
|
size: 8169
|
|
111
122
|
--cut
|
|
112
|
-
|
|
123
|
+
|
|
124
|
+
step: add-foo
|
|
125
|
+
status: completed
|
|
113
126
|
--cut
|
|
127
|
+
|
|
128
|
+
step: add-bar
|
|
114
129
|
error:
|
|
115
|
-
step: add-bar
|
|
116
130
|
code: ERROR
|
|
117
131
|
message: Something went wrong
|
|
132
|
+
status: completed
|
|
118
133
|
--cut--
|
|
119
134
|
"""
|
|
120
135
|
|
|
@@ -149,7 +164,7 @@ Feature: Octets storage workflows
|
|
|
149
164
|
"""
|
|
150
165
|
DELETE /10cf16b458f759e0d617f2f3d83599ff HTTP/1.1
|
|
151
166
|
host: nex.toa.io
|
|
152
|
-
accept: application/yaml
|
|
167
|
+
accept: application/yaml, multipart/yaml
|
|
153
168
|
"""
|
|
154
169
|
Then the following reply is sent:
|
|
155
170
|
"""
|
|
@@ -157,7 +172,9 @@ Feature: Octets storage workflows
|
|
|
157
172
|
content-type: multipart/yaml; boundary=cut
|
|
158
173
|
|
|
159
174
|
--cut
|
|
160
|
-
|
|
175
|
+
step: echo
|
|
176
|
+
status: completed
|
|
177
|
+
output: 10cf16b458f759e0d617f2f3d83599ff
|
|
161
178
|
--cut--
|
|
162
179
|
"""
|
|
163
180
|
When the following request is received:
|
|
@@ -201,7 +218,7 @@ Feature: Octets storage workflows
|
|
|
201
218
|
"""
|
|
202
219
|
DELETE /10cf16b458f759e0d617f2f3d83599ff HTTP/1.1
|
|
203
220
|
host: nex.toa.io
|
|
204
|
-
accept: application/yaml
|
|
221
|
+
accept: application/yaml, multipart/yaml
|
|
205
222
|
"""
|
|
206
223
|
Then the following reply is sent:
|
|
207
224
|
"""
|
|
@@ -209,8 +226,10 @@ Feature: Octets storage workflows
|
|
|
209
226
|
content-type: multipart/yaml; boundary=cut
|
|
210
227
|
|
|
211
228
|
--cut
|
|
229
|
+
|
|
230
|
+
step: err
|
|
231
|
+
status: completed
|
|
212
232
|
error:
|
|
213
|
-
step: err
|
|
214
233
|
code: ERROR
|
|
215
234
|
message: Something went wrong
|
|
216
235
|
--cut--
|
|
@@ -242,7 +261,7 @@ Feature: Octets storage workflows
|
|
|
242
261
|
"""
|
|
243
262
|
POST /hello/world/ HTTP/1.1
|
|
244
263
|
host: nex.toa.io
|
|
245
|
-
accept: application/yaml
|
|
264
|
+
accept: application/yaml, multipart/yaml
|
|
246
265
|
content-type: application/octet-stream
|
|
247
266
|
"""
|
|
248
267
|
Then the following reply is sent:
|
|
@@ -251,11 +270,53 @@ Feature: Octets storage workflows
|
|
|
251
270
|
content-type: multipart/yaml; boundary=cut
|
|
252
271
|
|
|
253
272
|
--cut
|
|
273
|
+
|
|
254
274
|
id: 10cf16b458f759e0d617f2f3d83599ff
|
|
255
275
|
type: application/octet-stream
|
|
256
276
|
size: 8169
|
|
257
277
|
--cut
|
|
258
|
-
|
|
278
|
+
|
|
279
|
+
step: concat
|
|
280
|
+
status: completed
|
|
281
|
+
output: hello world
|
|
282
|
+
--cut--
|
|
283
|
+
"""
|
|
284
|
+
|
|
285
|
+
Scenario: Passing authority to the workflow
|
|
286
|
+
Given the `octets.tester` is running
|
|
287
|
+
And the annotation:
|
|
288
|
+
"""yaml
|
|
289
|
+
/:
|
|
290
|
+
/:a/:b:
|
|
291
|
+
auth:anonymous: true
|
|
292
|
+
octets:context: octets
|
|
293
|
+
POST:
|
|
294
|
+
octets:store:
|
|
295
|
+
workflow:
|
|
296
|
+
authority: octets.tester.authority
|
|
297
|
+
"""
|
|
298
|
+
When the stream of `lenna.ascii` is received with the following headers:
|
|
299
|
+
"""
|
|
300
|
+
POST /hello/world/ HTTP/1.1
|
|
301
|
+
host: nex.toa.io
|
|
302
|
+
accept: application/yaml, multipart/yaml
|
|
303
|
+
content-type: application/octet-stream
|
|
304
|
+
"""
|
|
305
|
+
Then the following reply is sent:
|
|
306
|
+
"""
|
|
307
|
+
201 Created
|
|
308
|
+
content-type: multipart/yaml; boundary=cut
|
|
309
|
+
|
|
310
|
+
--cut
|
|
311
|
+
|
|
312
|
+
id: 10cf16b458f759e0d617f2f3d83599ff
|
|
313
|
+
type: application/octet-stream
|
|
314
|
+
size: 8169
|
|
315
|
+
--cut
|
|
316
|
+
|
|
317
|
+
step: authority
|
|
318
|
+
status: completed
|
|
319
|
+
output: nex
|
|
259
320
|
--cut--
|
|
260
321
|
"""
|
|
261
322
|
|
|
@@ -287,7 +348,7 @@ Feature: Octets storage workflows
|
|
|
287
348
|
"""
|
|
288
349
|
DELETE /10cf16b458f759e0d617f2f3d83599ff HTTP/1.1
|
|
289
350
|
host: nex.toa.io
|
|
290
|
-
accept: application/yaml
|
|
351
|
+
accept: application/yaml, multipart/yaml
|
|
291
352
|
"""
|
|
292
353
|
Then the following reply is sent:
|
|
293
354
|
"""
|
|
@@ -295,6 +356,110 @@ Feature: Octets storage workflows
|
|
|
295
356
|
content-type: multipart/yaml; boundary=cut
|
|
296
357
|
|
|
297
358
|
--cut
|
|
298
|
-
|
|
359
|
+
|
|
360
|
+
step: echo
|
|
361
|
+
status: completed
|
|
362
|
+
output: 10cf16b458f759e0d617f2f3d83599ff
|
|
363
|
+
|
|
364
|
+
--cut--
|
|
365
|
+
"""
|
|
366
|
+
|
|
367
|
+
Scenario: Workflow with streaming response
|
|
368
|
+
Given the `octets.tester` is running
|
|
369
|
+
And the annotation:
|
|
370
|
+
"""yaml
|
|
371
|
+
/:
|
|
372
|
+
auth:anonymous: true
|
|
373
|
+
octets:context: octets
|
|
374
|
+
POST:
|
|
375
|
+
octets:store:
|
|
376
|
+
workflow:
|
|
377
|
+
- foo: octets.tester.foo
|
|
378
|
+
- yield: octets.tester.yield
|
|
379
|
+
"""
|
|
380
|
+
When the stream of `lenna.ascii` is received with the following headers:
|
|
381
|
+
"""
|
|
382
|
+
POST / HTTP/1.1
|
|
383
|
+
host: nex.toa.io
|
|
384
|
+
accept: application/yaml, multipart/yaml
|
|
385
|
+
content-type: application/octet-stream
|
|
386
|
+
"""
|
|
387
|
+
Then the following reply is sent:
|
|
388
|
+
"""
|
|
389
|
+
201 Created
|
|
390
|
+
content-type: multipart/yaml; boundary=cut
|
|
391
|
+
|
|
392
|
+
--cut
|
|
393
|
+
|
|
394
|
+
id: 10cf16b458f759e0d617f2f3d83599ff
|
|
395
|
+
type: application/octet-stream
|
|
396
|
+
|
|
397
|
+
--cut
|
|
398
|
+
|
|
399
|
+
step: foo
|
|
400
|
+
status: completed
|
|
401
|
+
|
|
402
|
+
--cut
|
|
403
|
+
|
|
404
|
+
step: yield
|
|
405
|
+
output: hello
|
|
406
|
+
|
|
407
|
+
--cut
|
|
408
|
+
|
|
409
|
+
step: yield
|
|
410
|
+
output: world
|
|
411
|
+
|
|
412
|
+
--cut
|
|
413
|
+
|
|
414
|
+
step: yield
|
|
415
|
+
status: completed
|
|
416
|
+
|
|
417
|
+
--cut--
|
|
418
|
+
"""
|
|
419
|
+
|
|
420
|
+
Scenario: Workflow with streaming response and an exception
|
|
421
|
+
Given the `octets.tester` is running
|
|
422
|
+
And the annotation:
|
|
423
|
+
"""yaml
|
|
424
|
+
/:
|
|
425
|
+
auth:anonymous: true
|
|
426
|
+
octets:context: octets
|
|
427
|
+
POST:
|
|
428
|
+
octets:store:
|
|
429
|
+
workflow:
|
|
430
|
+
yield: octets.tester.yex
|
|
431
|
+
"""
|
|
432
|
+
When the stream of `lenna.ascii` is received with the following headers:
|
|
433
|
+
"""
|
|
434
|
+
POST / HTTP/1.1
|
|
435
|
+
host: nex.toa.io
|
|
436
|
+
accept: application/yaml, multipart/yaml
|
|
437
|
+
content-type: application/octet-stream
|
|
438
|
+
"""
|
|
439
|
+
Then the following reply is sent:
|
|
440
|
+
"""
|
|
441
|
+
201 Created
|
|
442
|
+
content-type: multipart/yaml; boundary=cut
|
|
443
|
+
|
|
444
|
+
--cut
|
|
445
|
+
|
|
446
|
+
id: 10cf16b458f759e0d617f2f3d83599ff
|
|
447
|
+
type: application/octet-stream
|
|
448
|
+
|
|
449
|
+
--cut
|
|
450
|
+
|
|
451
|
+
step: yield
|
|
452
|
+
output: hello
|
|
453
|
+
|
|
454
|
+
--cut
|
|
455
|
+
|
|
456
|
+
step: yield
|
|
457
|
+
output: world
|
|
458
|
+
|
|
459
|
+
--cut
|
|
460
|
+
|
|
461
|
+
step: yield
|
|
462
|
+
status: exception
|
|
463
|
+
|
|
299
464
|
--cut--
|
|
300
465
|
"""
|
|
@@ -7,9 +7,10 @@ storages: octets
|
|
|
7
7
|
operations:
|
|
8
8
|
foo: &operation
|
|
9
9
|
input:
|
|
10
|
+
authority*: string
|
|
10
11
|
storage*: string
|
|
11
12
|
path*: string
|
|
12
|
-
entry
|
|
13
|
+
entry*: object
|
|
13
14
|
parameters: object
|
|
14
15
|
bar: *operation
|
|
15
16
|
baz: *operation
|
|
@@ -17,3 +18,6 @@ operations:
|
|
|
17
18
|
echo: *operation
|
|
18
19
|
concat: *operation
|
|
19
20
|
diversify: *operation
|
|
21
|
+
yield: *operation
|
|
22
|
+
yex: *operation
|
|
23
|
+
authority: *operation
|
|
@@ -4,8 +4,7 @@ import { setTimeout } from 'node:timers/promises'
|
|
|
4
4
|
|
|
5
5
|
async function baz (input, context) {
|
|
6
6
|
await setTimeout(30)
|
|
7
|
-
|
|
8
|
-
return context.storages.octets.annotate(input.path, 'baz', 'qux')
|
|
7
|
+
await context.storages.octets.annotate(input.path, 'baz', 'qux')
|
|
9
8
|
}
|
|
10
9
|
|
|
11
10
|
exports.effect = baz
|
|
@@ -8,7 +8,9 @@ const lenna = join(__dirname, 'lenna.png')
|
|
|
8
8
|
async function diversify (input, context) {
|
|
9
9
|
const stream = createReadStream(lenna)
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
await context.storages[input.storage].diversify(input.path, 'hello.png', stream)
|
|
12
|
+
|
|
13
|
+
return 'hello'
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
exports.effect = diversify
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
import { setTimeout } from 'node:timers/promises'
|
|
4
|
+
|
|
5
|
+
async function * effect (_) {
|
|
6
|
+
await setTimeout(10)
|
|
7
|
+
yield 'hello'
|
|
8
|
+
|
|
9
|
+
await setTimeout(10)
|
|
10
|
+
yield 'world'
|
|
11
|
+
|
|
12
|
+
await setTimeout(10)
|
|
13
|
+
throw new Error('Oops!')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
exports.effect = effect
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/extensions.exposition",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.42",
|
|
4
4
|
"description": "Toa Exposition",
|
|
5
5
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/toa-io/toa#readme",
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@toa.io/core": "1.0.0-alpha.
|
|
21
|
-
"@toa.io/generic": "1.0.0-alpha.
|
|
22
|
-
"@toa.io/schemas": "1.0.0-alpha.
|
|
20
|
+
"@toa.io/core": "1.0.0-alpha.42",
|
|
21
|
+
"@toa.io/generic": "1.0.0-alpha.42",
|
|
22
|
+
"@toa.io/schemas": "1.0.0-alpha.42",
|
|
23
23
|
"bcryptjs": "2.4.3",
|
|
24
24
|
"error-value": "0.3.0",
|
|
25
25
|
"js-yaml": "4.1.0",
|
|
@@ -44,11 +44,11 @@
|
|
|
44
44
|
"features:security": "cucumber-js --tags @security"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@toa.io/agent": "1.0.0-alpha.
|
|
48
|
-
"@toa.io/extensions.storages": "1.0.0-alpha.
|
|
47
|
+
"@toa.io/agent": "1.0.0-alpha.42",
|
|
48
|
+
"@toa.io/extensions.storages": "1.0.0-alpha.42",
|
|
49
49
|
"@types/bcryptjs": "2.4.3",
|
|
50
50
|
"@types/cors": "2.8.13",
|
|
51
51
|
"@types/negotiator": "0.6.1"
|
|
52
52
|
},
|
|
53
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "6df7ba51d3f8f419b95253538e08dc7e9bc14072"
|
|
54
54
|
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { Readable } from 'node:stream'
|
|
1
|
+
import { PassThrough, Readable } from 'node:stream'
|
|
2
|
+
import * as streamConsumers from 'node:stream/consumers'
|
|
3
|
+
import { once } from 'node:events'
|
|
2
4
|
import { generate } from 'randomstring'
|
|
3
5
|
import * as msgpack from 'msgpackr'
|
|
4
|
-
import { read } from './messages'
|
|
6
|
+
import { multipart, read, type OutgoingMessage } from './messages'
|
|
5
7
|
import { BadRequest, UnsupportedMediaType } from './exceptions'
|
|
8
|
+
import { formats } from './formats'
|
|
6
9
|
import { Timing } from './Timing'
|
|
10
|
+
import type * as http from 'node:http'
|
|
7
11
|
import type { Context } from './Context'
|
|
8
12
|
|
|
9
13
|
beforeEach(() => {
|
|
@@ -69,6 +73,39 @@ describe('read', () => {
|
|
|
69
73
|
|
|
70
74
|
await expect(read(request)).rejects.toThrow(BadRequest)
|
|
71
75
|
})
|
|
76
|
+
|
|
77
|
+
it('should output correct mulitpart format', async () => {
|
|
78
|
+
const response = new class extends PassThrough {
|
|
79
|
+
public readonly headers = new Headers()
|
|
80
|
+
|
|
81
|
+
public setHeader (key: string, value: string): this {
|
|
82
|
+
this.headers.set(key, value)
|
|
83
|
+
|
|
84
|
+
return this
|
|
85
|
+
}
|
|
86
|
+
}()
|
|
87
|
+
|
|
88
|
+
const context = { encoder: formats['text/plain'] } as unknown as Context
|
|
89
|
+
const message = { body: Readable.from(['Hello', 'New', 'World']) } as unknown as OutgoingMessage
|
|
90
|
+
|
|
91
|
+
multipart(message, context, response as unknown as http.ServerResponse)
|
|
92
|
+
|
|
93
|
+
await once(message.body, 'end')
|
|
94
|
+
|
|
95
|
+
const result = await streamConsumers.text(response)
|
|
96
|
+
|
|
97
|
+
expect(result).toBe([
|
|
98
|
+
'--cut',
|
|
99
|
+
'',
|
|
100
|
+
'Hello',
|
|
101
|
+
'--cut',
|
|
102
|
+
'',
|
|
103
|
+
'New',
|
|
104
|
+
'--cut',
|
|
105
|
+
'',
|
|
106
|
+
'World',
|
|
107
|
+
'--cut--'].join('\r\n'))
|
|
108
|
+
})
|
|
72
109
|
})
|
|
73
110
|
|
|
74
111
|
export function createContext
|
package/source/HTTP/messages.ts
CHANGED
|
@@ -72,7 +72,7 @@ function stream
|
|
|
72
72
|
})
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
function multipart
|
|
75
|
+
export function multipart
|
|
76
76
|
(message: OutgoingMessage, context: Context, response: http.ServerResponse): void {
|
|
77
77
|
if (context.encoder === null)
|
|
78
78
|
throw new NotAcceptable()
|
|
@@ -82,7 +82,11 @@ function multipart
|
|
|
82
82
|
response.setHeader('content-type', `${encoder.multipart}; boundary=${BOUNDARY}`)
|
|
83
83
|
|
|
84
84
|
message.body
|
|
85
|
-
.map((part: unknown) => Buffer.concat([
|
|
85
|
+
.map((part: unknown) => Buffer.concat([
|
|
86
|
+
CUT,
|
|
87
|
+
CRLF /* indicates no boundary headers */,
|
|
88
|
+
encoder.encode(part),
|
|
89
|
+
CRLF]))
|
|
86
90
|
.on('end', () => response.end(FINALCUT))
|
|
87
91
|
.pipe(response)
|
|
88
92
|
}
|