@toa.io/extensions.storages 1.0.0-alpha.0 → 1.0.0-alpha.2
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 +12 -11
- package/readme.md +63 -19
- package/schemas/annotation.cos.yaml +10 -0
- package/schemas/fs.cos.yaml +9 -0
- package/schemas/mem.cos.yaml +6 -0
- package/schemas/s3.cos.yaml +16 -0
- package/schemas/test.cos.yaml +8 -0
- package/schemas/tmp.cos.yaml +9 -0
- package/source/Annotation.ts +39 -0
- package/source/Aspect.ts +6 -4
- package/source/Factory.ts +31 -28
- package/source/Provider.ts +30 -5
- package/source/Scanner.ts +3 -3
- package/source/Storage.test.ts +110 -105
- package/source/Storage.ts +13 -6
- package/source/deployment.ts +21 -29
- package/source/providers/Declaration.ts +10 -0
- package/source/providers/FileSystem.test.ts +1 -9
- package/source/providers/FileSystem.ts +20 -15
- package/source/providers/Memory.ts +41 -0
- package/source/providers/S3.test.ts +133 -0
- package/source/providers/S3.ts +114 -39
- package/source/providers/Temporary.ts +8 -6
- package/source/providers/Test.ts +8 -8
- package/source/providers/index.test.ts +24 -19
- package/source/providers/index.ts +10 -9
- package/source/providers/readme.md +1 -1
- package/source/schemas.test.ts +58 -0
- package/source/schemas.ts +15 -0
- package/source/test/util.ts +25 -54
- package/transpiled/Annotation.d.ts +3 -0
- package/transpiled/Annotation.js +57 -0
- package/transpiled/Annotation.js.map +1 -0
- package/transpiled/Aspect.d.ts +8 -0
- package/transpiled/Aspect.js +25 -0
- package/transpiled/Aspect.js.map +1 -0
- package/transpiled/Entry.d.ts +14 -0
- package/transpiled/Entry.js +3 -0
- package/transpiled/Entry.js.map +1 -0
- package/transpiled/Factory.d.ts +9 -0
- package/transpiled/Factory.js +53 -0
- package/transpiled/Factory.js.map +1 -0
- package/transpiled/Provider.d.ts +20 -0
- package/transpiled/Provider.js +36 -0
- package/transpiled/Provider.js.map +1 -0
- package/transpiled/Scanner.d.ts +26 -0
- package/transpiled/Scanner.js +98 -0
- package/transpiled/Scanner.js.map +1 -0
- package/transpiled/Storage.d.ts +32 -0
- package/transpiled/Storage.js +176 -0
- package/transpiled/Storage.js.map +1 -0
- package/transpiled/deployment.d.ts +5 -0
- package/transpiled/deployment.js +68 -0
- package/transpiled/deployment.js.map +1 -0
- package/transpiled/index.d.ts +4 -0
- package/transpiled/index.js +10 -0
- package/transpiled/index.js.map +1 -0
- package/transpiled/manifest.d.ts +1 -0
- package/transpiled/manifest.js +9 -0
- package/transpiled/manifest.js.map +1 -0
- package/transpiled/providers/Declaration.d.ts +14 -0
- package/transpiled/providers/Declaration.js +3 -0
- package/transpiled/providers/Declaration.js.map +1 -0
- package/transpiled/providers/FileSystem.d.ts +15 -0
- package/transpiled/providers/FileSystem.js +44 -0
- package/transpiled/providers/FileSystem.js.map +1 -0
- package/transpiled/providers/Memory.d.ts +13 -0
- package/transpiled/providers/Memory.js +60 -0
- package/transpiled/providers/Memory.js.map +1 -0
- package/transpiled/providers/S3.d.ts +27 -0
- package/transpiled/providers/S3.js +154 -0
- package/transpiled/providers/S3.js.map +1 -0
- package/transpiled/providers/Temporary.d.ts +8 -0
- package/transpiled/providers/Temporary.js +14 -0
- package/transpiled/providers/Temporary.js.map +1 -0
- package/transpiled/providers/Test.d.ts +6 -0
- package/transpiled/providers/Test.js +15 -0
- package/transpiled/providers/Test.js.map +1 -0
- package/transpiled/providers/index.d.ts +13 -0
- package/transpiled/providers/index.js +16 -0
- package/transpiled/providers/index.js.map +1 -0
- package/transpiled/schemas.d.ts +9 -0
- package/transpiled/schemas.js +14 -0
- package/transpiled/schemas.js.map +1 -0
- package/transpiled/test/util.d.ts +29 -0
- package/transpiled/test/util.js +38 -0
- package/transpiled/test/util.js.map +1 -0
- package/transpiled/tsconfig.tsbuildinfo +1 -0
package/source/Storage.test.ts
CHANGED
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
import { Readable } from 'node:stream'
|
|
2
|
-
import {
|
|
3
|
-
import { buffer } from '
|
|
2
|
+
import { randomUUID } from 'node:crypto'
|
|
3
|
+
import { buffer } from 'node:stream/consumers'
|
|
4
|
+
import { createReadStream } from 'node:fs'
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
import assert from 'node:assert'
|
|
4
7
|
import { Storage } from './Storage'
|
|
5
|
-
import {
|
|
8
|
+
import { suites } from './test/util'
|
|
6
9
|
import { type Entry } from './Entry'
|
|
7
10
|
import { providers } from './providers'
|
|
8
|
-
import type {
|
|
11
|
+
import type { ProviderConstructor } from './Provider'
|
|
9
12
|
|
|
10
13
|
let storage: Storage
|
|
11
14
|
let dir: string
|
|
12
15
|
|
|
13
|
-
const
|
|
16
|
+
const suite = suites[0]
|
|
17
|
+
|
|
18
|
+
beforeAll(async () => {
|
|
19
|
+
process.chdir(path.join(__dirname, 'test'))
|
|
20
|
+
})
|
|
14
21
|
|
|
15
22
|
beforeEach(() => {
|
|
16
|
-
dir = '/' +
|
|
23
|
+
dir = '/' + randomUUID()
|
|
17
24
|
|
|
18
|
-
const Provider = providers[
|
|
19
|
-
const provider = new Provider(
|
|
25
|
+
const Provider: ProviderConstructor = providers[suite.provider]
|
|
26
|
+
const provider = new Provider(suite.options)
|
|
20
27
|
|
|
21
28
|
storage = new Storage(provider)
|
|
22
29
|
})
|
|
@@ -28,9 +35,7 @@ it('should be', async () => {
|
|
|
28
35
|
it('should return error if entry is not found', async () => {
|
|
29
36
|
const result = await storage.get('not-found')
|
|
30
37
|
|
|
31
|
-
|
|
32
|
-
throw new Error('Expected error')
|
|
33
|
-
|
|
38
|
+
expect(result).toBeInstanceOf(Error)
|
|
34
39
|
expect(result).toMatchObject({ code: 'NOT_FOUND' })
|
|
35
40
|
})
|
|
36
41
|
|
|
@@ -39,10 +44,10 @@ describe('put', () => {
|
|
|
39
44
|
let startCreation: number
|
|
40
45
|
|
|
41
46
|
beforeEach(async () => {
|
|
42
|
-
const stream =
|
|
47
|
+
const stream = createReadStream('lenna.png')
|
|
43
48
|
|
|
44
49
|
startCreation = Date.now()
|
|
45
|
-
lenna = await storage.put(dir, stream) as Entry
|
|
50
|
+
lenna = (await storage.put(dir, stream)) as Entry
|
|
46
51
|
})
|
|
47
52
|
|
|
48
53
|
it('should not return error', async () => {
|
|
@@ -54,19 +59,19 @@ describe('put', () => {
|
|
|
54
59
|
})
|
|
55
60
|
|
|
56
61
|
it('should return id as checksum', async () => {
|
|
57
|
-
const stream =
|
|
58
|
-
const dir2 = '/' +
|
|
59
|
-
const copy = await storage.put(dir2, stream) as Entry
|
|
62
|
+
const stream = createReadStream('lenna.png')
|
|
63
|
+
const dir2 = '/' + randomUUID()
|
|
64
|
+
const copy = (await storage.put(dir2, stream)) as Entry
|
|
60
65
|
|
|
61
|
-
expect(copy
|
|
66
|
+
expect(copy).toHaveProperty('id', lenna.id)
|
|
62
67
|
})
|
|
63
68
|
|
|
64
69
|
it('should detect file type', async () => {
|
|
65
|
-
expect(lenna
|
|
70
|
+
expect(lenna).toHaveProperty('type', 'image/png')
|
|
66
71
|
})
|
|
67
72
|
|
|
68
73
|
it('should count size', async () => {
|
|
69
|
-
expect(lenna
|
|
74
|
+
expect(lenna).toHaveProperty('size', 473831)
|
|
70
75
|
})
|
|
71
76
|
|
|
72
77
|
it('should return entry', async () => {
|
|
@@ -81,18 +86,17 @@ describe('put', () => {
|
|
|
81
86
|
it('should create entry', async () => {
|
|
82
87
|
const entry = await storage.get(`${dir}/${lenna.id}`)
|
|
83
88
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}, undefined)
|
|
89
|
+
expect(entry).toMatchObject({
|
|
90
|
+
id: lenna.id,
|
|
91
|
+
type: 'image/png',
|
|
92
|
+
variants: [],
|
|
93
|
+
meta: {}
|
|
94
|
+
})
|
|
91
95
|
})
|
|
92
96
|
|
|
93
97
|
it('should set timestamp', async () => {
|
|
94
98
|
const now = Date.now()
|
|
95
|
-
const entry = await storage.get(`${dir}/${lenna.id}`) as Entry
|
|
99
|
+
const entry = (await storage.get(`${dir}/${lenna.id}`)) as Entry
|
|
96
100
|
|
|
97
101
|
expect(entry.created).toBeLessThanOrEqual(now)
|
|
98
102
|
expect(entry.created).toBeGreaterThanOrEqual(startCreation)
|
|
@@ -100,7 +104,7 @@ describe('put', () => {
|
|
|
100
104
|
|
|
101
105
|
describe('existing entry', () => {
|
|
102
106
|
it('should unhide existing', async () => {
|
|
103
|
-
const stream =
|
|
107
|
+
const stream = createReadStream('lenna.png')
|
|
104
108
|
const path = `${dir}/${lenna.id}`
|
|
105
109
|
|
|
106
110
|
await storage.conceal(path)
|
|
@@ -113,14 +117,14 @@ describe('put', () => {
|
|
|
113
117
|
|
|
114
118
|
it('should preserve meta', async () => {
|
|
115
119
|
const path = `${dir}/${lenna.id}`
|
|
116
|
-
const stream =
|
|
120
|
+
const stream = createReadStream('lenna.png')
|
|
117
121
|
|
|
118
122
|
await storage.annotate(path, 'foo', 'bar')
|
|
119
123
|
await storage.put(dir, stream)
|
|
120
124
|
|
|
121
|
-
const entry = await storage.get(path)
|
|
125
|
+
const entry = await storage.get(path)
|
|
122
126
|
|
|
123
|
-
expect(entry.meta
|
|
127
|
+
expect(entry).toHaveProperty('meta', expect.objectContaining({ foo: 'bar' }))
|
|
124
128
|
})
|
|
125
129
|
})
|
|
126
130
|
})
|
|
@@ -130,17 +134,17 @@ describe('list', () => {
|
|
|
130
134
|
let lenna: Entry
|
|
131
135
|
|
|
132
136
|
beforeEach(async () => {
|
|
133
|
-
const stream0 =
|
|
134
|
-
const stream1 =
|
|
137
|
+
const stream0 = createReadStream('albert.jpg')
|
|
138
|
+
const stream1 = createReadStream('lenna.png')
|
|
135
139
|
|
|
136
|
-
albert = await storage.put(dir, stream0) as Entry
|
|
137
|
-
lenna = await storage.put(dir, stream1) as Entry
|
|
140
|
+
albert = (await storage.put(dir, stream0)) as Entry
|
|
141
|
+
lenna = (await storage.put(dir, stream1)) as Entry
|
|
138
142
|
})
|
|
139
143
|
|
|
140
144
|
it('should list entries', async () => {
|
|
141
145
|
const list = await storage.list(dir)
|
|
142
146
|
|
|
143
|
-
expect(list).
|
|
147
|
+
expect(list).toEqual([albert.id, lenna.id])
|
|
144
148
|
})
|
|
145
149
|
|
|
146
150
|
it('should permutate', async () => {
|
|
@@ -150,7 +154,7 @@ describe('list', () => {
|
|
|
150
154
|
|
|
151
155
|
const list = await storage.list(dir)
|
|
152
156
|
|
|
153
|
-
expect(list).
|
|
157
|
+
expect(list).toEqual([lenna.id, albert.id])
|
|
154
158
|
})
|
|
155
159
|
|
|
156
160
|
it('should return PERMUTATION_MISMATCH', async () => {
|
|
@@ -165,7 +169,7 @@ describe('list', () => {
|
|
|
165
169
|
const error = await storage.permute(dir, permutation)
|
|
166
170
|
|
|
167
171
|
expect(error).toBeInstanceOf(Error)
|
|
168
|
-
expect(error).
|
|
172
|
+
expect(error).toHaveProperty('code', 'PERMUTATION_MISMATCH')
|
|
169
173
|
}
|
|
170
174
|
})
|
|
171
175
|
|
|
@@ -176,7 +180,7 @@ describe('list', () => {
|
|
|
176
180
|
|
|
177
181
|
const entries = await storage.list(dir)
|
|
178
182
|
|
|
179
|
-
expect(entries).
|
|
183
|
+
expect(entries).toEqual([albert.id])
|
|
180
184
|
})
|
|
181
185
|
|
|
182
186
|
it('should reveal', async () => {
|
|
@@ -188,7 +192,7 @@ describe('list', () => {
|
|
|
188
192
|
|
|
189
193
|
const entries = await storage.list(dir)
|
|
190
194
|
|
|
191
|
-
expect(entries).
|
|
195
|
+
expect(entries).toEqual([albert.id, lenna.id])
|
|
192
196
|
})
|
|
193
197
|
|
|
194
198
|
it('should return ERR_NOT_FOOUD if entry doesnt exist', async () => {
|
|
@@ -200,7 +204,7 @@ describe('list', () => {
|
|
|
200
204
|
const error = await storage[method](path)
|
|
201
205
|
|
|
202
206
|
expect(error).toBeInstanceOf(Error)
|
|
203
|
-
expect(error).
|
|
207
|
+
expect(error).toHaveProperty('code', 'NOT_FOUND')
|
|
204
208
|
}
|
|
205
209
|
})
|
|
206
210
|
})
|
|
@@ -209,9 +213,9 @@ describe('annotate', () => {
|
|
|
209
213
|
let lenna: Entry
|
|
210
214
|
|
|
211
215
|
beforeEach(async () => {
|
|
212
|
-
const stream =
|
|
216
|
+
const stream = createReadStream('lenna.png')
|
|
213
217
|
|
|
214
|
-
lenna = await storage.put(dir, stream) as Entry
|
|
218
|
+
lenna = (await storage.put(dir, stream)) as Entry
|
|
215
219
|
})
|
|
216
220
|
|
|
217
221
|
it('should set meta', async () => {
|
|
@@ -219,15 +223,15 @@ describe('annotate', () => {
|
|
|
219
223
|
|
|
220
224
|
await storage.annotate(path, 'foo', 'bar')
|
|
221
225
|
|
|
222
|
-
const state0 = await storage.get(path) as Entry
|
|
226
|
+
const state0 = (await storage.get(path)) as Entry
|
|
223
227
|
|
|
224
|
-
expect(state0.meta
|
|
228
|
+
expect(state0).toHaveProperty('meta.foo', 'bar')
|
|
225
229
|
|
|
226
230
|
await storage.annotate(path, 'foo')
|
|
227
231
|
|
|
228
|
-
const state1 = await storage.get(path) as Entry
|
|
232
|
+
const state1 = (await storage.get(path)) as Entry
|
|
229
233
|
|
|
230
|
-
expect(
|
|
234
|
+
expect(state1.meta).not.toHaveProperty('foo')
|
|
231
235
|
})
|
|
232
236
|
})
|
|
233
237
|
|
|
@@ -235,34 +239,36 @@ describe('variants', () => {
|
|
|
235
239
|
let lenna: Entry
|
|
236
240
|
|
|
237
241
|
beforeEach(async () => {
|
|
238
|
-
const stream =
|
|
242
|
+
const stream = createReadStream('lenna.png')
|
|
239
243
|
|
|
240
|
-
lenna = await storage.put(dir, stream) as Entry
|
|
244
|
+
lenna = (await storage.put(dir, stream)) as Entry
|
|
241
245
|
})
|
|
242
246
|
|
|
243
247
|
it('should add variant', async () => {
|
|
244
|
-
const stream =
|
|
248
|
+
const stream = createReadStream('sample.jpeg')
|
|
245
249
|
|
|
246
250
|
const path = `${dir}/${lenna.id}`
|
|
247
251
|
|
|
248
252
|
await storage.diversify(path, 'foo', stream)
|
|
249
253
|
|
|
250
|
-
const state = await storage.get(path) as Entry
|
|
254
|
+
const state = (await storage.get(path)) as Entry
|
|
251
255
|
|
|
252
|
-
expect(state
|
|
256
|
+
expect(state).toHaveProperty('variants',
|
|
257
|
+
expect.arrayContaining([{ name: 'foo', size: 73444, type: 'image/jpeg' }]))
|
|
253
258
|
})
|
|
254
259
|
|
|
255
260
|
it('should replace variant', async () => {
|
|
256
|
-
const stream0 =
|
|
257
|
-
const stream1 =
|
|
261
|
+
const stream0 = createReadStream('sample.jpeg')
|
|
262
|
+
const stream1 = createReadStream('sample.webp')
|
|
258
263
|
const path = `${dir}/${lenna.id}`
|
|
259
264
|
|
|
260
265
|
await storage.diversify(path, 'foo', stream0)
|
|
261
266
|
await storage.diversify(path, 'foo', stream1)
|
|
262
267
|
|
|
263
|
-
const state = await storage.get(path) as Entry
|
|
268
|
+
const state = (await storage.get(path)) as Entry
|
|
264
269
|
|
|
265
|
-
expect(state
|
|
270
|
+
expect(state).toHaveProperty('variants',
|
|
271
|
+
expect.arrayContaining([expect.objectContaining({ name: 'foo', type: 'image/webp' })]))
|
|
266
272
|
})
|
|
267
273
|
})
|
|
268
274
|
|
|
@@ -270,39 +276,38 @@ describe('fetch', () => {
|
|
|
270
276
|
let lenna: Entry
|
|
271
277
|
|
|
272
278
|
beforeEach(async () => {
|
|
273
|
-
const stream =
|
|
279
|
+
const stream = createReadStream('lenna.png')
|
|
274
280
|
|
|
275
|
-
lenna = await storage.put(dir, stream) as Entry
|
|
281
|
+
lenna = (await storage.put(dir, stream)) as Entry
|
|
276
282
|
})
|
|
277
283
|
|
|
278
284
|
it('should fetch', async () => {
|
|
279
285
|
const path = `${dir}/${lenna.id}`
|
|
280
286
|
const stream = await storage.fetch(path)
|
|
281
287
|
|
|
282
|
-
|
|
283
|
-
Readable, async (stream: Readable) => await buffer(stream))
|
|
288
|
+
assert.ok(stream instanceof Readable)
|
|
284
289
|
|
|
285
|
-
const
|
|
290
|
+
const stored = await buffer(stream)
|
|
291
|
+
const buf = await buffer(createReadStream('lenna.png'))
|
|
286
292
|
|
|
287
293
|
expect(stored.compare(buf)).toBe(0)
|
|
288
294
|
})
|
|
289
295
|
|
|
290
296
|
it('should fetch blob by id', async () => {
|
|
291
|
-
const stream =
|
|
292
|
-
const entry = await storage.put(dir, stream) as Entry
|
|
297
|
+
const stream = createReadStream('lenna.ascii')
|
|
298
|
+
const entry = (await storage.put(dir, stream)) as Entry
|
|
293
299
|
const stored = await storage.fetch(entry.id)
|
|
294
300
|
|
|
295
|
-
if (stored instanceof Error)
|
|
296
|
-
throw stored
|
|
301
|
+
if (stored instanceof Error) throw stored
|
|
297
302
|
|
|
298
303
|
const buf = await buffer(stored)
|
|
299
|
-
const expected = await buffer(
|
|
304
|
+
const expected = await buffer(createReadStream('lenna.ascii'))
|
|
300
305
|
|
|
301
306
|
expect(buf.compare(expected)).toBe(0)
|
|
302
307
|
})
|
|
303
308
|
|
|
304
309
|
it('should fetch variant', async () => {
|
|
305
|
-
const stream =
|
|
310
|
+
const stream = createReadStream('sample.jpeg')
|
|
306
311
|
|
|
307
312
|
const buf = await buffer(stream)
|
|
308
313
|
const path = `${dir}/${lenna.id}`
|
|
@@ -311,8 +316,9 @@ describe('fetch', () => {
|
|
|
311
316
|
|
|
312
317
|
const variant = await storage.fetch(`${path}.100x100.jpeg`)
|
|
313
318
|
|
|
314
|
-
|
|
315
|
-
|
|
319
|
+
assert.ok(variant instanceof Readable)
|
|
320
|
+
|
|
321
|
+
const stored = await buffer(variant)
|
|
316
322
|
|
|
317
323
|
expect(stored.compare(buf)).toBe(0)
|
|
318
324
|
})
|
|
@@ -320,8 +326,8 @@ describe('fetch', () => {
|
|
|
320
326
|
it('should not fetch blob by id and fake path', async () => {
|
|
321
327
|
const stored = await storage.fetch(`fake/${lenna.id}`)
|
|
322
328
|
|
|
323
|
-
|
|
324
|
-
|
|
329
|
+
expect(stored).toBeInstanceOf(Error)
|
|
330
|
+
expect(stored).toHaveProperty('code', 'NOT_FOUND')
|
|
325
331
|
})
|
|
326
332
|
})
|
|
327
333
|
|
|
@@ -329,9 +335,9 @@ describe('delete', () => {
|
|
|
329
335
|
let lenna: Entry
|
|
330
336
|
|
|
331
337
|
beforeEach(async () => {
|
|
332
|
-
const stream =
|
|
338
|
+
const stream = createReadStream('lenna.png')
|
|
333
339
|
|
|
334
|
-
lenna = await storage.put(dir, stream) as Entry
|
|
340
|
+
lenna = (await storage.put(dir, stream)) as Entry
|
|
335
341
|
})
|
|
336
342
|
|
|
337
343
|
it('should remove from the list', async () => {
|
|
@@ -347,24 +353,23 @@ describe('delete', () => {
|
|
|
347
353
|
|
|
348
354
|
const result = await storage.get(`${dir}/${lenna.id}`)
|
|
349
355
|
|
|
350
|
-
|
|
351
|
-
|
|
356
|
+
expect(result).toBeInstanceOf(Error)
|
|
357
|
+
expect(result).toHaveProperty('code', 'NOT_FOUND')
|
|
352
358
|
})
|
|
353
359
|
|
|
354
360
|
it('should delete variants', async () => {
|
|
355
|
-
const stream =
|
|
361
|
+
const stream = createReadStream('sample.jpeg')
|
|
356
362
|
|
|
357
363
|
const path = `${dir}/${lenna.id}`
|
|
358
364
|
|
|
359
365
|
await storage.diversify(path, 'foo', stream)
|
|
360
366
|
await storage.delete(`${dir}/${lenna.id}`)
|
|
367
|
+
stream.destroy()
|
|
361
368
|
|
|
362
369
|
const variant = await storage.fetch(`${path}.foo`)
|
|
363
370
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
stream.destroy()
|
|
371
|
+
expect(variant).toBeInstanceOf(Error)
|
|
372
|
+
expect(variant).toHaveProperty('code', 'NOT_FOUND')
|
|
368
373
|
})
|
|
369
374
|
|
|
370
375
|
it('should throw if path is not an entry', async () => {
|
|
@@ -378,72 +383,72 @@ describe('delete', () => {
|
|
|
378
383
|
describe('signatures', () => {
|
|
379
384
|
it.each(['jpeg', 'gif', 'webp', 'heic', 'jxl', 'avif'])('should detect image/%s',
|
|
380
385
|
async (type) => {
|
|
381
|
-
const stream =
|
|
386
|
+
const stream = createReadStream('sample.' + type)
|
|
382
387
|
|
|
383
|
-
const entry = await storage.put(dir, stream) as Entry
|
|
388
|
+
const entry = (await storage.put(dir, stream)) as Entry
|
|
384
389
|
|
|
385
|
-
expect(entry
|
|
390
|
+
expect(entry).toHaveProperty('type', 'image/' + type)
|
|
386
391
|
})
|
|
387
392
|
})
|
|
388
393
|
|
|
389
|
-
it(
|
|
390
|
-
const stream =
|
|
394
|
+
it("should return error if type doesn't match", async () => {
|
|
395
|
+
const stream = createReadStream('sample.jpeg')
|
|
391
396
|
|
|
392
397
|
const result = await storage.put(dir, stream, { claim: 'image/png' })
|
|
393
398
|
|
|
394
|
-
|
|
395
|
-
|
|
399
|
+
expect(result).toBeInstanceOf(Error)
|
|
400
|
+
expect(result).toHaveProperty('code', 'TYPE_MISMATCH')
|
|
396
401
|
})
|
|
397
402
|
|
|
398
403
|
it('should trust unknown types', async () => {
|
|
399
|
-
const stream =
|
|
404
|
+
const stream = createReadStream('lenna.ascii')
|
|
400
405
|
|
|
401
406
|
const result = await storage.put(dir, stream, { claim: 'text/plain' })
|
|
402
407
|
|
|
403
408
|
expect(result).not.toBeInstanceOf(Error)
|
|
404
|
-
expect(result).
|
|
409
|
+
expect(result).toHaveProperty('type', 'text/plain')
|
|
405
410
|
})
|
|
406
411
|
|
|
407
412
|
it('should return error if type is identifiable', async () => {
|
|
408
|
-
const stream =
|
|
413
|
+
const stream = createReadStream('lenna.ascii')
|
|
409
414
|
|
|
410
415
|
const result = await storage.put(dir, stream, { claim: 'image/jpeg' })
|
|
411
416
|
|
|
412
417
|
expect(result).toBeInstanceOf(Error)
|
|
413
|
-
expect(result).
|
|
418
|
+
expect(result).toHaveProperty('code', 'TYPE_MISMATCH')
|
|
414
419
|
})
|
|
415
420
|
|
|
416
421
|
it('should not return error if type application/octet-stream', async () => {
|
|
417
|
-
const stream =
|
|
422
|
+
const stream = createReadStream('sample.jpeg')
|
|
418
423
|
|
|
419
424
|
const result = await storage.put(dir, stream, { claim: 'application/octet-stream' })
|
|
420
425
|
|
|
421
426
|
expect(result).not.toBeInstanceOf(Error)
|
|
422
|
-
expect(result).
|
|
427
|
+
expect(result).toHaveProperty('type', 'image/jpeg')
|
|
423
428
|
})
|
|
424
429
|
|
|
425
430
|
it('should return error if type is not acceptable', async () => {
|
|
426
|
-
const stream =
|
|
431
|
+
const stream = createReadStream('sample.jpeg')
|
|
427
432
|
|
|
428
433
|
const result = await storage.put(dir, stream, { accept: 'image/png' })
|
|
429
434
|
|
|
430
|
-
|
|
431
|
-
|
|
435
|
+
expect(result).toBeInstanceOf(Error)
|
|
436
|
+
expect(result).toHaveProperty('code', 'NOT_ACCEPTABLE')
|
|
432
437
|
})
|
|
433
438
|
|
|
434
439
|
it('should accept wildcard types', async () => {
|
|
435
|
-
const stream =
|
|
440
|
+
const stream = createReadStream('sample.jpeg')
|
|
436
441
|
|
|
437
442
|
const result = await storage.put(dir, stream, { accept: 'image/*' })
|
|
438
443
|
|
|
439
444
|
expect(result).not.toBeInstanceOf(Error)
|
|
440
|
-
expect(result).
|
|
445
|
+
expect(result).toHaveProperty('type', 'image/jpeg')
|
|
441
446
|
})
|
|
442
447
|
|
|
443
448
|
it('should handle root entries', async () => {
|
|
444
|
-
const stream =
|
|
449
|
+
const stream = createReadStream('sample.jpeg')
|
|
445
450
|
|
|
446
|
-
const result = await storage.put('hello', stream) as Entry
|
|
451
|
+
const result = (await storage.put('hello', stream)) as Entry
|
|
447
452
|
|
|
448
453
|
expect(result).not.toBeInstanceOf(Error)
|
|
449
454
|
|
|
@@ -453,16 +458,16 @@ it('should handle root entries', async () => {
|
|
|
453
458
|
})
|
|
454
459
|
|
|
455
460
|
it('should store empty file', async () => {
|
|
456
|
-
const stream =
|
|
457
|
-
const result = await storage.put('empty', stream) as Entry
|
|
461
|
+
const stream = createReadStream('empty.txt')
|
|
462
|
+
const result = (await storage.put('empty', stream)) as Entry
|
|
458
463
|
|
|
459
|
-
expect(result
|
|
464
|
+
expect(result).toHaveProperty('size', 0)
|
|
460
465
|
|
|
461
|
-
const stored = await storage.fetch(result.id) as Readable
|
|
466
|
+
const stored = (await storage.fetch(result.id)) as Readable
|
|
462
467
|
|
|
463
468
|
expect(stored).not.toBeInstanceOf(Error)
|
|
464
469
|
|
|
465
470
|
const buf = await buffer(stored)
|
|
466
471
|
|
|
467
|
-
expect(buf
|
|
472
|
+
expect(buf).toHaveLength(0)
|
|
468
473
|
})
|
package/source/Storage.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { decode, encode } from 'msgpackr'
|
|
|
4
4
|
import { buffer, newid } from '@toa.io/generic'
|
|
5
5
|
import { Err } from 'error-value'
|
|
6
6
|
import { Scanner } from './Scanner'
|
|
7
|
-
import type {
|
|
7
|
+
import type { ScanOptions } from './Scanner'
|
|
8
8
|
import type { Provider } from './Provider'
|
|
9
9
|
import type { Entry } from './Entry'
|
|
10
10
|
|
|
@@ -15,8 +15,8 @@ export class Storage {
|
|
|
15
15
|
this.provider = provider
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
public async put (path: string, stream: Readable,
|
|
19
|
-
const scanner = new Scanner(
|
|
18
|
+
public async put (path: string, stream: Readable, options?: Options): Maybe<Entry> {
|
|
19
|
+
const scanner = new Scanner(options)
|
|
20
20
|
const pipe = stream.pipe(scanner)
|
|
21
21
|
const tempname = await this.transit(pipe)
|
|
22
22
|
|
|
@@ -27,7 +27,7 @@ export class Storage {
|
|
|
27
27
|
|
|
28
28
|
await this.persist(tempname, id)
|
|
29
29
|
|
|
30
|
-
return await this.create(path, id, scanner.size, scanner.type)
|
|
30
|
+
return await this.create(path, id, scanner.size, scanner.type, options?.meta)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
public async get (path: string): Maybe<Entry> {
|
|
@@ -160,14 +160,15 @@ export class Storage {
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
// eslint-disable-next-line max-params
|
|
163
|
-
private async create
|
|
163
|
+
private async create
|
|
164
|
+
(path: string, id: string, size: number, type: string, meta: Meta = {}): Promise<Entry> {
|
|
164
165
|
const entry: Entry = {
|
|
165
166
|
id,
|
|
166
167
|
size,
|
|
167
168
|
type,
|
|
168
169
|
created: Date.now(),
|
|
169
170
|
variants: [],
|
|
170
|
-
meta
|
|
171
|
+
meta
|
|
171
172
|
}
|
|
172
173
|
|
|
173
174
|
const metafile = posix.join(path, entry.id)
|
|
@@ -235,4 +236,10 @@ interface Path {
|
|
|
235
236
|
variant: string | null
|
|
236
237
|
}
|
|
237
238
|
|
|
239
|
+
type Meta = Record<string, string>
|
|
240
|
+
|
|
241
|
+
interface Options extends ScanOptions {
|
|
242
|
+
meta?: Meta
|
|
243
|
+
}
|
|
244
|
+
|
|
238
245
|
export type Storages = Record<string, Storage>
|
package/source/deployment.ts
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as assert from 'node:assert'
|
|
2
|
+
import { encode } from '@toa.io/generic'
|
|
2
3
|
import { providers } from './providers'
|
|
4
|
+
import { validateAnnotation } from './Annotation'
|
|
5
|
+
import type { Annotation } from './Annotation'
|
|
3
6
|
import type { Dependency, Variable } from '@toa.io/operations'
|
|
4
7
|
import type { context } from '@toa.io/norm'
|
|
5
8
|
|
|
6
|
-
export
|
|
9
|
+
export const SERIALIZATION_PREFIX = 'TOA_STORAGES'
|
|
10
|
+
|
|
11
|
+
export function deployment (instances: Instance[], annotation: unknown): Dependency {
|
|
7
12
|
validate(instances, annotation)
|
|
8
13
|
|
|
9
|
-
const value = encode(annotation)
|
|
10
|
-
const pointer: Variable = { name:
|
|
14
|
+
const value = encode(annotation)
|
|
15
|
+
const pointer: Variable = { name: SERIALIZATION_PREFIX, value }
|
|
11
16
|
const secrets = getSecrets(annotation)
|
|
12
17
|
|
|
13
18
|
return {
|
|
@@ -15,51 +20,38 @@ export function deployment (instances: Instance[], annotation: Annotation): Depe
|
|
|
15
20
|
}
|
|
16
21
|
}
|
|
17
22
|
|
|
18
|
-
function validate (instances: Instance[], annotation:
|
|
19
|
-
|
|
20
|
-
throw new Error('Storages annotation is required by: ' +
|
|
21
|
-
`'${instances.map((i) => i.component.locator.id).join("', '")}'`)
|
|
23
|
+
function validate (instances: Instance[], annotation: unknown): asserts annotation is Annotation {
|
|
24
|
+
validateAnnotation(annotation)
|
|
22
25
|
|
|
23
26
|
for (const instance of instances)
|
|
24
27
|
contains(instance, annotation)
|
|
25
|
-
|
|
26
|
-
for (const ref of Object.values(annotation)) {
|
|
27
|
-
const url = new URL(ref)
|
|
28
|
-
|
|
29
|
-
if (!(url.protocol in providers))
|
|
30
|
-
throw new Error(`Unknown storage provider '${url.protocol}'`)
|
|
31
|
-
}
|
|
32
28
|
}
|
|
33
29
|
|
|
34
30
|
function contains (instance: Instance, annotation: Annotation): void {
|
|
35
31
|
for (const name of instance.manifest)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
assert.ok(name in annotation,
|
|
33
|
+
`Missing '${name}' storage annotation ` +
|
|
34
|
+
`declared in '${instance.component.locator.id}'`)
|
|
39
35
|
}
|
|
40
36
|
|
|
41
37
|
function getSecrets (annotation: Annotation): Variable[] {
|
|
42
38
|
const secrets: Variable[] = []
|
|
43
39
|
|
|
44
|
-
for (const [
|
|
45
|
-
const
|
|
46
|
-
const Provider = providers[url.protocol]
|
|
47
|
-
|
|
48
|
-
if (Provider.SECRETS === undefined)
|
|
49
|
-
continue
|
|
40
|
+
for (const [name, declaration] of Object.entries(annotation)) {
|
|
41
|
+
const Provider = providers[declaration.provider]
|
|
50
42
|
|
|
51
43
|
for (const secret of Provider.SECRETS)
|
|
52
44
|
secrets.push({
|
|
53
|
-
name:
|
|
45
|
+
name: `${SERIALIZATION_PREFIX}_${name}_${secret.name}`.toUpperCase(),
|
|
54
46
|
secret: {
|
|
55
|
-
name: `toa-storages-${
|
|
56
|
-
key: secret
|
|
47
|
+
name: `toa-storages-${name}`,
|
|
48
|
+
key: secret.name,
|
|
49
|
+
optional: secret.optional
|
|
57
50
|
}
|
|
58
|
-
}
|
|
51
|
+
})
|
|
59
52
|
}
|
|
60
53
|
|
|
61
54
|
return secrets
|
|
62
55
|
}
|
|
63
56
|
|
|
64
|
-
type Annotation = Record<string, string>
|
|
65
57
|
export type Instance = context.Dependency<string[]>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { S3Options } from './S3'
|
|
2
|
+
import type { FileSystemOptions } from './FileSystem'
|
|
3
|
+
import type { TemporaryOptions } from './Temporary'
|
|
4
|
+
|
|
5
|
+
export type Declaration =
|
|
6
|
+
({ provider: 's3' } & S3Options)
|
|
7
|
+
| ({ provider: 'fs' } & FileSystemOptions)
|
|
8
|
+
| ({ provider: 'tmp' } & TemporaryOptions)
|
|
9
|
+
| ({ provider: 'mem' })
|
|
10
|
+
| ({ provider: 'test' } & TemporaryOptions)
|