@dwtechs/antity-pgsql 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 DWTechs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,289 @@
1
+
2
+ [![License: MIT](https://img.shields.io/npm/l/@dwtechs/antity-pgsql.svg?color=brightgreen)](https://opensource.org/licenses/MIT)
3
+ [![npm version](https://badge.fury.io/js/%40dwtechs%2Fantity.svg)](https://www.npmjs.com/package/@dwtechs/antity-pgsql)
4
+ [![last version release date](https://img.shields.io/github/release-date/DWTechs/Antity-pgsql.js)](https://www.npmjs.com/package/@dwtechs/antity-pgsql)
5
+ ![Jest:coverage](https://img.shields.io/badge/Jest:coverage-100%25-brightgreen.svg)
6
+ [![minified size](https://img.shields.io/bundlephobia/min/@dwtechs/antity-pgsql?color=brightgreen)](https://www.npmjs.com/package/@dwtechs/antity-pgsql)
7
+
8
+ - [Synopsis](#synopsis)
9
+ - [Support](#support)
10
+ - [Installation](#installation)
11
+ - [Usage](#usage)
12
+ - [ES6](#es6)
13
+ - [API Reference](#api-reference)
14
+ - [Contributors](#contributors)
15
+ - [Stack](#stack)
16
+
17
+
18
+ ## Synopsis
19
+
20
+ **[Antity-pgsql.js](https://github.com/DWTechs/Antity-pgsql.js)** adds PostgreSQL features to **Antity.js** library.
21
+
22
+ - Very lightweight
23
+ - Thoroughly tested
24
+ - Works in Javascript, Typescript
25
+ - Can be used as EcmaScrypt module
26
+ - Written in Typescript
27
+
28
+
29
+ ## Support
30
+
31
+ - node: 22
32
+
33
+ This is the oldest targeted versions. The library should work properly on older versions of Node.js but we do not support it officially.
34
+
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ $ npm i @dwtechs/antity-pgsql
40
+ ```
41
+
42
+
43
+ ## Usage
44
+
45
+
46
+ ### ES6 / TypeScript
47
+
48
+ ```javascript
49
+
50
+ import { SQLEntity } from "@dwtechs/antity-pgsql";
51
+ import { normalizeName, normalizeNickname } from "@dwtechs/checkard";
52
+
53
+ const entity = new Entity("consumers", [
54
+ {
55
+ key: "id",
56
+ type: "integer",
57
+ min: 0,
58
+ max: 120,
59
+ typeCheck: true,
60
+ methods: ["GET", "PUT", "DELETE"],
61
+ required: true,
62
+ safe: true,
63
+ sanitize: true,
64
+ normalize: true,
65
+ validate: true,
66
+ sanitizer: null,
67
+ normalizer: null,
68
+ validator: null,
69
+ },
70
+ {
71
+ key: "firstName",
72
+ type: "string",
73
+ min: 0,
74
+ max: 255,
75
+ typeCheck: true,
76
+ methods: ["GET", "POST", "PUT", "DELETE"],
77
+ required: true,
78
+ safe: true,
79
+ sanitize: true,
80
+ normalize: true,
81
+ validate: true,
82
+ sanitizer: null,
83
+ normalizer: normalizeName,
84
+ validator: null,
85
+ },
86
+ {
87
+ key: "lastName",
88
+ type: "string",
89
+ min: 0,
90
+ max: 255,
91
+ typeCheck: true,
92
+ methods: ["GET", "POST", "PUT", "DELETE"],
93
+ required: true,
94
+ safe: true,
95
+ sanitize: true,
96
+ normalize: true,
97
+ validate: true,
98
+ sanitizer: null,
99
+ normalizer: normalizeName,
100
+ validator: null,
101
+ },
102
+ {
103
+ key: "nickname",
104
+ type: "string",
105
+ min: 0,
106
+ max: 255,
107
+ typeCheck: true,
108
+ methods: ["GET", "POST", "PUT", "DELETE"],
109
+ required: true,
110
+ safe: true,
111
+ sanitize: true,
112
+ normalize: true,
113
+ validate: true,
114
+ sanitizer: null,
115
+ normalizer: normalizeNickname,
116
+ validator: null,
117
+ },
118
+ ]);
119
+
120
+ // add a consumer. Used when loggin in from user service
121
+ router.gett("/", ..., entity.get);
122
+ router.post("/", entity.normalize, entity.validate, ..., entity.add);
123
+ router.put("/", entity.normalize, entity.validate, ..., entity.update);
124
+ router.put("/", ..., entity.archive);
125
+
126
+ ```
127
+
128
+ ## API Reference
129
+
130
+
131
+ ```javascript
132
+
133
+ type Operation = "select" | "insert" | "update" | "merge" | "delete";
134
+
135
+ type MatchMode =
136
+ "startsWith" |
137
+ "endsWith" |
138
+ "contains" |
139
+ "notContains" |
140
+ "equals" |
141
+ "notEquals" |
142
+ "between" |
143
+ "in" |
144
+ "lt" |
145
+ "lte" |
146
+ "gt" |
147
+ "gte" |
148
+ "is" |
149
+ "isNot" |
150
+ "before" |
151
+ "after" |
152
+ "st_contains" |
153
+ "st_dwithin";
154
+
155
+
156
+ type Filter = {
157
+ value: string | number | boolean | Date | number[];
158
+ subProps?: string[];
159
+ matchMode?: MatchMode;
160
+ }
161
+
162
+ class SQLEntity {
163
+ constructor(name: string, properties: Property[]);
164
+ get name(): string;
165
+ get table(): string;
166
+ get unsafeProps(): string[];
167
+ get properties(): Property[];
168
+ set name(name: string);
169
+ set table(table: string);
170
+
171
+ query: {
172
+ select: (paginate: boolean) => string;
173
+ update: (chunk: Record<string, unknown>[], consumerId: number | string, consumerName: string) => {
174
+ query: string;
175
+ args: unknown[];
176
+ };
177
+ insert: (chunk: Record<string, unknown>[], consumerId: number | string, consumerName: string, rtn?: string) => {
178
+ query: string;
179
+ args: unknown[];
180
+ };
181
+ delete: () => string;
182
+ return: (prop: string) => string;
183
+ };
184
+ get(req: Request, res: Response, next: NextFunction): void;
185
+ add(req: Request, res: Response, next: NextFunction): Promise<void>;
186
+ update(req: Request, res: Response, next: NextFunction): Promise<void>;
187
+ archive(req: Request, res: Response, next: NextFunction): Promise<void>;
188
+ delete(req: Request, res: Response, next: NextFunction): void;
189
+
190
+ }
191
+
192
+ filter(
193
+ first: number,
194
+ rows: number | null,
195
+ sortField: string | null,
196
+ sortOrder: Sort,
197
+ filters: Filters | null,
198
+ ): { filterClause: string, args: (Filter["value"])[] } {
199
+
200
+
201
+ ```
202
+ get(), add(), update(), archive() and delete() methods are made to be used as Express.js middlewares.
203
+ Each method will look for data to work on in the **req.body.rows** parameter.
204
+
205
+
206
+ ## Match modes
207
+
208
+ List of possible match modes :
209
+
210
+ | Name | alias | types | Description |
211
+ | :---------- | :---- | :---------------------- | :-------------------------------------------------------- |
212
+ | startsWith | | string | Whether the value starts with the filter value |
213
+ | contains | | string | Whether the value contains the filter value |
214
+ | endsWith | | string | Whether the value ends with the filter value |
215
+ | notContains | | string | Whether the value does not contain filter value |
216
+ | equals | | string \| number | Whether the value equals the filter value |
217
+ | notEquals | | string \| number | Whether the value does not equal the filter value |
218
+ | in | | string[] \| number[] | Whether the value contains the filter value |
219
+ | lt | | string \| number | Whether the value is less than the filter value |
220
+ | lte | | string \| number | Whether the value is less than or equals to the filter value |
221
+ | gt | | string \| number | Whether the value is greater than the filter value |
222
+ | gte | | string \| number | Whether the value is greater than or equals to the filter value |
223
+ | is | | date \| boolean \| null | Whether the value equals the filter value, alias to equals |
224
+ | isNot | | date \| boolean \| null | Whether the value does not equal the filter value, alias to notEquals |
225
+ | before | | date | Whether the date value is before the filter date |
226
+ | after | | date | Whether the date value is after the filter date |
227
+ | between | | date[2] \| number[2] | Whether the value is between the filter values |
228
+ | st_contains | | geometry | Whether the geometry completely contains other geometries |
229
+ | st_dwithin | | geometry | Whether geometries are within a specified distance from another geometry |
230
+
231
+ ## Types
232
+
233
+ List of compatible match modes for each property types
234
+
235
+ | Name | Match modes |
236
+ | :---------- | :---------------------- |
237
+ | string | startsWith,<br>contains,<br>endsWith,<br>notContains,<br>equals,<br>notEquals,<br>lt,<br>lte,<br>gt,<br>gte |
238
+ | number | equals,<br>notEquals,<br>lt,<br>lte,<br>gt,<br>gte |
239
+ | date | is,<br>isNot,<br>before,<br>after |
240
+ | boolean | is,<br>isNot |
241
+ | string[] | in |
242
+ | number[] | in,<br>between |
243
+ | date[] | between |
244
+ | geometry | st_contains,<br>st_dwithin |
245
+
246
+ List of secondary types :
247
+
248
+ | Name | equivalent |
249
+ | :----------------- | :--------- |
250
+ | integer | number |
251
+ | float | number |
252
+ | even | number |
253
+ | odd | number |
254
+ | positive | number |
255
+ | negative | number |
256
+ | powerOfTwo | number |
257
+ | ascii | number |
258
+ | array | any[] |
259
+ | jwt | string |
260
+ | symbol | string |
261
+ | email | string |
262
+ | regex | string |
263
+ | ipAddress | string |
264
+ | slug | string |
265
+ | hexadecimal | string |
266
+ | date | date |
267
+ | timestamp | date |
268
+ | function | string |
269
+ | htmlElement | string |
270
+ | htmlEventAttribute | string |
271
+ | node | string |
272
+ | json | object |
273
+ | object | object |
274
+
275
+ ## Contributors
276
+
277
+ Antity.js is still in development and we would be glad to get all the help you can provide.
278
+ To contribute please read **[contributor.md](https://github.com/DWTechs/Antity.js/blob/main/contributor.md)** for detailed installation guide.
279
+
280
+
281
+ ## Stack
282
+
283
+ | Purpose | Choice | Motivation |
284
+ | :-------------- | :------------------------------------------: | -------------------------------------------------------------: |
285
+ | repository | [Github](https://github.com/) | hosting for software development version control using Git |
286
+ | package manager | [npm](https://www.npmjs.com/get-npm) | default node.js package manager |
287
+ | language | [TypeScript](https://www.typescriptlang.org) | static type checking along with the latest ECMAScript features |
288
+ | module bundler | [Rollup](https://rollupjs.org) | advanced module bundler for ES6 modules |
289
+ | unit testing | [Jest](https://jestjs.io/) | delightful testing with a focus on simplicity |
@@ -0,0 +1,117 @@
1
+ /*
2
+ MIT License
3
+
4
+ Copyright (c) 2025 DWTechs
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
23
+
24
+ https://github.com/DWTechs/Antity-pgsql.js
25
+ */
26
+
27
+ import { Entity, Property } from "@dwtechs/antity";
28
+ import type { Request, NextFunction } from 'express';
29
+
30
+ export type Operation = "SELECT" | "INSERT" | "UPDATE" | "DELETE";
31
+ export type Sort = "ASC" | "DESC";
32
+ export type Filters = {
33
+ [key: string]: Filter;
34
+ };
35
+ export type Filter = {
36
+ value: string | number | boolean | Date | number[];
37
+ subProps?: string[];
38
+ matchMode?: MatchMode;
39
+ };
40
+ export type LogicalOperator = "AND" | "OR";
41
+ export type Comparator = "=" | "<" | ">" | "<=" | ">=" | "<>" | "IS" | "IS NOT" | "IN" | "LIKE" | "NOT LIKE";
42
+ export type MatchMode = "startsWith" | "endsWith" | "contains" | "notContains" | "equals" | "notEquals" | "between" | "in" | "lt" | "lte" | "gt" | "gte" | "is" | "isNot" | "before" | "after" | "st_contains" | "st_dwithin";
43
+ export type MappedType = "string" | "number" | "date";
44
+ export type Type = "boolean" | "string" | "number" | "integer" | "float" | "even" | "odd" | "positive" | "negative" | "powerOfTwo" | "ascii" | "array" | "jwt" | "symbol" | "email" | "regex" | "json" | "ipAddress" | "slug" | "hexadecimal" | "date" | "timestamp" | "function" | "htmlElement" | "htmlEventAttribute" | "node" | "object" | "geometry";
45
+ export type Geometry = {
46
+ lng: number;
47
+ lat: number;
48
+ radius: number;
49
+ bounds: {
50
+ minLng: number;
51
+ minLat: number;
52
+ maxLng: number;
53
+ maxLat: number;
54
+ };
55
+ };
56
+ export type PGResponse = {
57
+ rows: Record<string, unknown>[];
58
+ rowCount: number;
59
+ total?: number;
60
+ command?: string;
61
+ oid?: number;
62
+ fields?: unknown[];
63
+ _parsers?: unknown[];
64
+ _types?: unknown;
65
+ RowCtor?: unknown;
66
+ rowAsArray?: boolean;
67
+ };
68
+ export type Response = {
69
+ rows: Record<string, unknown>[];
70
+ total?: number;
71
+ };
72
+
73
+ declare class SQLEntity extends Entity {
74
+ private _table;
75
+ private sel;
76
+ private ins;
77
+ private upd;
78
+ constructor(name: string, properties: Property[]);
79
+ get table(): string;
80
+ set table(table: string);
81
+ query: {
82
+ select: (paginate: boolean) => string;
83
+ update: (rows: Record<string, unknown>[], consumerId: number | string, consumerName: string) => {
84
+ query: string;
85
+ args: unknown[];
86
+ };
87
+ insert: (rows: Record<string, unknown>[], consumerId: number | string, consumerName: string, rtn?: string) => {
88
+ query: string;
89
+ args: unknown[];
90
+ };
91
+ delete: () => string;
92
+ return: (prop: string) => string;
93
+ };
94
+ get(req: Request, res: Response, next: NextFunction): void;
95
+ add(req: Request, res: Response, next: NextFunction): Promise<void>;
96
+ update(req: Request, res: Response, next: NextFunction): Promise<void>;
97
+ archive(req: Request, res: Response, next: NextFunction): Promise<void>;
98
+ delete(req: Request, res: Response, next: NextFunction): void;
99
+ private cleanFilters;
100
+ private mapProps;
101
+ }
102
+
103
+ declare filter(
104
+ first: number,
105
+ rows: number | null,
106
+ sortField: string | null,
107
+ sortOrder: Sort,
108
+ filters: Filters | null,
109
+ ): { filterClause: string, args: (Filter["value"])[] } {]
110
+
111
+
112
+ export {
113
+ SQLEntity,
114
+ Property,
115
+ filter,
116
+ };
117
+
@@ -0,0 +1,588 @@
1
+ /*
2
+ MIT License
3
+
4
+ Copyright (c) 2025 DWTechs
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
23
+
24
+ https://github.com/DWTechs/Antity-pgsql.js
25
+ */
26
+
27
+ import { isIn, isArray, isString } from '@dwtechs/checkard';
28
+ import { deleteProps, add as add$1, chunk, flatten } from '@dwtechs/sparray';
29
+ import { log } from '@dwtechs/winstan';
30
+ import { Entity } from '@dwtechs/antity';
31
+ import Pool from 'pg-pool';
32
+
33
+ function type(type) {
34
+ const s = "string";
35
+ const n = "number";
36
+ const d = "date";
37
+ switch (type) {
38
+ case "integer":
39
+ return n;
40
+ case "float":
41
+ return n;
42
+ case "even":
43
+ return n;
44
+ case "odd":
45
+ return n;
46
+ case "positive":
47
+ return n;
48
+ case "negative":
49
+ return n;
50
+ case "powerOfTwo":
51
+ return n;
52
+ case "ascii":
53
+ return n;
54
+ case "jwt":
55
+ return s;
56
+ case "symbol":
57
+ return s;
58
+ case "email":
59
+ return s;
60
+ case "regex":
61
+ return s;
62
+ case "ipAddress":
63
+ return s;
64
+ case "slug":
65
+ return s;
66
+ case "hexadecimal":
67
+ return s;
68
+ case "date":
69
+ return d;
70
+ case "timestamp":
71
+ return d;
72
+ case "function":
73
+ return s;
74
+ case "htmlElement":
75
+ return s;
76
+ case "htmlEventAttribute":
77
+ return s;
78
+ case "node":
79
+ return s;
80
+ case "json":
81
+ return s;
82
+ case "object":
83
+ return s;
84
+ default:
85
+ return s;
86
+ }
87
+ }
88
+
89
+ const matchModes = {
90
+ string: ["startsWith", "contains", "endsWith", "notContains", "equals", "notEquals", "lt", "lte", "gt", "gte"],
91
+ number: ["equals", "notEquals", "lt", "lte", "gt", "gte"],
92
+ date: ["is", "isNot", "dateAfter"],
93
+ };
94
+ function matchMode(type, matchMode) {
95
+ return isIn(matchModes[type], matchMode);
96
+ }
97
+
98
+ const { DB_HOST, DB_USER, DB_PWD, DB_NAME, DB_PORT, DB_MAX } = process.env;
99
+ var pool = new Pool({
100
+ host: DB_HOST,
101
+ user: DB_USER,
102
+ password: DB_PWD,
103
+ database: DB_NAME,
104
+ port: +(DB_PORT || 5432),
105
+ max: DB_MAX ? +DB_MAX : 10,
106
+ });
107
+
108
+ function start(query, args) {
109
+ const a = JSON.stringify(args);
110
+ const q = query.replace(/[\n\r]+/g, "").replace(/\s{2,}/g, " ");
111
+ log.debug(`Pgsql: { Query : '${q}', Args : '${a}' }`);
112
+ return Date.now();
113
+ }
114
+ function end(res, time) {
115
+ const r = JSON.stringify(res);
116
+ const t = Date.now() - time;
117
+ log.debug(`Pgsql response in ${t}ms : ${r}`);
118
+ }
119
+ var perf = {
120
+ start,
121
+ end,
122
+ };
123
+
124
+ function execute$1(query, args, clt) {
125
+ const time = perf.start(query, args);
126
+ const client = clt || pool;
127
+ return client
128
+ .query(query, args)
129
+ .then((res) => {
130
+ perf.end(res, time);
131
+ deleteIdleProperties(res);
132
+ return res;
133
+ })
134
+ .catch((err) => {
135
+ err.msg = `Postgre error: ${err.message}`;
136
+ throw err;
137
+ });
138
+ }
139
+ function deleteIdleProperties(res) {
140
+ res.command = undefined;
141
+ res.oid = undefined;
142
+ res.fields = undefined;
143
+ res._parsers = undefined;
144
+ res._types = undefined;
145
+ res.RowCtor = undefined;
146
+ res.rowAsArray = undefined;
147
+ }
148
+
149
+ class Select {
150
+ constructor() {
151
+ this._props = [];
152
+ this._cols = "";
153
+ this._count = ", COUNT(*) OVER () AS total";
154
+ }
155
+ addProp(prop) {
156
+ this._props.push(prop);
157
+ this._cols = this._props.join(", ");
158
+ }
159
+ get props() {
160
+ return this._cols;
161
+ }
162
+ query(table, paginate) {
163
+ const p = paginate ? this._count : '';
164
+ const c = this._cols ? this._cols : '*';
165
+ return `SELECT ${c}${p} FROM "${table}"`;
166
+ }
167
+ execute(query, args, client) {
168
+ return execute$1(query, args, client)
169
+ .then((r) => {
170
+ if (!r.rowCount)
171
+ throw { status: 404, msg: "Resource not found" };
172
+ const f = r.rows[0];
173
+ if (f.total) {
174
+ r.total = Number(f.total);
175
+ r.rows = deleteProps(r.rows, ["total"]);
176
+ }
177
+ return r;
178
+ });
179
+ }
180
+ }
181
+
182
+ function $i(qty, start) {
183
+ return `(${Array.from({ length: qty }, (_, i) => `$${start + i + 1}`).join(", ")})`;
184
+ }
185
+
186
+ class Insert {
187
+ constructor() {
188
+ this._props = ["consumerId", "consumerName"];
189
+ this._cols = "*";
190
+ this._nbProps = 2;
191
+ }
192
+ addProp(prop) {
193
+ this._props = add$1(this._props, prop, this._props.length - 2);
194
+ this._cols = this._props.join(", ");
195
+ this._nbProps++;
196
+ }
197
+ query(table, rows, consumerId, consumerName, rtn = "") {
198
+ let query = `INSERT INTO "${table}" (${this._cols}) VALUES `;
199
+ const args = [];
200
+ let i = 0;
201
+ for (const row of rows) {
202
+ row.consumerId = consumerId;
203
+ row.consumerName = consumerName;
204
+ query += `${$i(this._nbProps, i)}, `;
205
+ for (const prop of this._props) {
206
+ args.push(row[prop]);
207
+ }
208
+ i += this._nbProps;
209
+ }
210
+ query = query.slice(0, -2);
211
+ if (rtn)
212
+ query += ` ${rtn}`;
213
+ return { query, args };
214
+ }
215
+ rtn(prop) {
216
+ return `RETURNING "${prop}"`;
217
+ }
218
+ execute(query, args, client) {
219
+ return execute$1(query, args, client);
220
+ }
221
+ }
222
+
223
+ var __awaiter$2 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
224
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
225
+ return new (P || (P = Promise))(function (resolve, reject) {
226
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
227
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
228
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
229
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
230
+ });
231
+ };
232
+ class Update {
233
+ constructor() {
234
+ this._props = ["consumerId", "consumerName"];
235
+ }
236
+ addProp(prop) {
237
+ this._props = add$1(this._props, prop, this._props.length - 2);
238
+ }
239
+ query(table, rows, consumerId, consumerName) {
240
+ rows = this.addConsumer(rows, consumerId, consumerName);
241
+ const l = rows.length;
242
+ const args = rows.map(row => row.id);
243
+ let query = `UPDATE "${table}" SET `;
244
+ let i = args.length + 1;
245
+ for (const p of this._props) {
246
+ if (rows[0][p] === undefined)
247
+ continue;
248
+ query += `${p} = CASE `;
249
+ for (let j = 0; j < l; j++) {
250
+ const row = rows[j];
251
+ query += `WHEN id = $${j + 1} THEN $${i++} `;
252
+ args.push(row[p]);
253
+ }
254
+ query += `END, `;
255
+ }
256
+ query = `${query.slice(0, -2)} WHERE id IN ${$i(l, 0)}`;
257
+ return { query, args };
258
+ }
259
+ execute(query, args, client) {
260
+ return __awaiter$2(this, void 0, void 0, function* () {
261
+ return execute$1(query, args, client);
262
+ });
263
+ }
264
+ addConsumer(rows, consumerId, consumerName) {
265
+ return rows.map((row) => (Object.assign(Object.assign({}, row), { consumerId,
266
+ consumerName })));
267
+ }
268
+ }
269
+
270
+ var __awaiter$1 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
271
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
272
+ return new (P || (P = Promise))(function (resolve, reject) {
273
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
274
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
275
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
276
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
277
+ });
278
+ };
279
+ function query(table) {
280
+ return `DELETE FROM "${table}" WHERE "archivedAt" < $1`;
281
+ }
282
+ function execute(date, query, client) {
283
+ return __awaiter$1(this, void 0, void 0, function* () {
284
+ let db;
285
+ try {
286
+ db = yield execute$1(query, [date], client);
287
+ }
288
+ catch (err) {
289
+ throw err;
290
+ }
291
+ return db;
292
+ });
293
+ }
294
+
295
+ function index(index, matchMode) {
296
+ const i = index.map((i) => `$${i}`);
297
+ switch (matchMode) {
298
+ case "startsWith":
299
+ return `${i}%`;
300
+ case "endsWith":
301
+ return `%${i}`;
302
+ case "contains":
303
+ return `%${i}%`;
304
+ case "notContains":
305
+ return `%${i}%`;
306
+ case "in":
307
+ return `(${i})`;
308
+ default:
309
+ return `${i}`;
310
+ }
311
+ }
312
+
313
+ function comparator(matchMode) {
314
+ switch (matchMode) {
315
+ case "startsWith":
316
+ return "LIKE";
317
+ case "endsWith":
318
+ return "LIKE";
319
+ case "contains":
320
+ return "LIKE";
321
+ case "notContains":
322
+ return "NOT LIKE";
323
+ case "equals":
324
+ return "=";
325
+ case "notEquals":
326
+ return "<>";
327
+ case "in":
328
+ return "IN";
329
+ case "lt":
330
+ return "<";
331
+ case "lte":
332
+ return "<=";
333
+ case "gt":
334
+ return ">";
335
+ case "gte":
336
+ return ">=";
337
+ case "is":
338
+ return "IS";
339
+ case "isNot":
340
+ return "IS NOT";
341
+ case "before":
342
+ return "<";
343
+ case "after":
344
+ return ">";
345
+ default:
346
+ return null;
347
+ }
348
+ }
349
+
350
+ function add(filters) {
351
+ const conditions = [];
352
+ const args = [];
353
+ if (filters) {
354
+ let i = 1;
355
+ for (const k in filters) {
356
+ const { value, matchMode } = filters[k];
357
+ const indexes = isArray(value) ? value.map(() => i++) : [i++];
358
+ const cond = addOne(k, indexes, matchMode);
359
+ if (cond) {
360
+ conditions.push(cond);
361
+ if (isArray(value))
362
+ args.push(...value);
363
+ else
364
+ args.push(value);
365
+ }
366
+ }
367
+ }
368
+ return { conditions, args };
369
+ }
370
+ function addOne(key, indexes, matchMode) {
371
+ const sqlKey = `\"${key}\"`;
372
+ const comparator$1 = comparator(matchMode);
373
+ const index$1 = index(indexes, matchMode);
374
+ return comparator$1 ? `${sqlKey} ${comparator$1} ${index$1}` : "";
375
+ }
376
+
377
+ function filter(first, rows, sortField, sortOrder, filters) {
378
+ const { conditions, args } = add(filters);
379
+ const filterClause = where(conditions)
380
+ + orderBy(sortField, sortOrder)
381
+ + limit(rows, first);
382
+ return { filterClause, args };
383
+ }
384
+ function where(conditions, operator = "AND") {
385
+ if (!conditions.length)
386
+ return "";
387
+ const c = conditions.join(` ${operator} `).trim();
388
+ return ` WHERE ${c}`;
389
+ }
390
+ function orderBy(sortField, sortOrder) {
391
+ if (!sortField)
392
+ return "";
393
+ return ` ORDER BY "${sortField}" ${sortOrder}`;
394
+ }
395
+ function limit(rows, first) {
396
+ return rows ? ` LIMIT ${rows} OFFSET ${first}` : "";
397
+ }
398
+
399
+ var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
400
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
401
+ return new (P || (P = Promise))(function (resolve, reject) {
402
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
403
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
404
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
405
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
406
+ });
407
+ };
408
+ class SQLEntity extends Entity {
409
+ constructor(name, properties) {
410
+ super(name, properties);
411
+ this.sel = new Select();
412
+ this.ins = new Insert();
413
+ this.upd = new Update();
414
+ this.query = {
415
+ select: (paginate) => {
416
+ return this.sel.query(this.table, paginate);
417
+ },
418
+ update: (rows, consumerId, consumerName) => {
419
+ return this.upd.query(this.table, rows, consumerId, consumerName);
420
+ },
421
+ insert: (rows, consumerId, consumerName, rtn = "") => {
422
+ return this.ins.query(this.table, rows, consumerId, consumerName, rtn);
423
+ },
424
+ delete: () => {
425
+ return query(this.table);
426
+ },
427
+ return: (prop) => {
428
+ return this.ins.rtn(prop);
429
+ }
430
+ };
431
+ this._table = name;
432
+ for (const p of properties) {
433
+ this.mapProps(p.methods, p.key);
434
+ }
435
+ }
436
+ get table() {
437
+ return this._table;
438
+ }
439
+ set table(table) {
440
+ if (!isString(table, "!0"))
441
+ throw new Error('table must be a string of length > 0');
442
+ this._table = table;
443
+ }
444
+ get(req, res, next) {
445
+ var _a;
446
+ const l = res.locals;
447
+ const b = req.body;
448
+ const first = (_a = b === null || b === void 0 ? void 0 : b.first) !== null && _a !== void 0 ? _a : 0;
449
+ const rows = b.rows || null;
450
+ const sortField = b.sortField || null;
451
+ const sortOrder = b.sortOrder === -1 || b.sortOrder === "DESC" ? "DESC" : "ASC";
452
+ const filters = this.cleanFilters(b.filters) || null;
453
+ const pagination = b.pagination || false;
454
+ const dbClient = l.dbClient || null;
455
+ log.debug(`get(first='${first}', rows='${rows}',
456
+ sortOrder='${sortOrder}', sortField='${sortField}',
457
+ pagination=${pagination}, filters=${JSON.stringify(filters)}`);
458
+ const { filterClause, args } = filter(first, rows, sortField, sortOrder, filters);
459
+ const q = this.sel.query(this._table, pagination) + filterClause;
460
+ this.sel.execute(q, args, dbClient)
461
+ .then((r) => {
462
+ l.rows = r.rows;
463
+ l.total = r.total;
464
+ next();
465
+ })
466
+ .catch((err) => next(err));
467
+ }
468
+ add(req, res, next) {
469
+ return __awaiter(this, void 0, void 0, function* () {
470
+ const l = res.locals;
471
+ const rows = req.body.rows;
472
+ const dbClient = l.dbClient || null;
473
+ const cId = l.consumerId;
474
+ const cName = l.consumerName;
475
+ log.debug(`addMany(rows=${rows.length}, consumerId=${cId})`);
476
+ const rtn = this.ins.rtn("id");
477
+ const chunks = chunk(rows);
478
+ for (const c of chunks) {
479
+ const { query, args } = this.ins.query(this._table, c, cId, cName, rtn);
480
+ let db;
481
+ try {
482
+ db = yield execute$1(query, args, dbClient);
483
+ }
484
+ catch (err) {
485
+ return next(err);
486
+ }
487
+ const r = db.rows;
488
+ for (let i = 0; i < c.length; i++) {
489
+ c[i].id = r[i].id;
490
+ }
491
+ }
492
+ l.rows = flatten(chunks);
493
+ next();
494
+ });
495
+ }
496
+ update(req, res, next) {
497
+ return __awaiter(this, void 0, void 0, function* () {
498
+ const l = res.locals;
499
+ const rows = req.body.rows;
500
+ const dbClient = l.dbClient || null;
501
+ const cId = l.consumerId;
502
+ const cName = l.consumerName;
503
+ log.debug(`update(rows=${rows.length}, consumerId=${cId})`);
504
+ const chunks = chunk(rows);
505
+ for (const c of chunks) {
506
+ const { query, args } = this.upd.query(this._table, c, cId, cName);
507
+ try {
508
+ yield execute$1(query, args, dbClient);
509
+ }
510
+ catch (err) {
511
+ return next(err);
512
+ }
513
+ }
514
+ next();
515
+ });
516
+ }
517
+ archive(req, res, next) {
518
+ return __awaiter(this, void 0, void 0, function* () {
519
+ const l = res.locals;
520
+ let rows = req.body.rows;
521
+ const dbClient = l.dbClient || null;
522
+ const cId = l.consumerId;
523
+ const cName = l.consumerName;
524
+ log.debug(`archive(rows=${rows.length}, consumerId=${cId})`);
525
+ rows = rows.map((id) => (Object.assign(Object.assign({}, id), { archived: true })));
526
+ const chunks = chunk(rows);
527
+ for (const c of chunks) {
528
+ const { query, args } = this.upd.query(this._table, c, cId, cName);
529
+ try {
530
+ yield execute$1(query, args, dbClient);
531
+ }
532
+ catch (err) {
533
+ return next(err);
534
+ }
535
+ }
536
+ next();
537
+ });
538
+ }
539
+ delete(req, res, next) {
540
+ const date = req.body.date;
541
+ const dbClient = res.locals.dbClient || null;
542
+ log.debug(`delete archived`);
543
+ const q = query(this._table);
544
+ execute(date, q, dbClient)
545
+ .then(() => next())
546
+ .catch((err) => next(err));
547
+ }
548
+ cleanFilters(filters) {
549
+ for (const k in filters) {
550
+ if (filters.hasOwnProperty(k)) {
551
+ const prop = this.getProp(k);
552
+ if (!prop) {
553
+ log.warn(`Filters: skipping unknown property: ${k}`);
554
+ delete filters[k];
555
+ continue;
556
+ }
557
+ const type$1 = type(prop.type);
558
+ const { matchMode: matchMode$1 } = filters[k];
559
+ if (!matchMode$1 || !matchMode(type$1, matchMode$1)) {
560
+ log.warn(`Filters: skipping invalid match mode: "${matchMode$1}" for type: "${type$1}" at property: "${k}"`);
561
+ delete filters[k];
562
+ continue;
563
+ }
564
+ }
565
+ }
566
+ return filters;
567
+ }
568
+ mapProps(methods, key) {
569
+ for (const m of methods) {
570
+ switch (m) {
571
+ case "GET":
572
+ this.sel.addProp(key);
573
+ break;
574
+ case "PATCH":
575
+ this.upd.addProp(key);
576
+ break;
577
+ case "PUT":
578
+ this.upd.addProp(key);
579
+ break;
580
+ case "POST":
581
+ this.ins.addProp(key);
582
+ break;
583
+ }
584
+ }
585
+ }
586
+ }
587
+
588
+ export { SQLEntity, filter };
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@dwtechs/antity-pgsql",
3
+ "version": "0.1.0",
4
+ "description": "Open source library for easy entity management",
5
+ "keywords": [
6
+ "entities"
7
+ ],
8
+ "homepage": "https://github.com/DWTechs/Antity.js",
9
+ "main": "dist/antity",
10
+ "types": "dist/antity",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/DWTechs/Antity.js"
14
+ },
15
+ "bugs": {
16
+ "url": "https://github.com/DWTechs/Antity.js/issues",
17
+ "email": ""
18
+ },
19
+ "license": "MIT",
20
+ "author": {
21
+ "name": "Ludovic Cluber",
22
+ "email": "http://www.lcluber.com/contact",
23
+ "url": "http://www.lcluber.com"
24
+ },
25
+ "contributors": [],
26
+ "scripts": {
27
+ "start": "",
28
+ "prebuild": "npm install",
29
+ "build": "node ./scripts/clear && tsc && npm run rollup && node ./scripts/copy && npm run test",
30
+ "rollup:mjs": "rollup --config rollup.config.mjs",
31
+ "rollup:cjs": "rollup --config rollup.config.cjs.mjs",
32
+ "rollup": "npm run rollup:mjs",
33
+ "test": "jest --coverage"
34
+ },
35
+ "files": [
36
+ "dist/"
37
+ ],
38
+ "dependencies": {
39
+ "@dwtechs/checkard": "3.2.3",
40
+ "@dwtechs/winstan": "0.4.0",
41
+ "@dwtechs/antity": "0.9.0",
42
+ "@dwtechs/sparray": "0.2.0",
43
+ "pg": "8.13.1"
44
+ },
45
+ "devDependencies": {
46
+ "@babel/core": "7.26.0",
47
+ "@babel/preset-env": "7.26.0",
48
+ "@rollup/plugin-commonjs": "28.0.1",
49
+ "@rollup/plugin-node-resolve": "15.3.0",
50
+ "@types/jest": "29.5.14",
51
+ "@types/node": "22.10.1",
52
+ "@types/pg-pool": "2.0.6",
53
+ "@types/express": "5.0.0",
54
+ "babel-jest": "29.7.0",
55
+ "core-js": "3.33.0",
56
+ "jest": "29.7.0",
57
+ "rollup": "4.24.0",
58
+ "typescript": "5.6.3"
59
+ }
60
+ }