@toa.io/extensions.exposition 0.22.1 → 1.0.0-alpha.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 (77) hide show
  1. package/components/identity.basic/source/authenticate.ts +3 -2
  2. package/components/identity.basic/source/transit.ts +4 -3
  3. package/components/octets.storage/manifest.toa.yaml +26 -0
  4. package/components/octets.storage/operations/delete.js +7 -0
  5. package/components/octets.storage/operations/fetch.js +46 -0
  6. package/components/octets.storage/operations/get.js +7 -0
  7. package/components/octets.storage/operations/list.js +7 -0
  8. package/components/octets.storage/operations/permute.js +7 -0
  9. package/components/octets.storage/operations/store.js +11 -0
  10. package/cucumber.js +0 -1
  11. package/documentation/access.md +2 -3
  12. package/documentation/cache.md +42 -0
  13. package/documentation/octets.md +196 -0
  14. package/documentation/protocol.md +49 -5
  15. package/documentation/tree.md +1 -5
  16. package/features/access.feature +1 -0
  17. package/features/cache.feature +160 -0
  18. package/features/errors.feature +18 -0
  19. package/features/identity.basic.feature +2 -0
  20. package/features/octets.feature +295 -0
  21. package/features/octets.workflows.feature +114 -0
  22. package/features/routes.feature +40 -0
  23. package/features/steps/HTTP.ts +56 -6
  24. package/features/steps/Parameters.ts +5 -2
  25. package/features/steps/Workspace.ts +8 -5
  26. package/features/steps/components/octets.tester/manifest.toa.yaml +15 -0
  27. package/features/steps/components/octets.tester/operations/bar.js +12 -0
  28. package/features/steps/components/octets.tester/operations/baz.js +11 -0
  29. package/features/steps/components/octets.tester/operations/diversify.js +14 -0
  30. package/features/steps/components/octets.tester/operations/err.js +16 -0
  31. package/features/steps/components/octets.tester/operations/foo.js +7 -0
  32. package/features/steps/components/octets.tester/operations/lenna.png +0 -0
  33. package/features/steps/components/pots/manifest.toa.yaml +1 -1
  34. package/features/streams.feature +5 -1
  35. package/package.json +16 -9
  36. package/readme.md +8 -5
  37. package/schemas/octets/context.cos.yaml +1 -0
  38. package/schemas/octets/delete.cos.yaml +1 -0
  39. package/schemas/octets/fetch.cos.yaml +3 -0
  40. package/schemas/octets/list.cos.yaml +1 -0
  41. package/schemas/octets/permute.cos.yaml +1 -0
  42. package/schemas/octets/store.cos.yaml +3 -0
  43. package/source/Gateway.ts +9 -4
  44. package/source/HTTP/Server.fixtures.ts +2 -6
  45. package/source/HTTP/Server.test.ts +9 -31
  46. package/source/HTTP/Server.ts +33 -19
  47. package/source/HTTP/exceptions.ts +2 -12
  48. package/source/HTTP/formats/index.ts +7 -4
  49. package/source/HTTP/formats/json.ts +3 -0
  50. package/source/HTTP/formats/msgpack.ts +3 -0
  51. package/source/HTTP/formats/text.ts +3 -0
  52. package/source/HTTP/formats/yaml.ts +3 -0
  53. package/source/HTTP/messages.test.ts +3 -49
  54. package/source/HTTP/messages.ts +60 -35
  55. package/source/RTD/Route.ts +1 -1
  56. package/source/RTD/segment.ts +2 -1
  57. package/source/RTD/syntax/parse.ts +2 -1
  58. package/source/Remotes.ts +8 -0
  59. package/source/Tenant.ts +5 -0
  60. package/source/directives/auth/Family.ts +26 -22
  61. package/source/directives/auth/Rule.ts +1 -1
  62. package/source/directives/cache/Control.ts +59 -0
  63. package/source/directives/cache/Exact.ts +7 -0
  64. package/source/directives/cache/Family.ts +36 -0
  65. package/source/directives/cache/index.ts +3 -0
  66. package/source/directives/cache/types.ts +9 -0
  67. package/source/directives/index.ts +3 -1
  68. package/source/directives/octets/Context.ts +18 -0
  69. package/source/directives/octets/Delete.ts +32 -0
  70. package/source/directives/octets/Family.ts +68 -0
  71. package/source/directives/octets/Fetch.ts +85 -0
  72. package/source/directives/octets/List.ts +32 -0
  73. package/source/directives/octets/Permute.ts +37 -0
  74. package/source/directives/octets/Store.ts +158 -0
  75. package/source/directives/octets/index.ts +3 -0
  76. package/source/directives/octets/schemas.ts +12 -0
  77. package/source/directives/octets/types.ts +13 -0
@@ -191,3 +191,21 @@ Feature: Errors
191
191
  | debug | response |
192
192
  | false | content-length: 0 |
193
193
  | true | Error: Broken! |
194
+
195
+ Scenario: Not acceptable request
196
+ Given the annotation:
197
+ """yaml
198
+ /:
199
+ GET:
200
+ anonymous: true
201
+ dev:stub: hello
202
+ """
203
+ When the following request is received:
204
+ """
205
+ GET / HTTP/1.1
206
+ accept: image/jpeg
207
+ """
208
+ Then the following reply is sent:
209
+ """
210
+ 406 Not Acceptable
211
+ """
@@ -181,6 +181,7 @@ Feature: Basic authentication
181
181
  When the following request is received:
182
182
  """
183
183
  POST /identity/basic/ HTTP/1.1
184
+ accept: application/yaml
184
185
  content-type: application/yaml
185
186
 
186
187
  username: root
@@ -224,6 +225,7 @@ Feature: Basic authentication
224
225
  """
225
226
  PATCH /identity/basic/${{ id }}/ HTTP/1.1
226
227
  authorization: Token ${{ token }}
228
+ accept: application/yaml
227
229
  content-type: application/yaml
228
230
 
229
231
  username: admin
@@ -0,0 +1,295 @@
1
+ Feature: Octets directive family
2
+
3
+ Background:
4
+ Given the annotation:
5
+ """yaml
6
+ /:
7
+ auth:anonymous: true
8
+ octets:context: octets
9
+ POST:
10
+ octets:store: ~
11
+ GET:
12
+ octets:list: ~
13
+ PUT:
14
+ octets:permute: ~
15
+ /*:
16
+ GET:
17
+ octets:fetch: ~
18
+ DELETE:
19
+ octets:delete: ~
20
+ /media:
21
+ /jpeg:
22
+ POST:
23
+ octets:store:
24
+ accept: image/jpeg
25
+ /jpeg-or-png:
26
+ POST:
27
+ octets:store:
28
+ accept:
29
+ - image/jpeg
30
+ - image/png
31
+ /images:
32
+ POST:
33
+ octets:store:
34
+ accept: image/*
35
+ """
36
+
37
+ Scenario: Basic storage operations
38
+ When the stream of `lenna.ascii` is received with the following headers:
39
+ """
40
+ POST / HTTP/1.1
41
+ accept: application/yaml
42
+ content-type: application/octet-stream
43
+ """
44
+ Then the following reply is sent:
45
+ """
46
+ 201 Created
47
+ content-type: application/yaml
48
+
49
+ id: 10cf16b458f759e0d617f2f3d83599ff
50
+ type: application/octet-stream
51
+ size: 8169
52
+ """
53
+ When the following request is received:
54
+ """
55
+ GET /10cf16b458f759e0d617f2f3d83599ff HTTP/1.1
56
+ """
57
+ Then the stream equals to `lenna.ascii` is sent with the following headers:
58
+ """
59
+ 200 OK
60
+ content-type: application/octet-stream
61
+ content-length: 8169
62
+ etag: ${{ ETAG }}
63
+ """
64
+ When the following request is received:
65
+ """
66
+ GET /10cf16b458f759e0d617f2f3d83599ff HTTP/1.1
67
+ if-none-match: ${{ ETAG }}
68
+ """
69
+ Then the following reply is sent:
70
+ """
71
+ 304 Not Modified
72
+ """
73
+ When the following request is received:
74
+ """
75
+ GET /10cf16b458f759e0d617f2f3d83599ff:meta HTTP/1.1
76
+ accept: text/plain
77
+ """
78
+ Then the following reply is sent:
79
+ """
80
+ 403 Forbidden
81
+
82
+ Metadata is not accessible.
83
+ """
84
+ When the following request is received:
85
+ """
86
+ GET / HTTP/1.1
87
+ accept: application/yaml
88
+ """
89
+ Then the following reply is sent:
90
+ """
91
+ 200 OK
92
+ content-type: application/yaml
93
+
94
+ - 10cf16b458f759e0d617f2f3d83599ff
95
+ """
96
+ When the following request is received:
97
+ """
98
+ GET /10cf16b458f759e0d617f2f3d83599ff?foo=bar HTTP/1.1
99
+ """
100
+ Then the following reply is sent:
101
+ """
102
+ 404 Not Found
103
+ """
104
+ When the following request is received:
105
+ """
106
+ DELETE /10cf16b458f759e0d617f2f3d83599ff HTTP/1.1
107
+ """
108
+ Then the following reply is sent:
109
+ """
110
+ 204 No Content
111
+ """
112
+ When the following request is received:
113
+ """
114
+ GET /10cf16b458f759e0d617f2f3d83599ff HTTP/1.1
115
+ """
116
+ Then the following reply is sent:
117
+ """
118
+ 404 Not Found
119
+ """
120
+
121
+ Scenario: Entries permutation
122
+ When the stream of `lenna.ascii` is received with the following headers:
123
+ """
124
+ POST / HTTP/1.1
125
+ accept: application/yaml
126
+ content-type: application/octet-stream
127
+ """
128
+ And the stream of `lenna.png` is received with the following headers:
129
+ """
130
+ POST / HTTP/1.1
131
+ accept: application/yaml
132
+ content-type: application/octet-stream
133
+ """
134
+ When the following request is received:
135
+ """
136
+ GET / HTTP/1.1
137
+ accept: application/yaml
138
+ """
139
+ Then the following reply is sent:
140
+ """
141
+ 200 OK
142
+ content-type: application/yaml
143
+
144
+ - 10cf16b458f759e0d617f2f3d83599ff
145
+ - 814a0034f5549e957ee61360d87457e5
146
+ """
147
+ When the following request is received:
148
+ """
149
+ PUT / HTTP/1.1
150
+ content-type: application/yaml
151
+
152
+ - 814a0034f5549e957ee61360d87457e5
153
+ - 10cf16b458f759e0d617f2f3d83599ff
154
+ """
155
+ Then the following reply is sent:
156
+ """
157
+ 204 No Content
158
+ """
159
+ When the following request is received:
160
+ """
161
+ GET / HTTP/1.1
162
+ accept: application/yaml
163
+ """
164
+ Then the following reply is sent:
165
+ """
166
+ 200 OK
167
+ content-type: application/yaml
168
+
169
+ - 814a0034f5549e957ee61360d87457e5
170
+ - 10cf16b458f759e0d617f2f3d83599ff
171
+ """
172
+
173
+ Scenario: Media type control
174
+ When the stream of `lenna.png` is received with the following headers:
175
+ """
176
+ POST /media/jpeg-or-png/ HTTP/1.1
177
+ content-type: image/jpeg
178
+ """
179
+ Then the following reply is sent:
180
+ """
181
+ 400 Bad Request
182
+ """
183
+ When the stream of `lenna.png` is received with the following headers:
184
+ """
185
+ POST /media/jpeg/ HTTP/1.1
186
+ """
187
+ Then the following reply is sent:
188
+ """
189
+ 415 Unsupported Media Type
190
+ """
191
+ When the stream of `lenna.png` is received with the following headers:
192
+ """
193
+ POST /media/jpeg-or-png/ HTTP/1.1
194
+ """
195
+ Then the following reply is sent:
196
+ """
197
+ 201 Created
198
+ """
199
+
200
+ Scenario Outline: Receiving <format> images
201
+ When the stream of `sample.<format>` is received with the following headers:
202
+ """
203
+ POST /media/images/ HTTP/1.1
204
+ """
205
+ Then the following reply is sent:
206
+ """
207
+ 201 Created
208
+ """
209
+ Examples:
210
+ | format |
211
+ | jpeg |
212
+ | jxl |
213
+ | gif |
214
+ | heic |
215
+ | avif |
216
+ | webp |
217
+
218
+ Scenario: Fetching non-existent BLOB
219
+ When the following request is received:
220
+ """
221
+ GET /whatever HTTP/1.1
222
+ """
223
+ Then the following reply is sent:
224
+ """
225
+ 404 Not Found
226
+ """
227
+
228
+ Scenario: Fetching a BLOB with trailing slash
229
+ When the stream of `lenna.ascii` is received with the following headers:
230
+ """
231
+ POST / HTTP/1.1
232
+ accept: application/yaml
233
+ content-type: application/octet-stream
234
+ """
235
+ And the following request is received:
236
+ """
237
+ GET /10cf16b458f759e0d617f2f3d83599ff/ HTTP/1.1
238
+ accept: text/plain
239
+ """
240
+ Then the following reply is sent:
241
+ """
242
+ 404 Not Found
243
+ content-type: text/plain
244
+
245
+ Trailing slash is redundant.
246
+ """
247
+
248
+ Scenario: Accessing an Entry and the original BLOLB
249
+ Given the annotation:
250
+ """yaml
251
+ debug: true
252
+ /:
253
+ auth:anonymous: true
254
+ octets:context: octets
255
+ POST:
256
+ octets:store: ~
257
+ /*:
258
+ GET:
259
+ octets:fetch:
260
+ meta: true
261
+ blob: false
262
+ """
263
+ When the stream of `lenna.ascii` is received with the following headers:
264
+ """
265
+ POST / HTTP/1.1
266
+ """
267
+ Then the following reply is sent:
268
+ """
269
+ 201 Created
270
+ """
271
+ When the following request is received:
272
+ """
273
+ GET /10cf16b458f759e0d617f2f3d83599ff:meta HTTP/1.1
274
+ accept: application/yaml
275
+ """
276
+ Then the following reply is sent:
277
+ """
278
+ 200 OK
279
+ content-type: application/yaml
280
+ content-length: 124
281
+
282
+ id: 10cf16b458f759e0d617f2f3d83599ff
283
+ type: application/octet-stream
284
+ size: 8169
285
+ """
286
+ When the following request is received:
287
+ """
288
+ GET /10cf16b458f759e0d617f2f3d83599ff HTTP/1.1
289
+ """
290
+ Then the following reply is sent:
291
+ """
292
+ 403 Forbidden
293
+
294
+ BLOB variant must be specified.
295
+ """
@@ -0,0 +1,114 @@
1
+ Feature: Octets storage workflows
2
+
3
+ Scenario: Running a workflow
4
+ Given the `octets.tester` is running
5
+ Given the annotation:
6
+ """yaml
7
+ /:
8
+ auth:anonymous: true
9
+ octets:context: octets
10
+ POST:
11
+ octets:store:
12
+ workflow:
13
+ - add-foo: octets.tester.foo
14
+ add-bar: octets.tester.bar
15
+ - add-baz: octets.tester.baz
16
+ - diversify: octets.tester.diversify
17
+ /*:
18
+ GET:
19
+ octets:fetch:
20
+ meta: true
21
+ """
22
+ When the stream of `lenna.ascii` is received with the following headers:
23
+ """
24
+ POST / HTTP/1.1
25
+ accept: application/yaml
26
+ content-type: application/octet-stream
27
+ """
28
+ Then the following reply is sent:
29
+ """
30
+ 201 Created
31
+ content-type: multipart/yaml; boundary=cut
32
+
33
+ --cut
34
+ id: 10cf16b458f759e0d617f2f3d83599ff
35
+ type: application/octet-stream
36
+ size: 8169
37
+ --cut
38
+ add-foo: null
39
+ --cut
40
+ add-bar:
41
+ bar: baz
42
+ --cut
43
+ add-baz: null
44
+ --cut
45
+ diversify: null
46
+ --cut--
47
+ """
48
+ When the following request is received:
49
+ """
50
+ GET /10cf16b458f759e0d617f2f3d83599ff:meta HTTP/1.1
51
+ accept: application/yaml
52
+ """
53
+ Then the following reply is sent:
54
+ """
55
+ 200 OK
56
+ content-type: application/yaml
57
+
58
+ id: 10cf16b458f759e0d617f2f3d83599ff
59
+ type: application/octet-stream
60
+ size: 8169
61
+ meta:
62
+ foo: bar
63
+ bar: baz
64
+ baz: qux
65
+ """
66
+ When the following request is received:
67
+ """
68
+ GET /10cf16b458f759e0d617f2f3d83599ff.hello.png HTTP/1.1
69
+ """
70
+ Then the stream equals to `lenna.png` is sent with the following headers:
71
+ """
72
+ 200 OK
73
+ content-type: image/png
74
+ content-length: 473831
75
+ """
76
+
77
+ Scenario: Getting error when adding metadata to a file
78
+ Given the `octets.tester` is running
79
+ Given the annotation:
80
+ """yaml
81
+ /:
82
+ auth:anonymous: true
83
+ octets:context: octets
84
+ POST:
85
+ octets:store:
86
+ workflow:
87
+ add-foo: octets.tester.foo
88
+ add-bar: octets.tester.err
89
+ add-baz: octets.tester.baz
90
+ """
91
+ When the stream of `lenna.ascii` is received with the following headers:
92
+ """
93
+ POST / HTTP/1.1
94
+ accept: application/yaml
95
+ content-type: application/octet-stream
96
+ """
97
+ Then the following reply is sent:
98
+ """
99
+ 201 Created
100
+ content-type: multipart/yaml; boundary=cut
101
+
102
+ --cut
103
+ id: 10cf16b458f759e0d617f2f3d83599ff
104
+ type: application/octet-stream
105
+ size: 8169
106
+ --cut
107
+ add-foo: null
108
+ --cut
109
+ error:
110
+ step: add-bar
111
+ code: ERROR
112
+ message: Something went wrong
113
+ --cut--
114
+ """
@@ -47,3 +47,43 @@ Feature: Routes
47
47
 
48
48
  Hello
49
49
  """
50
+
51
+ Scenario: Wildcard routes
52
+ Given the `greeter` is running with the following manifest:
53
+ """yaml
54
+ exposition:
55
+ /*:
56
+ GET: greet
57
+ /foo/*/bar:
58
+ GET: greet
59
+ """
60
+ When the following request is received:
61
+ """
62
+ GET /greeter/baz/ HTTP/1.1
63
+ accept: text/plain
64
+ """
65
+ Then the following reply is sent:
66
+ """
67
+ 200 OK
68
+
69
+ Hello
70
+ """
71
+ When the following request is received:
72
+ """
73
+ GET /greeter/baz/qux/ HTTP/1.1
74
+ """
75
+ Then the following reply is sent:
76
+ """
77
+ 404 Not Found
78
+ """
79
+ When the following request is received:
80
+ """
81
+ GET /greeter/foo/baz/bar/ HTTP/1.1
82
+ accept: text/plain
83
+ """
84
+ Then the following reply is sent:
85
+ """
86
+ 200 OK
87
+
88
+ Hello
89
+ """
@@ -1,9 +1,10 @@
1
- import * as assert from 'assert'
2
- import { AssertionError } from 'assert'
3
- import { binding, when, then } from 'cucumber-tsflow'
1
+ import { AssertionError } from 'node:assert'
2
+ import { binding, then, when } from 'cucumber-tsflow'
4
3
  import * as http from '@toa.io/http'
5
4
  import { trim } from '@toa.io/generic'
6
- import { Parameters } from './parameters'
5
+ import { buffer } from '@toa.io/streams'
6
+ import { open } from '../../../storages/source/test/util'
7
+ import { Parameters } from './Parameters'
7
8
  import { Gateway } from './Gateway'
8
9
 
9
10
  @binding([Gateway, Parameters])
@@ -47,7 +48,11 @@ export class HTTP {
47
48
  const match = this.response.match(rx)
48
49
 
49
50
  if (match === null)
50
- throw new AssertionError({ message: `Response is missing '${line}'\n${this.response}` })
51
+ throw new AssertionError({
52
+ message: `Response is missing '${line}'`,
53
+ expected: line,
54
+ actual: this.response
55
+ })
51
56
 
52
57
  Object.assign(this.variables, match.groups)
53
58
  }
@@ -62,9 +67,54 @@ export class HTTP {
62
67
 
63
68
  const includes = this.response.includes(line)
64
69
 
65
- assert.equal(includes, false, `Response contains '${line}'\n${this.response}`)
70
+ if (includes)
71
+ throw new AssertionError({
72
+ message: `Response contains '${line}'`,
73
+ expected: line,
74
+ actual: this.response
75
+ })
66
76
  }
67
77
  }
78
+
79
+ @when('the stream of `{word}` is received with the following headers:')
80
+ public async streamRequest (filename: string, head: string): Promise<any> {
81
+ head = trim(head) + '\n\n'
82
+
83
+ await this.gateway.start()
84
+
85
+ const { url, method, headers } = http.parse.request(head)
86
+ const href = new URL(url, this.origin).href
87
+ const body = open(filename)
88
+
89
+ headers.connection = 'close' // required for interrupted streams
90
+
91
+ const request = {
92
+ method,
93
+ headers,
94
+ body: body as unknown as ReadableStream,
95
+ duplex: 'half'
96
+ }
97
+
98
+ try {
99
+ const response = await fetch(href, request)
100
+
101
+ this.response = await http.parse.response(response)
102
+ } catch (e: any) {
103
+ console.error(e)
104
+ console.error(e.cause)
105
+
106
+ throw e
107
+ }
108
+ }
109
+
110
+ @then('the stream equals to `{word}` is sent with the following headers:')
111
+ public async responseStreamMatch (filename: string, head: string): Promise<any> {
112
+ const buf = await buffer(open(filename))
113
+ const text = buf.toString('utf8')
114
+ const expected = head + '\n\n' + text
115
+
116
+ this.responseIncludes(expected)
117
+ }
68
118
  }
69
119
 
70
120
  const CAPTURE = /\\\$\\{\\{ (?<name>[A-Za-z_]{0,32}) \\}\\}/g
@@ -4,9 +4,12 @@ export class Parameters {
4
4
  public readonly origin: string
5
5
 
6
6
  public constructor () {
7
- this.origin = 'http://localhost:8000'
7
+ this.origin = 'http://127.0.0.1:8000'
8
8
  }
9
9
  }
10
10
 
11
- setDefaultTimeout(10 * 1000)
11
+ setDefaultTimeout(30 * 1000)
12
12
  process.env.TOA_DEV = '1'
13
+
14
+ // { octets: tmp:///exposition-octets }
15
+ process.env.TOA_STORAGES = '3gABpm9jdGV0c7h0bXA6Ly8vZXhwb3NpdGlvbi1vY3RldHM='
@@ -1,16 +1,18 @@
1
1
  import { join } from 'node:path'
2
- import { directory } from '@toa.io/filesystem'
2
+ import { tmpdir } from 'node:os'
3
+ import { mkdtemp, copy } from 'fs-extra'
3
4
  import * as yaml from '@toa.io/yaml'
4
5
 
5
6
  export class Workspace {
6
7
  private root: string = devnull
7
8
 
8
9
  public static exists
9
- (target: Workspace, key: string, descriptor: PropertyDescriptor): PropertyDescriptor {
10
+ (_0: unknown, _1: unknown, descriptor: PropertyDescriptor): PropertyDescriptor {
10
11
  const method = descriptor.value
11
12
 
12
13
  descriptor.value = async function (this: Workspace, ...args: any[]): Promise<any> {
13
- if (this.root === devnull) this.root = await directory.temp()
14
+ if (this.root === devnull) this.root =
15
+ await mkdtemp(join(tmpdir(), Math.random().toString(36).slice(2)))
14
16
 
15
17
  return method.apply(this, args)
16
18
  }
@@ -23,9 +25,10 @@ export class Workspace {
23
25
  const source = join(__dirname, 'components', name)
24
26
  const target = join(this.root, name)
25
27
 
26
- await directory.copy(source, target)
28
+ await copy(source, target)
27
29
 
28
- if (patch !== undefined) await this.patchManifest(target, patch)
30
+ if (patch !== undefined)
31
+ await this.patchManifest(target, patch)
29
32
 
30
33
  return target
31
34
  }
@@ -0,0 +1,15 @@
1
+ namespace: octets
2
+ name: tester
3
+
4
+ storages: octets
5
+
6
+ operations:
7
+ foo: &operation
8
+ input:
9
+ storage*: string
10
+ path*: string
11
+ entry: object
12
+ bar: *operation
13
+ baz: *operation
14
+ err: *operation
15
+ diversify: *operation
@@ -0,0 +1,12 @@
1
+ 'use strict'
2
+
3
+ import { setTimeout } from 'node:timers/promises'
4
+
5
+ async function bar (input, context) {
6
+ await setTimeout(10)
7
+ await context.storages.octets.annotate(input.path, 'bar', 'baz')
8
+
9
+ return { bar: 'baz' }
10
+ }
11
+
12
+ exports.effect = bar
@@ -0,0 +1,11 @@
1
+ 'use strict'
2
+
3
+ import { setTimeout } from 'node:timers/promises'
4
+
5
+ async function baz (input, context) {
6
+ await setTimeout(30)
7
+
8
+ return context.storages.octets.annotate(input.path, 'baz', 'qux')
9
+ }
10
+
11
+ exports.effect = baz
@@ -0,0 +1,14 @@
1
+ 'use strict'
2
+
3
+ const { join } = require('node:path')
4
+ const { createReadStream } = require('node:fs')
5
+
6
+ const lenna = join(__dirname, 'lenna.png')
7
+
8
+ async function diversify (input, context) {
9
+ const stream = createReadStream(lenna)
10
+
11
+ return context.storages[input.storage].diversify(input.path, 'hello.png', stream)
12
+ }
13
+
14
+ exports.effect = diversify
@@ -0,0 +1,16 @@
1
+ 'use strict'
2
+
3
+ import { setTimeout } from 'node:timers/promises'
4
+
5
+ async function err (_) {
6
+ await setTimeout(20)
7
+
8
+ const err = Object.create(Error.prototype)
9
+
10
+ err.code = 'ERROR'
11
+ err.message = 'Something went wrong'
12
+
13
+ return err
14
+ }
15
+
16
+ exports.effect = err