@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 +9 -5
- package/readme.md +27 -12
- package/source/Scanner.ts +41 -17
- package/source/Storage.test.ts +312 -289
- package/source/Storage.ts +6 -4
- package/source/deployment.ts +4 -0
- package/source/index.ts +2 -0
- package/source/manifest.ts +2 -2
- package/source/providers/S3.ts +91 -0
- package/source/providers/index.test.ts +14 -9
- package/source/providers/index.ts +3 -1
- package/source/providers/readme.md +6 -1
- package/source/test/.env.example +1 -0
- package/source/test/util.ts +74 -0
- package/source/.test/util.ts +0 -50
- /package/source/{.test → test}/albert.jpg +0 -0
- /package/source/{.test → test}/empty.txt +0 -0
- /package/source/{.test → test}/lenna.ascii +0 -0
- /package/source/{.test → test}/lenna.png +0 -0
- /package/source/{.test → test}/sample.avif +0 -0
- /package/source/{.test → test}/sample.gif +0 -0
- /package/source/{.test → test}/sample.heic +0 -0
- /package/source/{.test → test}/sample.jpeg +0 -0
- /package/source/{.test → test}/sample.jxl +0 -0
- /package/source/{.test → test}/sample.webp +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/extensions.storages",
|
|
3
|
-
"version": "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/
|
|
29
|
-
"@
|
|
30
|
-
"
|
|
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": "
|
|
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?:
|
|
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
|
-
|
|
57
|
-
is
|
|
58
|
-
|
|
59
|
-
and the value of `
|
|
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
|
|
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
|
|
129
|
+
Custom providers are not supported.
|
|
120
130
|
|
|
121
131
|
### Amazon S3
|
|
122
132
|
|
|
123
|
-
Annotation value
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
18
|
+
public constructor (control?: TypeControl) {
|
|
16
19
|
super()
|
|
17
20
|
|
|
18
|
-
this.
|
|
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.
|
|
59
|
+
const type = signature?.type ?? this.claim
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
if (type !== undefined) {
|
|
62
|
+
this.match(type)
|
|
63
|
+
this.type = type
|
|
64
|
+
}
|
|
60
65
|
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
this.verify(signature)
|
|
67
|
+
this.detected = true
|
|
63
68
|
}
|
|
64
69
|
|
|
65
70
|
private verify (signature: Signature | undefined): void {
|
|
66
|
-
if (this.
|
|
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.
|
|
71
|
-
: this.
|
|
75
|
+
? KNOWN_TYPES.has(this.claim)
|
|
76
|
+
: this.claim !== signature.type
|
|
72
77
|
|
|
73
|
-
if (mismatch)
|
|
74
|
-
this.
|
|
75
|
-
|
|
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 =
|
|
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
|