@toa.io/extensions.storages 0.22.0 → 0.23.0-dev.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/extensions.storages",
3
- "version": "0.22.0",
3
+ "version": "0.23.0-dev.0",
4
4
  "description": "Toa Storages",
5
5
  "author": "temich <tema.gurtovoy@gmail.com>",
6
6
  "homepage": "https://github.com/toa-io/toa#readme",
@@ -25,14 +25,18 @@
25
25
  "transpile": "rm -rf transpiled && npx tsc"
26
26
  },
27
27
  "devDependencies": {
28
- "@toa.io/match": "0.3.0",
29
- "@toa.io/streams": "0.1.0-alpha.2",
30
- "@types/fs-extra": "11.0.3"
28
+ "@toa.io/streams": "0.23.0-dev.0",
29
+ "@types/fs-extra": "11.0.3",
30
+ "dotenv": "16.3.1"
31
31
  },
32
32
  "dependencies": {
33
+ "@aws-sdk/client-s3": "3.435.0",
34
+ "@aws-sdk/lib-storage": "3.437.0",
33
35
  "@toa.io/generic": "0.20.0-alpha.2",
36
+ "error-value": "0.3.0",
34
37
  "fs-extra": "11.1.1",
38
+ "matchacho": "0.3.5",
35
39
  "msgpackr": "1.9.9"
36
40
  },
37
- "gitHead": "c463348c8eb54a43f7755ec8aeba9acafb56d53b"
41
+ "gitHead": "19d4af8ff3b8a1a8f191644f44113c88de4bb404"
38
42
  }
package/readme.md CHANGED
@@ -47,16 +47,26 @@ async function effect (_, context) {
47
47
 
48
48
  > `Maybe<T> = T | Error`
49
49
 
50
- #### `async put(path: string, stream: Readable, type?: string): Maybe<Entry>`
50
+ #### `async put(path: string, stream: Readable, type?: TypeControl): Maybe<Entry>`
51
+
52
+ ```
53
+ interface TypeControl {
54
+ claim?: string
55
+ accept?: string
56
+ }
57
+ ```
51
58
 
52
59
  Add a BLOB to the storage and create an entry under specified `path`.
53
60
 
54
61
  BLOB type is identified
55
62
  using [magick numbers](https://github.com/sindresorhus/file-type).
56
- If the `type` argument is specified and does not match the BLOB type, then a `TYPE_MISMATCH` error
57
- is returned.
58
- If the BLOB type cannot be identified
59
- and the value of `type` is not in the list of known types, then the given value is used.
63
+
64
+ If the `type` argument is specified and the value of the `claim` does not match the detected BLOB type, then
65
+ a `TYPE_MISMATCH` error is returned.
66
+ If the BLOB type cannot be identified and the value of the `claim` is not in the list of known types, then the given
67
+ value is used.
68
+ If the list of [acceptable types](https://datatracker.ietf.org/doc/html/rfc2616#section-14.1) is passed and the type of
69
+ the BLOB does not match any of its values, then a `NOT_ACCEPTABLE` error is returned.
60
70
 
61
71
  Known types
62
72
  are: `image/jpeg`, `image/png`, `image/gif`, `image/webp`, `image/heic`, `image/jxl`, `image/avif`.
@@ -89,7 +99,7 @@ Delete the entry specified by `path`.
89
99
 
90
100
  Get ordered list of `id`s of entries in under the `path`.
91
101
 
92
- #### `async reorder(path: string, ids: string[]): Maybe<void>`
102
+ #### `async permute(path: string, ids: string[]): Maybe<void>`
93
103
 
94
104
  Reorder entries under the `path`.
95
105
 
@@ -116,15 +126,19 @@ Set a `key` property in the `meta` of the entry specified by `path`.
116
126
 
117
127
  Storage uses underlying providers to store BLOBs and entries.
118
128
 
119
- Custom providers are not supported yet.
129
+ Custom providers are not supported.
120
130
 
121
131
  ### Amazon S3
122
132
 
123
- Annotation value format is `s3://{region}/{bucket}`.
133
+ Annotation value formats is `s3://{region}/{bucket}?endpoint={endpoint}`.
124
134
 
125
135
  Requires secrets for the access key and secret key.
136
+ See [`toa conceal`](/runtime/cli/readme.md#conceal) for deployment
137
+ and [`toa env`](/runtime/cli/readme.md#env)
138
+ for local environment.
139
+ `endpoint` parameter is optional.
126
140
 
127
- `s3://us-east-1/my-bucket`
141
+ `s3://us-east-1/my-bucket?endpoint=http://s3.my-instance.com:4566`
128
142
 
129
143
  ### Filesystem
130
144
 
@@ -142,8 +156,8 @@ Annotation value format is `tmp:///{path}`.
142
156
 
143
157
  ## Deduplication
144
158
 
145
- BLOBs are stored in the underlying storage with their checksum as the key, ensuring that identical BLOBs
146
- are stored only once.
159
+ BLOBs are stored in the underlying storage with their checksum as the key, ensuring that identical
160
+ BLOBs are stored only once.
147
161
  Variants, on the other hand, are not deduplicated across different entries.
148
162
 
149
163
  Underlying directory structure:
@@ -191,5 +205,6 @@ storages:
191
205
 
192
206
  ## Secrets
193
207
 
194
- Secrets declared by storage providers can be deployed by [`toa conceal`](/runtime/cli/readme.md#conceal),
208
+ Secrets declared by storage providers can be deployed
209
+ by [`toa conceal`](/runtime/cli/readme.md#conceal),
195
210
  or set locally by [`toa env`](/runtime/cli/readme.md#env).
package/source/Scanner.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { PassThrough, type TransformCallback } from 'node:stream'
2
2
  import { createHash } from 'node:crypto'
3
+ import { negotiate } from '@toa.io/http'
4
+ import { Err } from 'error-value'
3
5
 
4
6
  export class Scanner extends PassThrough {
5
7
  public size = 0
@@ -7,15 +9,17 @@ export class Scanner extends PassThrough {
7
9
  public error: Error | null = null
8
10
 
9
11
  private readonly hash = createHash('md5')
10
- private readonly assertion: string | undefined
12
+ private readonly claim?: string
13
+ private readonly accept?: string
11
14
  private position = 0
12
15
  private detected = false
13
16
  private readonly chunks: Buffer[] = []
14
17
 
15
- public constructor (assertion?: string) {
18
+ public constructor (control?: TypeControl) {
16
19
  super()
17
20
 
18
- this.assertion = assertion
21
+ this.claim = control?.claim
22
+ this.accept = control?.accept
19
23
  }
20
24
 
21
25
  public digest (): string {
@@ -47,33 +51,47 @@ export class Scanner extends PassThrough {
47
51
  }
48
52
 
49
53
  private complete (): void {
50
- this.detected = true
51
-
52
54
  const header = Buffer.concat(this.chunks).toString('hex')
53
55
 
54
56
  const signature = SIGNATURES
55
57
  .find(({ hex, off }) => header.slice(off, off + hex.length) === hex)
56
58
 
57
- this.verify(signature)
59
+ const type = signature?.type ?? this.claim
58
60
 
59
- const value = signature?.type ?? this.assertion
61
+ if (type !== undefined) {
62
+ this.match(type)
63
+ this.type = type
64
+ }
60
65
 
61
- if (value !== undefined)
62
- this.type = value
66
+ this.verify(signature)
67
+ this.detected = true
63
68
  }
64
69
 
65
70
  private verify (signature: Signature | undefined): void {
66
- if (this.assertion === undefined || this.assertion === 'application/octet-stream')
71
+ if (this.claim === undefined || this.claim === 'application/octet-stream')
67
72
  return
68
73
 
69
74
  const mismatch = signature === undefined
70
- ? KNOWN_TYPES.has(this.assertion)
71
- : this.assertion !== signature.type
75
+ ? KNOWN_TYPES.has(this.claim)
76
+ : this.claim !== signature.type
72
77
 
73
- if (mismatch) {
74
- this.error = ERR_TYPE_MISMATCH
75
- this.end()
76
- }
78
+ if (mismatch)
79
+ this.interrupt(ERR_TYPE_MISMATCH)
80
+ }
81
+
82
+ private match (type: string): void {
83
+ if (this.accept === undefined)
84
+ return
85
+
86
+ const unacceptable = negotiate(this.accept, [type]) === null
87
+
88
+ if (unacceptable)
89
+ this.interrupt(ERR_NOT_ACCEPTABLE)
90
+ }
91
+
92
+ private interrupt (error: Error): void {
93
+ this.error = error
94
+ this.end()
77
95
  }
78
96
  }
79
97
 
@@ -100,7 +118,13 @@ const HEADER_SIZE = SIGNATURES
100
118
 
101
119
  const KNOWN_TYPES = new Set(SIGNATURES.map(({ type }) => type))
102
120
 
103
- const ERR_TYPE_MISMATCH = new Error('TYPE_MISMATCH')
121
+ const ERR_TYPE_MISMATCH = Err('TYPE_MISMATCH')
122
+ const ERR_NOT_ACCEPTABLE = Err('NOT_ACCEPTABLE')
123
+
124
+ export interface TypeControl {
125
+ claim?: string
126
+ accept?: string
127
+ }
104
128
 
105
129
  interface Signature {
106
130
  hex: string