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
@@ -0,0 +1,155 @@
1
+ import zlib from "zlib";
2
+ import * as path from "path";
3
+ import { isString } from "lodash";
4
+
5
+ import S3Client from "../s3-client.class";
6
+ import Serializers from "./serializers.type";
7
+ import { JsonSerializer } from "./json.serializer";
8
+ import { AvroSerializer } from "./avro.serializer";
9
+
10
+ export default class S3Cache {
11
+ serializers: any;
12
+ s3Client: S3Client;
13
+ compressData: boolean;
14
+ serializer: Serializers;
15
+
16
+ constructor({
17
+ s3Client,
18
+ compressData = true,
19
+ serializer = Serializers.json,
20
+ }: {
21
+ s3Client: S3Client;
22
+ compressData?: boolean;
23
+ serializer?: Serializers;
24
+ }) {
25
+ this.s3Client = s3Client;
26
+ this.serializer = serializer;
27
+ this.compressData = compressData;
28
+
29
+ this.serializers = {
30
+ [Serializers.json]: JsonSerializer,
31
+ [Serializers.avro]: AvroSerializer,
32
+ };
33
+ }
34
+
35
+ getKey({
36
+ params,
37
+ hashed = true,
38
+ additionalPrefix = "",
39
+ }: {
40
+ params?: any;
41
+ hashed?: boolean;
42
+ additionalPrefix?: string;
43
+ }) {
44
+ let filename: any = Object.keys(params || {})
45
+ .sort()
46
+ .map((x) => `${x}:${params[x]}`)
47
+ .join("|");
48
+
49
+ if (filename.length === 0) filename = `empty`;
50
+
51
+ if (additionalPrefix.length > 0) {
52
+ filename = additionalPrefix + `|` + filename;
53
+ }
54
+
55
+ if (hashed) {
56
+ filename = Buffer.from(filename)
57
+ .toString("base64")
58
+ .split("")
59
+ .reverse()
60
+ .join("");
61
+ }
62
+
63
+ filename = filename + "." + this.serializer;
64
+ if (this.compressData) filename += ".gz";
65
+
66
+ return path.join("cache", filename);
67
+ }
68
+
69
+ async _put({ key, data }: { key: string; data: any }) {
70
+ const lengthRaw = isString(data)
71
+ ? data.length
72
+ : JSON.stringify(data).length;
73
+
74
+ let body: string | Uint8Array = this.serialize({ data });
75
+ const lengthSerialized = body.length;
76
+
77
+ if (this.compressData) {
78
+ body = zlib.gzipSync(body);
79
+ }
80
+
81
+ const metadata = {
82
+ compressor: "zlib",
83
+ "client-id": this.s3Client.id,
84
+ serializer: String(this.serializer),
85
+ compressed: String(this.compressData),
86
+ "length-raw": String(lengthRaw),
87
+ "length-serialized": String(lengthSerialized),
88
+ "length-compressed": String(body.length),
89
+ };
90
+
91
+ return this.s3Client.putObject({
92
+ key,
93
+ body,
94
+ metadata,
95
+ contentEncoding: this.compressData ? "gzip" : null,
96
+ contentType: this.compressData
97
+ ? "application/gzip"
98
+ : `application/${this.serializer}`,
99
+ });
100
+ }
101
+
102
+ async _get({ key }: { key: string }) {
103
+ try {
104
+ const res = await this.s3Client.getObject({ key });
105
+
106
+ if (!res.Body) return "";
107
+ let content = res.Body;
108
+
109
+ if (res.Metadata) {
110
+ const { serializer, compressor, compressed } = res.Metadata;
111
+
112
+ if (["true", true].includes(compressed)) {
113
+ if (compressor === `zlib`) {
114
+ content = zlib.unzipSync(content as Buffer);
115
+ }
116
+ }
117
+
118
+ const { data } = this.serializers[serializer].unserialize(content);
119
+ return data;
120
+ }
121
+
122
+ return this.unserialize(content);
123
+ } catch (error) {
124
+ if (error instanceof Error) {
125
+ if (error.name !== "ClientNoSuchKey") {
126
+ return Promise.reject(error);
127
+ }
128
+ }
129
+ }
130
+
131
+ return null;
132
+ }
133
+
134
+ async _delete({ key }: { key: string }) {
135
+ try {
136
+ await this.s3Client.deleteObject(key);
137
+ } catch (error) {
138
+ if (error instanceof Error) {
139
+ if (error.name !== "ClientNoSuchKey") {
140
+ return Promise.reject(error);
141
+ }
142
+ }
143
+ }
144
+
145
+ return true;
146
+ }
147
+
148
+ serialize(data: any) {
149
+ return this.serializers[this.serializer].serialize(data);
150
+ }
151
+
152
+ unserialize(data: any) {
153
+ return this.serializers[this.serializer].unserialize(data);
154
+ }
155
+ }
@@ -0,0 +1,75 @@
1
+ import S3Cache from "./s3-cache.class";
2
+ import Resource from "../resource.class";
3
+ import Serializers from "./serializers.type";
4
+
5
+ export default class S3ResourceCache extends S3Cache {
6
+ resource: Resource;
7
+
8
+ constructor({
9
+ resource,
10
+ compressData = true,
11
+ serializer = Serializers.json,
12
+ }: {
13
+ resource: Resource;
14
+ compressData?: boolean;
15
+ serializer?: Serializers;
16
+ }) {
17
+ super({
18
+ s3Client: resource.s3Client,
19
+ compressData: compressData,
20
+ serializer: serializer,
21
+ });
22
+
23
+ this.resource = resource;
24
+ }
25
+
26
+ getKey({ action = "list", params }: { action?: string; params?: any }) {
27
+ return super.getKey({
28
+ params,
29
+ additionalPrefix: `${this.resource.name}|${action}`,
30
+ });
31
+ }
32
+
33
+ async put({
34
+ action = "list",
35
+ params,
36
+ data,
37
+ }: {
38
+ action?: string;
39
+ params?: any;
40
+ data: any;
41
+ }) {
42
+ return super._put({
43
+ data,
44
+ key: this.getKey({ action, params }),
45
+ });
46
+ }
47
+
48
+ async get({ action = "list", params }: { action?: string; params?: any }) {
49
+ return super._get({
50
+ key: this.getKey({ action, params }),
51
+ });
52
+ }
53
+
54
+ async delete({ action = "list", params }: { action?: string; params: any }) {
55
+ const key = this.getKey({ action, params });
56
+
57
+ return super._delete({
58
+ key: this.getKey({ action, params }),
59
+ });
60
+ }
61
+
62
+ async purge() {
63
+ let keys = await this.s3Client.getAllKeys({ prefix: "cache" });
64
+
65
+ const key = Buffer.from(this.resource.name)
66
+ .toString("base64")
67
+ .split("")
68
+ .reverse()
69
+ .join("");
70
+
71
+ keys = keys.filter((k) => k.includes(key));
72
+
73
+ await this.s3Client.deleteObjects(keys);
74
+ }
75
+ }
@@ -0,0 +1,8 @@
1
+ export const Serializers = {
2
+ json: "json",
3
+ avro: "avro",
4
+ } as const
5
+
6
+ export type Serializers = typeof Serializers[keyof typeof Serializers]
7
+
8
+ export default Serializers
package/src/errors.ts ADDED
@@ -0,0 +1,96 @@
1
+ // Errors interfaces
2
+ export interface S3Error {
3
+ name: string;
4
+ message: string;
5
+ cause?: Error;
6
+ }
7
+
8
+ export interface S3dbError {
9
+ name: string;
10
+ message: string;
11
+ cause?: Error;
12
+ }
13
+
14
+ export class BaseError extends Error {
15
+ bucket: any;
16
+ thrownAt: Date;
17
+ cause: Error | undefined;
18
+
19
+ constructor({ bucket, message, cause }: { bucket: string; message: string, cause?: Error }) {
20
+ super(message);
21
+
22
+ if (typeof Error.captureStackTrace === 'function') {
23
+ Error.captureStackTrace(this, this.constructor);
24
+ } else {
25
+ this.stack = (new Error(message)).stack;
26
+ }
27
+
28
+ super.name = this.constructor.name;
29
+ this.name = this.constructor.name;
30
+ this.cause = cause
31
+ this.thrownAt = new Date();
32
+ }
33
+
34
+ toJson() {
35
+ return { ...this };
36
+ }
37
+
38
+ toString() {
39
+ return `${this.name} | ${this.message}`;
40
+ }
41
+ }
42
+
43
+ // AWS S3 errors
44
+ export abstract class BaseS3Error extends BaseError implements S3Error {
45
+ constructor({ bucket, message }: { bucket: string; message: string }) {
46
+ super({ bucket, message });
47
+ }
48
+ }
49
+
50
+ export class ClientNoSuchKey extends BaseS3Error {
51
+ key: string;
52
+ constructor({ bucket, key }: { bucket: string; key: string }) {
53
+ super({ bucket, message: `Key does not exists [s3://${bucket}/${key}]` });
54
+ this.key = key;
55
+ }
56
+ }
57
+
58
+ // Our errors
59
+ export abstract class BaseS3dbError extends BaseError implements S3dbError {
60
+ constructor({ bucket, message, cause }: { bucket: string; message: string, cause?: Error }) {
61
+ super({ bucket, message, cause });
62
+ }
63
+ }
64
+
65
+ export class S3dbMissingMetadata extends BaseS3dbError {
66
+ constructor({ bucket, cause }: { bucket: string, cause?: Error }) {
67
+ super({ bucket, cause, message: `Missing metadata for bucket [s3://${bucket}]` });
68
+ }
69
+ }
70
+
71
+ export class S3dbInvalidResource extends BaseS3dbError {
72
+ resourceName: any;
73
+ attributes: any;
74
+ validation: any;
75
+
76
+ constructor({
77
+ bucket,
78
+ resourceName,
79
+ attributes,
80
+ validation,
81
+ }: {
82
+ bucket: string;
83
+ resourceName: string;
84
+ attributes: string;
85
+ validation: any[];
86
+ }) {
87
+ super({
88
+ bucket,
89
+ message: `Resource is not valid. Name=${resourceName} [s3://${bucket}].\n${JSON.stringify(validation, null, 2)}`,
90
+ });
91
+
92
+ this.resourceName = resourceName;
93
+ this.attributes = attributes;
94
+ this.validation = validation;
95
+ }
96
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ import database from './s3db.class'
2
+
3
+ export const S3db = database
4
+ export default database
@@ -0,0 +1,4 @@
1
+ export default interface MetadataInterface {
2
+ version: string;
3
+ resources: any;
4
+ }
@@ -0,0 +1,4 @@
1
+ export default interface PluginInterface {
2
+ setup: Function
3
+ start: Function
4
+ }