Vergelijk commits

..

No commits in common. "efdfabb9a479cad542abdf78a4f5c3fec9ebadd1" and "498e8cca1cc4b2bc6e0857b2e1524842cf63dbeb" have entirely different histories.

6 gewijzigde bestanden met toevoegingen van 76 en 183 verwijderingen

Bestand weergeven

@ -13,28 +13,8 @@ A lightweight alternative to the S3 SDK that uses only @root/request and aws4.
### Download a file from S3 ### Download a file from S3
This library supports the same streaming options as [@root/request.js](https://git.rootprojects.org/root/request.js).
#### as a stream
```js ```js
var resp = await s3.get({ s3.get({
accessKeyId, // 'AKIAXXXXXXXXXXXXXXXX'
secretAccessKey, // 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
region, // 'us-east-2'
bucket, // 'bucket-name'
prefix, // 'my-prefix/' (optional)
key, // 'data/stats.csv' (omits prefix, if any)
stream // fs.createWriteStream('./path/to/file.bin')
});
await resp.stream;
```
#### in-memory
```js
var resp = await s3.get({
accessKeyId, // 'AKIAXXXXXXXXXXXXXXXX' accessKeyId, // 'AKIAXXXXXXXXXXXXXXXX'
secretAccessKey, // 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' secretAccessKey, // 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
region, // 'us-east-2' region, // 'us-east-2'
@ -42,14 +22,12 @@ var resp = await s3.get({
prefix, // 'my-prefix/' (optional) prefix, // 'my-prefix/' (optional)
key // 'data/stats.csv' (omits prefix, if any) key // 'data/stats.csv' (omits prefix, if any)
}); });
fs.writeFile(resp.body, './path/to/file.bin');
``` ```
### Upload a new file to S3 ### Upload a new file to S3
```js ```js
await s3.set({ s3.set({
accessKeyId, accessKeyId,
secretAccessKey, secretAccessKey,
region, region,
@ -63,36 +41,6 @@ await s3.set({
}); });
``` ```
### Check that a file exists
```js
var resp = await s3.head({
accessKeyId, // 'AKIAXXXXXXXXXXXXXXXX'
secretAccessKey, // 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
region, // 'us-east-2'
bucket, // 'bucket-name'
prefix, // 'my-prefix/' (optional)
key // 'data/stats.csv' (omits prefix, if any)
});
console.log(resp.headers);
```
### Delete file
```js
var resp = await s3.delete({
accessKeyId, // 'AKIAXXXXXXXXXXXXXXXX'
secretAccessKey, // 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
region, // 'us-east-2'
bucket, // 'bucket-name'
prefix, // 'my-prefix/' (optional)
key // 'data/stats.csv' (omits prefix, if any)
});
console.log(resp.headers);
```
### Return signed URL without fetching. ### Return signed URL without fetching.
```js ```js
@ -105,7 +53,7 @@ s3.sign({
prefix, prefix,
key key
}); });
```` ```
### A note on S3 terminology ### A note on S3 terminology

Bestand weergeven

@ -23,34 +23,32 @@ if (!key || !filepath) {
async function run() { async function run() {
// GET STREAMED FILE // GET STREAMED FILE
var resp = await s3.get({ await s3
accessKeyId, .get({
secretAccessKey, accessKeyId,
region, secretAccessKey,
bucket, region,
prefix, bucket,
key, prefix,
stream: filepath key
}); })
.then(function(resp) {
console.log('Downloading', resp.url); console.log(resp.url);
await resp.stream; return fs.promises.writeFile(filepath, resp.body);
})
console.log(''); .catch(function(err) {
console.log('Saved as', filepath); console.error('Error:');
console.log(''); if (err.response) {
console.error(err.url);
console.error('GET Response:');
console.error(err.response.statusCode);
console.error(err.response.headers);
console.error(err.response.body.toString('utf8'));
} else {
console.error(err);
}
process.exit(1);
});
} }
run().catch(function (err) { run();
console.error('Error:');
if (err.response) {
console.error(err.url);
console.error('GET Response:');
console.error(err.response.statusCode);
console.error(err.response.headers);
console.error(err.response.body.toString('utf8'));
} else {
console.error(err);
}
process.exit(1);
});

Bestand weergeven

@ -5,40 +5,10 @@ var request = require('@root/request');
var env = process.env; var env = process.env;
var S3; var S3;
function toAwsBucketHost(host, bucket, region) {
if (host) {
return [host];
}
// Handle simply if it contains only valid subdomain characters
// (most notably that it does not have a '.' or '_')
if (/^[a-z0-9-]+$/i.test(bucket)) {
return ['', bucket + '.s3.amazonaws.com'];
}
// Otherwise use region-specific handling rules
// (TODO: handle other regional exceptions)
// http://www.wryway.com/blog/aws-s3-url-styles/
if (!region || 'us-east-1' === region) {
return ['s3.amazonaws.com'];
}
return ['s3-' + region + '.amazonaws.com'];
}
module.exports = S3 = { module.exports = S3 = {
// HEAD // HEAD
head: function ( head: function (
{ { host, accessKeyId, secretAccessKey, region, bucket, prefix, key },
host,
accessKeyId,
secretAccessKey,
region,
bucket,
prefix,
key,
...requestOpts
},
_sign _sign
) { ) {
// TODO support minio // TODO support minio
@ -69,10 +39,9 @@ module.exports = S3 = {
// whatever/ => whatever/ // whatever/ => whatever/
prefix = prefix.replace(/\/?$/, '/'); prefix = prefix.replace(/\/?$/, '/');
} }
var [host, defaultHost] = toAwsBucketHost(host, bucket, region);
var signed = aws4.sign( var signed = aws4.sign(
{ {
host: host || defaultHost, host: host || (bucket + '.s3.amazonaws.com'),
service: 's3', service: 's3',
region: region, region: region,
path: (host ? '/' + bucket : '') + '/' + prefix + key, path: (host ? '/' + bucket : '') + '/' + prefix + key,
@ -86,9 +55,7 @@ module.exports = S3 = {
return url; return url;
} }
return request( return request({ method: 'HEAD', url }).then(function (resp) {
Object.assign(requestOpts, { method: 'HEAD', url })
).then(function (resp) {
if (200 === resp.statusCode) { if (200 === resp.statusCode) {
resp.url = url; resp.url = url;
return resp; return resp;
@ -114,8 +81,7 @@ module.exports = S3 = {
bucket, bucket,
prefix, prefix,
key, key,
json, json
...requestOpts
}, },
_sign _sign
) { ) {
@ -123,10 +89,9 @@ module.exports = S3 = {
if (prefix) { if (prefix) {
prefix = prefix.replace(/\/?$/, '/'); prefix = prefix.replace(/\/?$/, '/');
} }
var [host, defaultHost] = toAwsBucketHost(host, bucket, region);
var signed = aws4.sign( var signed = aws4.sign(
{ {
host: host || defaultHost, host: host || (bucket + '.s3.amazonaws.com'),
service: 's3', service: 's3',
region: region, region: region,
path: (host ? '/' + bucket : '') + '/' + prefix + key, path: (host ? '/' + bucket : '') + '/' + prefix + key,
@ -145,14 +110,12 @@ module.exports = S3 = {
if (json) { if (json) {
encoding = undefined; encoding = undefined;
} }
return request( return request({
Object.assign(requestOpts, { method: 'GET',
method: 'GET', url,
url, encoding: encoding,
encoding: encoding, json: json
json: json }).then(function (resp) {
})
).then(function (resp) {
if (200 === resp.statusCode) { if (200 === resp.statusCode) {
resp.url = url; resp.url = url;
return resp; return resp;
@ -179,8 +142,7 @@ module.exports = S3 = {
prefix, prefix,
key, key,
body, body,
size, size
...requestOpts
}, },
_sign _sign
) { ) {
@ -188,10 +150,9 @@ module.exports = S3 = {
if (prefix) { if (prefix) {
prefix = prefix.replace(/\/?$/, '/'); prefix = prefix.replace(/\/?$/, '/');
} }
var [host, defaultHost] = toAwsBucketHost(host, bucket, region);
var signed = aws4.sign( var signed = aws4.sign(
{ {
host: host || defaultHost, host: host || (bucket + '.s3.amazonaws.com'),
service: 's3', service: 's3',
region: region, region: region,
path: (host ? '/' + bucket : '') + '/' + prefix + key, path: (host ? '/' + bucket : '') + '/' + prefix + key,
@ -206,9 +167,9 @@ module.exports = S3 = {
headers['Content-Length'] = size; headers['Content-Length'] = size;
} }
return request( return request({ method: 'PUT', url, body, headers }).then(function (
Object.assign(requestOpts, { method: 'PUT', url, body, headers }) resp
).then(function (resp) { ) {
if (200 === resp.statusCode) { if (200 === resp.statusCode) {
resp.url = url; resp.url = url;
return resp; return resp;
@ -225,27 +186,17 @@ module.exports = S3 = {
}, },
// DELETE // DELETE
delete: function ( del: function (
{ { host, accessKeyId, secretAccessKey, region, bucket, prefix, key },
host,
accessKeyId,
secretAccessKey,
region,
bucket,
prefix,
key,
...requestOpts
},
_sign _sign
) { ) {
prefix = prefix || ''; prefix = prefix || '';
if (prefix) { if (prefix) {
prefix = prefix.replace(/\/?$/, '/'); prefix = prefix.replace(/\/?$/, '/');
} }
var [host, defaultHost] = toAwsBucketHost(host, bucket, region);
var signed = aws4.sign( var signed = aws4.sign(
{ {
host: host || defaultHost, host: host || (bucket + '.s3.amazonaws.com'),
service: 's3', service: 's3',
region: region, region: region,
path: (host ? '/' + bucket : '') + '/' + prefix + key, path: (host ? '/' + bucket : '') + '/' + prefix + key,
@ -256,9 +207,7 @@ module.exports = S3 = {
); );
var url = 'https://' + signed.host + signed.path; var url = 'https://' + signed.host + signed.path;
return request( return request({ method: 'DELETE', url }).then(function (resp) {
Object.assign(requestOpts, { method: 'DELETE', url })
).then(function (resp) {
if (204 === resp.statusCode) { if (204 === resp.statusCode) {
resp.url = url; resp.url = url;
return resp; return resp;
@ -297,4 +246,3 @@ module.exports = S3 = {
} }
} }
}; };
S3.del = S3.delete;

8
package-lock.json gegenereerd
Bestand weergeven

@ -1,13 +1,13 @@
{ {
"name": "@root/s3", "name": "@root/s3",
"version": "1.2.0", "version": "1.1.3",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"@root/request": { "@root/request": {
"version": "1.7.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/@root/request/-/request-1.7.0.tgz", "resolved": "https://registry.npmjs.org/@root/request/-/request-1.5.0.tgz",
"integrity": "sha512-lre7XVeEwszgyrayWWb/kRn5fuJfa+n0Nh+rflM9E+EpC28yIYA+FPm/OL1uhzp3TxhQM0HFN4FE2RDIPGlnmg==" "integrity": "sha512-J9RUIwVU99/cOVuDVYlNpr4G0A1/3ZxhCXIRiTZzu8RntOnb0lmDBMckhaus5ry9x/dBqJKDplFIgwHbLi6rLA=="
}, },
"aws4": { "aws4": {
"version": "1.9.1", "version": "1.9.1",

Bestand weergeven

@ -1,6 +1,6 @@
{ {
"name": "@root/s3", "name": "@root/s3",
"version": "1.2.0", "version": "1.1.3",
"description": "A simple, lightweight s3 client with only 2 dependencies", "description": "A simple, lightweight s3 client with only 2 dependencies",
"main": "index.js", "main": "index.js",
"bin": { "bin": {
@ -13,7 +13,6 @@
"example": "examples" "example": "examples"
}, },
"scripts": { "scripts": {
"prettier": "npx prettier -w '**/*.js'",
"test": "node test.js" "test": "node test.js"
}, },
"repository": { "repository": {
@ -28,7 +27,7 @@
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)", "author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
"license": "(MIT OR Apache-2.0)", "license": "(MIT OR Apache-2.0)",
"dependencies": { "dependencies": {
"@root/request": "^1.7.0", "@root/request": "^1.5.0",
"aws4": "^1.9.1" "aws4": "^1.9.1"
}, },
"devDependencies": { "devDependencies": {

40
test.js
Bestand weergeven

@ -32,11 +32,11 @@ async function run() {
body: stream, body: stream,
size size
}) })
.then(function (resp) { .then(function(resp) {
console.info('PASS: stream uploaded file'); console.info('PASS: stream uploaded file');
return null; return null;
}) })
.catch(function (err) { .catch(function(err) {
console.error('Error:'); console.error('Error:');
console.error('PUT Response:'); console.error('PUT Response:');
if (err.response) { if (err.response) {
@ -62,11 +62,11 @@ async function run() {
prefix, prefix,
key key
}) })
.then(function (resp) { .then(function(resp) {
console.info('PASS: streamed file exists'); console.info('PASS: streamed file exists');
return null; return null;
}) })
.catch(function (err) { .catch(function(err) {
console.error('HEAD Response:'); console.error('HEAD Response:');
if (err.response) { if (err.response) {
console.error(err.response.statusCode); console.error(err.response.statusCode);
@ -87,7 +87,7 @@ async function run() {
prefix, prefix,
key key
}) })
.then(function (resp) { .then(function(resp) {
if (file.toString('binary') === resp.body.toString('binary')) { if (file.toString('binary') === resp.body.toString('binary')) {
console.info( console.info(
'PASS: streamed file downloaded with same contents' 'PASS: streamed file downloaded with same contents'
@ -96,7 +96,7 @@ async function run() {
} }
throw new Error("file contents don't match"); throw new Error("file contents don't match");
}) })
.catch(function (err) { .catch(function(err) {
console.error('Error:'); console.error('Error:');
console.error('GET Response:'); console.error('GET Response:');
if (err.response) { if (err.response) {
@ -118,11 +118,11 @@ async function run() {
prefix, prefix,
key key
}) })
.then(function (resp) { .then(function(resp) {
console.info('PASS: delete file'); console.info('PASS: delete file');
return null; return null;
}) })
.catch(function (err) { .catch(function(err) {
console.error('Error:'); console.error('Error:');
console.error('DELETE Response:'); console.error('DELETE Response:');
if (err.response) { if (err.response) {
@ -144,12 +144,12 @@ async function run() {
prefix, prefix,
key key
}) })
.then(function (resp) { .then(function(resp) {
var err = new Error('file should not exist'); var err = new Error('file should not exist');
err.response = resp; err.response = resp;
throw err; throw err;
}) })
.catch(function (err) { .catch(function(err) {
if (err.response && 404 === err.response.statusCode) { if (err.response && 404 === err.response.statusCode) {
console.info('PASS: streamed file deleted'); console.info('PASS: streamed file deleted');
return null; return null;
@ -176,11 +176,11 @@ async function run() {
key, key,
body: file body: file
}) })
.then(function (resp) { .then(function(resp) {
console.info('PASS: one-shot upload'); console.info('PASS: one-shot upload');
return null; return null;
}) })
.catch(function (err) { .catch(function(err) {
console.error('Error:'); console.error('Error:');
console.error('PUT Response:'); console.error('PUT Response:');
if (err.response) { if (err.response) {
@ -206,11 +206,11 @@ async function run() {
prefix, prefix,
key key
}) })
.then(function (resp) { .then(function(resp) {
console.info('PASS: one-shot upload exists'); console.info('PASS: one-shot upload exists');
return null; return null;
}) })
.catch(function (err) { .catch(function(err) {
console.error('Error:'); console.error('Error:');
console.error('HEAD Response:'); console.error('HEAD Response:');
if (err.response) { if (err.response) {
@ -232,7 +232,7 @@ async function run() {
prefix, prefix,
key key
}) })
.then(function (resp) { .then(function(resp) {
if (file.toString('binary') === resp.body.toString('binary')) { if (file.toString('binary') === resp.body.toString('binary')) {
console.info( console.info(
'PASS: one-shot file downloaded with same contents' 'PASS: one-shot file downloaded with same contents'
@ -241,7 +241,7 @@ async function run() {
} }
throw new Error("file contents don't match"); throw new Error("file contents don't match");
}) })
.catch(function (err) { .catch(function(err) {
console.error('Error:'); console.error('Error:');
console.error('GET Response:'); console.error('GET Response:');
if (err.response) { if (err.response) {
@ -263,11 +263,11 @@ async function run() {
prefix, prefix,
key key
}) })
.then(function (resp) { .then(function(resp) {
console.info('PASS: DELETE'); console.info('PASS: DELETE');
return null; return null;
}) })
.catch(function (err) { .catch(function(err) {
console.error('Error:'); console.error('Error:');
console.error('DELETE Response:'); console.error('DELETE Response:');
if (err.response) { if (err.response) {
@ -289,12 +289,12 @@ async function run() {
prefix, prefix,
key key
}) })
.then(function (resp) { .then(function(resp) {
var err = new Error('file should not exist'); var err = new Error('file should not exist');
err.response = resp; err.response = resp;
throw err; throw err;
}) })
.catch(function (err) { .catch(function(err) {
if (err.response && 404 === err.response.statusCode) { if (err.response && 404 === err.response.statusCode) {
console.info('PASS: streamed file deleted'); console.info('PASS: streamed file deleted');
return null; return null;