s3db.js 1.0.0
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/.github/workflows/pipeline.yml +16 -0
- package/README.md +742 -0
- package/build/cache/avro.serializer.js +16 -0
- package/build/cache/json.serializer.js +7 -0
- package/build/cache/s3-cache.class.js +157 -0
- package/build/cache/s3-resource-cache.class.js +77 -0
- package/build/cache/serializers.type.js +8 -0
- package/build/errors.js +64 -0
- package/build/index.js +9 -0
- package/build/metadata.interface.js +2 -0
- package/build/plugin.interface.js +2 -0
- package/build/resource.class.js +485 -0
- package/build/resource.interface.js +2 -0
- package/build/s3-client.class.js +274 -0
- package/build/s3db-config.interface.js +2 -0
- package/build/s3db.class.js +185 -0
- package/build/stream/resource-ids-read-stream.class.js +100 -0
- package/build/stream/resource-ids-transformer.class.js +40 -0
- package/build/stream/resource-write-stream.class.js +76 -0
- package/build/validator.js +37 -0
- package/examples/1-bulk-insert.js +64 -0
- package/examples/2-read-stream.js +61 -0
- package/examples/3-read-stream-to-csv.js +57 -0
- package/examples/4-read-stream-to-zip.js +56 -0
- package/examples/5-write-stream.js +98 -0
- package/examples/6-jwt-tokens.js +124 -0
- package/examples/concerns/index.js +64 -0
- package/jest.config.ts +10 -0
- package/package.json +51 -0
- package/src/cache/avro.serializer.ts +12 -0
- package/src/cache/json.serializer.ts +4 -0
- package/src/cache/s3-cache.class.ts +155 -0
- package/src/cache/s3-resource-cache.class.ts +75 -0
- package/src/cache/serializers.type.ts +8 -0
- package/src/errors.ts +96 -0
- package/src/index.ts +4 -0
- package/src/metadata.interface.ts +4 -0
- package/src/plugin.interface.ts +4 -0
- package/src/resource.class.ts +531 -0
- package/src/resource.interface.ts +21 -0
- package/src/s3-client.class.ts +297 -0
- package/src/s3db-config.interface.ts +9 -0
- package/src/s3db.class.ts +215 -0
- package/src/stream/resource-ids-read-stream.class.ts +90 -0
- package/src/stream/resource-ids-transformer.class.ts +38 -0
- package/src/stream/resource-write-stream.class.ts +78 -0
- package/src/validator.ts +39 -0
- package/tests/cache.spec.ts +187 -0
- package/tests/concerns/index.ts +16 -0
- package/tests/config.spec.ts +29 -0
- package/tests/resources.spec.ts +197 -0
- package/tsconfig.json +111 -0
package/README.md
ADDED
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
# s3db.js
|
|
2
|
+
|
|
3
|
+
Hey guys, there is an another way to create a cheap database with an easy ORM to handle your dataset!
|
|
4
|
+
|
|
5
|
+
1. <a href="#motivation">Motivation</a>
|
|
6
|
+
1. <a href="#install">Install</a>
|
|
7
|
+
1. <a href="#usage">Usage</a>
|
|
8
|
+
1. <a href="#quick-setup">Quick Setup</a>
|
|
9
|
+
1. <a href="#insights">Insights</a>
|
|
10
|
+
1. <a href="#client">Client</a>
|
|
11
|
+
1. <a href="#resources">Resources</a>
|
|
12
|
+
1. <a href="#create-a-resource">Create a resource</a>
|
|
13
|
+
1. <a href="#insert-data">Insert data</a>
|
|
14
|
+
1. <a href="#bulk-insert-data">Bulk insert data</a>
|
|
15
|
+
1. <a href="#get-data">Get data</a>
|
|
16
|
+
1. <a href="#delete-data">Delete data</a>
|
|
17
|
+
1. <a href="#resource-read-stream">Resource read stream</a>
|
|
18
|
+
1. List data (coming soon)
|
|
19
|
+
1. Write stream (coming soon)
|
|
20
|
+
1. <a href="#events">Events</a>
|
|
21
|
+
1. <a href="#s3-client">S3 Client</a>
|
|
22
|
+
1. <a href="#examples">Examples</a>
|
|
23
|
+
1. <a href="#cost-simulation">Cost Simulation</a>
|
|
24
|
+
1. <a href="#big-example">Big Example</a>
|
|
25
|
+
1. <a href="#small-example">Small example</a>
|
|
26
|
+
|
|
27
|
+
## Motivation
|
|
28
|
+
|
|
29
|
+
First of all:
|
|
30
|
+
|
|
31
|
+
1. Nothing is for free, but it can be cheaper.
|
|
32
|
+
2. I'm not responsible for your AWS Costs strategy, use `s3db.js` at your own risk.
|
|
33
|
+
3. Please, do not use in production!
|
|
34
|
+
|
|
35
|
+
**Let's go!**
|
|
36
|
+
|
|
37
|
+
You might know AWS's S3 product for its high availability and its cheap pricing rules. I'll show you another clever and funny way to use S3.
|
|
38
|
+
|
|
39
|
+
AWS allows you define `Metadata` to every single file you upload into your bucket. This attribute must be defined within a **2kb** limit using in `UTF-8` encoding. As this encoding [may vary the bytes width for each symbol](https://en.wikipedia.org/wiki/UTF-8) you may use [500 to 2000] chars of metadata storage. Follow the docs at [AWS S3 User Guide: Using metadata](https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingMetadata.html#object-metadata).
|
|
40
|
+
|
|
41
|
+
There is another management subset of data called `tags` that is used globally as [key, value] params. You can assign 10 tags with the conditions of: the key must be at most 128 unicode chars lengthy and the value up to 256 chars. With those key-values we can use more `2.5kb` of data, unicode will allow you to use up to 2500 more chars. Follow the official docs at [AWS User Guide: Object Tagging](https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-tagging.html).
|
|
42
|
+
|
|
43
|
+
With all this set you may store objects that should be able to store up to `4.5kb` of free space **per object**.
|
|
44
|
+
|
|
45
|
+
Check the <a href="#cost-simulation">cost simulation</a> section below for a deep cost dive!
|
|
46
|
+
|
|
47
|
+
Lets give it a try! :)
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm i https://github.com/forattini-dev/s3db.js
|
|
53
|
+
# or
|
|
54
|
+
yarn add https://github.com/forattini-dev/s3db.js
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
You may check the snippets bellow or go straight to the <a href="#examples">Examples</a> section!
|
|
60
|
+
|
|
61
|
+
### Quick setup
|
|
62
|
+
|
|
63
|
+
Our S3db client use connection string params.
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
import S3db from "s3db.js";
|
|
67
|
+
|
|
68
|
+
const {
|
|
69
|
+
AWS_BUCKET,
|
|
70
|
+
AWS_ACCESS_KEY_ID,
|
|
71
|
+
AWS_SECRET_ACCESS_KEY,
|
|
72
|
+
} = process.env
|
|
73
|
+
|
|
74
|
+
const s3db = new S3db({
|
|
75
|
+
uri: `s3://${AWS_ACCESS_KEY_ID}:${AWS_SECRET_ACCESS_KEY}@${AWS_BUCKET}/databases/mydatabase`
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
s3db
|
|
79
|
+
.connect()
|
|
80
|
+
.then(() => console.log('connected!')))
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
If you do use `dotenv` package:
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
import * as dotenv from "dotenv";
|
|
87
|
+
dotenv.config();
|
|
88
|
+
|
|
89
|
+
import S3db from "s3db.js";
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Insights
|
|
93
|
+
|
|
94
|
+
- This implementation of ORM simulates a document repository. Due to the fact that `s3db.js` uses `aws-sdk`'s' S3 api; all requests are GET/PUT as `key=value` resources. So the best case scenario is to access like a document implementation.
|
|
95
|
+
|
|
96
|
+
- For better use of the <a href="#cache">`cache`</a> (and listing), the best ID format is to use sequential ids with leading zeros (eq: 00001, 00002, 00003) due to S3 internal keys sorting method.
|
|
97
|
+
|
|
98
|
+
### Client
|
|
99
|
+
|
|
100
|
+
Your `s3db.js` client can be initiated with options:
|
|
101
|
+
|
|
102
|
+
| option | optional | description | type | default |
|
|
103
|
+
| :---------: | :------: | :-------------------------------------------------: | :-------: | :---------: |
|
|
104
|
+
| cache | true | (Coming soon) If your read methods can be proxied | `boolean` | `undefined` |
|
|
105
|
+
| parallelism | true | Number of simultaneous tasks | `number` | 10 |
|
|
106
|
+
| passphrase | true | Your encryption secret | `string` | `undefined` |
|
|
107
|
+
| ttl | true | (Coming soon) TTL to your cache duration in seconds | `number` | 86400 |
|
|
108
|
+
| uri | false | A url as your S3 connection string | `string` | `undefined` |
|
|
109
|
+
|
|
110
|
+
URI as a connection string:
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
const {
|
|
114
|
+
AWS_BUCKET = "my-bucket",
|
|
115
|
+
AWS_ACCESS_KEY_ID = "secret",
|
|
116
|
+
AWS_SECRET_ACCESS_KEY = "secret",
|
|
117
|
+
AWS_BUCKET_PREFIX = "databases/test-" + Date.now(),
|
|
118
|
+
} = process.env;
|
|
119
|
+
|
|
120
|
+
const uri = `s3://${AWS_ACCESS_KEY_ID}:${AWS_SECRET_ACCESS_KEY}@${AWS_BUCKET}/${AWS_BUCKET_PREFIX}`;
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Config example:
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
const options = {
|
|
127
|
+
uri,
|
|
128
|
+
parallelism: 25,
|
|
129
|
+
passphrase: fs.readFileSync("./cert.pem"),
|
|
130
|
+
};
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
##### s3db.connect()
|
|
134
|
+
|
|
135
|
+
Interacts with the bucket to check:
|
|
136
|
+
|
|
137
|
+
1. If the client has access to the S3 bucket with current keys
|
|
138
|
+
1. If there is already a defined database at this prefix. If there is, it downloads the medatada and loads each Resource definition.
|
|
139
|
+
1. If there isnt any database defined in this prefix, it will generate an empty metadata file into this prefix.
|
|
140
|
+
|
|
141
|
+
#### Metadata file
|
|
142
|
+
|
|
143
|
+
`s3db.js` will generate a file `s3db.json` at the defined prefix.
|
|
144
|
+
|
|
145
|
+
It has this structure:
|
|
146
|
+
|
|
147
|
+
```javascript
|
|
148
|
+
{
|
|
149
|
+
// file version
|
|
150
|
+
"version": "1",
|
|
151
|
+
|
|
152
|
+
// previously defined resources
|
|
153
|
+
"resources": {
|
|
154
|
+
// definition example
|
|
155
|
+
"leads": {
|
|
156
|
+
"name": "leads",
|
|
157
|
+
|
|
158
|
+
// resource options
|
|
159
|
+
"options": {},
|
|
160
|
+
|
|
161
|
+
// resource defined schema
|
|
162
|
+
"schema": {
|
|
163
|
+
"name": "string",
|
|
164
|
+
"token": "secret"
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
// rules to simplify metadata usage
|
|
168
|
+
"mapper": {
|
|
169
|
+
"name": "0",
|
|
170
|
+
"token": "1"
|
|
171
|
+
},
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Resources
|
|
178
|
+
|
|
179
|
+
#### Create a resource
|
|
180
|
+
|
|
181
|
+
Resources are definitions of data collections.
|
|
182
|
+
|
|
183
|
+
```javascript
|
|
184
|
+
// resource
|
|
185
|
+
const attributes = {
|
|
186
|
+
utm: {
|
|
187
|
+
source: "string|optional",
|
|
188
|
+
medium: "string|optional",
|
|
189
|
+
campaign: "string|optional",
|
|
190
|
+
term: "string|optional",
|
|
191
|
+
},
|
|
192
|
+
lead: {
|
|
193
|
+
fullName: "string",
|
|
194
|
+
mobileNumber: "string",
|
|
195
|
+
personalEmail: "email",
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
await s3db.createResource({
|
|
200
|
+
resourceName: "leads",
|
|
201
|
+
attributes,
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Resources' names **cannot** prefix each other, like: `leads` and `leads-copy`! S3's api will consider both one single resource.
|
|
206
|
+
|
|
207
|
+
##### Attributes
|
|
208
|
+
|
|
209
|
+
`s3db.js` use the [fastest-validator](https://www.npmjs.com/package/fastest-validator) package to define and validate your resource. Some few examples:
|
|
210
|
+
|
|
211
|
+
```javascript
|
|
212
|
+
const attributes = {
|
|
213
|
+
// few simple examples
|
|
214
|
+
name: "string|min:4|max:64|trim",
|
|
215
|
+
email: "email|nullable",
|
|
216
|
+
mobile: "string|optional",
|
|
217
|
+
count: "number|integer|positive",
|
|
218
|
+
corrency: "corrency|symbol:R$",
|
|
219
|
+
createdAt: "date",
|
|
220
|
+
website: "url",
|
|
221
|
+
id: "uuid",
|
|
222
|
+
ids: "array|items:uuid|unique",
|
|
223
|
+
|
|
224
|
+
// s3db defines a custom type "secret" that is encrypted
|
|
225
|
+
token: "secret",
|
|
226
|
+
|
|
227
|
+
// nested data works aswell
|
|
228
|
+
geo: {
|
|
229
|
+
lat: "number",
|
|
230
|
+
long: "number",
|
|
231
|
+
city: "string",
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
##### Limitations:
|
|
237
|
+
|
|
238
|
+
As we need to store the resource definition within a JSON file, to keep your definitions intact the best way is to use the [string-based shorthand definitions](https://github.com/icebob/fastest-validator#shorthand-definitions) in your resource definition.
|
|
239
|
+
|
|
240
|
+
By design, in your resource definition, `s3db.js` **will not handle functions** on your attributes like default value generators, etc.
|
|
241
|
+
|
|
242
|
+
The `fastest-validator` starts with the params below:
|
|
243
|
+
|
|
244
|
+
```javascript
|
|
245
|
+
// fastest-validator params
|
|
246
|
+
{
|
|
247
|
+
useNewCustomCheckerFunction: true,
|
|
248
|
+
defaults: {
|
|
249
|
+
object: {
|
|
250
|
+
strict: "remove",
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
##### Reference:
|
|
257
|
+
|
|
258
|
+
You may just use the reference:
|
|
259
|
+
|
|
260
|
+
```javascript
|
|
261
|
+
const Leads = s3db.resource("leads");
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
#### Insert data
|
|
265
|
+
|
|
266
|
+
```javascript
|
|
267
|
+
// data
|
|
268
|
+
const attributes = {
|
|
269
|
+
id: "mypersonal@email.com", // if not defined a id will be generated!
|
|
270
|
+
utm: {
|
|
271
|
+
source: "abc",
|
|
272
|
+
},
|
|
273
|
+
lead: {
|
|
274
|
+
fullName: "My Complex Name",
|
|
275
|
+
personalEmail: "mypersonal@email.com",
|
|
276
|
+
mobileNumber: "+5511234567890",
|
|
277
|
+
},
|
|
278
|
+
invalidAttr: "this attribute will disappear",
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const insertedData = await s3db.resource("leads").insert(attributes);
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
If not defined an id attribute, `s3db.js` will use [`nanoid`](https://github.com/ai/nanoid) to generate a random unique id!
|
|
285
|
+
|
|
286
|
+
#### Bulk insert data
|
|
287
|
+
|
|
288
|
+
You may bulk insert data with a friendly method.
|
|
289
|
+
|
|
290
|
+
This method uses [`supercharge/promise-pool`](https://github.com/supercharge/promise-pool) to organize the parallelism of your promises.
|
|
291
|
+
|
|
292
|
+
```javascript
|
|
293
|
+
const s3db = new S3db({
|
|
294
|
+
parallelism: 10,
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Bulk insert:
|
|
299
|
+
|
|
300
|
+
```javascript
|
|
301
|
+
const objects = new Array(100).fill(0).map((v, k) => ({
|
|
302
|
+
id: `bulk-${k}@mymail.com`,
|
|
303
|
+
lead: {
|
|
304
|
+
fullName: "My Test Name",
|
|
305
|
+
personalEmail: `bulk-${k}@mymail.com`,
|
|
306
|
+
mobileNumber: "+55 34 234567890",
|
|
307
|
+
},
|
|
308
|
+
}));
|
|
309
|
+
|
|
310
|
+
await s3db.resource("leads").bulkInsert(objects);
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
#### Get data
|
|
314
|
+
|
|
315
|
+
```javascript
|
|
316
|
+
const obj = await s3db.resource("leads").get("mypersonal@email.com");
|
|
317
|
+
|
|
318
|
+
// {
|
|
319
|
+
// id: "mypersonal@email.com",
|
|
320
|
+
// utm: {
|
|
321
|
+
// source: "abc",
|
|
322
|
+
// },
|
|
323
|
+
// lead: {
|
|
324
|
+
// fullName: "My Complex Name",
|
|
325
|
+
// personalEmail: "mypersonal@email.com",
|
|
326
|
+
// mobileNumber: "+5511234567890",
|
|
327
|
+
// },
|
|
328
|
+
// }
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
#### Resource read stream
|
|
332
|
+
|
|
333
|
+
```javascript
|
|
334
|
+
const readStream = await s3db.resource("leads").stream();
|
|
335
|
+
|
|
336
|
+
readStream.on("id", (id) => console.log("id =", id));
|
|
337
|
+
readStream.on("data", (lead) => console.log("lead.id =", lead.id));
|
|
338
|
+
readStream.on("end", console.log("end"));
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
#### Delete data
|
|
342
|
+
|
|
343
|
+
```javascript
|
|
344
|
+
await s3db.resource("leads").deleteById(id);
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
#### List data (coming soon)
|
|
348
|
+
|
|
349
|
+
```javascript
|
|
350
|
+
await s3db.resource("leads").list();
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
#### Write stream (coming soon)
|
|
354
|
+
|
|
355
|
+
```javascript
|
|
356
|
+
// code
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Events
|
|
360
|
+
|
|
361
|
+
1. s3db
|
|
362
|
+
- connected
|
|
363
|
+
- resource.created
|
|
364
|
+
- resource.inserted
|
|
365
|
+
- resource.deleted
|
|
366
|
+
- error
|
|
367
|
+
1. client
|
|
368
|
+
- action
|
|
369
|
+
- error
|
|
370
|
+
1. resource
|
|
371
|
+
- id
|
|
372
|
+
- inserted
|
|
373
|
+
- deleted
|
|
374
|
+
- error
|
|
375
|
+
1. stream
|
|
376
|
+
- resource.id
|
|
377
|
+
- resource.data
|
|
378
|
+
- error
|
|
379
|
+
|
|
380
|
+
#### s3db
|
|
381
|
+
|
|
382
|
+
##### connected
|
|
383
|
+
|
|
384
|
+
```javascript
|
|
385
|
+
s3db.on("connected", () => console.log("s3db connected"));
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
##### resource.created
|
|
389
|
+
|
|
390
|
+
```javascript
|
|
391
|
+
s3db.on("resource.created", (resourceName) =>
|
|
392
|
+
console.log(`resource ${resourceName} created`)
|
|
393
|
+
);
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
##### resource.inserted
|
|
397
|
+
|
|
398
|
+
```javascript
|
|
399
|
+
s3db.on("resource.inserted", (resourceName, data) =>
|
|
400
|
+
console.log(`inserted ${resourceName}.id=${data.id}`)
|
|
401
|
+
);
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
##### resource.deleted
|
|
405
|
+
|
|
406
|
+
```javascript
|
|
407
|
+
s3db.on("resource.deleted", (resourceName, data) =>
|
|
408
|
+
console.log(`deleted ${resourceName}.id=${data.id}`)
|
|
409
|
+
);
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
##### error
|
|
413
|
+
|
|
414
|
+
```javascript
|
|
415
|
+
s3db.on("error", (error) => console.error(error));
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
#### s3Client
|
|
419
|
+
|
|
420
|
+
##### action
|
|
421
|
+
|
|
422
|
+
```javascript
|
|
423
|
+
s3db.client.on("action", (action) =>
|
|
424
|
+
console.log(`resource ${resourceName} created`)
|
|
425
|
+
);
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
##### error
|
|
429
|
+
|
|
430
|
+
```javascript
|
|
431
|
+
s3db.client.on("error", (error) => console.error(error));
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
#### resource
|
|
435
|
+
|
|
436
|
+
#### on: id
|
|
437
|
+
|
|
438
|
+
```javascript
|
|
439
|
+
const stream = s3db.resource("leads").stream();
|
|
440
|
+
|
|
441
|
+
stream.on("data", (id) => console.log("id = ", id));
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
#### on: data
|
|
445
|
+
|
|
446
|
+
```javascript
|
|
447
|
+
const stream = s3db.resource("leads").stream();
|
|
448
|
+
|
|
449
|
+
stream.on("data", (obj) => console.log("id = ", obj.id));
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### S3 Client
|
|
453
|
+
|
|
454
|
+
`s3db.js` has a S3 proxied client named [`S3Client`](https://github.com/forattini-dev/s3db.js/blob/main/src/s3-client.class.ts). It brings a few handy and less verbose functions to deal with AWS S3's api.
|
|
455
|
+
|
|
456
|
+
```javascript
|
|
457
|
+
import { S3Client } from "s3db.js/src/client";
|
|
458
|
+
|
|
459
|
+
const client = new S3Client({ connectionString });
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
##### s3client.getObject()
|
|
463
|
+
|
|
464
|
+
```javascript
|
|
465
|
+
const { Body, Metadata } = await client.getObject({
|
|
466
|
+
key: `my-prefixed-file.csv`,
|
|
467
|
+
});
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
##### s3client.putObject()
|
|
471
|
+
|
|
472
|
+
```javascript
|
|
473
|
+
const response = await client.putObject({
|
|
474
|
+
key: `my-prefixed-file.csv`,
|
|
475
|
+
contentType: "text/csv",
|
|
476
|
+
metadata: { a: "1", b: "2", c: "3" },
|
|
477
|
+
body: "a;b;c\n1;2;3\n4;5;6",
|
|
478
|
+
});
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
##### s3client.headObject()
|
|
482
|
+
|
|
483
|
+
```javascript
|
|
484
|
+
const { Metadata } = await client.headObject({
|
|
485
|
+
key: `my-prefixed-file.csv`,
|
|
486
|
+
});
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
##### s3client.deleteObject()
|
|
490
|
+
|
|
491
|
+
```javascript
|
|
492
|
+
const response = await client.deleteObject({
|
|
493
|
+
key: `my-prefixed-file.csv`,
|
|
494
|
+
});
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
##### s3client.deleteObjects()
|
|
498
|
+
|
|
499
|
+
```javascript
|
|
500
|
+
const response = await client.deleteObjects({
|
|
501
|
+
keys: [`my-prefixed-file.csv`, `my-other-prefixed-file.csv`],
|
|
502
|
+
});
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
##### s3client.listObjects()
|
|
506
|
+
|
|
507
|
+
```javascript
|
|
508
|
+
const response = await client.listObjects({
|
|
509
|
+
prefix: `my-subdir`,
|
|
510
|
+
});
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
##### s3client.count()
|
|
514
|
+
|
|
515
|
+
```javascript
|
|
516
|
+
const count = await client.count({
|
|
517
|
+
prefix: `my-subdir`,
|
|
518
|
+
});
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
##### s3client.getAllKeys()
|
|
522
|
+
|
|
523
|
+
All keys have the fullpath replaced into the current "scope" path.
|
|
524
|
+
|
|
525
|
+
```javascript
|
|
526
|
+
const keys = await client.getAllKeys({
|
|
527
|
+
prefix: `my-subdir`,
|
|
528
|
+
});
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
## Examples
|
|
532
|
+
|
|
533
|
+
The processing power here was not the priority, just used my little nodebook Dell XPS. Check the `./examples` directory to get some ideas on how to use this package and the code of the examples below.
|
|
534
|
+
|
|
535
|
+
Examples' random data uses [`fakerator`](https://github.com/icebob/fakerator), git it a try!
|
|
536
|
+
|
|
537
|
+
#### [Bulk insert](https://github.com/forattini-dev/s3db.js/blob/main/examples/1-bulk-insert.js)
|
|
538
|
+
|
|
539
|
+
```bash
|
|
540
|
+
$ npm run example:1
|
|
541
|
+
|
|
542
|
+
> s3db.js@1.0.0 example:1
|
|
543
|
+
> cd examples; node 1-bulk-insert.js
|
|
544
|
+
|
|
545
|
+
creating 10000 leads.
|
|
546
|
+
parallelism of 250 requests.
|
|
547
|
+
|
|
548
|
+
bulk-writing 10000/10000 (100%) [==============================] 255/bps 0.0s (39.2s) [10001 requests]
|
|
549
|
+
bulk-writing: 40.404s
|
|
550
|
+
|
|
551
|
+
Total cost: 0.0500 USD
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
#### [Resource read stream](https://github.com/forattini-dev/s3db.js/blob/main/examples/2-read-stream.js)
|
|
555
|
+
|
|
556
|
+
```bash
|
|
557
|
+
$ npm run example:2
|
|
558
|
+
|
|
559
|
+
> s3db.js@1.0.0 example:2
|
|
560
|
+
> cd examples; node 2-read-stream.js
|
|
561
|
+
|
|
562
|
+
reading 10000 leads.
|
|
563
|
+
parallelism of 250 requests.
|
|
564
|
+
|
|
565
|
+
reading-pages 40/1 (100%) [==============================] 1/bps 0.0s (64.4s)
|
|
566
|
+
reading-ids 10000/10000 (100%) [==============================] 155/bps 0.0s (64.5s)
|
|
567
|
+
reading-data 10000/10000 (100%) [==============================] 153/bps 0.0s (65.3s)
|
|
568
|
+
reading: 1:07.246 (m:ss.mmm)
|
|
569
|
+
|
|
570
|
+
Total cost: 0.0041 USD
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
#### [Resource read stream writing into a csv](https://github.com/forattini-dev/s3db.js/blob/main/examples/3-read-stream-to-csv.js)
|
|
574
|
+
|
|
575
|
+
```bash
|
|
576
|
+
$ npm run example:3
|
|
577
|
+
|
|
578
|
+
> s3db.js@1.0.0 example:3
|
|
579
|
+
> cd examples; node 3-read-stream-to-csv.js
|
|
580
|
+
|
|
581
|
+
reading 10000 leads.
|
|
582
|
+
parallelism of 250 requests.
|
|
583
|
+
|
|
584
|
+
reading-data 10000/10000 (100%) [==============================] 123/bps 0.0s (81.3s)
|
|
585
|
+
reading-data: 1:23.852 (m:ss.mmm)
|
|
586
|
+
|
|
587
|
+
Total size: 1.31 Mb
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
#### [Resource read stream writing into a zipped csv](https://github.com/forattini-dev/s3db.js/blob/main/examples/4-read-stream-to-zip.js)
|
|
591
|
+
|
|
592
|
+
```bash
|
|
593
|
+
$ npm run example:4
|
|
594
|
+
|
|
595
|
+
> s3db.js@1.0.0 example:4
|
|
596
|
+
> cd examples; node 4-read-stream-to-zip.js
|
|
597
|
+
|
|
598
|
+
reading 10000 leads.
|
|
599
|
+
parallelism of 250 requests.
|
|
600
|
+
|
|
601
|
+
reading-data 10000/10000 (100%) [==============================] 141/bps 0.0s (71.0s)
|
|
602
|
+
reading-data: 1:13.078 (m:ss.mmm)
|
|
603
|
+
|
|
604
|
+
Total zip size: 0.68 Mb
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
#### [Write Stream](https://github.com/forattini-dev/s3db.js/blob/main/examples/5-write-stream.js)
|
|
608
|
+
|
|
609
|
+
```bash
|
|
610
|
+
$ npm run example:5
|
|
611
|
+
|
|
612
|
+
> s3db.js@1.0.0 example:6
|
|
613
|
+
> cd examples; node 5-write-stream.js
|
|
614
|
+
|
|
615
|
+
reading 10000 leads.
|
|
616
|
+
parallelism of 250 requests.
|
|
617
|
+
|
|
618
|
+
requests 20010/1 (100%) [==============================] 49/bps 0.0s (410.0s)
|
|
619
|
+
reading-pages 40/1 (100%) [==============================] 0/bps 0.0s (395.6s)
|
|
620
|
+
reading-ids 10000/10000 (100%) [==============================] 25/bps 0.0s (395.6s)
|
|
621
|
+
reading-data 10000/10000 (100%) [==============================] 25/bps 0.0s (401.5s)
|
|
622
|
+
writing-ids 10000/10000 (100%) [==============================] 25/bps 0.0s (395.7s)
|
|
623
|
+
writing-data 10000/10000 (100%) [==============================] 25/bps 0.0s (395.7s)
|
|
624
|
+
copying-data: 6:51.352 (m:ss.mmm)
|
|
625
|
+
|
|
626
|
+
Total cost: 0.0541 USD
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
#### [JWT Token validator](https://github.com/forattini-dev/s3db.js/blob/main/examples/6-jwt-tokens.js)
|
|
630
|
+
|
|
631
|
+
```bash
|
|
632
|
+
$ npm run example:6
|
|
633
|
+
|
|
634
|
+
> s3db.js@1.0.0 example:6
|
|
635
|
+
> cd examples; node jwt-tokens.js
|
|
636
|
+
|
|
637
|
+
Created tokens: .....
|
|
638
|
+
Validated tokens: .....
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
## Cost simulation
|
|
642
|
+
|
|
643
|
+
S3's pricing deep dive:
|
|
644
|
+
|
|
645
|
+
- Data volume [1 GB x 0.023 USD]: it relates to the total volume of storage used and requests volume but, in this implementation, we just upload `0 bytes` files.
|
|
646
|
+
- GET Requests [1,000 GET requests in a month x 0.0000004 USD per request = 0.0004 USD]: every read requests
|
|
647
|
+
- PUT Requests [1,000 PUT requests for S3 Standard Storage x 0.000005 USD per request = 0.005 USD]: every write request
|
|
648
|
+
- Data transfer [Internet: 1 GB x 0.09 USD per GB = 0.09 USD]:
|
|
649
|
+
|
|
650
|
+
Check by yourself the pricing page details at https://aws.amazon.com/s3/pricing/ and https://calculator.aws/#/addService/S3.
|
|
651
|
+
|
|
652
|
+
### Big example
|
|
653
|
+
|
|
654
|
+
Lets try to simulate a big project where you have a database with a few tables:
|
|
655
|
+
|
|
656
|
+
- pageviews: 100,000,000 lines of 100 bytes each
|
|
657
|
+
- leads: 1,000,000 lines of 200 bytes each
|
|
658
|
+
|
|
659
|
+
```javascript
|
|
660
|
+
const Fakerator = require("fakerator");
|
|
661
|
+
const fake = Fakerator("pt-BR");
|
|
662
|
+
|
|
663
|
+
const pageview = {
|
|
664
|
+
ip: this.faker.internet.ip(),
|
|
665
|
+
domain: this.faker.internet.url(),
|
|
666
|
+
path: this.faker.internet.url(),
|
|
667
|
+
query: `?q=${this.faker.lorem.word()}`,
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
const lead = {
|
|
671
|
+
name: fake.names.name(),
|
|
672
|
+
mobile: fake.phone.number(),
|
|
673
|
+
email: fake.internet.email(),
|
|
674
|
+
country: "Brazil",
|
|
675
|
+
city: fake.address.city(),
|
|
676
|
+
state: fake.address.countryCode(),
|
|
677
|
+
address: fake.address.street(),
|
|
678
|
+
};
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
If you write the whole database of:
|
|
682
|
+
|
|
683
|
+
- pageviews:
|
|
684
|
+
- 100,000,000 PUT requests for S3 Standard Storage x 0.000005 USD per request = 500.00 USD (S3 Standard PUT requests cost)
|
|
685
|
+
- leads:
|
|
686
|
+
- 1,000,000 PUT requests for S3 Standard Storage x 0.000005 USD per request = 5.00 USD (S3 Standard PUT requests cost)
|
|
687
|
+
|
|
688
|
+
It will cost 505.00 USD, once.
|
|
689
|
+
|
|
690
|
+
If you want to read the whole database:
|
|
691
|
+
|
|
692
|
+
- pageviews:
|
|
693
|
+
- 100,000,000 GET requests in a month x 0.0000004 USD per request = 40.00 USD (S3 Standard GET requests cost)
|
|
694
|
+
- (100,000,000 × 100 bytes)÷(1024×1000×1000) ≅ 10 Gb
|
|
695
|
+
Internet: 10 GB x 0.09 USD per GB = 0.90 USD
|
|
696
|
+
- leads:
|
|
697
|
+
- 1,000,000 GET requests in a month x 0.0000004 USD per request = 0.40 USD (S3 Standard GET requests cost)
|
|
698
|
+
- (1,000,000 × 200 bytes)÷(1024×1000×1000) ≅ 0.19 Gb
|
|
699
|
+
Internet: 1 GB x 0.09 USD per GB = 0.09 USD
|
|
700
|
+
|
|
701
|
+
It will cost 41.39 USD, once.
|
|
702
|
+
|
|
703
|
+
### Small example
|
|
704
|
+
|
|
705
|
+
Lets save some JWT tokens using the [RFC:7519](https://www.rfc-editor.org/rfc/rfc7519.html).
|
|
706
|
+
|
|
707
|
+
```javascript
|
|
708
|
+
await s3db.createResource({
|
|
709
|
+
resourceName: "tokens",
|
|
710
|
+
attributes: {
|
|
711
|
+
iss: 'url|max:256',
|
|
712
|
+
sub: 'string',
|
|
713
|
+
aud: 'string',
|
|
714
|
+
exp: 'number',
|
|
715
|
+
email: 'email',
|
|
716
|
+
name: 'string',
|
|
717
|
+
scope: 'string',
|
|
718
|
+
email_verified: 'boolean',
|
|
719
|
+
})
|
|
720
|
+
|
|
721
|
+
function generateToken () {
|
|
722
|
+
const token = createTokenLib(...)
|
|
723
|
+
|
|
724
|
+
await resource.insert({
|
|
725
|
+
id: token.jti || md5(token)
|
|
726
|
+
...token,
|
|
727
|
+
})
|
|
728
|
+
|
|
729
|
+
return token
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function validateToken (token) {
|
|
733
|
+
const id = token.jti || md5(token)
|
|
734
|
+
|
|
735
|
+
if (!validateTokenSignature(token, ...)) {
|
|
736
|
+
await resource.deleteById(id)
|
|
737
|
+
throw new Error('invalid-token')
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
return resource.getById(id)
|
|
741
|
+
}
|
|
742
|
+
```
|