Compare commits

..

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

6 changed files with 76 additions and 183 deletions

View File

@ -13,28 +13,8 @@ A lightweight alternative to the S3 SDK that uses only @root/request and aws4.
### 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
var resp = await 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({
s3.get({
accessKeyId, // 'AKIAXXXXXXXXXXXXXXXX'
secretAccessKey, // 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
region, // 'us-east-2'
@ -42,14 +22,12 @@ var resp = await s3.get({
prefix, // 'my-prefix/' (optional)
key // 'data/stats.csv' (omits prefix, if any)
});
fs.writeFile(resp.body, './path/to/file.bin');
```
### Upload a new file to S3
```js
await s3.set({
s3.set({
accessKeyId,
secretAccessKey,
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.
```js
@ -105,7 +53,7 @@ s3.sign({
prefix,
key
});
````
```
### A note on S3 terminology

View File

@ -23,34 +23,32 @@ if (!key || !filepath) {
async function run() {
// GET STREAMED FILE
var resp = await s3.get({
accessKeyId,
secretAccessKey,
region,
bucket,
prefix,
key,
stream: filepath
});
console.log('Downloading', resp.url);
await resp.stream;
console.log('');
console.log('Saved as', filepath);
console.log('');
await s3
.get({
accessKeyId,
secretAccessKey,
region,
bucket,
prefix,
key
})
.then(function(resp) {
console.log(resp.url);
return fs.promises.writeFile(filepath, resp.body);
})
.catch(function(err) {
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);
});
}
run().catch(function (err) {
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);
});
run();

View File

@ -5,40 +5,10 @@ var request = require('@root/request');
var env = process.env;
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 = {
// HEAD
head: function (
{
host,
accessKeyId,
secretAccessKey,
region,
bucket,
prefix,
key,
...requestOpts
},
{ host, accessKeyId, secretAccessKey, region, bucket, prefix, key },
_sign
) {
// TODO support minio
@ -69,10 +39,9 @@ module.exports = S3 = {
// whatever/ => whatever/
prefix = prefix.replace(/\/?$/, '/');
}
var [host, defaultHost] = toAwsBucketHost(host, bucket, region);
var signed = aws4.sign(
{
host: host || defaultHost,
host: host || (bucket + '.s3.amazonaws.com'),
service: 's3',
region: region,
path: (host ? '/' + bucket : '') + '/' + prefix + key,
@ -86,9 +55,7 @@ module.exports = S3 = {
return url;
}
return request(
Object.assign(requestOpts, { method: 'HEAD', url })
).then(function (resp) {
return request({ method: 'HEAD', url }).then(function (resp) {
if (200 === resp.statusCode) {
resp.url = url;
return resp;
@ -114,8 +81,7 @@ module.exports = S3 = {
bucket,
prefix,
key,
json,
...requestOpts
json
},
_sign
) {
@ -123,10 +89,9 @@ module.exports = S3 = {
if (prefix) {
prefix = prefix.replace(/\/?$/, '/');
}
var [host, defaultHost] = toAwsBucketHost(host, bucket, region);
var signed = aws4.sign(
{
host: host || defaultHost,
host: host || (bucket + '.s3.amazonaws.com'),
service: 's3',
region: region,
path: (host ? '/' + bucket : '') + '/' + prefix + key,
@ -145,14 +110,12 @@ module.exports = S3 = {
if (json) {
encoding = undefined;
}
return request(
Object.assign(requestOpts, {
method: 'GET',
url,
encoding: encoding,
json: json
})
).then(function (resp) {
return request({
method: 'GET',
url,
encoding: encoding,
json: json
}).then(function (resp) {
if (200 === resp.statusCode) {
resp.url = url;
return resp;
@ -179,8 +142,7 @@ module.exports = S3 = {
prefix,
key,
body,
size,
...requestOpts
size
},
_sign
) {
@ -188,10 +150,9 @@ module.exports = S3 = {
if (prefix) {
prefix = prefix.replace(/\/?$/, '/');
}
var [host, defaultHost] = toAwsBucketHost(host, bucket, region);
var signed = aws4.sign(
{
host: host || defaultHost,
host: host || (bucket + '.s3.amazonaws.com'),
service: 's3',
region: region,
path: (host ? '/' + bucket : '') + '/' + prefix + key,
@ -206,9 +167,9 @@ module.exports = S3 = {
headers['Content-Length'] = size;
}
return request(
Object.assign(requestOpts, { method: 'PUT', url, body, headers })
).then(function (resp) {
return request({ method: 'PUT', url, body, headers }).then(function (
resp
) {
if (200 === resp.statusCode) {
resp.url = url;
return resp;
@ -225,27 +186,17 @@ module.exports = S3 = {
},
// DELETE
delete: function (
{
host,
accessKeyId,
secretAccessKey,
region,
bucket,
prefix,
key,
...requestOpts
},
del: function (
{ host, accessKeyId, secretAccessKey, region, bucket, prefix, key },
_sign
) {
prefix = prefix || '';
if (prefix) {
prefix = prefix.replace(/\/?$/, '/');
}
var [host, defaultHost] = toAwsBucketHost(host, bucket, region);
var signed = aws4.sign(
{
host: host || defaultHost,
host: host || (bucket + '.s3.amazonaws.com'),
service: 's3',
region: region,
path: (host ? '/' + bucket : '') + '/' + prefix + key,
@ -256,9 +207,7 @@ module.exports = S3 = {
);
var url = 'https://' + signed.host + signed.path;
return request(
Object.assign(requestOpts, { method: 'DELETE', url })
).then(function (resp) {
return request({ method: 'DELETE', url }).then(function (resp) {
if (204 === resp.statusCode) {
resp.url = url;
return resp;
@ -297,4 +246,3 @@ module.exports = S3 = {
}
}
};
S3.del = S3.delete;

8
package-lock.json generated
View File

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

View File

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

40
test.js
View File

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