@storecraft/storage-s3-compatible 1.0.10 → 1.0.12
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/README.md +5 -10
- package/adapter.js +130 -61
- package/aws4fetch.js +1 -1
- package/package.json +1 -1
- package/tests/storage.aws-s3.test.js +1 -0
- package/tests/storage.r2.test.js +4 -4
- package/tests/storage.s3-compatible.test.js +5 -4
- package/types.public.d.ts +45 -8
package/README.md
CHANGED
@@ -28,10 +28,10 @@ npm i @storecraft/storage-s3-compatible
|
|
28
28
|
import { R2, S3, DigitalOceanSpaces, S3CompatibleStorage } from '@storecraft/storage-s3-compatible'
|
29
29
|
|
30
30
|
const storage = new R2({
|
31
|
-
accessKeyId: process.env.
|
32
|
-
account_id: process.env.
|
33
|
-
bucket: process.env.
|
34
|
-
secretAccessKey: process.env.
|
31
|
+
accessKeyId: process.env.S3_ACCESS_KEY_ID,
|
32
|
+
account_id: process.env.CF_ACCOUNT_ID,
|
33
|
+
bucket: process.env.S3_BUCKET,
|
34
|
+
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY
|
35
35
|
});
|
36
36
|
|
37
37
|
// write
|
@@ -69,12 +69,7 @@ const app = new App(
|
|
69
69
|
.withPlatform(new NodePlatform())
|
70
70
|
.withDatabase(new MongoDB())
|
71
71
|
.withStorage(
|
72
|
-
new R2(
|
73
|
-
process.env.R2_BUCKET,
|
74
|
-
process.env.R2_ACCOUNT_ID,
|
75
|
-
process.env.R2_ACCESS_KEY_ID,
|
76
|
-
process.env.R2_SECRET_ACCESS_KEY
|
77
|
-
)
|
72
|
+
new R2() // config will be inferred by env variables
|
78
73
|
);
|
79
74
|
|
80
75
|
await app.init();
|
package/adapter.js
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
/**
|
2
|
+
* @import { AwsS3Config, Config, R2Config } from './types.public.js'
|
3
|
+
* @import { ENV } from '@storecraft/core';
|
4
|
+
* @import { storage_driver, StorageFeatures } from '@storecraft/core/storage'
|
5
|
+
*/
|
1
6
|
import { App } from '@storecraft/core'
|
2
7
|
import { AwsClient } from './aws4fetch.js';
|
3
8
|
|
@@ -23,38 +28,23 @@ const infer_content_type = (name) => {
|
|
23
28
|
return type ?? 'application/octet-stream';
|
24
29
|
}
|
25
30
|
|
26
|
-
|
27
|
-
/**
|
28
|
-
* @typedef {import('./types.public.d.ts').Config} Config
|
29
|
-
*/
|
30
|
-
|
31
31
|
/**
|
32
32
|
* @description The base S3 compatible class
|
33
33
|
*
|
34
|
-
* @
|
35
|
-
*
|
36
|
-
* @implements {storage}
|
34
|
+
* @implements {storage_driver}
|
37
35
|
*/
|
38
36
|
export class S3CompatibleStorage {
|
39
37
|
|
38
|
+
/** @satisfies {ENV<Config>} */
|
39
|
+
static EnvConfig = /** @type{const} */ ({
|
40
|
+
accessKeyId: 'S3_ACCESS_KEY_ID',
|
41
|
+
secretAccessKey: 'S3_SECRET_ACCESS_KEY',
|
42
|
+
bucket: 'S3_BUCKET',
|
43
|
+
region: 'S3_REGION',
|
44
|
+
});
|
45
|
+
|
40
46
|
/** @type {AwsClient} */ #_client;
|
41
47
|
/** @type {Config} */ #_config;
|
42
|
-
/** @type {string} */ #_url;
|
43
|
-
|
44
|
-
/**
|
45
|
-
*
|
46
|
-
* @param {Config} options
|
47
|
-
*/
|
48
|
-
#compute_url(options) {
|
49
|
-
const url = new URL(options.endpoint);
|
50
|
-
if(options.forcePathStyle) {
|
51
|
-
url.pathname = options.bucket;
|
52
|
-
} else {
|
53
|
-
url.host = `${options.bucket}.${url.host}`;
|
54
|
-
}
|
55
|
-
return url.toString();
|
56
|
-
}
|
57
|
-
|
58
48
|
|
59
49
|
/**
|
60
50
|
*
|
@@ -62,20 +52,23 @@ export class S3CompatibleStorage {
|
|
62
52
|
*/
|
63
53
|
constructor(config) {
|
64
54
|
this.#_config = config;
|
65
|
-
this.#_client = new AwsClient({
|
66
|
-
accessKeyId: config.accessKeyId, secretAccessKey: config.secretAccessKey,
|
67
|
-
region: config.region ?? 'auto', service: 's3'
|
68
|
-
});
|
69
|
-
|
70
|
-
this.#_url = this.#compute_url(config);
|
71
55
|
}
|
72
56
|
|
73
|
-
get url() { return this.#_url; }
|
74
|
-
get client() { return this.#_client; }
|
75
57
|
get config() { return this.#_config; }
|
58
|
+
get client() {
|
59
|
+
this.#_client = this.#_client ?? new AwsClient(
|
60
|
+
{
|
61
|
+
accessKeyId: this.config.accessKeyId,
|
62
|
+
secretAccessKey: this.config.secretAccessKey,
|
63
|
+
region: this.config.region ?? 'auto',
|
64
|
+
service: 's3'
|
65
|
+
}
|
66
|
+
);
|
67
|
+
return this.#_client;
|
68
|
+
}
|
76
69
|
|
77
70
|
features() {
|
78
|
-
/** @type {
|
71
|
+
/** @type {StorageFeatures} */
|
79
72
|
const f = {
|
80
73
|
supports_signed_urls: true
|
81
74
|
}
|
@@ -85,9 +78,16 @@ export class S3CompatibleStorage {
|
|
85
78
|
|
86
79
|
/**
|
87
80
|
*
|
88
|
-
* @type {
|
81
|
+
* @type {storage_driver["init"]}
|
89
82
|
*/
|
90
|
-
async init(app) {
|
83
|
+
async init(app) {
|
84
|
+
this.config.accessKeyId ??= app.platform.env[S3CompatibleStorage.EnvConfig.accessKeyId];
|
85
|
+
this.config.secretAccessKey ??= app.platform.env[S3CompatibleStorage.EnvConfig.secretAccessKey];
|
86
|
+
this.config.bucket ??= app.platform.env[S3CompatibleStorage.EnvConfig.bucket];
|
87
|
+
// @ts-ignore
|
88
|
+
this.config.region ??= app.platform.env[S3CompatibleStorage.EnvConfig.region];
|
89
|
+
return this;
|
90
|
+
}
|
91
91
|
|
92
92
|
// puts
|
93
93
|
|
@@ -118,7 +118,7 @@ export class S3CompatibleStorage {
|
|
118
118
|
|
119
119
|
/**
|
120
120
|
*
|
121
|
-
* @type {
|
121
|
+
* @type {storage_driver["putBlob"]}
|
122
122
|
*/
|
123
123
|
async putBlob(key, blob) {
|
124
124
|
return this.#put_internal(key, blob);
|
@@ -126,7 +126,7 @@ export class S3CompatibleStorage {
|
|
126
126
|
|
127
127
|
/**
|
128
128
|
*
|
129
|
-
* @type {
|
129
|
+
* @type {storage_driver["putArraybuffer"]}
|
130
130
|
*/
|
131
131
|
async putArraybuffer(key, buffer) {
|
132
132
|
return this.#put_internal(key, buffer);
|
@@ -134,7 +134,7 @@ export class S3CompatibleStorage {
|
|
134
134
|
|
135
135
|
/**
|
136
136
|
*
|
137
|
-
* @type {
|
137
|
+
* @type {storage_driver["putStream"]}
|
138
138
|
*/
|
139
139
|
async putStream(key, stream, meta={}, bytesLength=0) {
|
140
140
|
const extra_headers = {};
|
@@ -150,7 +150,7 @@ export class S3CompatibleStorage {
|
|
150
150
|
|
151
151
|
/**
|
152
152
|
*
|
153
|
-
* @type {
|
153
|
+
* @type {storage_driver["putSigned"]}
|
154
154
|
*/
|
155
155
|
async putSigned(key) {
|
156
156
|
const url = new URL(this.get_file_url(key));
|
@@ -172,9 +172,19 @@ export class S3CompatibleStorage {
|
|
172
172
|
|
173
173
|
// gets
|
174
174
|
|
175
|
+
get url() {
|
176
|
+
const url = new URL(this.config.endpoint);
|
177
|
+
if(this.config.forcePathStyle) {
|
178
|
+
url.pathname = this.config.bucket;
|
179
|
+
} else {
|
180
|
+
url.host = `${this.config.bucket}.${url.host}`;
|
181
|
+
}
|
182
|
+
return url.toString();
|
183
|
+
}
|
184
|
+
|
175
185
|
/** @param {string} key */
|
176
186
|
get_file_url(key) {
|
177
|
-
return `${this
|
187
|
+
return `${this.url}/${key}`;
|
178
188
|
}
|
179
189
|
|
180
190
|
/** @param {string} key */
|
@@ -183,7 +193,7 @@ export class S3CompatibleStorage {
|
|
183
193
|
}
|
184
194
|
|
185
195
|
/**
|
186
|
-
* @type {
|
196
|
+
* @type {storage_driver["getArraybuffer"]}
|
187
197
|
*/
|
188
198
|
async getArraybuffer(key) {
|
189
199
|
const r = await this.#get_request(key);
|
@@ -199,7 +209,7 @@ export class S3CompatibleStorage {
|
|
199
209
|
|
200
210
|
/**
|
201
211
|
*
|
202
|
-
* @type {
|
212
|
+
* @type {storage_driver["getBlob"]}
|
203
213
|
*/
|
204
214
|
async getBlob(key) {
|
205
215
|
const r = await this.#get_request(key);
|
@@ -214,7 +224,7 @@ export class S3CompatibleStorage {
|
|
214
224
|
}
|
215
225
|
|
216
226
|
/**
|
217
|
-
* @type {
|
227
|
+
* @type {storage_driver["getStream"]}
|
218
228
|
*/
|
219
229
|
async getStream(key) {
|
220
230
|
const r = await this.#get_request(key);
|
@@ -231,7 +241,7 @@ export class S3CompatibleStorage {
|
|
231
241
|
|
232
242
|
/**
|
233
243
|
*
|
234
|
-
* @type {
|
244
|
+
* @type {storage_driver["getSigned"]}
|
235
245
|
*/
|
236
246
|
async getSigned(key) {
|
237
247
|
const url = new URL(this.get_file_url(key));
|
@@ -256,7 +266,7 @@ export class S3CompatibleStorage {
|
|
256
266
|
|
257
267
|
/**
|
258
268
|
*
|
259
|
-
* @type {
|
269
|
+
* @type {storage_driver["remove"]}
|
260
270
|
*/
|
261
271
|
async remove(key) {
|
262
272
|
const r = await this.client.fetch(
|
@@ -271,58 +281,117 @@ export class S3CompatibleStorage {
|
|
271
281
|
*/
|
272
282
|
export class R2 extends S3CompatibleStorage {
|
273
283
|
|
284
|
+
/** @satisfies {ENV<R2Config>} */
|
285
|
+
static R2EnvConfig = /** @type{const} */ ({
|
286
|
+
accessKeyId: 'S3_ACCESS_KEY_ID',
|
287
|
+
secretAccessKey: 'S3_SECRET_ACCESS_KEY',
|
288
|
+
bucket: 'S3_BUCKET',
|
289
|
+
account_id: 'CF_ACCOUNT_ID',
|
290
|
+
});
|
291
|
+
|
274
292
|
/**
|
275
|
-
* @param {
|
293
|
+
* @param {R2Config} config
|
276
294
|
*/
|
277
|
-
constructor({
|
295
|
+
constructor(config={}) {
|
278
296
|
super(
|
279
297
|
{
|
280
|
-
endpoint:
|
281
|
-
|
282
|
-
|
298
|
+
endpoint: config.account_id ?
|
299
|
+
`https://${config.account_id}.r2.cloudflarestorage.com` :
|
300
|
+
undefined,
|
301
|
+
accessKeyId: config.accessKeyId,
|
302
|
+
secretAccessKey:config.secretAccessKey,
|
303
|
+
bucket: config.bucket,
|
304
|
+
forcePathStyle: true,
|
305
|
+
region: 'auto'
|
283
306
|
}
|
284
|
-
)
|
307
|
+
);
|
308
|
+
this.r2_config = config;
|
285
309
|
}
|
286
310
|
|
311
|
+
/** @type {S3CompatibleStorage["init"]} */
|
312
|
+
init = async (app) => {
|
313
|
+
await super.init(app);
|
314
|
+
this.r2_config.account_id ??= app.platform.env[R2.R2EnvConfig.account_id];
|
315
|
+
this.config.endpoint ??= `https://${this.r2_config.account_id}.r2.cloudflarestorage.com`;
|
316
|
+
return this;
|
317
|
+
}
|
318
|
+
|
287
319
|
}
|
288
320
|
|
321
|
+
|
289
322
|
/**
|
290
323
|
* Amazon S3
|
291
324
|
*/
|
292
325
|
export class S3 extends S3CompatibleStorage {
|
293
326
|
|
327
|
+
/** @satisfies {ENV<AwsS3Config>} */
|
328
|
+
static AWSS3EnvConfig = /** @type{const} */ ({
|
329
|
+
...S3CompatibleStorage.EnvConfig
|
330
|
+
});
|
331
|
+
|
294
332
|
/**
|
295
|
-
* @param {
|
333
|
+
* @param {Partial<AwsS3Config>} config
|
296
334
|
*/
|
297
|
-
constructor(
|
335
|
+
constructor(config = {}) {
|
298
336
|
super(
|
299
337
|
{
|
300
|
-
endpoint: `https://s3
|
301
|
-
accessKeyId
|
302
|
-
|
338
|
+
endpoint: config.region ? `https://s3.${config.region}.amazonaws.com` : undefined,
|
339
|
+
accessKeyId: config.accessKeyId,
|
340
|
+
secretAccessKey: config.secretAccessKey,
|
341
|
+
bucket: config.bucket,
|
342
|
+
forcePathStyle: config.forcePathStyle ?? false,
|
343
|
+
region: config.region
|
303
344
|
}
|
304
345
|
)
|
305
346
|
}
|
306
347
|
|
348
|
+
/** @type {S3CompatibleStorage["init"]} */
|
349
|
+
init = async (app) => {
|
350
|
+
await super.init(app);
|
351
|
+
this.config.endpoint = `https://s3${this.config.region ?
|
352
|
+
('.'+this.config.region) : ''}.amazonaws.com`;
|
353
|
+
return this;
|
354
|
+
}
|
355
|
+
|
307
356
|
}
|
308
357
|
|
358
|
+
|
309
359
|
/**
|
310
360
|
* Digital Ocean spaces
|
311
361
|
*/
|
312
362
|
export class DigitalOceanSpaces extends S3CompatibleStorage {
|
313
363
|
|
364
|
+
/** @satisfies {ENV<AwsS3Config>} */
|
365
|
+
static DOEnvConfig = /** @type{const} */ ({
|
366
|
+
...S3CompatibleStorage.EnvConfig
|
367
|
+
});
|
368
|
+
|
314
369
|
/**
|
315
|
-
* @param {Omit<
|
370
|
+
* @param {Partial<Omit<Config, 'endpoint' | 'forcePathStyle'>>} config
|
316
371
|
*/
|
317
|
-
constructor(
|
372
|
+
constructor(config = {}) {
|
318
373
|
super(
|
319
374
|
{
|
320
|
-
endpoint:
|
321
|
-
|
322
|
-
|
375
|
+
endpoint: config.region ?
|
376
|
+
`https://${config.region}.digitaloceanspaces.com` : undefined,
|
377
|
+
accessKeyId: config.accessKeyId,
|
378
|
+
secretAccessKey: config.secretAccessKey,
|
379
|
+
bucket: config.bucket,
|
380
|
+
forcePathStyle: false, region: 'auto'
|
323
381
|
}
|
324
382
|
)
|
325
383
|
}
|
326
384
|
|
385
|
+
/** @type {S3CompatibleStorage["init"]} */
|
386
|
+
init = async (app) => {
|
387
|
+
await super.init(app);
|
388
|
+
this.config.endpoint = this.config.region ?
|
389
|
+
`https://${this.config.region}.digitaloceanspaces.com` :
|
390
|
+
undefined;
|
391
|
+
|
392
|
+
return this;
|
393
|
+
}
|
394
|
+
|
395
|
+
|
327
396
|
}
|
328
397
|
|
package/aws4fetch.js
CHANGED
package/package.json
CHANGED
package/tests/storage.r2.test.js
CHANGED
@@ -12,10 +12,10 @@ const areBlobsEqual = async (blob1, blob2) => {
|
|
12
12
|
};
|
13
13
|
|
14
14
|
const storage = new R2({
|
15
|
-
accessKeyId: process.env.
|
16
|
-
account_id: process.env.
|
17
|
-
bucket: process.env.
|
18
|
-
secretAccessKey: process.env.
|
15
|
+
accessKeyId: process.env.S3_ACCESS_KEY_ID,
|
16
|
+
account_id: process.env.CF_ACCOUNT_ID,
|
17
|
+
bucket: process.env.S3_BUCKET,
|
18
|
+
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY
|
19
19
|
});
|
20
20
|
|
21
21
|
const suite = storage_test_runner.create(storage);
|
@@ -6,12 +6,13 @@ const FORCE_PATH_STYLE = true;
|
|
6
6
|
|
7
7
|
const storage = new S3CompatibleStorage(
|
8
8
|
{
|
9
|
-
accessKeyId: process.env.
|
10
|
-
secretAccessKey: process.env.
|
11
|
-
bucket: process.env.
|
9
|
+
accessKeyId: process.env.S3_ACCESS_KEY_ID,
|
10
|
+
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
|
11
|
+
bucket: process.env.S3_BUCKET,
|
12
12
|
endpoint: process.env.ENDPOINT,
|
13
13
|
forcePathStyle: FORCE_PATH_STYLE,
|
14
|
-
|
14
|
+
// @ts-ignore
|
15
|
+
region: process.env.S3_REGION
|
15
16
|
}
|
16
17
|
);
|
17
18
|
|
package/types.public.d.ts
CHANGED
@@ -1,21 +1,58 @@
|
|
1
1
|
export { DigitalOceanSpaces, R2, S3, S3CompatibleStorage } from './adapter.js';
|
2
2
|
|
3
3
|
export type Config = {
|
4
|
-
endpoint
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
4
|
+
/** Optional, complete endpoint if you know it in advance */
|
5
|
+
endpoint?: string;
|
6
|
+
/** If missing, will be inferred by env variable `S3_BUCKET` */
|
7
|
+
bucket?: string;
|
8
|
+
/** If missing, will be inferred by env variable `S3_ACCESS_KEY_ID` */
|
9
|
+
accessKeyId?: string;
|
10
|
+
/** If missing, will be inferred by env variable `S3_SECRET_ACCESS_KEY` */
|
11
|
+
secretAccessKey?: string;
|
12
|
+
/** If missing, will be inferred by env variable `S3_REGION` */
|
13
|
+
region?: 'auto' | AWSRegion;
|
14
|
+
forcePathStyle?: boolean;
|
10
15
|
}
|
11
16
|
|
12
17
|
export type R2Config = Omit<Config, 'region' | 'forcePathStyle' | 'endpoint'> & {
|
13
18
|
/**
|
14
|
-
* @description cloudflare account id
|
19
|
+
* @description cloudflare account id. If missing will be inferred by env variable `CF_ACCOUNT_ID`
|
15
20
|
*/
|
16
|
-
account_id
|
21
|
+
account_id?: string;
|
17
22
|
};
|
18
23
|
|
19
24
|
|
20
25
|
export type AwsS3Config = Omit<Config, 'endpoint'>;
|
21
26
|
|
27
|
+
type AWSRegion =
|
28
|
+
| "us-east-1"
|
29
|
+
| "us-east-2"
|
30
|
+
| "us-west-1"
|
31
|
+
| "us-west-2"
|
32
|
+
| "af-south-1"
|
33
|
+
| "ap-east-1"
|
34
|
+
| "ap-south-1"
|
35
|
+
| "ap-south-2"
|
36
|
+
| "ap-southeast-1"
|
37
|
+
| "ap-southeast-2"
|
38
|
+
| "ap-southeast-3"
|
39
|
+
| "ap-northeast-1"
|
40
|
+
| "ap-northeast-2"
|
41
|
+
| "ap-northeast-3"
|
42
|
+
| "ca-central-1"
|
43
|
+
| "ca-west-1"
|
44
|
+
| "eu-central-1"
|
45
|
+
| "eu-central-2"
|
46
|
+
| "eu-west-1"
|
47
|
+
| "eu-west-2"
|
48
|
+
| "eu-west-3"
|
49
|
+
| "eu-north-1"
|
50
|
+
| "eu-south-1"
|
51
|
+
| "eu-south-2"
|
52
|
+
| "me-central-1"
|
53
|
+
| "me-south-1"
|
54
|
+
| "sa-east-1"
|
55
|
+
| "us-gov-east-1"
|
56
|
+
| "us-gov-west-1"
|
57
|
+
| "cn-north-1"
|
58
|
+
| "cn-northwest-1";
|