@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.
Files changed (88) hide show
  1. package/package.json +12 -11
  2. package/readme.md +63 -19
  3. package/schemas/annotation.cos.yaml +10 -0
  4. package/schemas/fs.cos.yaml +9 -0
  5. package/schemas/mem.cos.yaml +6 -0
  6. package/schemas/s3.cos.yaml +16 -0
  7. package/schemas/test.cos.yaml +8 -0
  8. package/schemas/tmp.cos.yaml +9 -0
  9. package/source/Annotation.ts +39 -0
  10. package/source/Aspect.ts +6 -4
  11. package/source/Factory.ts +31 -28
  12. package/source/Provider.ts +30 -5
  13. package/source/Scanner.ts +3 -3
  14. package/source/Storage.test.ts +110 -105
  15. package/source/Storage.ts +13 -6
  16. package/source/deployment.ts +21 -29
  17. package/source/providers/Declaration.ts +10 -0
  18. package/source/providers/FileSystem.test.ts +1 -9
  19. package/source/providers/FileSystem.ts +20 -15
  20. package/source/providers/Memory.ts +41 -0
  21. package/source/providers/S3.test.ts +133 -0
  22. package/source/providers/S3.ts +114 -39
  23. package/source/providers/Temporary.ts +8 -6
  24. package/source/providers/Test.ts +8 -8
  25. package/source/providers/index.test.ts +24 -19
  26. package/source/providers/index.ts +10 -9
  27. package/source/providers/readme.md +1 -1
  28. package/source/schemas.test.ts +58 -0
  29. package/source/schemas.ts +15 -0
  30. package/source/test/util.ts +25 -54
  31. package/transpiled/Annotation.d.ts +3 -0
  32. package/transpiled/Annotation.js +57 -0
  33. package/transpiled/Annotation.js.map +1 -0
  34. package/transpiled/Aspect.d.ts +8 -0
  35. package/transpiled/Aspect.js +25 -0
  36. package/transpiled/Aspect.js.map +1 -0
  37. package/transpiled/Entry.d.ts +14 -0
  38. package/transpiled/Entry.js +3 -0
  39. package/transpiled/Entry.js.map +1 -0
  40. package/transpiled/Factory.d.ts +9 -0
  41. package/transpiled/Factory.js +53 -0
  42. package/transpiled/Factory.js.map +1 -0
  43. package/transpiled/Provider.d.ts +20 -0
  44. package/transpiled/Provider.js +36 -0
  45. package/transpiled/Provider.js.map +1 -0
  46. package/transpiled/Scanner.d.ts +26 -0
  47. package/transpiled/Scanner.js +98 -0
  48. package/transpiled/Scanner.js.map +1 -0
  49. package/transpiled/Storage.d.ts +32 -0
  50. package/transpiled/Storage.js +176 -0
  51. package/transpiled/Storage.js.map +1 -0
  52. package/transpiled/deployment.d.ts +5 -0
  53. package/transpiled/deployment.js +68 -0
  54. package/transpiled/deployment.js.map +1 -0
  55. package/transpiled/index.d.ts +4 -0
  56. package/transpiled/index.js +10 -0
  57. package/transpiled/index.js.map +1 -0
  58. package/transpiled/manifest.d.ts +1 -0
  59. package/transpiled/manifest.js +9 -0
  60. package/transpiled/manifest.js.map +1 -0
  61. package/transpiled/providers/Declaration.d.ts +14 -0
  62. package/transpiled/providers/Declaration.js +3 -0
  63. package/transpiled/providers/Declaration.js.map +1 -0
  64. package/transpiled/providers/FileSystem.d.ts +15 -0
  65. package/transpiled/providers/FileSystem.js +44 -0
  66. package/transpiled/providers/FileSystem.js.map +1 -0
  67. package/transpiled/providers/Memory.d.ts +13 -0
  68. package/transpiled/providers/Memory.js +60 -0
  69. package/transpiled/providers/Memory.js.map +1 -0
  70. package/transpiled/providers/S3.d.ts +27 -0
  71. package/transpiled/providers/S3.js +154 -0
  72. package/transpiled/providers/S3.js.map +1 -0
  73. package/transpiled/providers/Temporary.d.ts +8 -0
  74. package/transpiled/providers/Temporary.js +14 -0
  75. package/transpiled/providers/Temporary.js.map +1 -0
  76. package/transpiled/providers/Test.d.ts +6 -0
  77. package/transpiled/providers/Test.js +15 -0
  78. package/transpiled/providers/Test.js.map +1 -0
  79. package/transpiled/providers/index.d.ts +13 -0
  80. package/transpiled/providers/index.js +16 -0
  81. package/transpiled/providers/index.js.map +1 -0
  82. package/transpiled/schemas.d.ts +9 -0
  83. package/transpiled/schemas.js +14 -0
  84. package/transpiled/schemas.js.map +1 -0
  85. package/transpiled/test/util.d.ts +29 -0
  86. package/transpiled/test/util.js +38 -0
  87. package/transpiled/test/util.js.map +1 -0
  88. package/transpiled/tsconfig.tsbuildinfo +1 -0
@@ -1,22 +1,29 @@
1
1
  import { Readable } from 'node:stream'
2
- import { match } from 'matchacho'
3
- import { buffer } from '@toa.io/generic'
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 { cases, open, rnd } from './test/util'
8
+ import { suites } from './test/util'
6
9
  import { type Entry } from './Entry'
7
10
  import { providers } from './providers'
8
- import type { ErrorType } from 'error-value'
11
+ import type { ProviderConstructor } from './Provider'
9
12
 
10
13
  let storage: Storage
11
14
  let dir: string
12
15
 
13
- const [, url, secrets] = cases[0]
16
+ const suite = suites[0]
17
+
18
+ beforeAll(async () => {
19
+ process.chdir(path.join(__dirname, 'test'))
20
+ })
14
21
 
15
22
  beforeEach(() => {
16
- dir = '/' + rnd()
23
+ dir = '/' + randomUUID()
17
24
 
18
- const Provider = providers[url.protocol]
19
- const provider = new Provider(url, secrets)
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
- if (!(result instanceof Error))
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 = open('lenna.png')
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 = open('lenna.png')
58
- const dir2 = '/' + rnd()
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.id).toBe(lenna.id)
66
+ expect(copy).toHaveProperty('id', lenna.id)
62
67
  })
63
68
 
64
69
  it('should detect file type', async () => {
65
- expect(lenna.type).toBe('image/png')
70
+ expect(lenna).toHaveProperty('type', 'image/png')
66
71
  })
67
72
 
68
73
  it('should count size', async () => {
69
- expect(lenna.size).toBe(473831)
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
- match(entry,
85
- {
86
- id: lenna.id,
87
- type: 'image/png',
88
- variants: [],
89
- meta: {}
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 = open('lenna.png')
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 = open('lenna.png')
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) as Entry
125
+ const entry = await storage.get(path)
122
126
 
123
- expect(entry.meta).toMatchObject({ foo: 'bar' })
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 = open('albert.jpg')
134
- const stream1 = open('lenna.png')
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).toMatchObject([albert.id, lenna.id])
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).toMatchObject([lenna.id, albert.id])
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).toMatchObject({ code: 'PERMUTATION_MISMATCH' })
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).toMatchObject([albert.id])
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).toMatchObject([albert.id, lenna.id])
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).toMatchObject({ code: 'NOT_FOUND' })
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 = open('lenna.png')
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).toMatchObject({ foo: 'bar' })
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('foo' in state1.meta).toBe(false)
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 = open('lenna.png')
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 = open('sample.jpeg')
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.variants).toMatchObject([{ name: 'foo', size: 73444, type: 'image/jpeg' }])
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 = open('sample.jpeg')
257
- const stream1 = open('sample.webp')
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.variants).toMatchObject([{ name: 'foo', type: 'image/webp' }])
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 = open('lenna.png')
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
- const stored: Buffer = await match(stream,
283
- Readable, async (stream: Readable) => await buffer(stream))
288
+ assert.ok(stream instanceof Readable)
284
289
 
285
- const buf = await buffer(open('lenna.png'))
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 = open('lenna.ascii')
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(open('lenna.ascii'))
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 = open('sample.jpeg')
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
- const stored = await match<Promise<Buffer>>(variant,
315
- Readable, async (stream: Readable) => await buffer(stream))
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
- match(stored,
324
- Error, (error: ErrorType) => expect(error.code).toBe('NOT_FOUND'))
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 = open('lenna.png')
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
- match(result,
351
- Error, (error: ErrorType) => expect(error.code).toBe('NOT_FOUND'))
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 = open('sample.jpeg')
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
- match(variant,
365
- Error, (error: ErrorType) => expect(error.code).toBe('NOT_FOUND'))
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 = open('sample.' + type)
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.type).toBe('image/' + type)
390
+ expect(entry).toHaveProperty('type', 'image/' + type)
386
391
  })
387
392
  })
388
393
 
389
- it('should return error if type doesnt match', async () => {
390
- const stream = open('sample.jpeg')
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
- match(result,
395
- Error, (error: ErrorType) => expect(error.code).toBe('TYPE_MISMATCH'))
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 = open('lenna.ascii')
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).toMatchObject({ type: 'text/plain' })
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 = open('lenna.ascii')
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).toMatchObject({ code: 'TYPE_MISMATCH' })
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 = open('sample.jpeg')
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).toMatchObject({ type: 'image/jpeg' })
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 = open('sample.jpeg')
431
+ const stream = createReadStream('sample.jpeg')
427
432
 
428
433
  const result = await storage.put(dir, stream, { accept: 'image/png' })
429
434
 
430
- match(result,
431
- Error, (error: ErrorType) => expect(error.code).toBe('NOT_ACCEPTABLE'))
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 = open('sample.jpeg')
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).toMatchObject({ type: 'image/jpeg' })
445
+ expect(result).toHaveProperty('type', 'image/jpeg')
441
446
  })
442
447
 
443
448
  it('should handle root entries', async () => {
444
- const stream = open('sample.jpeg')
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 = open('empty.txt')
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.size).toBe(0)
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.length).toBe(0)
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 { TypeControl } from './Scanner'
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, type?: TypeControl): Maybe<Entry> {
19
- const scanner = new Scanner(type)
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 (path: string, id: string, size: number, type: string): Promise<Entry> {
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>
@@ -1,13 +1,18 @@
1
- import { encode } from 'msgpackr'
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 function deployment (instances: Instance[], annotation: Annotation): Dependency {
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).toString('base64')
10
- const pointer: Variable = { name: 'TOA_STORAGES', value }
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: Annotation): void {
19
- if (annotation === undefined)
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
- if (!(name in annotation))
37
- throw new Error(`Missing '${name}' storage annotation ` +
38
- `declared in '${instance.component.locator.id}'`)
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 [storage, ref] of Object.entries(annotation)) {
45
- const url = new URL(ref)
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: `TOA_STORAGES_${storage.toUpperCase()}_${secret.toUpperCase()}`,
45
+ name: `${SERIALIZATION_PREFIX}_${name}_${secret.name}`.toUpperCase(),
54
46
  secret: {
55
- name: `toa-storages-${storage}`,
56
- key: secret
47
+ name: `toa-storages-${name}`,
48
+ key: secret.name,
49
+ optional: secret.optional
57
50
  }
58
- } satisfies Variable)
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)