@directus/storage-driver-s3 10.0.11 → 10.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +7 -6
- package/dist/index.js +160 -152
- package/package.json +8 -7
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { Driver, Range } from '@directus/storage';
|
|
2
|
+
import { Readable } from 'node:stream';
|
|
3
|
+
|
|
4
|
+
type DriverS3Config = {
|
|
5
5
|
root?: string;
|
|
6
6
|
key?: string;
|
|
7
7
|
secret?: string;
|
|
@@ -12,7 +12,7 @@ export type DriverS3Config = {
|
|
|
12
12
|
region?: string;
|
|
13
13
|
forcePathStyle?: boolean;
|
|
14
14
|
};
|
|
15
|
-
|
|
15
|
+
declare class DriverS3 implements Driver {
|
|
16
16
|
private config;
|
|
17
17
|
private client;
|
|
18
18
|
private root;
|
|
@@ -31,4 +31,5 @@ export declare class DriverS3 implements Driver {
|
|
|
31
31
|
delete(filepath: string): Promise<void>;
|
|
32
32
|
list(prefix?: string): AsyncGenerator<string, void, unknown>;
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
|
|
35
|
+
export { DriverS3, DriverS3Config, DriverS3 as default };
|
package/dist/index.js
CHANGED
|
@@ -1,163 +1,171 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
CopyObjectCommand,
|
|
4
|
+
DeleteObjectCommand,
|
|
5
|
+
GetObjectCommand,
|
|
6
|
+
HeadObjectCommand,
|
|
7
|
+
ListObjectsV2Command,
|
|
8
|
+
S3Client
|
|
9
|
+
} from "@aws-sdk/client-s3";
|
|
10
|
+
import { Upload } from "@aws-sdk/lib-storage";
|
|
11
|
+
import { NodeHttpHandler } from "@aws-sdk/node-http-handler";
|
|
12
|
+
import { normalizePath } from "@directus/utils";
|
|
13
|
+
import { isReadableStream } from "@directus/utils/node";
|
|
14
|
+
import { Agent as HttpAgent } from "http";
|
|
15
|
+
import { Agent as HttpsAgent } from "https";
|
|
16
|
+
import { join } from "path";
|
|
17
|
+
var DriverS3 = class {
|
|
18
|
+
config;
|
|
19
|
+
client;
|
|
20
|
+
root;
|
|
21
|
+
constructor(config) {
|
|
22
|
+
this.config = config;
|
|
23
|
+
this.client = this.getClient();
|
|
24
|
+
this.root = this.config.root ? normalizePath(this.config.root, { removeLeading: true }) : "";
|
|
25
|
+
}
|
|
26
|
+
getClient() {
|
|
27
|
+
const connectionTimeout = 5e3;
|
|
28
|
+
const socketTimeout = 12e4;
|
|
29
|
+
const maxSockets = 500;
|
|
30
|
+
const keepAlive = true;
|
|
31
|
+
const s3ClientConfig = {
|
|
32
|
+
requestHandler: new NodeHttpHandler({
|
|
33
|
+
connectionTimeout,
|
|
34
|
+
socketTimeout,
|
|
35
|
+
httpAgent: new HttpAgent({ maxSockets, keepAlive }),
|
|
36
|
+
httpsAgent: new HttpsAgent({ maxSockets, keepAlive })
|
|
37
|
+
})
|
|
38
|
+
};
|
|
39
|
+
if (this.config.key && !this.config.secret || this.config.secret && !this.config.key) {
|
|
40
|
+
throw new Error("Both `key` and `secret` are required when defined");
|
|
17
41
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
*/
|
|
24
|
-
const connectionTimeout = 5000;
|
|
25
|
-
const socketTimeout = 120000;
|
|
26
|
-
const maxSockets = 500;
|
|
27
|
-
const keepAlive = true;
|
|
28
|
-
const s3ClientConfig = {
|
|
29
|
-
requestHandler: new NodeHttpHandler({
|
|
30
|
-
connectionTimeout,
|
|
31
|
-
socketTimeout,
|
|
32
|
-
httpAgent: new HttpAgent({ maxSockets, keepAlive }),
|
|
33
|
-
httpsAgent: new HttpsAgent({ maxSockets, keepAlive }),
|
|
34
|
-
}),
|
|
35
|
-
};
|
|
36
|
-
if ((this.config.key && !this.config.secret) || (this.config.secret && !this.config.key)) {
|
|
37
|
-
throw new Error('Both `key` and `secret` are required when defined');
|
|
38
|
-
}
|
|
39
|
-
if (this.config.key && this.config.secret) {
|
|
40
|
-
s3ClientConfig.credentials = {
|
|
41
|
-
accessKeyId: this.config.key,
|
|
42
|
-
secretAccessKey: this.config.secret,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
if (this.config.endpoint) {
|
|
46
|
-
const protocol = this.config.endpoint.startsWith('http://') ? 'http:' : 'https:';
|
|
47
|
-
const hostname = this.config.endpoint.replace('https://', '').replace('http://', '');
|
|
48
|
-
s3ClientConfig.endpoint = {
|
|
49
|
-
hostname,
|
|
50
|
-
protocol,
|
|
51
|
-
path: '/',
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
if (this.config.region) {
|
|
55
|
-
s3ClientConfig.region = this.config.region;
|
|
56
|
-
}
|
|
57
|
-
if (this.config.forcePathStyle !== undefined) {
|
|
58
|
-
s3ClientConfig.forcePathStyle = this.config.forcePathStyle;
|
|
59
|
-
}
|
|
60
|
-
return new S3Client(s3ClientConfig);
|
|
42
|
+
if (this.config.key && this.config.secret) {
|
|
43
|
+
s3ClientConfig.credentials = {
|
|
44
|
+
accessKeyId: this.config.key,
|
|
45
|
+
secretAccessKey: this.config.secret
|
|
46
|
+
};
|
|
61
47
|
}
|
|
62
|
-
|
|
63
|
-
|
|
48
|
+
if (this.config.endpoint) {
|
|
49
|
+
const protocol = this.config.endpoint.startsWith("http://") ? "http:" : "https:";
|
|
50
|
+
const hostname = this.config.endpoint.replace("https://", "").replace("http://", "");
|
|
51
|
+
s3ClientConfig.endpoint = {
|
|
52
|
+
hostname,
|
|
53
|
+
protocol,
|
|
54
|
+
path: "/"
|
|
55
|
+
};
|
|
64
56
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
Key: this.fullPath(filepath),
|
|
68
|
-
Bucket: this.config.bucket,
|
|
69
|
-
};
|
|
70
|
-
if (range) {
|
|
71
|
-
commandInput.Range = `bytes=${range.start ?? ''}-${range.end ?? ''}`;
|
|
72
|
-
}
|
|
73
|
-
const { Body: stream } = await this.client.send(new GetObjectCommand(commandInput));
|
|
74
|
-
if (!stream || !isReadableStream(stream)) {
|
|
75
|
-
throw new Error(`No stream returned for file "${filepath}"`);
|
|
76
|
-
}
|
|
77
|
-
return stream;
|
|
57
|
+
if (this.config.region) {
|
|
58
|
+
s3ClientConfig.region = this.config.region;
|
|
78
59
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
Key: this.fullPath(filepath),
|
|
82
|
-
Bucket: this.config.bucket,
|
|
83
|
-
}));
|
|
84
|
-
return {
|
|
85
|
-
size: ContentLength,
|
|
86
|
-
modified: LastModified,
|
|
87
|
-
};
|
|
60
|
+
if (this.config.forcePathStyle !== void 0) {
|
|
61
|
+
s3ClientConfig.forcePathStyle = this.config.forcePathStyle;
|
|
88
62
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
63
|
+
return new S3Client(s3ClientConfig);
|
|
64
|
+
}
|
|
65
|
+
fullPath(filepath) {
|
|
66
|
+
return normalizePath(join(this.root, filepath));
|
|
67
|
+
}
|
|
68
|
+
async read(filepath, range) {
|
|
69
|
+
const commandInput = {
|
|
70
|
+
Key: this.fullPath(filepath),
|
|
71
|
+
Bucket: this.config.bucket
|
|
72
|
+
};
|
|
73
|
+
if (range) {
|
|
74
|
+
commandInput.Range = `bytes=${range.start ?? ""}-${range.end ?? ""}`;
|
|
97
75
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
76
|
+
const { Body: stream } = await this.client.send(new GetObjectCommand(commandInput));
|
|
77
|
+
if (!stream || !isReadableStream(stream)) {
|
|
78
|
+
throw new Error(`No stream returned for file "${filepath}"`);
|
|
101
79
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
80
|
+
return stream;
|
|
81
|
+
}
|
|
82
|
+
async stat(filepath) {
|
|
83
|
+
const { ContentLength, LastModified } = await this.client.send(
|
|
84
|
+
new HeadObjectCommand({
|
|
85
|
+
Key: this.fullPath(filepath),
|
|
86
|
+
Bucket: this.config.bucket
|
|
87
|
+
})
|
|
88
|
+
);
|
|
89
|
+
return {
|
|
90
|
+
size: ContentLength,
|
|
91
|
+
modified: LastModified
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
async exists(filepath) {
|
|
95
|
+
try {
|
|
96
|
+
await this.stat(filepath);
|
|
97
|
+
return true;
|
|
98
|
+
} catch {
|
|
99
|
+
return false;
|
|
115
100
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
client: this.client,
|
|
133
|
-
params,
|
|
134
|
-
});
|
|
135
|
-
await upload.done();
|
|
101
|
+
}
|
|
102
|
+
async move(src, dest) {
|
|
103
|
+
await this.copy(src, dest);
|
|
104
|
+
await this.delete(src);
|
|
105
|
+
}
|
|
106
|
+
async copy(src, dest) {
|
|
107
|
+
const params = {
|
|
108
|
+
Key: this.fullPath(dest),
|
|
109
|
+
Bucket: this.config.bucket,
|
|
110
|
+
CopySource: `/${this.config.bucket}/${this.fullPath(src)}`
|
|
111
|
+
};
|
|
112
|
+
if (this.config.serverSideEncryption) {
|
|
113
|
+
params.ServerSideEncryption = this.config.serverSideEncryption;
|
|
114
|
+
}
|
|
115
|
+
if (this.config.acl) {
|
|
116
|
+
params.ACL = this.config.acl;
|
|
136
117
|
}
|
|
137
|
-
|
|
138
|
-
|
|
118
|
+
await this.client.send(new CopyObjectCommand(params));
|
|
119
|
+
}
|
|
120
|
+
async write(filepath, content, type) {
|
|
121
|
+
const params = {
|
|
122
|
+
Key: this.fullPath(filepath),
|
|
123
|
+
Body: content,
|
|
124
|
+
Bucket: this.config.bucket
|
|
125
|
+
};
|
|
126
|
+
if (type) {
|
|
127
|
+
params.ContentType = type;
|
|
139
128
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
do {
|
|
143
|
-
const listObjectsV2CommandInput = {
|
|
144
|
-
Bucket: this.config.bucket,
|
|
145
|
-
Prefix: this.fullPath(prefix),
|
|
146
|
-
MaxKeys: 1000,
|
|
147
|
-
};
|
|
148
|
-
if (continuationToken) {
|
|
149
|
-
listObjectsV2CommandInput.ContinuationToken = continuationToken;
|
|
150
|
-
}
|
|
151
|
-
const response = await this.client.send(new ListObjectsV2Command(listObjectsV2CommandInput));
|
|
152
|
-
continuationToken = response.NextContinuationToken;
|
|
153
|
-
if (response.Contents) {
|
|
154
|
-
for (const file of response.Contents) {
|
|
155
|
-
if (file.Key) {
|
|
156
|
-
yield file.Key.substring(this.root.length);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
} while (continuationToken);
|
|
129
|
+
if (this.config.acl) {
|
|
130
|
+
params.ACL = this.config.acl;
|
|
161
131
|
}
|
|
162
|
-
|
|
163
|
-
|
|
132
|
+
if (this.config.serverSideEncryption) {
|
|
133
|
+
params.ServerSideEncryption = this.config.serverSideEncryption;
|
|
134
|
+
}
|
|
135
|
+
const upload = new Upload({
|
|
136
|
+
client: this.client,
|
|
137
|
+
params
|
|
138
|
+
});
|
|
139
|
+
await upload.done();
|
|
140
|
+
}
|
|
141
|
+
async delete(filepath) {
|
|
142
|
+
await this.client.send(new DeleteObjectCommand({ Key: this.fullPath(filepath), Bucket: this.config.bucket }));
|
|
143
|
+
}
|
|
144
|
+
async *list(prefix = "") {
|
|
145
|
+
let continuationToken = void 0;
|
|
146
|
+
do {
|
|
147
|
+
const listObjectsV2CommandInput = {
|
|
148
|
+
Bucket: this.config.bucket,
|
|
149
|
+
Prefix: this.fullPath(prefix),
|
|
150
|
+
MaxKeys: 1e3
|
|
151
|
+
};
|
|
152
|
+
if (continuationToken) {
|
|
153
|
+
listObjectsV2CommandInput.ContinuationToken = continuationToken;
|
|
154
|
+
}
|
|
155
|
+
const response = await this.client.send(new ListObjectsV2Command(listObjectsV2CommandInput));
|
|
156
|
+
continuationToken = response.NextContinuationToken;
|
|
157
|
+
if (response.Contents) {
|
|
158
|
+
for (const file of response.Contents) {
|
|
159
|
+
if (file.Key) {
|
|
160
|
+
yield file.Key.substring(this.root.length);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
} while (continuationToken);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
var src_default = DriverS3;
|
|
168
|
+
export {
|
|
169
|
+
DriverS3,
|
|
170
|
+
src_default as default
|
|
171
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@directus/storage-driver-s3",
|
|
3
|
-
"version": "10.0.
|
|
3
|
+
"version": "10.0.13",
|
|
4
4
|
"description": "S3 file storage abstraction for `@directus/storage`",
|
|
5
5
|
"homepage": "https://directus.io",
|
|
6
6
|
"repository": {
|
|
@@ -25,19 +25,20 @@
|
|
|
25
25
|
"@aws-sdk/client-s3": "3.332.0",
|
|
26
26
|
"@aws-sdk/lib-storage": "3.332.0",
|
|
27
27
|
"@aws-sdk/node-http-handler": "3.344.0",
|
|
28
|
-
"@directus/storage": "10.0.
|
|
29
|
-
"@directus/utils": "
|
|
28
|
+
"@directus/storage": "10.0.7",
|
|
29
|
+
"@directus/utils": "11.0.1"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@ngneat/falso": "6.4.0",
|
|
33
33
|
"@vitest/coverage-c8": "0.31.1",
|
|
34
|
-
"
|
|
34
|
+
"tsup": "7.2.0",
|
|
35
|
+
"typescript": "5.2.2",
|
|
35
36
|
"vitest": "0.31.1",
|
|
36
|
-
"@directus/tsconfig": "1.0.
|
|
37
|
+
"@directus/tsconfig": "1.0.1"
|
|
37
38
|
},
|
|
38
39
|
"scripts": {
|
|
39
|
-
"build": "
|
|
40
|
-
"dev": "
|
|
40
|
+
"build": "tsup src/index.ts --format=esm --dts",
|
|
41
|
+
"dev": "tsup src/index.ts --format=esm --dts --watch",
|
|
41
42
|
"test": "vitest --watch=false"
|
|
42
43
|
}
|
|
43
44
|
}
|