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