@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.
Files changed (122) hide show
  1. package/package.json +38 -18
  2. package/readme.md +115 -47
  3. package/transpiled/Aspect.d.ts +8 -4
  4. package/transpiled/Aspect.js +12 -14
  5. package/transpiled/Aspect.js.map +1 -1
  6. package/transpiled/Entry.d.ts +14 -11
  7. package/transpiled/Entry.js +1 -2
  8. package/transpiled/Factory.d.ts +1 -1
  9. package/transpiled/Factory.js +29 -30
  10. package/transpiled/Factory.js.map +1 -1
  11. package/transpiled/Provider.d.ts +17 -19
  12. package/transpiled/Provider.js +10 -33
  13. package/transpiled/Provider.js.map +1 -1
  14. package/transpiled/Scanner.d.ts +5 -4
  15. package/transpiled/Scanner.js +33 -26
  16. package/transpiled/Scanner.js.map +1 -1
  17. package/transpiled/Secrets.d.ts +5 -0
  18. package/transpiled/Secrets.js +2 -0
  19. package/transpiled/Secrets.js.map +1 -0
  20. package/transpiled/Storage.d.ts +23 -22
  21. package/transpiled/Storage.js +79 -142
  22. package/transpiled/Storage.js.map +1 -1
  23. package/transpiled/errors.d.ts +7 -0
  24. package/transpiled/errors.js +4 -0
  25. package/transpiled/errors.js.map +1 -0
  26. package/transpiled/index.d.ts +4 -4
  27. package/transpiled/index.js +1 -9
  28. package/transpiled/index.js.map +1 -1
  29. package/transpiled/measurements.d.ts +3 -0
  30. package/transpiled/measurements.js +23 -0
  31. package/transpiled/measurements.js.map +1 -0
  32. package/transpiled/providers/Cloudinary.d.ts +50 -0
  33. package/transpiled/providers/Cloudinary.js +223 -0
  34. package/transpiled/providers/Cloudinary.js.map +1 -0
  35. package/transpiled/providers/Declaration.d.ts +9 -5
  36. package/transpiled/providers/Declaration.js +1 -2
  37. package/transpiled/providers/FileSystem.d.ts +16 -11
  38. package/transpiled/providers/FileSystem.js +64 -43
  39. package/transpiled/providers/FileSystem.js.map +1 -1
  40. package/transpiled/providers/S3.d.ts +14 -19
  41. package/transpiled/providers/S3.js +72 -123
  42. package/transpiled/providers/S3.js.map +1 -1
  43. package/transpiled/providers/Spaces.d.ts +12 -0
  44. package/transpiled/providers/Spaces.js +13 -0
  45. package/transpiled/providers/Spaces.js.map +1 -0
  46. package/transpiled/providers/Temporary.d.ts +2 -3
  47. package/transpiled/providers/Temporary.js +7 -11
  48. package/transpiled/providers/Temporary.js.map +1 -1
  49. package/transpiled/providers/Test.d.ts +4 -4
  50. package/transpiled/providers/Test.js +6 -12
  51. package/transpiled/providers/Test.js.map +1 -1
  52. package/transpiled/providers/index.d.ts +23 -11
  53. package/transpiled/providers/index.js +30 -14
  54. package/transpiled/providers/index.js.map +1 -1
  55. package/transpiled/providers/index.types.d.ts +5 -0
  56. package/transpiled/providers/index.types.js +2 -0
  57. package/transpiled/providers/index.types.js.map +1 -0
  58. package/transpiled/test/util.d.ts +113 -6
  59. package/transpiled/test/util.js +68 -15
  60. package/transpiled/test/util.js.map +1 -1
  61. package/schemas/annotation.cos.yaml +0 -10
  62. package/schemas/fs.cos.yaml +0 -9
  63. package/schemas/mem.cos.yaml +0 -6
  64. package/schemas/s3.cos.yaml +0 -16
  65. package/schemas/test.cos.yaml +0 -8
  66. package/schemas/tmp.cos.yaml +0 -9
  67. package/source/Annotation.ts +0 -39
  68. package/source/Aspect.ts +0 -25
  69. package/source/Entry.ts +0 -14
  70. package/source/Factory.ts +0 -68
  71. package/source/Provider.ts +0 -37
  72. package/source/Scanner.ts +0 -153
  73. package/source/Storage.test.ts +0 -472
  74. package/source/Storage.ts +0 -235
  75. package/source/deployment.ts +0 -57
  76. package/source/index.ts +0 -5
  77. package/source/manifest.ts +0 -8
  78. package/source/providers/Declaration.ts +0 -10
  79. package/source/providers/FileSystem.test.ts +0 -5
  80. package/source/providers/FileSystem.ts +0 -63
  81. package/source/providers/Memory.ts +0 -55
  82. package/source/providers/S3.test.ts +0 -134
  83. package/source/providers/S3.ts +0 -196
  84. package/source/providers/Temporary.ts +0 -16
  85. package/source/providers/Test.ts +0 -13
  86. package/source/providers/index.test.ts +0 -136
  87. package/source/providers/index.ts +0 -16
  88. package/source/providers/readme.md +0 -25
  89. package/source/schemas.test.ts +0 -58
  90. package/source/schemas.ts +0 -15
  91. package/source/test/.env.example +0 -1
  92. package/source/test/albert.jpg +0 -0
  93. package/source/test/empty.txt +0 -0
  94. package/source/test/lenna.ascii +0 -64
  95. package/source/test/lenna.png +0 -0
  96. package/source/test/sample.avi +0 -0
  97. package/source/test/sample.avif +0 -0
  98. package/source/test/sample.gif +0 -0
  99. package/source/test/sample.heic +0 -0
  100. package/source/test/sample.jpeg +0 -0
  101. package/source/test/sample.jxl +0 -0
  102. package/source/test/sample.svg +0 -4
  103. package/source/test/sample.wav +0 -0
  104. package/source/test/sample.webp +0 -0
  105. package/source/test/util.ts +0 -45
  106. package/transpiled/Annotation.d.ts +0 -3
  107. package/transpiled/Annotation.js +0 -57
  108. package/transpiled/Annotation.js.map +0 -1
  109. package/transpiled/deployment.d.ts +0 -5
  110. package/transpiled/deployment.js +0 -68
  111. package/transpiled/deployment.js.map +0 -1
  112. package/transpiled/manifest.d.ts +0 -1
  113. package/transpiled/manifest.js +0 -9
  114. package/transpiled/manifest.js.map +0 -1
  115. package/transpiled/providers/Memory.d.ts +0 -15
  116. package/transpiled/providers/Memory.js +0 -72
  117. package/transpiled/providers/Memory.js.map +0 -1
  118. package/transpiled/schemas.d.ts +0 -9
  119. package/transpiled/schemas.js +0 -14
  120. package/transpiled/schemas.js.map +0 -1
  121. package/transpiled/tsconfig.tsbuildinfo +0 -1
  122. package/tsconfig.json +0 -12
@@ -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>