@storecraft/storage-s3-compatible 1.0.11 → 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 +113 -42
- 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,5 +1,6 @@
|
|
1
1
|
/**
|
2
2
|
* @import { AwsS3Config, Config, R2Config } from './types.public.js'
|
3
|
+
* @import { ENV } from '@storecraft/core';
|
3
4
|
* @import { storage_driver, StorageFeatures } from '@storecraft/core/storage'
|
4
5
|
*/
|
5
6
|
import { App } from '@storecraft/core'
|
@@ -27,7 +28,6 @@ const infer_content_type = (name) => {
|
|
27
28
|
return type ?? 'application/octet-stream';
|
28
29
|
}
|
29
30
|
|
30
|
-
|
31
31
|
/**
|
32
32
|
* @description The base S3 compatible class
|
33
33
|
*
|
@@ -35,24 +35,16 @@ const infer_content_type = (name) => {
|
|
35
35
|
*/
|
36
36
|
export class S3CompatibleStorage {
|
37
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
|
+
|
38
46
|
/** @type {AwsClient} */ #_client;
|
39
47
|
/** @type {Config} */ #_config;
|
40
|
-
/** @type {string} */ #_url;
|
41
|
-
|
42
|
-
/**
|
43
|
-
*
|
44
|
-
* @param {Config} options
|
45
|
-
*/
|
46
|
-
#compute_url(options) {
|
47
|
-
const url = new URL(options.endpoint);
|
48
|
-
if(options.forcePathStyle) {
|
49
|
-
url.pathname = options.bucket;
|
50
|
-
} else {
|
51
|
-
url.host = `${options.bucket}.${url.host}`;
|
52
|
-
}
|
53
|
-
return url.toString();
|
54
|
-
}
|
55
|
-
|
56
48
|
|
57
49
|
/**
|
58
50
|
*
|
@@ -60,17 +52,20 @@ export class S3CompatibleStorage {
|
|
60
52
|
*/
|
61
53
|
constructor(config) {
|
62
54
|
this.#_config = config;
|
63
|
-
this.#_client = new AwsClient({
|
64
|
-
accessKeyId: config.accessKeyId, secretAccessKey: config.secretAccessKey,
|
65
|
-
region: config.region ?? 'auto', service: 's3'
|
66
|
-
});
|
67
|
-
|
68
|
-
this.#_url = this.#compute_url(config);
|
69
55
|
}
|
70
56
|
|
71
|
-
get url() { return this.#_url; }
|
72
|
-
get client() { return this.#_client; }
|
73
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
|
+
}
|
74
69
|
|
75
70
|
features() {
|
76
71
|
/** @type {StorageFeatures} */
|
@@ -85,7 +80,14 @@ export class S3CompatibleStorage {
|
|
85
80
|
*
|
86
81
|
* @type {storage_driver["init"]}
|
87
82
|
*/
|
88
|
-
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
|
+
}
|
89
91
|
|
90
92
|
// puts
|
91
93
|
|
@@ -170,9 +172,19 @@ export class S3CompatibleStorage {
|
|
170
172
|
|
171
173
|
// gets
|
172
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
|
+
|
173
185
|
/** @param {string} key */
|
174
186
|
get_file_url(key) {
|
175
|
-
return `${this
|
187
|
+
return `${this.url}/${key}`;
|
176
188
|
}
|
177
189
|
|
178
190
|
/** @param {string} key */
|
@@ -269,58 +281,117 @@ export class S3CompatibleStorage {
|
|
269
281
|
*/
|
270
282
|
export class R2 extends S3CompatibleStorage {
|
271
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
|
+
|
272
292
|
/**
|
273
293
|
* @param {R2Config} config
|
274
294
|
*/
|
275
|
-
constructor({
|
295
|
+
constructor(config={}) {
|
276
296
|
super(
|
277
297
|
{
|
278
|
-
endpoint:
|
279
|
-
|
280
|
-
|
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'
|
281
306
|
}
|
282
|
-
)
|
307
|
+
);
|
308
|
+
this.r2_config = config;
|
283
309
|
}
|
284
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
|
+
|
285
319
|
}
|
286
320
|
|
321
|
+
|
287
322
|
/**
|
288
323
|
* Amazon S3
|
289
324
|
*/
|
290
325
|
export class S3 extends S3CompatibleStorage {
|
291
326
|
|
327
|
+
/** @satisfies {ENV<AwsS3Config>} */
|
328
|
+
static AWSS3EnvConfig = /** @type{const} */ ({
|
329
|
+
...S3CompatibleStorage.EnvConfig
|
330
|
+
});
|
331
|
+
|
292
332
|
/**
|
293
|
-
* @param {AwsS3Config} config
|
333
|
+
* @param {Partial<AwsS3Config>} config
|
294
334
|
*/
|
295
|
-
constructor(
|
335
|
+
constructor(config = {}) {
|
296
336
|
super(
|
297
337
|
{
|
298
|
-
endpoint: `https://s3
|
299
|
-
accessKeyId
|
300
|
-
|
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
|
301
344
|
}
|
302
345
|
)
|
303
346
|
}
|
304
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
|
+
|
305
356
|
}
|
306
357
|
|
358
|
+
|
307
359
|
/**
|
308
360
|
* Digital Ocean spaces
|
309
361
|
*/
|
310
362
|
export class DigitalOceanSpaces extends S3CompatibleStorage {
|
311
363
|
|
364
|
+
/** @satisfies {ENV<AwsS3Config>} */
|
365
|
+
static DOEnvConfig = /** @type{const} */ ({
|
366
|
+
...S3CompatibleStorage.EnvConfig
|
367
|
+
});
|
368
|
+
|
312
369
|
/**
|
313
|
-
* @param {Omit<Config, 'endpoint' | 'forcePathStyle'
|
370
|
+
* @param {Partial<Omit<Config, 'endpoint' | 'forcePathStyle'>>} config
|
314
371
|
*/
|
315
|
-
constructor(
|
372
|
+
constructor(config = {}) {
|
316
373
|
super(
|
317
374
|
{
|
318
|
-
endpoint:
|
319
|
-
|
320
|
-
|
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'
|
321
381
|
}
|
322
382
|
)
|
323
383
|
}
|
324
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
|
+
|
325
396
|
}
|
326
397
|
|
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";
|