@toa.io/extensions.storages 1.0.0-alpha.282 → 1.0.0-alpha.286

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 (57) hide show
  1. package/package.json +4 -4
  2. package/readme.md +1 -1
  3. package/source/Annotation.ts +25 -12
  4. package/source/Aspect.ts +4 -3
  5. package/source/Factory.ts +10 -9
  6. package/source/Provider.ts +13 -9
  7. package/source/Scanner.ts +32 -34
  8. package/source/Storage.test.ts +19 -20
  9. package/source/Storage.ts +15 -17
  10. package/source/context.ts +14 -0
  11. package/source/deployment.ts +14 -11
  12. package/source/index.ts +3 -0
  13. package/source/manifest.ts +3 -5
  14. package/source/providers/Cloudinary.ts +76 -76
  15. package/source/providers/Declaration.ts +1 -1
  16. package/source/providers/FileSystem.ts +15 -21
  17. package/source/providers/S3.ts +77 -65
  18. package/source/providers/Spaces.ts +9 -6
  19. package/source/providers/Temporary.ts +1 -1
  20. package/source/providers/Test.ts +1 -1
  21. package/source/providers/index.test.ts +85 -85
  22. package/source/schemas.test.ts +3 -4
  23. package/source/test/util.ts +7 -2
  24. package/transpiled/Annotation.js +6 -2
  25. package/transpiled/Annotation.js.map +1 -1
  26. package/transpiled/Aspect.d.ts +2 -1
  27. package/transpiled/Aspect.js.map +1 -1
  28. package/transpiled/Factory.js.map +1 -1
  29. package/transpiled/Provider.d.ts +1 -1
  30. package/transpiled/Provider.js.map +1 -1
  31. package/transpiled/Scanner.js +3 -7
  32. package/transpiled/Scanner.js.map +1 -1
  33. package/transpiled/Storage.js +3 -2
  34. package/transpiled/Storage.js.map +1 -1
  35. package/transpiled/context.d.ts +3 -0
  36. package/transpiled/context.js +12 -0
  37. package/transpiled/context.js.map +1 -0
  38. package/transpiled/deployment.js.map +1 -1
  39. package/transpiled/index.d.ts +1 -0
  40. package/transpiled/index.js +2 -0
  41. package/transpiled/index.js.map +1 -1
  42. package/transpiled/manifest.js.map +1 -1
  43. package/transpiled/providers/Cloudinary.d.ts +1 -1
  44. package/transpiled/providers/Cloudinary.js +8 -2
  45. package/transpiled/providers/Cloudinary.js.map +1 -1
  46. package/transpiled/providers/FileSystem.d.ts +1 -1
  47. package/transpiled/providers/FileSystem.js +1 -4
  48. package/transpiled/providers/FileSystem.js.map +1 -1
  49. package/transpiled/providers/S3.d.ts +1 -1
  50. package/transpiled/providers/S3.js.map +1 -1
  51. package/transpiled/providers/Spaces.js.map +1 -1
  52. package/transpiled/providers/Temporary.js.map +1 -1
  53. package/transpiled/providers/Test.js.map +1 -1
  54. package/transpiled/test/util.js +1 -1
  55. package/transpiled/test/util.js.map +1 -1
  56. package/tsconfig.tsbuildinfo +1 -1
  57. package/CHANGELOG.md +0 -74
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/extensions.storages",
3
- "version": "1.0.0-alpha.282",
3
+ "version": "1.0.0-alpha.286",
4
4
  "type": "module",
5
5
  "description": "Toa Storages",
6
6
  "author": "temich <tema.gurtovoy@gmail.com>",
@@ -27,10 +27,10 @@
27
27
  "dependencies": {
28
28
  "@aws-sdk/client-s3": "3.1125.0",
29
29
  "@aws-sdk/lib-storage": "3.1125.0",
30
- "@toa.io/schemas": "1.0.0-alpha.282",
30
+ "@toa.io/schemas": "1.0.0-alpha.286",
31
31
  "cloudinary": "2.11.0",
32
32
  "negotiator": "1.1.0",
33
- "openspan": "1.0.0-alpha.277"
33
+ "openspan": "1.0.0-alpha.286"
34
34
  },
35
- "gitHead": "4007c814725dd015d3bbebb9a28e28a247645c4f"
35
+ "gitHead": "81f3d0688f17668b902be336d1749cc715edcdb6"
36
36
  }
package/readme.md CHANGED
@@ -29,7 +29,7 @@ The Storages extension provides `storages` aspect,
29
29
  containing named Storage instances, according to the annotation.
30
30
 
31
31
  ```javascript
32
- async function effect (_, context) {
32
+ async function effect(_, context) {
33
33
  await context.storages.photos.get('/path/to/b4f577e0.thumbnail.jpeg')
34
34
  }
35
35
  ```
@@ -6,7 +6,9 @@ import type { Schema } from '@toa.io/schemas'
6
6
 
7
7
  export type Annotation = Record<string, Declaration>
8
8
 
9
- export function validateAnnotation (annotation: unknown): asserts annotation is Annotation {
9
+ export function validateAnnotation(
10
+ annotation: unknown
11
+ ): asserts annotation is Annotation {
10
12
  try {
11
13
  schemas.annotation.validate(annotation)
12
14
  } catch (error) {
@@ -20,19 +22,30 @@ export function validateAnnotation (annotation: unknown): asserts annotation is
20
22
  /*
21
23
  It is required because `oneOf` schema is used for the annotation validation.
22
24
  */
23
- function explain (annotation: unknown): void {
24
- assert.ok(typeof annotation === 'object' && annotation !== null,
25
- 'TOA_STORAGES is not an object')
25
+ function explain(annotation: unknown): void {
26
+ assert.ok(
27
+ typeof annotation === 'object' && annotation !== null,
28
+ 'TOA_STORAGES is not an object'
29
+ )
26
30
 
27
31
  for (const declaration of Object.values(annotation)) {
28
- assert.ok(typeof declaration === 'object' && declaration !== null &&
29
- declaration.provider in providers,
30
- `Unknown provider '${declaration.provider}'`)
31
-
32
- assert.ok(declaration.provider in schemas,
33
- `No schema for provider '${declaration.provider}'`)
34
-
35
- const schema: Schema<Declaration> = schemas[declaration.provider as keyof typeof providers]
32
+ assert.ok(
33
+ typeof declaration === 'object' &&
34
+ declaration !== null &&
35
+ declaration.provider in providers,
36
+ `Unknown provider '${declaration.provider}'`
37
+ )
38
+
39
+ assert.ok(
40
+ declaration.provider in schemas,
41
+ `No schema for provider '${declaration.provider}'`
42
+ )
43
+
44
+ const provider = declaration.provider as keyof typeof providers
45
+
46
+ // the declaration names the provider
47
+ // oxlint-disable-next-line import/namespace
48
+ const schema: Schema<Declaration> = schemas[provider]
36
49
 
37
50
  schema.validate(declaration, `Storage '${declaration.provider}' annotation`)
38
51
  }
package/source/Aspect.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import assert from 'node:assert'
2
- import { Connector, type extensions } from '@toa.io/core'
2
+ import { Connector } from '@toa.io/core'
3
+ import type { extensions } from '@toa.io/core/types'
3
4
  import { type Storage, type Storages } from './Storage.js'
4
5
 
5
6
  export class Aspect extends Connector implements extensions.Aspect {
@@ -7,13 +8,13 @@ export class Aspect extends Connector implements extensions.Aspect {
7
8
 
8
9
  private readonly storages: Storages
9
10
 
10
- public constructor (storages: Storages) {
11
+ public constructor(storages: Storages) {
11
12
  super()
12
13
 
13
14
  this.storages = storages
14
15
  }
15
16
 
16
- public invoke (name: string, method: keyof Storage, ...args: unknown[]): unknown {
17
+ public invoke(name: string, method: keyof Storage, ...args: unknown[]): unknown {
17
18
  const storage = this.storages[name]
18
19
 
19
20
  assert.ok(storage !== undefined, `Storage '${name}' is not defined`)
package/source/Factory.ts CHANGED
@@ -13,7 +13,7 @@ import type { Secrets } from './Secrets.js'
13
13
  export class Factory {
14
14
  private readonly annotation: Annotation
15
15
 
16
- public constructor () {
16
+ public constructor() {
17
17
  const env = process.env[ENV_PREFIX]
18
18
 
19
19
  assert.ok(env !== undefined, `${ENV_PREFIX} is not defined`)
@@ -23,13 +23,13 @@ export class Factory {
23
23
  validateAnnotation(this.annotation)
24
24
  }
25
25
 
26
- public aspect (): Aspect {
26
+ public aspect(): Aspect {
27
27
  const storages = this.createStorages()
28
28
 
29
29
  return new Aspect(storages)
30
30
  }
31
31
 
32
- private createStorages (): Storages {
32
+ private createStorages(): Storages {
33
33
  const storages: Storages = {}
34
34
 
35
35
  for (const [name, declaration] of Object.entries(this.annotation))
@@ -38,7 +38,7 @@ export class Factory {
38
38
  return storages
39
39
  }
40
40
 
41
- private createStorage (name: string, declaration: Declaration): Storage {
41
+ private createStorage(name: string, declaration: Declaration): Storage {
42
42
  const { provider: id, ...options } = declaration
43
43
  const Provider: Constructor = providers[id]
44
44
  const secrets = this.resolveSecrets(name, Provider)
@@ -53,9 +53,8 @@ export class Factory {
53
53
  return new Storage(provider, { name, provider: id })
54
54
  }
55
55
 
56
- private resolveSecrets (storageName: string, Class: Constructor): Secrets {
57
- if (Class.SECRETS === undefined)
58
- return {}
56
+ private resolveSecrets(storageName: string, Class: Constructor): Secrets {
57
+ if (Class.SECRETS === undefined) return {}
59
58
 
60
59
  const secrets: Record<string, string | undefined> = {}
61
60
 
@@ -63,8 +62,10 @@ export class Factory {
63
62
  const variable = `${ENV_PREFIX}_${storageName}_${secret.name}`.toUpperCase()
64
63
  const value = process.env[variable]
65
64
 
66
- assert.ok(secret.optional === true || value !== undefined,
67
- `'${variable}' is not defined`)
65
+ assert.ok(
66
+ secret.optional === true || value !== undefined,
67
+ `'${variable}' is not defined`
68
+ )
68
69
 
69
70
  secrets[secret.name] = value
70
71
  }
@@ -1,7 +1,7 @@
1
1
  import * as assert from 'node:assert'
2
2
  import type { Metadata, Stream } from './Entry.js'
3
3
  import type { Readable } from 'node:stream'
4
- import type { Maybe } from '@toa.io/types'
4
+ import type { Maybe } from '@toa.io/core/types'
5
5
  import type { Secret, Secrets } from './Secrets.js'
6
6
 
7
7
  export abstract class Provider<Options = unknown> {
@@ -9,24 +9,28 @@ export abstract class Provider<Options = unknown> {
9
9
  public readonly root?: string
10
10
  public readonly options: Options
11
11
 
12
- protected constructor (options: Options, secrets?: Secrets) {
12
+ protected constructor(options: Options, secrets?: Secrets) {
13
13
  this.options = options
14
14
 
15
15
  new.target.SECRETS?.forEach(({ name, optional }) =>
16
- assert.ok(optional === true || secrets?.[name] !== undefined, `Missing secret '${name}'`))
16
+ assert.ok(
17
+ optional === true || secrets?.[name] !== undefined,
18
+ `Missing secret '${name}'`
19
+ )
20
+ )
17
21
  }
18
22
 
19
- public abstract get (path: string, options?: unknown): Promise<Maybe<Stream>>
23
+ public abstract get(path: string, options?: unknown): Promise<Maybe<Stream>>
20
24
 
21
- public abstract head (path: string): Promise<Maybe<Metadata>>
25
+ public abstract head(path: string): Promise<Maybe<Metadata>>
22
26
 
23
- public abstract put (path: string, stream: Readable): Promise<void>
27
+ public abstract put(path: string, stream: Readable): Promise<void>
24
28
 
25
- public abstract commit (path: string, metadata: Metadata): Promise<void>
29
+ public abstract commit(path: string, metadata: Metadata): Promise<void>
26
30
 
27
- public abstract delete (path: string): Promise<void>
31
+ public abstract delete(path: string): Promise<void>
28
32
 
29
- public abstract move (from: string, to: string): Promise<Maybe<void>>
33
+ public abstract move(from: string, to: string): Promise<Maybe<void>>
30
34
  }
31
35
 
32
36
  export interface Constructor<Options = any> {
package/source/Scanner.ts CHANGED
@@ -15,7 +15,7 @@ export class Scanner extends PassThrough {
15
15
  private completed = false
16
16
  private readonly chunks: Buffer[] = []
17
17
 
18
- public constructor (control?: ScanOptions) {
18
+ public constructor(control?: ScanOptions) {
19
19
  super()
20
20
 
21
21
  this.claim = control?.claim
@@ -23,11 +23,15 @@ export class Scanner extends PassThrough {
23
23
  this.limit = control?.limit
24
24
  }
25
25
 
26
- public digest (): string {
26
+ public digest(): string {
27
27
  return this.hash.digest('hex')
28
28
  }
29
29
 
30
- public override _transform (buffer: Buffer, encoding: BufferEncoding, callback: TransformCallback): void {
30
+ public override _transform(
31
+ buffer: Buffer,
32
+ encoding: BufferEncoding,
33
+ callback: TransformCallback
34
+ ): void {
31
35
  super._transform(buffer, encoding, callback)
32
36
 
33
37
  this.process(buffer)
@@ -37,8 +41,7 @@ export class Scanner extends PassThrough {
37
41
  this.size += buffer.length
38
42
  this.hash.update(buffer)
39
43
 
40
- if (this.completed)
41
- return
44
+ if (this.completed) return
42
45
 
43
46
  if (this.position + buffer.length > HEADER_SIZE)
44
47
  buffer = buffer.subarray(0, HEADER_SIZE - this.position)
@@ -46,24 +49,20 @@ export class Scanner extends PassThrough {
46
49
  this.chunks.push(buffer)
47
50
  this.position += buffer.length
48
51
 
49
- if (this.position === HEADER_SIZE)
50
- this.complete()
52
+ if (this.position === HEADER_SIZE) this.complete()
51
53
 
52
54
  if (this.limit !== undefined && this.size > this.limit)
53
55
  this.interrupt(ERR_LIMIT_EXCEEDED)
54
56
  }
55
57
 
56
- private complete (): void {
58
+ private complete(): void {
57
59
  const header = Buffer.concat(this.chunks).toString('hex')
58
60
 
59
- const signature = SIGNATURES
60
- .find(({ off, hex, expression }) => {
61
- const sig = header.slice(off, off + hex.length)
61
+ const signature = SIGNATURES.find(({ off, hex, expression }) => {
62
+ const sig = header.slice(off, off + hex.length)
62
63
 
63
- return expression === undefined
64
- ? sig === hex
65
- : expression.test(sig)
66
- })
64
+ return expression === undefined ? sig === hex : expression.test(sig)
65
+ })
67
66
 
68
67
  const type = signature?.type ?? this.claim
69
68
 
@@ -76,33 +75,30 @@ export class Scanner extends PassThrough {
76
75
  this.completed = true
77
76
  }
78
77
 
79
- private verify (signature: Signature | undefined): void {
80
- if (this.claim === undefined || this.claim === 'application/octet-stream')
81
- return
78
+ private verify(signature: Signature | undefined): void {
79
+ if (this.claim === undefined || this.claim === 'application/octet-stream') return
82
80
 
83
- const mismatch = signature === undefined
84
- ? KNOWN_TYPES.has(this.claim)
85
- : this.claim !== signature.type
81
+ const mismatch =
82
+ signature === undefined
83
+ ? KNOWN_TYPES.has(this.claim)
84
+ : this.claim !== signature.type
86
85
 
87
- if (mismatch)
88
- this.interrupt(ERR_TYPE_MISMATCH)
86
+ if (mismatch) this.interrupt(ERR_TYPE_MISMATCH)
89
87
  }
90
88
 
91
- private match (type: string): void {
92
- if (this.accept === undefined)
93
- return
89
+ private match(type: string): void {
90
+ if (this.accept === undefined) return
94
91
 
95
92
  const unacceptable = this.negotiate(this.accept, [type]) === null
96
93
 
97
- if (unacceptable)
98
- this.interrupt(ERR_NOT_ACCEPTABLE)
94
+ if (unacceptable) this.interrupt(ERR_NOT_ACCEPTABLE)
99
95
  }
100
96
 
101
- private negotiate (accept: string, type: string[]): string | null {
97
+ private negotiate(accept: string, type: string[]): string | null {
102
98
  return new Negotiator({ headers: { accept } }).mediaType(type) ?? null
103
99
  }
104
100
 
105
- private interrupt (error: Error): void {
101
+ private interrupt(error: Error): void {
106
102
  this.completed = true
107
103
  this.error = error
108
104
  this.destroy(error)
@@ -132,8 +128,10 @@ const SIGNATURES: Signature[] = [
132
128
  signature.hex = signature.hex.replaceAll(' ', '')
133
129
 
134
130
  if (signature.hex.includes('??')) {
135
- const expression = signature.hex.replaceAll(/(?<wildcards>\?{1,24})/g,
136
- (_, wildcards) => `[0-9a-f]{${wildcards.length}}`)
131
+ const expression = signature.hex.replaceAll(
132
+ /(?<wildcards>\?{1,24})/g,
133
+ (_, wildcards) => `[0-9a-f]{${wildcards.length}}`
134
+ )
137
135
 
138
136
  signature.expression = new RegExp(expression, 'i')
139
137
  }
@@ -141,8 +139,8 @@ const SIGNATURES: Signature[] = [
141
139
  return signature
142
140
  })
143
141
 
144
- const HEADER_SIZE = SIGNATURES
145
- .reduce((max, { off, hex }) => Math.max(max, off + hex.length), 0) / 2
142
+ const HEADER_SIZE =
143
+ SIGNATURES.reduce((max, { off, hex }) => Math.max(max, off + hex.length), 0) / 2
146
144
 
147
145
  const KNOWN_TYPES = new Set(SIGNATURES.map(({ type }) => type))
148
146
 
@@ -41,7 +41,7 @@ describe('put', () => {
41
41
  const stream = createReadStream('lenna.png')
42
42
 
43
43
  startCreation = Date.now()
44
- lenna = await storage.put(dir, stream) as Entry
44
+ lenna = (await storage.put(dir, stream)) as Entry
45
45
  path = `${dir}/${lenna.id}`
46
46
 
47
47
  assert(!(lenna instanceof Error))
@@ -76,7 +76,7 @@ describe('put', () => {
76
76
  })
77
77
 
78
78
  it('should create metadata', async () => {
79
- const entry = await storage.head(path) as Entry
79
+ const entry = (await storage.head(path)) as Entry
80
80
 
81
81
  assert.partialDeepStrictEqual(entry, {
82
82
  type: 'image/png',
@@ -89,7 +89,7 @@ describe('put', () => {
89
89
 
90
90
  it('should set timestamp', async () => {
91
91
  const now = Date.now() + 1000 // Cloudinary has 1s resolution
92
- const entry = await storage.get(path) as Stream
92
+ const entry = (await storage.get(path)) as Stream
93
93
  const created = new Date(entry.created).getTime()
94
94
 
95
95
  assert.ok(created <= now)
@@ -100,14 +100,13 @@ describe('put', () => {
100
100
  const stream = createReadStream('lenna.png')
101
101
  const id = Math.random().toString(36).slice(2)
102
102
 
103
- const entry = await storage.put(dir, stream, { id }) as Entry
103
+ const entry = (await storage.put(dir, stream, { id })) as Entry
104
104
 
105
105
  assert.strictEqual(entry.id, id)
106
106
 
107
107
  const check = await storage.head(`${dir}/${id}`)
108
108
 
109
- if (check instanceof Error)
110
- throw check
109
+ if (check instanceof Error) throw check
111
110
 
112
111
  assert.strictEqual(check.id, id)
113
112
  })
@@ -120,13 +119,13 @@ describe('get, head', () => {
120
119
  beforeEach(async () => {
121
120
  const stream = createReadStream('lenna.png')
122
121
 
123
- lenna = await storage.put(dir, stream) as Entry
122
+ lenna = (await storage.put(dir, stream)) as Entry
124
123
 
125
124
  path = `${dir}/${lenna.id}`
126
125
  })
127
126
 
128
127
  it('should get', async () => {
129
- const entry = await storage.get(path) as Stream
128
+ const entry = (await storage.get(path)) as Stream
130
129
  const stored = await buffer(entry.stream)
131
130
  const buf = await buffer(createReadStream('lenna.png'))
132
131
 
@@ -134,26 +133,26 @@ describe('get, head', () => {
134
133
  })
135
134
 
136
135
  it('should get entry', async () => {
137
- const entry = await storage.head(path) as Entry
136
+ const entry = (await storage.head(path)) as Entry
138
137
 
139
138
  assert.strictEqual(entry.id, lenna.id)
140
139
  })
141
140
 
142
141
  if (suite.provider === 'cloudinary') {
143
142
  it('should return cloudinary url', async () => {
144
- const entry = await storage.head(`${path}.jpeg`) as Entry
143
+ const entry = (await storage.head(`${path}.jpeg`)) as Entry
145
144
 
146
145
  assert.match(entry.attributes.url, /^https:\/\//)
147
146
  })
148
147
 
149
148
  it('should support conditions', async () => {
150
- const entry = await storage.head(`${path}.vertical.jpeg`) as Entry
149
+ const entry = (await storage.head(`${path}.vertical.jpeg`)) as Entry
151
150
 
152
151
  assert.strictEqual(entry.attributes.url.includes('/if_w_gt_h/a_90/if_end/'), true)
153
152
  })
154
153
 
155
154
  it('should return entry id when requested with extensions', async () => {
156
- const entry = await storage.head(`${path}.jpeg`) as Entry
155
+ const entry = (await storage.head(`${path}.jpeg`)) as Entry
157
156
 
158
157
  assert.strictEqual(entry.id, lenna.id)
159
158
  })
@@ -166,7 +165,7 @@ describe('delete', () => {
166
165
  beforeEach(async () => {
167
166
  const stream = createReadStream('lenna.png')
168
167
 
169
- lenna = await storage.put(dir, stream) as Entry
168
+ lenna = (await storage.put(dir, stream)) as Entry
170
169
  })
171
170
 
172
171
  it('should delete entry', async () => {
@@ -193,21 +192,21 @@ describe('signatures', () => {
193
192
  for (const type of ['jpeg', 'gif', 'webp', 'heic', 'jxl', 'avif'])
194
193
  it(`should detect image/${type}`, async () => {
195
194
  const stream = createReadStream('sample.' + type)
196
- const entry = await storage.put(dir, stream) as Entry
195
+ const entry = (await storage.put(dir, stream)) as Entry
197
196
 
198
197
  assert.deepStrictEqual(entry['type'], 'image/' + type)
199
198
  })
200
199
 
201
200
  for (const type of ['avi'])
202
- it(`should detect video/${type}`, async () => {
201
+ it(`should detect video/${type}`, async () => {
203
202
  const stream = createReadStream('sample.' + type)
204
- const entry = await storage.put(dir, stream) as Entry
203
+ const entry = (await storage.put(dir, stream)) as Entry
205
204
 
206
205
  assert.deepStrictEqual(entry['type'], 'video/' + type)
207
206
  })
208
207
 
209
208
  for (const type of ['wav'])
210
- it(`should detect audio/${type}`, async () => {
209
+ it(`should detect audio/${type}`, async () => {
211
210
  const stream = createReadStream('sample.' + type)
212
211
  const entry = (await storage.put(dir, stream)) as Entry
213
212
 
@@ -222,7 +221,7 @@ describe('signatures', () => {
222
221
  })
223
222
  })
224
223
 
225
- it('should return error if type doesn\'t match', async () => {
224
+ it("should return error if type doesn't match", async () => {
226
225
  const stream = createReadStream('sample.jpeg')
227
226
 
228
227
  const result = await storage.put(dir, stream, { claim: 'image/png' })
@@ -278,7 +277,7 @@ it('should accept wildcard types', async () => {
278
277
 
279
278
  it('should handle root entries', async () => {
280
279
  const stream = createReadStream('sample.jpeg')
281
- const result = await storage.put('/', stream) as Entry
280
+ const result = (await storage.put('/', stream)) as Entry
282
281
 
283
282
  assert.ok(!(result instanceof Error))
284
283
 
@@ -299,7 +298,7 @@ if (suite.provider !== 'cloudinary')
299
298
  it('should add origin attribute', async () => {
300
299
  const origin = 'https://example.com/image.jpeg'
301
300
  const stream = createReadStream('sample.jpeg')
302
- const result = await storage.put('/origins', stream, { origin }) as Entry
301
+ const result = (await storage.put('/origins', stream, { origin })) as Entry
303
302
 
304
303
  assert.ok(!(result instanceof Error))
305
304
 
package/source/Storage.ts CHANGED
@@ -11,16 +11,16 @@ export class Storage<T extends Provider = Provider> {
11
11
  private readonly provider: T
12
12
  private readonly scope?: Scope
13
13
 
14
- public constructor (provider: T, scope?: Scope) {
14
+ public constructor(provider: T, scope?: Scope) {
15
15
  this.provider = provider
16
16
  this.scope = scope
17
17
  }
18
18
 
19
- public options (): T['options'] {
19
+ public options(): T['options'] {
20
20
  return this.provider.options
21
21
  }
22
22
 
23
- public async put (path: string, stream: Readable, options?: Options): Maybe<Entry> {
23
+ public async put(path: string, stream: Readable, options?: Options): Maybe<Entry> {
24
24
  return await console.span(this.span('put', path), async () => {
25
25
  const scanner = new Scanner(options)
26
26
  const pipe = stream.pipe(scanner).on('error', () => undefined)
@@ -31,14 +31,14 @@ export class Storage<T extends Provider = Provider> {
31
31
  * Provider can return or throw an error.
32
32
  * If thrown error is TYPE_MISMATCH from the Scanner, it should be returned.
33
33
  */
34
- const error: Error | undefined = await this.provider.put(location, pipe)
34
+ const error: Error | undefined = await this.provider
35
+ .put(location, pipe)
35
36
  .catch((error: any) => {
36
37
  if (error === scanner.error) return error
37
38
  else throw error
38
39
  })
39
40
 
40
- if (error instanceof Error)
41
- return error
41
+ if (error instanceof Error) return error
42
42
 
43
43
  const metadata: Entry = {
44
44
  id,
@@ -49,8 +49,7 @@ export class Storage<T extends Provider = Provider> {
49
49
  attributes: options?.attributes ?? {}
50
50
  }
51
51
 
52
- if (options?.origin !== undefined)
53
- metadata.attributes.origin = options.origin
52
+ if (options?.origin !== undefined) metadata.attributes.origin = options.origin
54
53
 
55
54
  await this.provider.commit(location, metadata)
56
55
 
@@ -58,7 +57,7 @@ export class Storage<T extends Provider = Provider> {
58
57
  })
59
58
  }
60
59
 
61
- public async get (path: string, options?: unknown): Maybe<Stream> {
60
+ public async get(path: string, options?: unknown): Maybe<Stream> {
62
61
  return await console.span(this.span('get', dirname(path)), async () => {
63
62
  const location = this.locate(path)
64
63
 
@@ -66,14 +65,13 @@ export class Storage<T extends Provider = Provider> {
66
65
  })
67
66
  }
68
67
 
69
- public async head (path: string): Promise<Maybe<Entry>> {
68
+ public async head(path: string): Promise<Maybe<Entry>> {
70
69
  return await console.span(this.span('head', dirname(path)), async () => {
71
70
  const id = basename(path).split('.')[0]
72
71
  const location = this.locate(path)
73
72
  const metadata = await this.provider.head(location)
74
73
 
75
- if (metadata instanceof Error)
76
- return metadata
74
+ if (metadata instanceof Error) return metadata
77
75
 
78
76
  return {
79
77
  id,
@@ -82,7 +80,7 @@ export class Storage<T extends Provider = Provider> {
82
80
  })
83
81
  }
84
82
 
85
- public async delete (path: string): Maybe<void> {
83
+ public async delete(path: string): Maybe<void> {
86
84
  return await console.span(this.span('delete', dirname(path)), async () => {
87
85
  const location = this.locate(path)
88
86
 
@@ -90,20 +88,20 @@ export class Storage<T extends Provider = Provider> {
90
88
  })
91
89
  }
92
90
 
93
- public path (): string | null {
91
+ public path(): string | null {
94
92
  return this.provider.root ?? null
95
93
  }
96
94
 
97
- private locate (...rel: string[]): string {
95
+ private locate(...rel: string[]): string {
98
96
  return join(ENTRIES, ...rel)
99
97
  }
100
98
 
101
- private span (method: string, path: string): SpanOptions {
99
+ private span(method: string, path: string): SpanOptions {
102
100
  return {
103
101
  name: `${method} ${this.scope?.name ?? 'storage'}`,
104
102
  kind: 'client',
105
103
  attributes: {
106
- ...this.scope === undefined ? {} : { provider: this.scope.provider },
104
+ ...(this.scope === undefined ? {} : { provider: this.scope.provider }),
107
105
  path
108
106
  }
109
107
  }
@@ -0,0 +1,14 @@
1
+ import type { Contribution } from '@toa.io/core/types'
2
+
3
+ /** What this extension puts on the context of a component that declares it. */
4
+ export function context(declaration: string[]): Contribution | null {
5
+ if (!Array.isArray(declaration) || declaration.length === 0) return null
6
+
7
+ const names = declaration.map((name) => JSON.stringify(name)).join(' | ')
8
+
9
+ return {
10
+ name: 'storages',
11
+ type: `Record<${names}, Storage>`,
12
+ imports: { '@toa.io/extensions.storages': ['Storage'] }
13
+ }
14
+ }