@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,57 +0,0 @@
1
- import * as assert from 'node:assert'
2
- import { encode } from '@toa.io/generic'
3
- import { providers } from './providers'
4
- import { validateAnnotation } from './Annotation'
5
- import type { Annotation } from './Annotation'
6
- import type { Dependency, Variable } from '@toa.io/operations'
7
- import type { context } from '@toa.io/norm'
8
-
9
- export const SERIALIZATION_PREFIX = 'TOA_STORAGES'
10
-
11
- export function deployment (instances: Instance[], annotation: unknown): Dependency {
12
- validate(instances, annotation)
13
-
14
- const value = encode(annotation)
15
- const pointer: Variable = { name: SERIALIZATION_PREFIX, value }
16
- const secrets = getSecrets(annotation)
17
-
18
- return {
19
- variables: { global: [pointer, ...secrets] }
20
- }
21
- }
22
-
23
- function validate (instances: Instance[], annotation: unknown): asserts annotation is Annotation {
24
- validateAnnotation(annotation)
25
-
26
- for (const instance of instances)
27
- contains(instance, annotation)
28
- }
29
-
30
- function contains (instance: Instance, annotation: Annotation): void {
31
- for (const name of instance.manifest)
32
- assert.ok(name in annotation,
33
- `Missing '${name}' storage annotation ` +
34
- `declared in '${instance.component.locator.id}'`)
35
- }
36
-
37
- function getSecrets (annotation: Annotation): Variable[] {
38
- const secrets: Variable[] = []
39
-
40
- for (const [name, declaration] of Object.entries(annotation)) {
41
- const Provider = providers[declaration.provider]
42
-
43
- for (const secret of Provider.SECRETS)
44
- secrets.push({
45
- name: `${SERIALIZATION_PREFIX}_${name}_${secret.name}`.toUpperCase(),
46
- secret: {
47
- name: `toa-storages-${name}`,
48
- key: secret.name,
49
- optional: secret.optional
50
- }
51
- })
52
- }
53
-
54
- return secrets
55
- }
56
-
57
- export type Instance = context.Dependency<string[]>
package/source/index.ts DELETED
@@ -1,5 +0,0 @@
1
- export { Factory } from './Factory'
2
- export { deployment } from './deployment'
3
- export { manifest } from './manifest'
4
-
5
- export type { Entry } from './Entry'
@@ -1,8 +0,0 @@
1
- import { match } from 'matchacho'
2
-
3
- export function manifest (manifest: string | string[] | null): string[] {
4
- return match(manifest,
5
- String, (name: string) => [name],
6
- Array, manifest,
7
- null, [])
8
- }
@@ -1,10 +0,0 @@
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)
@@ -1,5 +0,0 @@
1
- import { FileSystem } from './FileSystem'
2
-
3
- it('should be ok', async () => {
4
- expect(() => new FileSystem({ path: 'path' })).not.toThrow()
5
- })
@@ -1,63 +0,0 @@
1
- import { type Readable } from 'node:stream'
2
- import { dirname, join } from 'node:path'
3
- import fs from 'node:fs/promises'
4
- import { Provider } from '../Provider'
5
- import type { ProviderSecrets } from '../Provider'
6
-
7
- export interface FileSystemOptions {
8
- path: string
9
- }
10
-
11
- export class FileSystem extends Provider<FileSystemOptions> {
12
- protected readonly path: string
13
-
14
- public constructor (options: FileSystemOptions, secrets?: ProviderSecrets) {
15
- super(options, secrets)
16
-
17
- this.path = options.path
18
- }
19
-
20
- public async get (path: string): Promise<Readable | null> {
21
- try {
22
- const fd = await fs.open(join(this.path, path))
23
-
24
- return fd.createReadStream()
25
- } catch (err) {
26
- if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') return null
27
-
28
- throw err
29
- }
30
- }
31
-
32
- public async list (path: string): Promise<string[]> {
33
- const dir = join(this.path, path)
34
-
35
- return (await fs.readdir(dir, { withFileTypes: true }))
36
- .filter((dirent) => dirent.isFile())
37
- .map((dirent) => dirent.name)
38
- }
39
-
40
- public async put (rel: string, filename: string, stream: Readable): Promise<void> {
41
- const dir = join(this.path, rel)
42
- const path = join(dir, filename)
43
-
44
- await fs.mkdir(dir, { recursive: true })
45
- await fs.writeFile(path, stream)
46
- }
47
-
48
- public async delete (path: string): Promise<void> {
49
- await fs.rm(join(this.path, path), { recursive: true, force: true })
50
- }
51
-
52
- public async move (from: string, to: string): Promise<void> {
53
- from = join(this.path, from)
54
- to = join(this.path, to)
55
-
56
- await fs.mkdir(dirname(to), { recursive: true })
57
- await fs.rename(from, to)
58
- }
59
-
60
- public async moveDir (from: string, to: string): Promise<void> {
61
- await this.move(from, to).catch(() => null)
62
- }
63
- }
@@ -1,55 +0,0 @@
1
- import { Readable } from 'node:stream'
2
- import { join, posix } from 'node:path'
3
- import { buffer } from 'node:stream/consumers'
4
- import * as assert from 'node:assert'
5
-
6
- import { Provider } from '../Provider'
7
-
8
- /**
9
- * In-memory provider
10
- */
11
- export class InMemory extends Provider {
12
- private readonly storage = new Map<string, Buffer>()
13
-
14
- public async get (path: string): Promise<Readable | null> {
15
- const data = this.storage.get(path)
16
-
17
- if (data === undefined) return null
18
-
19
- return Readable.from(data)
20
- }
21
-
22
- public async list (path: string): Promise<string[]> {
23
- return Array.from(this.storage.keys())
24
- .filter((f) => posix.dirname(f) === path)
25
- .map((f) => posix.basename(f))
26
- }
27
-
28
- public async put (path: string, filename: string, stream: Readable): Promise<void> {
29
- this.storage.set(join(path, filename), await buffer(stream))
30
- }
31
-
32
- public async delete (path: string): Promise<void> {
33
- for (const f of this.storage.keys())
34
- if (f.startsWith(path)) this.storage.delete(f)
35
- }
36
-
37
- public async move (from: string, to: string): Promise<void> {
38
- const buf = this.storage.get(from)
39
-
40
- assert.ok(buf !== undefined, `File not found: ${from}`)
41
-
42
- this.storage.set(to, buf)
43
- this.storage.delete(from)
44
- }
45
-
46
- public async moveDir (from: string, to: string): Promise<void> {
47
- for (const f of this.storage.keys())
48
- if (f.startsWith(from)) {
49
- const toPath = to + f.slice(from.length)
50
-
51
- this.storage.set(toPath, this.storage.get(f)!)
52
- this.storage.delete(f)
53
- }
54
- }
55
- }
@@ -1,134 +0,0 @@
1
- import { Readable } from 'node:stream'
2
- import { randomUUID } from 'node:crypto'
3
- import streamConsumers from 'node:stream/consumers'
4
- import { http, HttpResponse } from 'msw'
5
- import { setupServer } from 'msw/node'
6
-
7
- import { S3 } from './S3'
8
-
9
- /* eslint-disable @typescript-eslint/no-non-null-assertion -- jest expect is not type guard */
10
-
11
- describe('S3 storage provider', () => {
12
- let provider: S3
13
- let s3endpoint: ReturnType<typeof setupServer>
14
-
15
- const AWS_REGION = 'us-east-1'
16
-
17
- const TEST_BUCKET_NAME = 'test-bucket'
18
- const S3_ENDPOINT = `https://${TEST_BUCKET_NAME}.s3.${AWS_REGION}.amazonaws.com`
19
-
20
- beforeAll(async () => {
21
- jest.useRealTimers()
22
- s3endpoint = setupServer()
23
- s3endpoint.listen({ onUnhandledRequest: 'error' })
24
-
25
- provider = new S3({
26
- bucket: TEST_BUCKET_NAME,
27
- region: AWS_REGION
28
- }, {
29
- ACCESS_KEY_ID: 'test-key',
30
- SECRET_ACCESS_KEY: 'test-key-secret'
31
- })
32
- })
33
-
34
- afterEach(() => {
35
- s3endpoint.resetHandlers()
36
- })
37
-
38
- afterAll(() => {
39
- s3endpoint.close()
40
- })
41
-
42
- test('should be able to get file from S3 handling leading slash', async () => {
43
- const testBody = randomUUID().repeat(10)
44
-
45
- s3endpoint.use(http.get(`${S3_ENDPOINT}/some/absolute/path/filename`,
46
- () => HttpResponse.text(testBody)))
47
-
48
- const body = await provider.get('/some/absolute/path/filename')
49
-
50
- expect(body).toBeInstanceOf(Readable)
51
- expect((await streamConsumers.text(body!))).toBe(testBody)
52
- })
53
-
54
- test('should remove folder with all files', async () => {
55
- const headHandler = jest.fn().mockReturnValue(HttpResponse.xml('', { status: 404 }))
56
-
57
- const listHandler = jest.fn().mockReturnValueOnce(HttpResponse.xml(`
58
- <ListBucketResult>
59
- <NextContinuationToken>someToken</NextContinuationToken>
60
- <KeyCount>1001</KeyCount>
61
- <MaxKeys>1000</MaxKeys>
62
- <IsTruncated>true</IsTruncated>
63
- <Contents>
64
- <Key>happy_face1.jpg</Key>
65
- </Contents>
66
- </ListBucketResult>
67
- `, {
68
- status: 200
69
- })).mockReturnValueOnce(HttpResponse.xml(`
70
- <ListBucketResult>
71
- <KeyCount>1001</KeyCount>
72
- <MaxKeys>1000</MaxKeys>
73
- <Contents>
74
- <Key>happy_face2.jpg</Key>
75
- </Contents>
76
- <Contents>
77
- <Key>happy_face3.jpg</Key>
78
- </Contents>
79
- </ListBucketResult>
80
- `, {
81
- status: 200
82
- }))
83
-
84
- const deleteHandler = jest.fn().mockReturnValue(HttpResponse.xml(`
85
- <DeleteResult>
86
- <Deleted>
87
- <Key>happy_face1.jpg</Key>
88
- </Deleted>
89
- <Deleted>
90
- <Key>happy_face2.jpg</Key>
91
- </Deleted>
92
- <Deleted>
93
- <Key>happy_face3.jpg</Key>
94
- </Deleted>
95
- </DeleteResult>
96
- `, {
97
- status: 200
98
- }))
99
-
100
- s3endpoint.use(http.head(`${S3_ENDPOINT}/some/absolute/path_to_remove`,
101
- headHandler),
102
- http.get(`${S3_ENDPOINT}/`,
103
- listHandler),
104
- http.post(`${S3_ENDPOINT}/`,
105
- deleteHandler))
106
-
107
- await provider.delete('/some/absolute/path_to_remove')
108
-
109
- expect(headHandler).toHaveBeenCalledTimes(1)
110
- expect(listHandler).toHaveBeenCalledTimes(2)
111
- expect(deleteHandler).toHaveBeenCalledTimes(1)
112
- })
113
-
114
- test('should be able to upload a file to S3', async () => {
115
- const body = Readable.from('test content')
116
-
117
- const handler = jest.fn().mockReturnValue(HttpResponse.xml('', {
118
- status: 200,
119
- headers: {
120
- ETag: 'test-etag',
121
- 'x-amz-id-2': 'test_id_2',
122
- 'x-amz-request-id': 'test_request_id',
123
- Server: 'AmazonS3'
124
- }
125
- }))
126
-
127
- s3endpoint.use(http.put(`${S3_ENDPOINT}/some/absolute/path/filename`,
128
- handler))
129
-
130
- await provider.put('/some/absolute/path', 'filename', body)
131
- expect(handler).toHaveBeenCalledTimes(1)
132
- expect(body.readableEnded).toBe(true)
133
- })
134
- })
@@ -1,196 +0,0 @@
1
- import { Readable } from 'node:stream'
2
- import { Blob } from 'node:buffer'
3
- import { join } from 'node:path/posix'
4
- import assert from 'node:assert'
5
- import { posix } from 'node:path'
6
- import { Upload } from '@aws-sdk/lib-storage'
7
- import {
8
- S3Client,
9
- GetObjectCommand,
10
- CopyObjectCommand,
11
- DeleteObjectsCommand,
12
- paginateListObjectsV2,
13
- HeadObjectCommand,
14
- NotFound,
15
- DeleteObjectCommand,
16
- NoSuchKey,
17
- type S3ClientConfigType,
18
- type ObjectIdentifier
19
- } from '@aws-sdk/client-s3'
20
- import * as nodeNativeFetch from 'smithy-node-native-fetch'
21
- import { Provider, type ProviderSecret, type ProviderSecrets } from '../Provider'
22
- import type { ReadableStream } from 'node:stream/web'
23
-
24
- export interface S3Options {
25
- bucket: string
26
- region?: string
27
- prefix?: string
28
- endpoint?: string
29
- }
30
-
31
- type S3Secrets = ProviderSecrets<'ACCESS_KEY_ID' | 'SECRET_ACCESS_KEY'>
32
-
33
- export class S3 extends Provider<S3Options> {
34
- public static override readonly SECRETS: readonly ProviderSecret[] = [
35
- { name: 'ACCESS_KEY_ID', optional: true },
36
- { name: 'SECRET_ACCESS_KEY', optional: true }
37
- ]
38
-
39
- protected readonly bucket: string
40
- protected readonly client: S3Client
41
-
42
- public constructor (options: S3Options, secrets?: S3Secrets) {
43
- super(options)
44
-
45
- this.bucket = options.bucket
46
-
47
- const s3Config: S3ClientConfigType = {
48
- retryMode: 'adaptive',
49
- ...nodeNativeFetch
50
- }
51
-
52
- if (options.endpoint !== undefined) {
53
- s3Config.forcePathStyle = options.endpoint.startsWith('http://')
54
- s3Config.endpoint = options.endpoint
55
- }
56
-
57
- if (options.region !== undefined) s3Config.region = options.region
58
-
59
- if (typeof secrets?.ACCESS_KEY_ID === 'string') {
60
- assert.ok(secrets.SECRET_ACCESS_KEY !== undefined,
61
- 'SECRET_ACCESS_KEY is required if ACCESS_KEY_ID is provided')
62
-
63
- s3Config.credentials = {
64
- accessKeyId: secrets.ACCESS_KEY_ID,
65
- secretAccessKey: secrets.SECRET_ACCESS_KEY
66
- }
67
- }
68
-
69
- this.client = new S3Client(s3Config)
70
-
71
- this.client.middlewareStack.add((next, _context) => async (args) => {
72
- if ('Key' in args.input && typeof args.input.Key === 'string')
73
- // removes leading slash
74
-
75
- args.input.Key = args.input.Key.replace(/^\//, '')
76
-
77
- if ('Prefix' in args.input && typeof args.input.Prefix === 'string')
78
- // removes leading slash and ensures finishing slash
79
-
80
- args.input.Prefix = args.input.Prefix.replace(/^\/|\/$/g, '') + '/'
81
-
82
- return next(args)
83
- },
84
- {
85
- step: 'initialize',
86
- priority: 'high',
87
- name: 'normalizesSlashesInPath'
88
- })
89
- }
90
-
91
- public async get (Key: string): Promise<Readable | null> {
92
- try {
93
- const fileResponse = await this.client.send(new GetObjectCommand({
94
- Bucket: this.bucket,
95
- Key
96
- }))
97
-
98
- if (fileResponse.Body === undefined) return null // should never happen
99
-
100
- if (fileResponse.Body instanceof Readable) return fileResponse.Body
101
-
102
- return Readable.fromWeb((fileResponse.Body instanceof Blob
103
- ? fileResponse.Body.stream()
104
- : fileResponse.Body) as ReadableStream) // types mismatch between Node 20 and aws-sdk
105
- } catch (err) {
106
- if (err instanceof NotFound || err instanceof NoSuchKey)
107
- return null
108
- else
109
- throw err
110
- }
111
- }
112
-
113
- public async list (prefix: string): Promise<string[]> {
114
- return (await this.listObjects(prefix))
115
- .map(({ Key }) => posix.basename(Key!))
116
- }
117
-
118
- public async put (path: string, filename: string, stream: Readable): Promise<void> {
119
- await new Upload({
120
- client: this.client,
121
- params: {
122
- Bucket: this.bucket,
123
- Key: join(path, filename),
124
- Body: stream
125
- }
126
- }).done()
127
- }
128
-
129
- /**
130
- * Deletes either a single object or "directory" - all objects
131
- * with given prefix (prefix will be enforced to finish with `/`)
132
- * @param Key - key name or path prefix
133
- */
134
- public async delete (Key: string): Promise<void> {
135
- const { client, bucket: Bucket } = this
136
-
137
- // checking if given key is a single file
138
- if (!Key.endsWith('/'))
139
- try {
140
- // DeleteObject on S3 returns no error if object does not exist
141
- await client.send(new HeadObjectCommand({ Bucket, Key }))
142
- await client.send(new DeleteObjectCommand({ Bucket, Key }))
143
-
144
- return
145
- } catch (err) {
146
- assert.ok(err instanceof NotFound || err instanceof NoSuchKey, err as Error)
147
- }
148
-
149
- const objectsToRemove: ObjectIdentifier[] = await this.listObjects(Key)
150
-
151
- // Removing all objects in parallel in batches
152
- await Promise.all((function * () {
153
- while (objectsToRemove.length > 0)
154
- yield client.send(new DeleteObjectsCommand({
155
- Bucket,
156
- Delete: {
157
- Objects: objectsToRemove.splice(0,
158
- 1000 /* max batch size for DeleteObjects */)
159
- }
160
- }))
161
- })())
162
- }
163
-
164
- public async move (from: string, keyTo: string): Promise<void> {
165
- await this.client.send(new CopyObjectCommand({
166
- Bucket: this.bucket,
167
- Key: keyTo,
168
- CopySource: join(this.bucket, from)
169
- }))
170
-
171
- await this.client.send(new DeleteObjectCommand({ Bucket: this.bucket, Key: from }))
172
- }
173
-
174
- public async moveDir (from: string, to: string): Promise<void> {
175
- const objects: ObjectIdentifier[] = await this.listObjects(from)
176
-
177
- await Promise.all(objects.map(async ({ Key }) => {
178
- const ent = posix.basename(Key!)
179
- const source = posix.join(from, ent)
180
- const target = posix.join(to, ent)
181
-
182
- return this.move(source, target)
183
- }))
184
- }
185
-
186
- private async listObjects (Prefix: string): Promise<ObjectIdentifier[]> {
187
- const { client, bucket: Bucket } = this
188
- const objects: ObjectIdentifier[] = []
189
-
190
- for await (const page of paginateListObjectsV2({ client }, { Bucket, Prefix }))
191
- for (const { Key } of page.Contents ?? [])
192
- objects.push({ Key })
193
-
194
- return objects
195
- }
196
- }
@@ -1,16 +0,0 @@
1
- import { tmpdir } from 'node:os'
2
- import { join } from 'node:path'
3
- import { FileSystem } from './FileSystem'
4
- import type { ProviderSecrets } from '../Provider'
5
-
6
- export interface TemporaryOptions {
7
- directory: string
8
- }
9
-
10
- export class Temporary extends FileSystem {
11
- public constructor (options: TemporaryOptions, secrets?: ProviderSecrets) {
12
- const path = join(tmpdir(), options.directory)
13
-
14
- super({ path }, secrets)
15
- }
16
- }
@@ -1,13 +0,0 @@
1
- import { Temporary, type TemporaryOptions } from './Temporary'
2
- import type { ProviderSecret, ProviderSecrets } from '../Provider'
3
-
4
- export class Test extends Temporary {
5
- public static override readonly SECRETS: readonly ProviderSecret[] = [
6
- { name: 'USERNAME' },
7
- { name: 'PASSWORD' }
8
- ]
9
-
10
- public constructor (options: TemporaryOptions, secrets?: ProviderSecrets) {
11
- super(options, secrets)
12
- }
13
- }