ismx-nexo-node-app 0.3.24

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 (65) hide show
  1. package/dist/js/api/Service.js +45 -0
  2. package/dist/js/api/ServiceRest.js +33 -0
  3. package/dist/js/api/ServiceRestFormal.js +97 -0
  4. package/dist/js/api/ServiceRestFormalTemplate.js +127 -0
  5. package/dist/js/business/Business.js +5 -0
  6. package/dist/js/business/BusinessErrors.js +101 -0
  7. package/dist/js/business/BusinessLogger.js +9 -0
  8. package/dist/js/business/BusinessProxy.js +90 -0
  9. package/dist/js/business/BusinessServer.js +97 -0
  10. package/dist/js/business/BusinessState.js +5 -0
  11. package/dist/js/business/BusinessThread.js +23 -0
  12. package/dist/js/business/utils/CryptoUtils.js +30 -0
  13. package/dist/js/business/utils/NumberUtils.js +8 -0
  14. package/dist/js/business/utils/StringUtils.js +20 -0
  15. package/dist/js/index.js +56 -0
  16. package/dist/js/repository/Repository.js +5 -0
  17. package/dist/js/repository/RepositoryPostgres.js +215 -0
  18. package/dist/js/repository/RepositoryRest.js +55 -0
  19. package/dist/js/repository/RepositoryRestFormal.js +46 -0
  20. package/dist/js/repository/utils/PostgresUtils.js +51 -0
  21. package/dist/js/repository/utils/QueryUtils.js +13 -0
  22. package/dist/types/api/Service.d.ts +45 -0
  23. package/dist/types/api/ServiceRest.d.ts +6 -0
  24. package/dist/types/api/ServiceRestFormal.d.ts +23 -0
  25. package/dist/types/api/ServiceRestFormalTemplate.d.ts +40 -0
  26. package/dist/types/business/Business.d.ts +2 -0
  27. package/dist/types/business/BusinessErrors.d.ts +39 -0
  28. package/dist/types/business/BusinessLogger.d.ts +3 -0
  29. package/dist/types/business/BusinessProxy.d.ts +15 -0
  30. package/dist/types/business/BusinessServer.d.ts +15 -0
  31. package/dist/types/business/BusinessState.d.ts +2 -0
  32. package/dist/types/business/BusinessThread.d.ts +8 -0
  33. package/dist/types/business/utils/CryptoUtils.d.ts +5 -0
  34. package/dist/types/business/utils/NumberUtils.d.ts +3 -0
  35. package/dist/types/business/utils/StringUtils.d.ts +5 -0
  36. package/dist/types/index.d.ts +35 -0
  37. package/dist/types/repository/Repository.d.ts +2 -0
  38. package/dist/types/repository/RepositoryPostgres.d.ts +75 -0
  39. package/dist/types/repository/RepositoryRest.d.ts +17 -0
  40. package/dist/types/repository/RepositoryRestFormal.d.ts +7 -0
  41. package/dist/types/repository/utils/PostgresUtils.d.ts +17 -0
  42. package/dist/types/repository/utils/QueryUtils.d.ts +5 -0
  43. package/package.json +35 -0
  44. package/src/main/node/api/Service.ts +55 -0
  45. package/src/main/node/api/ServiceRest.ts +19 -0
  46. package/src/main/node/api/ServiceRestFormal.ts +58 -0
  47. package/src/main/node/api/ServiceRestFormalTemplate.ts +133 -0
  48. package/src/main/node/business/Business.ts +3 -0
  49. package/src/main/node/business/BusinessErrors.ts +94 -0
  50. package/src/main/node/business/BusinessLogger.ts +6 -0
  51. package/src/main/node/business/BusinessProxy.ts +57 -0
  52. package/src/main/node/business/BusinessServer.ts +76 -0
  53. package/src/main/node/business/BusinessState.ts +4 -0
  54. package/src/main/node/business/BusinessThread.ts +19 -0
  55. package/src/main/node/business/utils/CryptoUtils.ts +32 -0
  56. package/src/main/node/business/utils/NumberUtils.ts +6 -0
  57. package/src/main/node/business/utils/StringUtils.ts +20 -0
  58. package/src/main/node/index.ts +45 -0
  59. package/src/main/node/repository/Repository.ts +3 -0
  60. package/src/main/node/repository/RepositoryPostgres.ts +246 -0
  61. package/src/main/node/repository/RepositoryRest.ts +70 -0
  62. package/src/main/node/repository/RepositoryRestFormal.ts +37 -0
  63. package/src/main/node/repository/utils/PostgresUtils.ts +59 -0
  64. package/src/main/node/repository/utils/QueryUtils.ts +10 -0
  65. package/tsconfig.json +18 -0
@@ -0,0 +1,57 @@
1
+ import { HttpResponse, HttpRequest } from "../api/Service"
2
+ import fetch, {Body, Headers} from 'node-fetch';
3
+ import {Wrapper} from "../api/ServiceRestFormal";
4
+
5
+ export interface Module {
6
+ id: string;
7
+ tag: string;
8
+ host: string;
9
+ }
10
+
11
+ export default class BusinessProxy
12
+ {
13
+ private modules: {[key:string]: Module} = {};
14
+
15
+ setModules(modules: {[key:string]: Module}) {
16
+ this.modules = modules
17
+ }
18
+
19
+ async callFormal<Req, Res>(tag: string, method: string, endpoint: string, request: HttpRequest<Req>): Promise<Res | undefined> {
20
+ let response = await this.call<Req, Wrapper<Res>>(tag, method, endpoint, request);
21
+ if (response?.code === "0") return response.data;
22
+ else throw response;
23
+ }
24
+
25
+ async call<Req, Res>(tag: string, method: string, endpoint: string, request: HttpRequest<Req>): Promise<Res> {
26
+ let module = this.modules[tag];
27
+ if (!module) throw new Error(`Module ${tag} does not exist`);
28
+
29
+ let headers = new Headers();
30
+ headers.set('Content-Type', 'application/json');
31
+ headers.set('Cache-Control', 'no-cache');
32
+ headers.set('Pragma', 'no-cache');
33
+ for (let header in request.headers)
34
+ headers.set(header, request.headers[header]);
35
+
36
+ let response = null;
37
+ try {
38
+ response = await fetch(`${module.host}${endpoint}${this.map(request.query)}`, {
39
+ method: method,
40
+ body: method !== "GET" ? JSON.stringify(request.body) : undefined,
41
+ headers: headers,
42
+ });
43
+ } catch (error) { throw new Error(`Module ${tag} is not responding`); }
44
+
45
+ if (response.ok) return await response.json();
46
+ else throw await response.json();
47
+ }
48
+
49
+ private map(query: { [key:string]: string } = {}) {
50
+ let queryString = "?"
51
+ for (const param in query) {
52
+ if (query[param] === undefined) continue;
53
+ queryString += `${param}=${query[param]}&`
54
+ }
55
+ return queryString;
56
+ }
57
+ }
@@ -0,0 +1,76 @@
1
+ import Service, {HttpRequest, HttpResponse} from "../api/Service";
2
+ import express from "express";
3
+ import core from "express"
4
+ import Business from "./Business";
5
+ import cors from 'cors';
6
+
7
+ export default class BusinessServer extends Business
8
+ {
9
+ private app: core.Express;
10
+ protected onStarted?: ()=>any;
11
+ protected onRequest?: (request: HttpRequest) => any;
12
+ protected onResponse?: (request: HttpRequest, response: HttpResponse) => HttpResponse<any> | undefined;
13
+ protected onError?: (request: HttpRequest, error: Error) => HttpResponse<any> | undefined | void
14
+ protected onEnd?: (request: HttpRequest, response: HttpResponse) => any;
15
+
16
+ constructor() {
17
+ super()
18
+ this.app = express();
19
+ this.app.use(express.urlencoded({ extended: false }));
20
+ this.app.use(express.json());
21
+ this.app.use(cors());
22
+
23
+ this.publish = this.publish.bind(this);
24
+ }
25
+
26
+ start(port: number) {
27
+ this.app.listen(port, this.onStarted)
28
+ }
29
+
30
+ setServices(services: Service<any, any>[]) {
31
+ services.forEach(this.publish);
32
+ }
33
+
34
+ private publish<Req=any, Res=any>(service: Service<Req, Res>) {
35
+ switch (service.method) {
36
+ case "GET": this.app.get(service.endpoint, this.run(service)); break;
37
+ case "POST": this.app.post(service.endpoint, this.run(service)); break;
38
+ case "PUT": this.app.put(service.endpoint, this.run(service)); break;
39
+ case "DELETE": this.app.delete(service.endpoint, this.run(service)); break;
40
+ case "PATCH": this.app.patch(service.endpoint, this.run(service)); break;
41
+ case "*": this.app.all(service.endpoint, this.run(service)); break;
42
+ }
43
+ }
44
+
45
+ private run<Req=any, Res=any>(service: Service<Req, Res>) { return async(request: any, socket: any) =>
46
+ {
47
+ let code;
48
+ let response: HttpResponse<Res> = HttpResponse.ko(500, "Internal server error");
49
+
50
+ // Asigna un valor único a la llamada de la API.
51
+ request.id = Math.ceil(Math.random() * 1000000).toString();
52
+ request.timestamp = new Date();
53
+
54
+ let indexParams = request.url.indexOf('?');
55
+ let endpoint = indexParams > 0 ? request.url.substring(0,indexParams) : request.url
56
+ request.endpoint = !!endpoint ? endpoint : "/";
57
+
58
+ try {
59
+ try { this.onRequest?.(request); } catch(e) { }
60
+ response = await service.serve?.(request);
61
+ response.httpCode = response.httpCode ?? 200;
62
+ try { response = (this.onResponse?.(request, response) ?? response) } catch(e) { }
63
+
64
+ } catch (error: any) {
65
+ this.onError?.(request, error);
66
+ let debug = error.message;
67
+ if (!error.code) error = { code: "-1", description: "Internal server error", debug };
68
+ let content = { code: error.code, description: error.description, debug };
69
+ response = HttpResponse.ko(error.httpCode, content);
70
+
71
+ } finally {
72
+ socket.status(response?.httpCode ?? 500).send(response?.content);
73
+ try { this.onEnd?.(request, response); } catch(e) { }
74
+ }
75
+ }}
76
+ }
@@ -0,0 +1,4 @@
1
+ export default class BusinessState<Model>
2
+ {
3
+
4
+ }
@@ -0,0 +1,19 @@
1
+ import Business from "./Business";
2
+
3
+ export default abstract class BusinessThread extends Business
4
+ {
5
+ constructor() {
6
+ super();
7
+ setTimeout(() => {
8
+ setInterval(() => {
9
+ try { this.run() }
10
+ catch (err) { this.onError() }
11
+ }, this.interval());
12
+ }, this.delay())
13
+ }
14
+
15
+ abstract run(): void
16
+ abstract interval(): number
17
+ abstract delay(): number
18
+ onError() {}
19
+ }
@@ -0,0 +1,32 @@
1
+ import crypto from "crypto";
2
+
3
+ export default abstract class CryptoUtils {
4
+
5
+ static zeroPad(buf: Buffer, blocksize: number) {
6
+ const pad = Buffer.alloc((blocksize - (buf.length % blocksize)) % blocksize, 0);
7
+ return Buffer.concat([buf, pad]);
8
+ }
9
+
10
+ static encrypt3DES(key: string, message: string)
11
+ {
12
+ const keyBuf = Buffer.from(key, 'base64');
13
+ const iv = Buffer.alloc(8, 0);
14
+
15
+ const messageBuf = Buffer.from(message.toString(), 'utf8');
16
+ const paddedMessageBuf = CryptoUtils.zeroPad(messageBuf, 8);
17
+
18
+ const cipher = crypto.createCipheriv('des-ede3-cbc', keyBuf, iv);
19
+ cipher.setAutoPadding(false);
20
+ const encryptedBuf = Buffer.concat([
21
+ cipher.update(paddedMessageBuf), cipher.final()
22
+ ]);
23
+
24
+ const maxLength = Math.ceil(messageBuf.length / 8) * 8;
25
+ return encryptedBuf.slice(0, maxLength);
26
+ }
27
+
28
+ static sha256Sign(merchantKey: string, orderId: string, encodedOrder: string) {
29
+ const orderKeyBuf = CryptoUtils.encrypt3DES(merchantKey, orderId);
30
+ return crypto.createHmac('sha256', orderKeyBuf).update(encodedOrder).digest('base64');
31
+ }
32
+ }
@@ -0,0 +1,6 @@
1
+ export default abstract class NumberUtils {
2
+
3
+ static random(lower:number, upper:number) {
4
+ return Math.floor(Math.random() * (upper + 1 - lower)) + lower;
5
+ }
6
+ }
@@ -0,0 +1,20 @@
1
+ export default abstract class StringUtils {
2
+
3
+ static base64Decode(base64: string) {
4
+ return Buffer.from(base64, 'base64');
5
+ }
6
+
7
+ static base64Encode(buffer: Buffer) {
8
+ return buffer.toString('base64');
9
+ }
10
+
11
+ static base64UrlDecode(base64: string) {
12
+ let base64String = base64.replace(/-/g, '+').replace(/_/g, '/');
13
+
14
+ const padding = base64String.length % 4;
15
+ if (padding === 2) base64String += '==';
16
+ else if (padding === 3) base64String += '=';
17
+
18
+ return StringUtils.base64Decode(base64String);
19
+ }
20
+ }
@@ -0,0 +1,45 @@
1
+ import BaseService, { HttpRequest as BaseHttpRequest, HttpResponse as BaseHttpResponse } from "./api/Service"
2
+ export const Service = BaseService;
3
+ export interface HttpRequest extends BaseHttpRequest {}
4
+ export const HttpResponse = BaseHttpResponse;
5
+
6
+ import BaseServiceRest from "./api/ServiceRest"
7
+ export const ServiceRest = BaseServiceRest;
8
+
9
+ import BaseServiceRestFormal, { Wrapper as BaseWrapper } from "./api/ServiceRestFormal"
10
+ export const ServiceRestFormal = BaseServiceRestFormal;
11
+ export interface Wrapper<T> extends BaseWrapper<T> {}
12
+
13
+ import BaseServiceRestFormalTemplate from "./api/ServiceRestFormalTemplate"
14
+ export const ServiceRestFormalTemplate = BaseServiceRestFormalTemplate;
15
+
16
+
17
+ import BaseBusiness from "./business/Business";
18
+ export const Business = BaseBusiness;
19
+
20
+ import BaseBusinessState from "./business/BusinessState";
21
+ export const BusinessState = BaseBusinessState;
22
+
23
+ import BaseBusinessProxy, { Module as BaseModule } from "./business/BusinessProxy";
24
+ export const BusinessProxy = BaseBusinessProxy;
25
+ export interface Module extends BaseModule {}
26
+
27
+ import BaseBusinessServer from "./business/BusinessServer";
28
+ export const BusinessServer = BaseBusinessServer;
29
+
30
+ import BaseBusinessThread from "./business/BusinessThread";
31
+ export const BusinessThread = BaseBusinessThread;
32
+
33
+ import BaseBusinessErrors, { FormalError as BaseFormalError } from "./business/BusinessErrors";
34
+ export const BusinessErrors = BaseBusinessErrors;
35
+ export interface FormalError extends BaseFormalError {}
36
+
37
+ import BaseBusinessLogger from "./business/BusinessLogger";
38
+ export const BusinessLogger = BaseBusinessLogger;
39
+
40
+
41
+ import BaseRepository from "./repository/Repository";
42
+ export const Repository = BaseRepository
43
+
44
+ import BaseRepositoryPostgres from "./repository/RepositoryPostgres";
45
+ export const RepositoryPostgres = BaseRepositoryPostgres
@@ -0,0 +1,3 @@
1
+ export default class Repository {
2
+
3
+ }
@@ -0,0 +1,246 @@
1
+ import pg, {ClientConfig, QueryResult, QueryResultRow} from "pg";
2
+ import PostgresUtils from "./utils/PostgresUtils";
3
+
4
+ export interface QueryOptions {
5
+ schema?: string;
6
+ filters?: string;
7
+ }
8
+
9
+ export interface Chain extends ClientConfig {
10
+ host: string;
11
+ port: number;
12
+ user: string;
13
+ password: string;
14
+ database: string;
15
+ schema: string;
16
+ }
17
+
18
+ export interface Column {
19
+ name: string
20
+ default?: string
21
+ }
22
+
23
+ export interface Pagination<T> {
24
+ total: number;
25
+ elements: T[];
26
+ }
27
+
28
+ export type Primitive = string | number | Date | null | undefined;
29
+
30
+ export default abstract class RepositoryPostgres
31
+ {
32
+ protected chain!: Chain;
33
+
34
+ protected client!: pg.Client;
35
+
36
+ private connected: boolean = false;
37
+
38
+ private tables: { [key: string]: Column[] } = {};
39
+
40
+ private onConnectListeners: (()=>void)[] = [];
41
+
42
+ private onQueryWillExecuteListeners: ((key:string)=>void)[] = [];
43
+
44
+ private onQueryDidExecuteListeners: ((key:string)=>void)[] = [];
45
+
46
+ private onIntrospectedListener: ((tables: { [p: string]: Column[] })=>void)[] = [];
47
+
48
+ private introspectIntervalTime = 5 * 60 * 1000
49
+
50
+ private schema: string = "public";
51
+
52
+ constructor()
53
+ {
54
+ this.query = this.query.bind(this)
55
+ this.one = this.one.bind(this)
56
+ this.add = this.add.bind(this)
57
+ this.update = this.update.bind(this)
58
+ this.del = this.del.bind(this)
59
+ this.introspect = this.introspect.bind(this)
60
+ }
61
+
62
+ init(chain: Chain)
63
+ {
64
+ this.chain = chain;
65
+ this.schema = chain.schema || "public";
66
+ this.connect(chain);
67
+ }
68
+
69
+ isConnected() {
70
+ return this.connected;
71
+ }
72
+
73
+ addOnConnect(listener: () => void) { this.onConnectListeners.push(listener); }
74
+ addOnQueryWillExecuteListener(listener: (query: string) => void) { this.onQueryWillExecuteListeners.push(listener); }
75
+ setIntrospectInterval(time: number) { this.introspectIntervalTime = time; }
76
+ addOnIntrospected(listener: (tables: { [p: string]: Column[] })=>void) { this.onIntrospectedListener.push(listener); }
77
+
78
+ connect(connection: Chain) {
79
+ this.client = new pg.Client(connection);
80
+ this.client.connect().then(async () => {
81
+ await this.introspect();
82
+ setInterval(this.introspect, this.introspectIntervalTime);
83
+ this.connected = true;
84
+ this.onConnectListeners.map(l => l());
85
+ });
86
+ }
87
+
88
+ async native(query: string, values: any[] = [], options: QueryOptions = {}): Promise<QueryResult>
89
+ {
90
+ this.onQueryWillExecuteListeners.forEach((l) => l(query));
91
+ let cursor = this.client.query(query, values);
92
+ this.onQueryDidExecuteListeners.forEach((l) => l(query));
93
+ return cursor;
94
+ }
95
+
96
+ async query<E>(query: string, values: any[] = [], options: QueryOptions = {}): Promise<E[]>
97
+ {
98
+ return await this.native(query, values, options).then((result) => {
99
+ return PostgresUtils.snakeToCamel(result.rows);
100
+ });
101
+ }
102
+
103
+ async one<E>(tableName: string, id: string): Promise<E>
104
+ {
105
+ return this.any<E>(tableName, { id });
106
+ }
107
+
108
+ async any<E>(tableName: string, filters: { [key: string]: Primitive | Array<any> }): Promise<E>
109
+ {
110
+ return await this.find<E>(tableName, filters).then((rows) => rows[0]);
111
+ }
112
+
113
+ async find<T>(tableName: string, filters: { [key: string]: Primitive | Array<any> }): Promise<T[]>
114
+ {
115
+ if (!this.tables[tableName]) throw new Error(`table ${tableName} does not exist`);
116
+ let { where, values } = this.toQuery(tableName, filters);
117
+ let query = `SELECT * FROM ${this.schema}.${tableName} WHERE ${where}`
118
+ return this.query<T>(query, values);
119
+ }
120
+
121
+ async all_depr<E>(tableName: string, options: QueryOptions = {}): Promise<E[]>
122
+ {
123
+ if (!this.tables[tableName]) throw new Error(`table ${tableName} does not exist`);
124
+ let table = this.tables[tableName];
125
+ let schema = options.schema ?? this.schema;
126
+
127
+ return this.query(`
128
+ SELECT * FROM ${schema}.${tableName}
129
+ WHERE ${ options.filters ?? "TRUE" }
130
+ `);
131
+ }
132
+
133
+ async add<E>(tableName: string, object: {[key:string]:Primitive} | any, id?: string): Promise<E>
134
+ {
135
+ return this.addAll<E>(tableName, [ { id: id, ...object } ]).then((rows) => rows[0]);
136
+ }
137
+
138
+ async addAll<T>(tableName: string, objects: {[key:string]:Primitive }[]): Promise<T[]>
139
+ {
140
+ if (!this.tables[tableName]) throw new Error(`table ${tableName} does not exist`);
141
+ let table = this.tables[tableName];
142
+ let columns = PostgresUtils.columns(table);
143
+ let query = `INSERT INTO ${this.schema}.${tableName} (${columns.join(",")}) VALUES `;
144
+
145
+ let values = []; let index = 1;
146
+ for (let object of objects) {
147
+ let { bindings, values: valueList } = PostgresUtils.bindOrDefault(table, object, index);
148
+ index += valueList.length;
149
+ values.push(...valueList)
150
+ query += `(${bindings.join(",")}), `;
151
+ }
152
+ query = query.slice(0, -2);
153
+ query += ` RETURNING *`;
154
+
155
+ return this.query(query, values);
156
+ }
157
+
158
+ async update(tableName: string, id: string, object: {[key:string]:Primitive})
159
+ {
160
+ if (!id) throw new Error(`field 'id' is mandatory when updating ${tableName}`);
161
+ if (!this.tables[tableName]) throw new Error(`table ${tableName} does not exist`);
162
+ let table = this.tables[tableName];
163
+ let columns = PostgresUtils.columns(table);
164
+
165
+ let params = columns.map((column, index) => `\$${index+2}`).join(",");
166
+ let values = [ id, ...columns.map((column) => object[PostgresUtils.stringToCamel(column)]) ];
167
+ let query = `UPDATE ${this.schema}.${tableName} SET (${columns.join(",")}) = (${params}) WHERE id = $1 `
168
+ query += `RETURNING *`;
169
+
170
+ return this.query(query, values);
171
+ }
172
+
173
+ async page<T>(tableName: string, sortKey: string, maxResults: number = 50, pageNumber: number = 0, filters?: {[key:string]:Primitive|Array<Primitive>}): Promise<Pagination<T>>
174
+ {
175
+ if (!this.tables[tableName]) throw new Error(`table ${tableName} does not exist`);
176
+ let table = this.tables[tableName];
177
+ let columns = PostgresUtils.columns(table);
178
+ let offset = maxResults * pageNumber;
179
+
180
+ let { where, values } = this.toQuery(tableName, filters, "", 4);
181
+ let query = `SELECT * FROM ${this.schema}.${tableName} WHERE ${ where } ORDER BY $1 LIMIT $2 OFFSET $3`;
182
+ let elements = this.query<T>(query, [sortKey, maxResults, offset, ...values])
183
+
184
+ let total = this.count(tableName, filters)
185
+ return Promise.all([total, elements]).then(
186
+ ([total, elements]) => ({ total, elements })
187
+ );
188
+ }
189
+
190
+ async count(tableName: string, filters?: { [key: string]: Primitive | Array<Primitive> }): Promise<number>
191
+ {
192
+ if (!this.tables[tableName]) throw new Error(`table ${tableName} does not exist`);
193
+ let { where, values } = this.toQuery(tableName, filters);
194
+ let query = `SELECT count(*) as count FROM ${this.schema}.${tableName} WHERE ${where}`
195
+ return this.query<{count:number}>(query, values).then((result) => result[0].count);
196
+ }
197
+
198
+ async select(tableName: string, select: string, filters?: { [key: string]: Primitive | Array<Primitive> }): Promise<string[]>
199
+ {
200
+ if (!this.tables[tableName]) throw new Error(`table ${tableName} does not exist`);
201
+ let { where, values } = this.toQuery(tableName, filters);
202
+ let query = `SELECT json_agg(${select}) as list FROM ${this.schema}.${tableName} WHERE ${where}`
203
+ return this.query<{list:string[]}>(query, values).then((result) => result[0].list);
204
+ }
205
+
206
+ async del(tableName: string, id: string)
207
+ {
208
+ throw new Error(`not implemented yet`);
209
+ }
210
+
211
+ private toQuery(tableName: string, filters?: { [key: string]: Primitive | Array<Primitive> }, alias:string = "", index=1)
212
+ {
213
+ let table = this.tables[tableName];
214
+ let columns = PostgresUtils.columns(table);
215
+ let elements = Object.entries(filters ?? []);
216
+ let where = "TRUE"; let iter = index; let values = [];
217
+
218
+ for (let [key, value] of elements) {
219
+ let colum = PostgresUtils.camelToSnake(key)
220
+ if (key !== "id" && !columns.includes(colum)) continue;
221
+ if (alias !== "") alias += "."
222
+ if (value instanceof Array) where += ` AND ${alias}${colum} = ANY(\$${iter++})`
223
+ else where += ` AND ${alias}${colum}=\$${iter++}`
224
+ values.push(value);
225
+ }
226
+
227
+ return { where, values };
228
+ }
229
+
230
+ private async introspect() {
231
+ let result = await this.native(`
232
+ SELECT table_name as name,
233
+ json_agg(json_build_object('name', column_name, 'default', column_default)) as columns
234
+ FROM information_schema.columns
235
+ WHERE (table_schema = $1 or table_schema = 'public')
236
+ GROUP BY table_name
237
+ `, [this.schema]);
238
+
239
+ for (let table of result.rows)
240
+ this.tables[table["name"]] = table["columns"];
241
+
242
+ this.onIntrospectedListener.forEach((l) => l(this.tables));
243
+
244
+ }
245
+
246
+ }
@@ -0,0 +1,70 @@
1
+ import Repository from './Repository';
2
+ import {HttpRequest, HttpResponse} from "../api/Service";
3
+ import QueryUtils from "./utils/QueryUtils";
4
+
5
+ export interface RestResponse<E=any> extends Response {
6
+ timestamp?: Date;
7
+ duration?: number;
8
+ }
9
+
10
+ export interface RestOptions {
11
+ interceptor: (response: RestResponse) => void;
12
+ }
13
+
14
+ export default class RepositoryRest<S=any> extends Repository
15
+ {
16
+ private readonly baseUrl: string;
17
+
18
+ private readonly interceptor?: (response: RestResponse) => void;
19
+
20
+ private onRequestListener?: (method: string, endpoint: string, request: HttpRequest)=>void;
21
+
22
+ constructor(baseUrl: string, options?: RestOptions) {
23
+ super();
24
+ this.baseUrl = baseUrl;
25
+ this.interceptor = options?.interceptor;
26
+ }
27
+
28
+ setOnRequestListener(listener: (method: string, endpoint: string, request: HttpRequest)=>void) {
29
+ this.onRequestListener = listener;
30
+ }
31
+
32
+ async call<E = S>(
33
+ method: string = 'GET',
34
+ endpoint: string = '/',
35
+ request: HttpRequest = {},
36
+ ): Promise<RestResponse<E>>
37
+ {
38
+ // Especifica el método de la llamada HTTP al recurso.
39
+ request.method = method;
40
+
41
+ // Completa la información de las cabeceras con cabeceras comunes por llamada a API.
42
+ let headers = new Headers(request.headers);
43
+ headers.set('Content-Type', 'application/json');
44
+ headers.set('Cache-Control', 'no-cache');
45
+ headers.set('Pragma', 'no-cache');
46
+ for (let header in request.headers)
47
+ headers.set(header, request.headers[header]);
48
+
49
+ // Notifica que una petición REST va a ser realizada
50
+ this.onRequestListener?.(method, endpoint, request);
51
+
52
+ // Realiza la llamada a la API con las cabeceras y la URL base de la llamada.
53
+ let timestamp = new Date();
54
+ let call = fetch(this.baseUrl + endpoint + QueryUtils.map(request.query), {
55
+ ...request,
56
+ headers
57
+ });
58
+
59
+ // Añade información adicional a la respuesta y devuelve el resultado (o el error en su caso).
60
+ return call.then((res: Response) => {
61
+ let response: RestResponse<E> = res;
62
+ response.duration = new Date().valueOf() - timestamp.valueOf()
63
+ response.timestamp = timestamp;
64
+ this.interceptor?.(response);
65
+ return response;
66
+ });
67
+ }
68
+
69
+
70
+ }
@@ -0,0 +1,37 @@
1
+ import RepositoryRest from "./RepositoryRest";
2
+ import {Wrapper} from "../api/ServiceRestFormal";
3
+ import {HttpRequest} from "../api/Service";
4
+ import { FormalError } from "../business/BusinessErrors";
5
+
6
+ export default class RepositoryRestFormal<S=any> extends RepositoryRest<Wrapper<S>> {
7
+
8
+ constructor(baseUrl: string, ...methods: RepositoryRest[]) {
9
+ super(baseUrl);
10
+ }
11
+
12
+ async call<E = S>(
13
+ method: string = 'GET',
14
+ endpoint: string = '/',
15
+ request: HttpRequest = {},
16
+ ): Promise<E> {
17
+
18
+ request.body = JSON.stringify(request.body);
19
+
20
+ return super.call<E>(method, endpoint, request)
21
+ .then(async (call) => {
22
+ let json = await call.json();
23
+ if (json.code === "0") return json.data;
24
+ else { // @ts-ignore
25
+ throw new FormalError(json.code, json.description, call);
26
+ }
27
+
28
+ })
29
+ .catch((error: any) => {
30
+ // @ts-ignore
31
+ if (error instanceof FormalError) throw error;
32
+ else { // @ts-ignore
33
+ throw new FormalError("-1", "ConnectionError", undefined, error)
34
+ }
35
+ });
36
+ }
37
+ }
@@ -0,0 +1,59 @@
1
+ import {Column, Primitive} from "../RepositoryPostgres";
2
+
3
+ export default abstract class PostgresUtils
4
+ {
5
+ static stringToCamel(column: string): string
6
+ {
7
+ return column.toLowerCase().replace(/([-_][a-z])/g, group =>
8
+ group.toUpperCase().replace('-', '').replace('_', '')
9
+ );
10
+ }
11
+
12
+ static camelToSnake(column: string) {
13
+ return column.replace(/([A-Z])/g, '_$1').toLowerCase();
14
+ }
15
+
16
+ static snakeToCamel(obj: any): any
17
+ {
18
+ if (Array.isArray(obj)) return obj.map(el => PostgresUtils.snakeToCamel(el));
19
+ else if (typeof obj === 'function' || obj !== Object(obj) || obj instanceof Date) return obj;
20
+ else return PostgresUtils.fromEntries(
21
+ Object.entries(obj).map(([key, value]) => [
22
+ key.replace(/([-_][a-z])/gi, c => c.toUpperCase().replace(/[-_]/g, '')),
23
+ PostgresUtils.snakeToCamel(value),
24
+ ]),
25
+ );
26
+ }
27
+
28
+ static columns(table: Column[]): string[] {
29
+ return table.map((t) => t.name);
30
+ }
31
+
32
+ static bindOrDefault(table: Column[], object: { [p: string]: Primitive }, startIndex: any): { bindings: string[], values: any[] } {
33
+
34
+ let index = startIndex;
35
+ let bindings: string[] = [];
36
+ let values: any[] = []
37
+ table.forEach((colum) => {
38
+ let value = object[PostgresUtils.stringToCamel(colum.name)];
39
+ if (value !== undefined || colum.default === undefined) {
40
+ bindings.push(`\$${index++}`)
41
+ values.push(value)
42
+ } else bindings.push("DEFAULT")
43
+ });
44
+ return { bindings, values }
45
+ }
46
+
47
+ static valueOrDefault(columns: [string], object:any):string[] {
48
+ return columns.map((column) => {
49
+ return object[PostgresUtils.stringToCamel(column)];
50
+ });
51
+ }
52
+
53
+ static fromEntries(iterable: [string,any][]): {[key:string]:any} {
54
+ const result: {[key:string]:any} = {};
55
+ for (const [key, value] of iterable)
56
+ result[key] = value;
57
+ return result;
58
+ }
59
+ }