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.
Files changed (52) hide show
  1. package/.github/workflows/pipeline.yml +16 -0
  2. package/README.md +742 -0
  3. package/build/cache/avro.serializer.js +16 -0
  4. package/build/cache/json.serializer.js +7 -0
  5. package/build/cache/s3-cache.class.js +157 -0
  6. package/build/cache/s3-resource-cache.class.js +77 -0
  7. package/build/cache/serializers.type.js +8 -0
  8. package/build/errors.js +64 -0
  9. package/build/index.js +9 -0
  10. package/build/metadata.interface.js +2 -0
  11. package/build/plugin.interface.js +2 -0
  12. package/build/resource.class.js +485 -0
  13. package/build/resource.interface.js +2 -0
  14. package/build/s3-client.class.js +274 -0
  15. package/build/s3db-config.interface.js +2 -0
  16. package/build/s3db.class.js +185 -0
  17. package/build/stream/resource-ids-read-stream.class.js +100 -0
  18. package/build/stream/resource-ids-transformer.class.js +40 -0
  19. package/build/stream/resource-write-stream.class.js +76 -0
  20. package/build/validator.js +37 -0
  21. package/examples/1-bulk-insert.js +64 -0
  22. package/examples/2-read-stream.js +61 -0
  23. package/examples/3-read-stream-to-csv.js +57 -0
  24. package/examples/4-read-stream-to-zip.js +56 -0
  25. package/examples/5-write-stream.js +98 -0
  26. package/examples/6-jwt-tokens.js +124 -0
  27. package/examples/concerns/index.js +64 -0
  28. package/jest.config.ts +10 -0
  29. package/package.json +51 -0
  30. package/src/cache/avro.serializer.ts +12 -0
  31. package/src/cache/json.serializer.ts +4 -0
  32. package/src/cache/s3-cache.class.ts +155 -0
  33. package/src/cache/s3-resource-cache.class.ts +75 -0
  34. package/src/cache/serializers.type.ts +8 -0
  35. package/src/errors.ts +96 -0
  36. package/src/index.ts +4 -0
  37. package/src/metadata.interface.ts +4 -0
  38. package/src/plugin.interface.ts +4 -0
  39. package/src/resource.class.ts +531 -0
  40. package/src/resource.interface.ts +21 -0
  41. package/src/s3-client.class.ts +297 -0
  42. package/src/s3db-config.interface.ts +9 -0
  43. package/src/s3db.class.ts +215 -0
  44. package/src/stream/resource-ids-read-stream.class.ts +90 -0
  45. package/src/stream/resource-ids-transformer.class.ts +38 -0
  46. package/src/stream/resource-write-stream.class.ts +78 -0
  47. package/src/validator.ts +39 -0
  48. package/tests/cache.spec.ts +187 -0
  49. package/tests/concerns/index.ts +16 -0
  50. package/tests/config.spec.ts +29 -0
  51. package/tests/resources.spec.ts +197 -0
  52. 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
+ ```