@toa.io/extensions.storages 1.0.0-alpha.21 → 1.0.0-alpha.219
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 +14 -14
- package/readme.md +96 -51
- package/schemas/annotation.cos.yaml +1 -0
- package/schemas/cloudinary.cos.yaml +37 -0
- package/schemas/fs.cos.yaml +2 -0
- package/schemas/s3.cos.yaml +1 -0
- package/schemas/tmp.cos.yaml +0 -1
- package/source/Entry.ts +13 -11
- package/source/Factory.ts +16 -9
- package/source/Provider.ts +22 -19
- package/source/Scanner.ts +58 -26
- package/source/Secrets.ts +6 -0
- package/source/Storage.test.ts +118 -274
- package/source/Storage.ts +62 -207
- package/source/deployment.ts +52 -16
- package/source/errors.ts +3 -0
- package/source/index.ts +3 -1
- package/source/manifest.ts +7 -6
- package/source/providers/Cloudinary.ts +320 -0
- package/source/providers/Declaration.ts +2 -1
- package/source/providers/FileSystem.ts +79 -25
- package/source/providers/S3.ts +81 -80
- package/source/providers/Temporary.ts +2 -3
- package/source/providers/Test.ts +4 -4
- package/source/providers/index.test.ts +102 -76
- package/source/providers/index.ts +10 -4
- package/source/schemas.ts +1 -0
- package/source/test/.env.example +4 -0
- package/source/test/arny.jpg +0 -0
- package/source/test/lenna.48x48.jpeg +0 -0
- package/source/test/plank.mp4 +0 -0
- package/source/test/sample.avi +0 -0
- package/source/test/sample.wav +0 -0
- package/source/test/sport.mp4 +0 -0
- package/source/test/util.ts +62 -12
- package/transpiled/Entry.d.ts +15 -11
- package/transpiled/Factory.js +11 -5
- package/transpiled/Factory.js.map +1 -1
- package/transpiled/Provider.d.ts +17 -16
- package/transpiled/Provider.js +6 -4
- package/transpiled/Provider.js.map +1 -1
- package/transpiled/Scanner.d.ts +6 -2
- package/transpiled/Scanner.js +49 -22
- package/transpiled/Scanner.js.map +1 -1
- package/transpiled/Secrets.d.ts +5 -0
- package/transpiled/Secrets.js +3 -0
- package/transpiled/Secrets.js.map +1 -0
- package/transpiled/Storage.d.ts +13 -21
- package/transpiled/Storage.js +52 -155
- package/transpiled/Storage.js.map +1 -1
- package/transpiled/deployment.d.ts +1 -1
- package/transpiled/deployment.js +43 -16
- package/transpiled/deployment.js.map +1 -1
- package/transpiled/errors.d.ts +2 -0
- package/transpiled/errors.js +6 -0
- package/transpiled/errors.js.map +1 -0
- package/transpiled/index.d.ts +3 -1
- package/transpiled/manifest.js +5 -2
- package/transpiled/manifest.js.map +1 -1
- package/transpiled/providers/Cloudinary.d.ts +51 -0
- package/transpiled/providers/Cloudinary.js +223 -0
- package/transpiled/providers/Cloudinary.js.map +1 -0
- package/transpiled/providers/Declaration.d.ts +3 -2
- package/transpiled/providers/FileSystem.d.ts +15 -7
- package/transpiled/providers/FileSystem.js +64 -24
- package/transpiled/providers/FileSystem.js.map +1 -1
- package/transpiled/providers/S3.d.ts +14 -14
- package/transpiled/providers/S3.js +62 -60
- package/transpiled/providers/S3.js.map +1 -1
- package/transpiled/providers/Temporary.d.ts +1 -2
- package/transpiled/providers/Temporary.js +2 -2
- package/transpiled/providers/Temporary.js.map +1 -1
- package/transpiled/providers/Test.d.ts +3 -3
- package/transpiled/providers/Test.js +2 -2
- package/transpiled/providers/Test.js.map +1 -1
- package/transpiled/providers/index.d.ts +7 -2
- package/transpiled/providers/index.js +2 -2
- package/transpiled/providers/index.js.map +1 -1
- package/transpiled/schemas.d.ts +1 -0
- package/transpiled/schemas.js +2 -1
- package/transpiled/schemas.js.map +1 -1
- package/transpiled/test/util.d.ts +89 -5
- package/transpiled/test/util.js +51 -4
- package/transpiled/test/util.js.map +1 -1
- package/transpiled/tsconfig.tsbuildinfo +1 -1
- package/source/providers/FileSystem.test.ts +0 -5
- package/source/providers/Memory.ts +0 -41
- package/source/providers/S3.test.ts +0 -133
- package/transpiled/providers/Memory.d.ts +0 -13
- package/transpiled/providers/Memory.js +0 -60
- package/transpiled/providers/Memory.js.map +0 -1
package/source/Storage.ts
CHANGED
|
@@ -1,245 +1,100 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { decode, encode } from 'msgpackr'
|
|
4
|
-
import { buffer, newid } from '@toa.io/generic'
|
|
5
|
-
import { Err } from 'error-value'
|
|
1
|
+
import { basename, join } from 'node:path'
|
|
2
|
+
import { randomUUID } from 'node:crypto'
|
|
6
3
|
import { Scanner } from './Scanner'
|
|
4
|
+
import type { Readable } from 'node:stream'
|
|
5
|
+
import type { Attributes, Entry, Stream } from './Entry'
|
|
7
6
|
import type { ScanOptions } from './Scanner'
|
|
8
7
|
import type { Provider } from './Provider'
|
|
9
|
-
import type { Entry } from './Entry'
|
|
10
8
|
|
|
11
|
-
export class Storage {
|
|
12
|
-
private readonly provider:
|
|
9
|
+
export class Storage<T extends Provider = Provider> {
|
|
10
|
+
private readonly provider: T
|
|
13
11
|
|
|
14
|
-
public constructor (provider:
|
|
12
|
+
public constructor (provider: T) {
|
|
15
13
|
this.provider = provider
|
|
16
14
|
}
|
|
17
15
|
|
|
18
|
-
public
|
|
19
|
-
|
|
20
|
-
const pipe = stream.pipe(scanner)
|
|
21
|
-
const tempname = await this.transit(pipe)
|
|
22
|
-
|
|
23
|
-
if (scanner.error !== null)
|
|
24
|
-
return scanner.error
|
|
25
|
-
|
|
26
|
-
const id = scanner.digest()
|
|
27
|
-
|
|
28
|
-
await this.persist(tempname, id)
|
|
29
|
-
|
|
30
|
-
return await this.create(path, id, scanner.size, scanner.type, options?.meta)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
public async get (path: string): Maybe<Entry> {
|
|
34
|
-
const metapath = posix.join(ENTRIES, path, META)
|
|
35
|
-
const result = await this.provider.get(metapath)
|
|
36
|
-
|
|
37
|
-
if (result === null) return ERR_NOT_FOUND
|
|
38
|
-
else return decode(await buffer(result))
|
|
16
|
+
public options (): T['options'] {
|
|
17
|
+
return this.provider.options
|
|
39
18
|
}
|
|
40
19
|
|
|
41
|
-
public async
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
20
|
+
public async put (path: string, stream: Readable, options?: Options): Maybe<Entry> {
|
|
21
|
+
const scanner = new Scanner(options)
|
|
22
|
+
const pipe = stream.pipe(scanner).on('error', () => undefined)
|
|
23
|
+
const id = options?.id ?? randomUUID().replace(/-/g, '')
|
|
24
|
+
const location = this.locate(path, id)
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Provider can return or throw an error.
|
|
28
|
+
* If thrown error is TYPE_MISMATCH from the Scanner, it should be returned.
|
|
29
|
+
*/
|
|
30
|
+
const error: Error | undefined = await this.provider.put(location, pipe).catch((error: any) => {
|
|
31
|
+
if (error === scanner.error) return error
|
|
32
|
+
else throw error
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
if (error instanceof Error)
|
|
36
|
+
return error
|
|
37
|
+
|
|
38
|
+
const metadata: Entry = {
|
|
39
|
+
id,
|
|
40
|
+
size: scanner.size,
|
|
41
|
+
type: scanner.type,
|
|
42
|
+
checksum: scanner.digest(),
|
|
43
|
+
created: new Date().toISOString(),
|
|
44
|
+
attributes: options?.attributes ?? {}
|
|
49
45
|
}
|
|
50
46
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
: posix.join(ENTRIES, rel, id, variant)
|
|
47
|
+
if (options?.origin !== undefined)
|
|
48
|
+
metadata.attributes.origin = options.origin
|
|
54
49
|
|
|
55
|
-
|
|
50
|
+
await this.provider.commit(location, metadata)
|
|
56
51
|
|
|
57
|
-
|
|
58
|
-
else return stream
|
|
52
|
+
return metadata
|
|
59
53
|
}
|
|
60
54
|
|
|
61
|
-
public async
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
if (entry instanceof Error)
|
|
65
|
-
return entry
|
|
55
|
+
public async get (path: string, options?: unknown): Maybe<Stream> {
|
|
56
|
+
const location = this.locate(path)
|
|
66
57
|
|
|
67
|
-
await this.
|
|
68
|
-
await this.provider.delete(posix.join(ENTRIES, path))
|
|
58
|
+
return await this.provider.get(location, options)
|
|
69
59
|
}
|
|
70
60
|
|
|
71
|
-
public async
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
return stream === null ? [] : decode(await buffer(stream))
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
public async permute (path: string, ids: string[]): Maybe<void> {
|
|
79
|
-
const unique = new Set(ids)
|
|
80
|
-
const dir = posix.join(ENTRIES, path)
|
|
81
|
-
const list = await this.list(path)
|
|
82
|
-
|
|
83
|
-
if (list.length !== ids.length || unique.size !== ids.length)
|
|
84
|
-
return ERR_PERMUTATION_MISMATCH
|
|
85
|
-
|
|
86
|
-
for (const id of ids)
|
|
87
|
-
if (!list.includes(id))
|
|
88
|
-
return ERR_PERMUTATION_MISMATCH
|
|
89
|
-
|
|
90
|
-
await this.provider.put(dir, LIST, Readable.from(encode(ids)))
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
public async conceal (path: string): Maybe<void> {
|
|
94
|
-
const { id, rel } = this.parse(path)
|
|
95
|
-
const dir = posix.join(ENTRIES, rel)
|
|
96
|
-
const list = await this.list(rel)
|
|
97
|
-
const index = list.indexOf(id)
|
|
98
|
-
|
|
99
|
-
if (index === -1)
|
|
100
|
-
return ERR_NOT_FOUND
|
|
101
|
-
|
|
102
|
-
list.splice(index, 1)
|
|
103
|
-
|
|
104
|
-
await this.provider.put(dir, LIST, Readable.from(encode(list)))
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
public async reveal (path: string): Maybe<void> {
|
|
108
|
-
const { id, rel } = this.parse(path)
|
|
109
|
-
|
|
110
|
-
return await this.enroll(rel, id)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
public async diversify (path: string, name: string, stream: Readable): Maybe<void> {
|
|
114
|
-
const scanner = new Scanner()
|
|
115
|
-
const pipe = stream.pipe(scanner)
|
|
116
|
-
|
|
117
|
-
await this.provider.put(posix.join(ENTRIES, path), name, pipe)
|
|
118
|
-
|
|
119
|
-
if (scanner.error !== null)
|
|
120
|
-
return scanner.error
|
|
121
|
-
|
|
122
|
-
const { size, type } = scanner
|
|
123
|
-
const entry = await this.get(path)
|
|
61
|
+
public async head (path: string): Promise<Maybe<Entry>> {
|
|
62
|
+
const id = basename(path).split('.')[0]
|
|
63
|
+
const location = this.locate(path)
|
|
64
|
+
const metadata = await this.provider.head(location)
|
|
124
65
|
|
|
125
|
-
if (
|
|
126
|
-
return
|
|
66
|
+
if (metadata instanceof Error)
|
|
67
|
+
return metadata
|
|
127
68
|
|
|
128
|
-
|
|
129
|
-
entry.variants.push({ name, size, type })
|
|
130
|
-
|
|
131
|
-
await this.save(path, entry)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
public async annotate (path: string, key: string, value?: unknown): Maybe<void> {
|
|
135
|
-
const entry = await this.get(path)
|
|
136
|
-
|
|
137
|
-
if (entry instanceof Error)
|
|
138
|
-
return entry
|
|
139
|
-
|
|
140
|
-
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
141
|
-
if (value === undefined) delete entry.meta[key]
|
|
142
|
-
else entry.meta[key] = value
|
|
143
|
-
|
|
144
|
-
await this.save(path, entry)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
private async transit (stream: Readable): Promise<string> {
|
|
148
|
-
const tempname = newid()
|
|
149
|
-
|
|
150
|
-
await this.provider.put(TEMP, tempname, stream)
|
|
151
|
-
|
|
152
|
-
return tempname
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
private async persist (tempname: string, id: string): Promise<void> {
|
|
156
|
-
const temp = posix.join(TEMP, tempname)
|
|
157
|
-
const blob = posix.join(BLOBs, id)
|
|
158
|
-
|
|
159
|
-
await this.provider.move(temp, blob)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// eslint-disable-next-line max-params
|
|
163
|
-
private async create
|
|
164
|
-
(path: string, id: string, size: number, type: string, meta: Meta = {}): Promise<Entry> {
|
|
165
|
-
const entry: Entry = {
|
|
69
|
+
return {
|
|
166
70
|
id,
|
|
167
|
-
|
|
168
|
-
type,
|
|
169
|
-
created: Date.now(),
|
|
170
|
-
variants: [],
|
|
171
|
-
meta
|
|
71
|
+
...metadata
|
|
172
72
|
}
|
|
173
|
-
|
|
174
|
-
const metafile = posix.join(path, entry.id)
|
|
175
|
-
const existing = await this.get(metafile)
|
|
176
|
-
|
|
177
|
-
if (existing instanceof Error)
|
|
178
|
-
await this.save(metafile, entry)
|
|
179
|
-
|
|
180
|
-
await this.enroll(path, id, true)
|
|
181
|
-
|
|
182
|
-
return entry
|
|
183
73
|
}
|
|
184
74
|
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
const stream = Readable.from(buffer)
|
|
75
|
+
public async delete (path: string): Maybe<void> {
|
|
76
|
+
const location = this.locate(path)
|
|
188
77
|
|
|
189
|
-
|
|
78
|
+
return this.provider.delete(location)
|
|
190
79
|
}
|
|
191
80
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const list = await this.list(path)
|
|
195
|
-
const index = list.indexOf(id)
|
|
196
|
-
|
|
197
|
-
if (index !== -1)
|
|
198
|
-
if (addition) list.splice(index, 1)
|
|
199
|
-
else return
|
|
200
|
-
else if (!addition) {
|
|
201
|
-
const entry = await this.get(posix.join(path, id))
|
|
202
|
-
|
|
203
|
-
if (entry instanceof Error)
|
|
204
|
-
return entry
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
list.push(id)
|
|
208
|
-
|
|
209
|
-
await this.provider.put(dir, LIST, Readable.from(encode(list)))
|
|
81
|
+
public path (): string | null {
|
|
82
|
+
return this.provider.root ?? null
|
|
210
83
|
}
|
|
211
84
|
|
|
212
|
-
private
|
|
213
|
-
|
|
214
|
-
const [id, ...rest] = last.split('.')
|
|
215
|
-
const variant = rest.length > 0 ? rest.join('.') : null
|
|
216
|
-
const rel = segments.reverse().join('/')
|
|
217
|
-
|
|
218
|
-
return { rel, id, variant }
|
|
85
|
+
private locate (...rel: string[]): string {
|
|
86
|
+
return join(ENTRIES, ...rel)
|
|
219
87
|
}
|
|
220
88
|
}
|
|
221
89
|
|
|
222
|
-
const
|
|
223
|
-
const ERR_PERMUTATION_MISMATCH = Err('PERMUTATION_MISMATCH')
|
|
224
|
-
|
|
225
|
-
const TEMP = '/temp'
|
|
226
|
-
const BLOBs = '/blobs'
|
|
227
|
-
const ENTRIES = '/entries'
|
|
228
|
-
const LIST = '.list'
|
|
229
|
-
const META = '.meta'
|
|
230
|
-
|
|
231
|
-
type Maybe<T> = Promise<T | Error>
|
|
232
|
-
|
|
233
|
-
interface Path {
|
|
234
|
-
rel: string
|
|
235
|
-
id: string
|
|
236
|
-
variant: string | null
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
type Meta = Record<string, string>
|
|
90
|
+
const ENTRIES = '/'
|
|
240
91
|
|
|
241
92
|
interface Options extends ScanOptions {
|
|
242
|
-
|
|
93
|
+
id?: string
|
|
94
|
+
origin?: string
|
|
95
|
+
attributes?: Attributes
|
|
243
96
|
}
|
|
244
97
|
|
|
98
|
+
type Maybe<T> = Promise<T | Error>
|
|
99
|
+
|
|
245
100
|
export type Storages = Record<string, Storage>
|
package/source/deployment.ts
CHANGED
|
@@ -3,28 +3,35 @@ import { encode } from '@toa.io/generic'
|
|
|
3
3
|
import { providers } from './providers'
|
|
4
4
|
import { validateAnnotation } from './Annotation'
|
|
5
5
|
import type { Annotation } from './Annotation'
|
|
6
|
-
import type { Dependency, Variable } from '@toa.io/operations'
|
|
6
|
+
import type { Dependency, Variable, Mounts } from '@toa.io/operations'
|
|
7
7
|
import type { context } from '@toa.io/norm'
|
|
8
8
|
|
|
9
|
-
export const
|
|
9
|
+
export const ENV_PREFIX = 'TOA_STORAGES'
|
|
10
10
|
|
|
11
11
|
export function deployment (instances: Instance[], annotation: unknown): Dependency {
|
|
12
12
|
validate(instances, annotation)
|
|
13
13
|
|
|
14
14
|
const value = encode(annotation)
|
|
15
|
-
const pointer: Variable = { name:
|
|
15
|
+
const pointer: Variable = { name: ENV_PREFIX, value }
|
|
16
16
|
const secrets = getSecrets(annotation)
|
|
17
|
+
const mounts = getMounts(instances, annotation)
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
const dependency: Dependency = { variables: { global: [pointer, ...secrets] } }
|
|
20
|
+
|
|
21
|
+
if (mounts !== null)
|
|
22
|
+
dependency.mounts = mounts
|
|
23
|
+
|
|
24
|
+
return dependency
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
function validate (instances: Instance[], annotation: unknown): asserts annotation is Annotation {
|
|
24
28
|
validateAnnotation(annotation)
|
|
25
29
|
|
|
26
|
-
for (const instance of instances)
|
|
30
|
+
for (const instance of instances) {
|
|
31
|
+
instance.manifest ??= []
|
|
32
|
+
|
|
27
33
|
contains(instance, annotation)
|
|
34
|
+
}
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
function contains (instance: Instance, annotation: Annotation): void {
|
|
@@ -40,18 +47,47 @@ function getSecrets (annotation: Annotation): Variable[] {
|
|
|
40
47
|
for (const [name, declaration] of Object.entries(annotation)) {
|
|
41
48
|
const Provider = providers[declaration.provider]
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
name:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
if (Provider.SECRETS !== undefined)
|
|
51
|
+
// eslint-disable-next-line max-depth
|
|
52
|
+
for (const secret of Provider.SECRETS)
|
|
53
|
+
secrets.push({
|
|
54
|
+
name: `${ENV_PREFIX}_${name}_${secret.name}`.toUpperCase(),
|
|
55
|
+
secret: {
|
|
56
|
+
name: `toa-storages-${name}`,
|
|
57
|
+
key: secret.name,
|
|
58
|
+
optional: secret.optional
|
|
59
|
+
}
|
|
60
|
+
})
|
|
52
61
|
}
|
|
53
62
|
|
|
54
63
|
return secrets
|
|
55
64
|
}
|
|
56
65
|
|
|
66
|
+
function getMounts (instances: Instance[], annotation: Annotation): Mounts | null {
|
|
67
|
+
let mounts: Mounts | null = null
|
|
68
|
+
|
|
69
|
+
for (const { locator, manifest } of instances)
|
|
70
|
+
for (const name of manifest) {
|
|
71
|
+
const declaration = annotation[name]
|
|
72
|
+
|
|
73
|
+
// eslint-disable-next-line max-depth
|
|
74
|
+
if (declaration.provider !== 'fs')
|
|
75
|
+
continue
|
|
76
|
+
|
|
77
|
+
// eslint-disable-next-line max-depth
|
|
78
|
+
if (declaration.claim !== undefined) {
|
|
79
|
+
mounts ??= {}
|
|
80
|
+
mounts[locator.label] ??= []
|
|
81
|
+
|
|
82
|
+
mounts[locator.label].push({
|
|
83
|
+
name,
|
|
84
|
+
path: declaration.path,
|
|
85
|
+
claim: declaration.claim
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return mounts
|
|
91
|
+
}
|
|
92
|
+
|
|
57
93
|
export type Instance = context.Dependency<string[]>
|
package/source/errors.ts
ADDED
package/source/index.ts
CHANGED
|
@@ -2,4 +2,6 @@ export { Factory } from './Factory'
|
|
|
2
2
|
export { deployment } from './deployment'
|
|
3
3
|
export { manifest } from './manifest'
|
|
4
4
|
|
|
5
|
-
export type { Entry } from './Entry'
|
|
5
|
+
export type { Entry, Stream } from './Entry'
|
|
6
|
+
export type { Storage } from './Storage'
|
|
7
|
+
export type * from './providers'
|
package/source/manifest.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { match } from 'matchacho'
|
|
2
|
-
|
|
3
1
|
export function manifest (manifest: string | string[] | null): string[] {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
if (manifest === null)
|
|
3
|
+
return []
|
|
4
|
+
|
|
5
|
+
if (typeof manifest === 'string')
|
|
6
|
+
return [manifest]
|
|
7
|
+
|
|
8
|
+
return manifest
|
|
8
9
|
}
|