anote-server-libs 0.0.5 → 0.1.2

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.
@@ -0,0 +1,238 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ModelDao = exports.promiseAllStepN = void 0;
4
+ function promiseAllStepN(n, list) {
5
+ if (!list || !list.length)
6
+ return Promise.resolve([]);
7
+ const tail = list.splice(n);
8
+ const head = list;
9
+ const resolved = [];
10
+ let processed = 0;
11
+ return new Promise(resolve => {
12
+ head.forEach(x => {
13
+ const res = x();
14
+ resolved.push(res);
15
+ res.then((y) => {
16
+ runNext();
17
+ return y;
18
+ });
19
+ });
20
+ function runNext() {
21
+ if (processed === tail.length) {
22
+ resolve(Promise.all(resolved));
23
+ }
24
+ else {
25
+ resolved.push(tail[processed]().then((x) => {
26
+ runNext();
27
+ return x;
28
+ }));
29
+ processed++;
30
+ }
31
+ }
32
+ });
33
+ }
34
+ exports.promiseAllStepN = promiseAllStepN;
35
+ Promise.allConcurrent = (n) => (list) => promiseAllStepN(n, list);
36
+ class Dao {
37
+ constructor(pool, poolMssql, logger, table, nFields, updateDefinition) {
38
+ this.pool = pool;
39
+ this.poolMssql = poolMssql;
40
+ this.logger = logger;
41
+ this.table = table;
42
+ this.nFields = nFields;
43
+ this.updateDefinition = updateDefinition;
44
+ }
45
+ groupResultSet(q, key) {
46
+ const storage = {};
47
+ for (let i = 0; i < q.length; i++) {
48
+ storage[q[i][key]] = storage[q[i][key]] || [];
49
+ storage[q[i][key]].push(q[i]);
50
+ }
51
+ return Object.getOwnPropertyNames(storage).map(k => storage[k]);
52
+ }
53
+ groupResultSetBy(q, key) {
54
+ const storage = {};
55
+ for (let i = 0; i < q.length; i++) {
56
+ storage[key(q[i])] = storage[key(q[i])] || [];
57
+ storage[key(q[i])].push(q[i]);
58
+ }
59
+ return Object.getOwnPropertyNames(storage).map(k => storage[k]);
60
+ }
61
+ update(instance, client) {
62
+ if (instance.archivedOn)
63
+ return Promise.reject('Record archived!');
64
+ instance.updatedOn = new Date();
65
+ if (this.pool) {
66
+ const props = this.serialize(instance);
67
+ props.push(instance.id);
68
+ return client.query('UPDATE ' + this.table + ' SET ' + this.updateDefinition + ' WHERE id=$' + (this.nFields + 1), props).then(() => instance.id);
69
+ }
70
+ else {
71
+ const request = client.request();
72
+ this.serialize(instance, request);
73
+ request.input(String(this.nFields + 1), instance.id);
74
+ return request.query('UPDATE ' + this.table + ' SET ' + this.updateDefinition + ' WHERE id=@' + (this.nFields + 1)).then(() => instance.id);
75
+ }
76
+ }
77
+ replace(instance, client) {
78
+ if (instance.archivedOn)
79
+ return Promise.reject('Record archived!');
80
+ return this.create({ ...instance, replaced_id: instance.id }, client).then(id => {
81
+ return this.archive(instance.id, id, client).then(() => id);
82
+ });
83
+ }
84
+ create(instance, client) {
85
+ instance.createdOn = instance.updatedOn = new Date();
86
+ if (this.pool) {
87
+ const props = this.serialize(instance);
88
+ return (client || this.pool).query('INSERT INTO ' + this.table + '(' + this.updateDefinition.replace(/=\$\d+/g, '') + ')'
89
+ + ' VALUES(' + new Array(this.nFields).fill(undefined).map((_, i) => '$' + (i + 1)).join(',') + ') RETURNING id', props).then(q => {
90
+ const idNum = parseInt(q.rows[0].id, 10);
91
+ if (String(idNum) !== q.rows[0].id)
92
+ return q.rows[0].id;
93
+ return idNum;
94
+ });
95
+ }
96
+ else {
97
+ const request = (client || this.poolMssql).request();
98
+ this.serialize(instance, request);
99
+ return request.query('INSERT INTO ' + this.table + '(' + this.updateDefinition.replace(/=@\d+/g, '').replace(/=[^)]+\)/g, '') + ')'
100
+ + ' VALUES(' + new Array(this.nFields).fill(undefined).map((_, i) => '@' + (i + 1)).join(',') + '); SELECT SCOPE_IDENTITY() AS id').then(q => {
101
+ return q.recordsets[0][0].id || instance.id;
102
+ });
103
+ }
104
+ }
105
+ createSeveral(instances, client) {
106
+ if (!instances.length)
107
+ return Promise.resolve([]);
108
+ const now = new Date();
109
+ instances.forEach(instance => instance.updatedOn = now);
110
+ const props = [].concat.apply([], instances.map(instance => this.serialize(instance)));
111
+ return (client || this.pool).query('INSERT INTO ' + this.table + '(' + this.updateDefinition.replace(/=\$\d+/g, '') + ')'
112
+ + ' VALUES' + instances.map((_, j) => ('(' + new Array(this.nFields).fill(undefined).map((__, i) => '$' + (j * this.nFields + i + 1)).join(', ') + ')')).join(',') + ' RETURNING id', props).then(q => q.rows.map(r => {
113
+ const idNum = parseInt(r.id, 10);
114
+ if (String(idNum) !== q.rows[0].id)
115
+ return r.id;
116
+ return idNum;
117
+ }));
118
+ }
119
+ }
120
+ class ModelDao extends Dao {
121
+ get(id, client, lock = true) {
122
+ if (this.pool) {
123
+ return (client || this.pool).query('SELECT * FROM ' + this.table + ' WHERE id=$1' + ((client && lock) ? ' FOR UPDATE' : ''), [id]).then(q => this.buildObject(q.rows[0]));
124
+ }
125
+ else {
126
+ const request = (client || this.poolMssql).request();
127
+ request.input('1', id);
128
+ return request.query('SELECT * FROM ' + this.table + ((client && lock) ? ' WITH (ROWLOCK)' : '') + ' WHERE id=@1').then(q => this.buildObject(q.recordsets[0][0]));
129
+ }
130
+ }
131
+ count(where, inputs = [], client) {
132
+ if (this.pool) {
133
+ return (client || this.pool).query('SELECT count(*) AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : ''), inputs).then(q => parseInt(q.rows[0].cnt, 10));
134
+ }
135
+ else {
136
+ const request = (client || this.poolMssql).request();
137
+ if (where)
138
+ where.match(/(@\d+)/g).forEach((match, i) => request.input(match.substr(1), inputs[i]));
139
+ return request.query('SELECT count(*) AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : '')).then(q => q.recordsets[0][0].cnt);
140
+ }
141
+ }
142
+ getList(ids, client, lock = true) {
143
+ if (this.pool) {
144
+ return (client || this.pool).query('SELECT * FROM ' + this.table + ' WHERE id=ANY($1)' + ((client && lock) ? ' FOR UPDATE' : ''), [ids])
145
+ .then(q => q.rows.map(r => this.buildObject(r)));
146
+ }
147
+ else {
148
+ const request = (client || this.poolMssql).request();
149
+ return request.query('SELECT * FROM ' + this.table + ((client && lock) ? ' WITH (ROWLOCK)' : '') + ' WHERE id IN ('
150
+ + (ids.length > 0 ? (typeof ids[0] === 'string' ? '\'' + ids.join('\',\'') + '\'' : ids.join(',')) : '') + ')')
151
+ .then(q => q.recordsets[0].map(r => this.buildObject(r)));
152
+ }
153
+ }
154
+ getAllBy(order, offset, limit, where, inputs = [], client, lock = true) {
155
+ if (this.pool) {
156
+ return (client || this.pool).query('SELECT * FROM ' + this.table + (where ? (' WHERE ' + where) : '') + (order ? (' ORDER BY ' + order) : '')
157
+ + (offset ? (' OFFSET ' + offset) : '') + (limit !== undefined ? (' LIMIT ' + limit) : '') + ((client && lock) ? ' FOR UPDATE' : ''), inputs)
158
+ .then(q => q.rows.map(r => this.buildObject(r)));
159
+ }
160
+ else {
161
+ const request = (client || this.poolMssql).request();
162
+ if (where)
163
+ where.match(/(@\d+)/g).forEach((match, i) => request.input(match.substr(1), inputs[i]));
164
+ return request.query('SELECT * FROM ' + this.table + ((client && lock) ? ' WITH (ROWLOCK)' : '') + (where ? (' WHERE ' + where) : '')
165
+ + (order ? (' ORDER BY ' + order) : '') + (offset !== undefined ? (' OFFSET ' + offset + ' ROWS') : '') + (limit !== undefined ? (' FETCH NEXT ' + limit + ' ROWS ONLY') : ''))
166
+ .then(q => q.recordsets[0].map(r => this.buildObject(r)));
167
+ }
168
+ }
169
+ getViewCountBy(order, offset, limit, where, inputs = [], client, lock = true) {
170
+ if (this.pool) {
171
+ return (client || this.pool).query('SELECT *, COUNT(*) OVER() AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : '') + (order ? (' ORDER BY ' + order) : '')
172
+ + (offset ? (' OFFSET ' + offset) : '') + (limit !== undefined ? (' LIMIT ' + limit) : '') + ((client && lock) ? ' FOR UPDATE' : ''), inputs)
173
+ .then(q => ({
174
+ views: q.rows.map(r => this.buildObject(r)),
175
+ count: q.rows.length ? parseInt(q.rows[0].cnt, 10) : 0
176
+ }));
177
+ }
178
+ else {
179
+ const request = (client || this.poolMssql).request();
180
+ if (where)
181
+ where.match(/(@\d+)/g).forEach((match, i) => request.input(match.substr(1), inputs[i]));
182
+ return Promise.allConcurrent(1)([
183
+ () => request.query('SELECT * FROM ' + this.table + ((client && lock) ? ' WITH (ROWLOCK)' : '') + (where ? (' WHERE ' + where) : '')
184
+ + (order ? (' ORDER BY ' + order) : '') + (offset !== undefined ? (' OFFSET ' + offset + ' ROWS') : '') + (limit !== undefined ? (' FETCH NEXT ' + limit + ' ROWS ONLY') : '')),
185
+ () => request.query('SELECT COUNT(DISTINCT id) AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : ''))
186
+ ]).then(([q1, q2]) => ({
187
+ views: q1.recordsets[0].map((r) => this.buildObject(r)),
188
+ count: q2.recordsets.length ? q2.recordsets[0].reduce((p, n) => p + n.cnt, 0) : 0
189
+ }));
190
+ }
191
+ }
192
+ getRowsViewCountBy(rows, order, offset, limit, where, inputs = [], client, lock = true) {
193
+ if (this.pool) {
194
+ return (client || this.pool).query('SELECT ' + rows.map(r => '"' + r + '"').join(',') + ', COUNT(*) OVER() AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : '') + (order ? (' ORDER BY ' + order) : '')
195
+ + (offset ? (' OFFSET ' + offset) : '') + (limit !== undefined ? (' LIMIT ' + limit) : '') + ((client && lock) ? ' FOR UPDATE' : ''), inputs)
196
+ .then(q => ({
197
+ views: q.rows.map(r => this.buildObject(r)),
198
+ count: q.rows.length ? parseInt(q.rows[0].cnt, 10) : 0
199
+ }));
200
+ }
201
+ else {
202
+ const request = (client || this.poolMssql).request();
203
+ if (where)
204
+ where.match(/(@\d+)/g).forEach((match, i) => request.input(match.substr(1), inputs[i]));
205
+ return Promise.allConcurrent(1)([
206
+ () => request.query('SELECT ' + rows.map(r => '"' + r + '"').join(',') + ' FROM ' + this.table
207
+ + ((client && lock) ? ' WITH (ROWLOCK)' : '') + (where ? (' WHERE ' + where) : '') + (order ? (' ORDER BY ' + order) : '')
208
+ + (offset !== undefined ? (' OFFSET ' + offset + ' ROWS') : '') + (limit !== undefined ? (' FETCH NEXT ' + limit + ' ROWS ONLY') : '')),
209
+ () => request.query('SELECT COUNT(DISTINCT id) AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : ''))
210
+ ]).then(([q1, q2]) => ({
211
+ views: q1.recordsets[0].map((r) => this.buildObject(r)),
212
+ count: q2.recordsets.length ? q2.recordsets[0].reduce((p, n) => p + n.cnt, 0) : 0
213
+ }));
214
+ }
215
+ }
216
+ archive(id, replacedById, client) {
217
+ if (this.pool) {
218
+ return client.query('UPDATE ' + this.table + ' SET "archivedOn"=now(),replaced_by_id=$1 WHERE id=$2', [replacedById, id]);
219
+ }
220
+ else {
221
+ const request = client.request();
222
+ request.input('1', replacedById);
223
+ request.input('2', id);
224
+ return request.query('UPDATE ' + this.table + ' SET "archivedOn"=now(),replaced_by_id=@1 WHERE id=@2');
225
+ }
226
+ }
227
+ delete(id, client) {
228
+ if (this.pool) {
229
+ return client.query('DELETE FROM ' + this.table + ' WHERE id=$1', [id]);
230
+ }
231
+ else {
232
+ const request = client.request();
233
+ request.input('1', id);
234
+ return request.query('DELETE FROM ' + this.table + ' WHERE id=@1');
235
+ }
236
+ }
237
+ }
238
+ exports.ModelDao = ModelDao;
@@ -1,11 +1,54 @@
1
+
2
+ import { ConnectionPool, Request, Transaction } from 'mssql';
1
3
  import {ClientBase, Pool} from 'pg';
2
4
  import {Logger} from 'winston';
3
5
 
6
+ declare const Promise: PromiseConstructor & {
7
+ allConcurrent: <T>(n: number) => ((promiseProxies: (() => Promise<T>)[]) => Promise<T[]>);
8
+ };
9
+
10
+ export function promiseAllStepN<T>(n: number, list: (() => Promise<T>)[]): Promise<T[]> {
11
+ if(!list || !list.length) return Promise.resolve([]);
12
+ const tail = list.splice(n);
13
+ const head = list;
14
+ const resolved: any[] = [];
15
+ let processed = 0;
16
+ return new Promise(resolve => {
17
+ head.forEach(x => {
18
+ const res = x();
19
+ resolved.push(res);
20
+ res.then((y: any) => {
21
+ runNext();
22
+ return y;
23
+ });
24
+ });
25
+ function runNext() {
26
+ if(processed === tail.length) {
27
+ resolve(Promise.all(resolved));
28
+ } else {
29
+ resolved.push(tail[processed]().then((x: any) => {
30
+ runNext();
31
+ return x;
32
+ }));
33
+ processed++;
34
+ }
35
+ }
36
+ });
37
+ }
38
+ Promise.allConcurrent = <T>(n: number) => (list: (() => Promise<T>)[]) => promiseAllStepN(n, list);
39
+
4
40
  export interface Model<T> {
5
41
  id?: T;
6
42
  updatedOn?: Date;
7
43
  }
8
44
 
45
+ export interface ArchivedModel<T> extends Model<T> {
46
+ createdOn?: Date;
47
+ archivedOn?: Date;
48
+ replaced_id?: T;
49
+ replaced_by_id?: T;
50
+ }
51
+
9
52
  export interface ModelRepr {
10
53
  table: string;
11
54
  }
@@ -16,11 +59,20 @@ export interface ViewCount<T> {
16
59
  }
17
60
 
18
61
  abstract class Dao<R, T extends Model<R>> implements ModelRepr {
19
- constructor(protected pool: Pool, protected logger: Logger, public table: string, protected nFields: number,
62
+ constructor(protected pool: Pool, protected poolMssql: ConnectionPool, protected logger: Logger, public table: string, protected nFields: number,
20
63
  protected updateDefinition: string) {
21
64
  }
22
65
 
23
- groupResultSet(q: any[], key: (qs: any) => string): any[][] {
66
+ groupResultSet(q: any[], key: string): any[][] {
67
+ const storage = {};
68
+ for(let i = 0; i < q.length; i++) {
69
+ storage[q[i][key]] = storage[q[i][key]] || [];
70
+ storage[q[i][key]].push(q[i]);
71
+ }
72
+ return Object.getOwnPropertyNames(storage).map(k => storage[k]);
73
+ }
74
+
75
+ groupResultSetBy(q: any[], key: (qs: any) => string): any[][] {
24
76
  const storage = {};
25
77
  for(let i = 0; i < q.length; i++) {
26
78
  storage[key(q[i])] = storage[key(q[i])] || [];
@@ -29,23 +81,47 @@ abstract class Dao<R, T extends Model<R>> implements ModelRepr {
29
81
  return Object.getOwnPropertyNames(storage).map(k => storage[k]);
30
82
  }
31
83
 
32
- update(instance: T, client: ClientBase): Promise<R> {
84
+ update(instance: T, client: ClientBase | ConnectionPool | Transaction): Promise<R> {
85
+ if((<any>instance).archivedOn) return Promise.reject('Record archived!');
33
86
  instance.updatedOn = new Date();
34
- const props = this.serialize(instance);
35
- props.push(instance.id);
36
- return client.query('UPDATE ' + this.table + ' SET ' + this.updateDefinition + ' WHERE id=$' + (this.nFields + 1), props).then(() => instance.id);
87
+ if(this.pool) {
88
+ const props = this.serialize(instance);
89
+ props.push(instance.id);
90
+ return (<ClientBase>client).query('UPDATE ' + this.table + ' SET ' + this.updateDefinition + ' WHERE id=$' + (this.nFields + 1), props).then(() => instance.id);
91
+ } else {
92
+ const request = (<ConnectionPool | Transaction>client).request();
93
+ this.serialize(instance, request);
94
+ request.input(String(this.nFields + 1), instance.id);
95
+ return request.query('UPDATE ' + this.table + ' SET ' + this.updateDefinition + ' WHERE id=@' + (this.nFields + 1)).then(() => instance.id);
96
+ }
37
97
  }
38
98
 
39
- create(instance: T, client?: ClientBase): Promise<R> {
40
- instance.updatedOn = new Date();
41
- const props = this.serialize(instance);
42
- return (client || this.pool).query('INSERT INTO ' + this.table + '(' + this.updateDefinition.replace(/=\$\d+/g, '') + ')'
43
- + ' VALUES(' + new Array(this.nFields).fill(undefined).map((_, i: number) => '$' + (i + 1)).join(',') + ') RETURNING id',
44
- props).then(q => {
45
- const idNum = parseInt(q.rows[0].id, 10);
46
- if(String(idNum) !== q.rows[0].id) return q.rows[0].id;
47
- return idNum;
48
- });
99
+ replace(this: ModelDao<R, T>, instance: T, client: ClientBase | Transaction): Promise<R> {
100
+ if((<any>instance).archivedOn) return Promise.reject('Record archived!');
101
+ return this.create({...instance, replaced_id: instance.id}, client).then(id => {
102
+ return this.archive(instance.id, id, client).then(() => id);
103
+ });
104
+ }
105
+
106
+ create(instance: T, client?: ClientBase | Transaction): Promise<R> {
107
+ (<any>instance).createdOn = instance.updatedOn = new Date();
108
+ if(this.pool) {
109
+ const props = this.serialize(instance);
110
+ return (<ClientBase | Pool>(client || this.pool)).query('INSERT INTO ' + this.table + '(' + this.updateDefinition.replace(/=\$\d+/g, '') + ')'
111
+ + ' VALUES(' + new Array(this.nFields).fill(undefined).map((_, i: number) => '$' + (i + 1)).join(',') + ') RETURNING id',
112
+ props).then(q => {
113
+ const idNum = parseInt(q.rows[0].id, 10);
114
+ if(String(idNum) !== q.rows[0].id) return q.rows[0].id;
115
+ return idNum;
116
+ });
117
+ } else {
118
+ const request = (<Transaction | ConnectionPool>(client || this.poolMssql)).request();
119
+ this.serialize(instance, request);
120
+ return request.query('INSERT INTO ' + this.table + '(' + this.updateDefinition.replace(/=@\d+/g, '').replace(/=[^)]+\)/g, '') + ')'
121
+ + ' VALUES(' + new Array(this.nFields).fill(undefined).map((_, i: number) => '@' + (i + 1)).join(',') + '); SELECT SCOPE_IDENTITY() AS id').then(q => {
122
+ return q.recordsets[0][0].id || instance.id;
123
+ });
124
+ }
49
125
  }
50
126
 
51
127
  createSeveral(instances: T[], client?: ClientBase): Promise<R[]> {
@@ -65,39 +141,119 @@ abstract class Dao<R, T extends Model<R>> implements ModelRepr {
65
141
 
66
142
  protected abstract buildObject(q: any): T;
67
143
 
68
- protected abstract serialize(instance: T): any[];
144
+ protected abstract serialize(instance: T, request?: Request): any[];
69
145
  }
70
146
 
71
147
  export abstract class ModelDao<R, T extends Model<R>> extends Dao<R, T> {
72
- get(id: R, client?: ClientBase, lock = true): Promise<T> {
73
- return (client || this.pool).query('SELECT * FROM ' + this.table + ' WHERE id=$1' + ((client && lock) ? ' FOR UPDATE' : ''), [id]).then(q => this.buildObject(q.rows[0]));
148
+ get(id: R, client?: ClientBase | Transaction, lock = true): Promise<T> {
149
+ if(this.pool) {
150
+ return (<ClientBase | Pool>(client || this.pool)).query('SELECT * FROM ' + this.table + ' WHERE id=$1' + ((client && lock) ? ' FOR UPDATE' : ''), [id]).then(q => this.buildObject(q.rows[0]));
151
+ } else {
152
+ const request = (<Transaction | ConnectionPool>(client || this.poolMssql)).request();
153
+ request.input('1', id);
154
+ return request.query('SELECT * FROM ' + this.table + ((client && lock) ? ' WITH (ROWLOCK)' : '') + ' WHERE id=@1').then(q => this.buildObject(q.recordsets[0][0]));
155
+ }
74
156
  }
75
157
 
76
- count(where?: string, inputs: any[] = [], client?: ClientBase): Promise<number> {
77
- return (client || this.pool).query('SELECT count(*) AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : ''), inputs).then(q => parseInt(q.rows[0].cnt, 10));
158
+ count(where?: string, inputs: any[] = [], client?: ClientBase | Transaction): Promise<number> {
159
+ if(this.pool) {
160
+ return (<ClientBase | Pool>(client || this.pool)).query('SELECT count(*) AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : ''), inputs).then(q => parseInt(q.rows[0].cnt, 10));
161
+ } else {
162
+ const request = (<Transaction | ConnectionPool>(client || this.poolMssql)).request();
163
+ if(where) where.match(/(@\d+)/g).forEach((match, i) => request.input(match.substr(1), inputs[i]));
164
+ return request.query('SELECT count(*) AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : '')).then(q => q.recordsets[0][0].cnt);
165
+ }
78
166
  }
79
167
 
80
- getList(ids: R[], client?: ClientBase, lock = true): Promise<T[]> {
81
- return (client || this.pool).query('SELECT * FROM ' + this.table + ' WHERE id=ANY($1)' + ((client && lock) ? ' FOR UPDATE' : ''), [ids])
82
- .then(q => q.rows.map(r => this.buildObject(r)));
168
+ getList(ids: R[], client?: ClientBase | Transaction, lock = true): Promise<T[]> {
169
+ if(this.pool) {
170
+ return (<ClientBase | Pool>(client || this.pool)).query('SELECT * FROM ' + this.table + ' WHERE id=ANY($1)' + ((client && lock) ? ' FOR UPDATE' : ''), [ids])
171
+ .then(q => q.rows.map(r => this.buildObject(r)));
172
+ } else {
173
+ const request = (<Transaction | ConnectionPool>(client || this.poolMssql)).request();
174
+ return request.query('SELECT * FROM ' + this.table + ((client && lock) ? ' WITH (ROWLOCK)' : '') + ' WHERE id IN ('
175
+ + (ids.length > 0 ? (typeof ids[0] === 'string' ? '\'' + ids.join('\',\'') + '\'' : ids.join(',')) : '') + ')')
176
+ .then(q => q.recordsets[0].map(r => this.buildObject(r)));
177
+ }
83
178
  }
84
179
 
85
- getAllBy(order?: string, offset?: number, limit?: number, where?: string, inputs: any[] = [], client?: ClientBase, lock = true): Promise<T[]> {
86
- return (client || this.pool).query('SELECT * FROM ' + this.table + (where ? (' WHERE ' + where) : '') + (order ? (' ORDER BY ' + order) : '')
87
- + (offset ? (' OFFSET ' + offset) : '') + (limit !== undefined ? (' LIMIT ' + limit) : '') + ((client && lock) ? ' FOR UPDATE' : ''), inputs)
88
- .then(q => q.rows.map(r => this.buildObject(r)));
180
+ getAllBy(order?: string, offset?: number, limit?: number, where?: string, inputs: any[] = [], client?: ClientBase | Transaction, lock = true): Promise<T[]> {
181
+ if(this.pool) {
182
+ return (<ClientBase | Pool>(client || this.pool)).query('SELECT * FROM ' + this.table + (where ? (' WHERE ' + where) : '') + (order ? (' ORDER BY ' + order) : '')
183
+ + (offset ? (' OFFSET ' + offset) : '') + (limit !== undefined ? (' LIMIT ' + limit) : '') + ((client && lock) ? ' FOR UPDATE' : ''), inputs)
184
+ .then(q => q.rows.map(r => this.buildObject(r)));
185
+ } else {
186
+ const request = (<Transaction | ConnectionPool>(client || this.poolMssql)).request();
187
+ if(where) where.match(/(@\d+)/g).forEach((match, i) => request.input(match.substr(1), inputs[i]));
188
+ return request.query('SELECT * FROM ' + this.table + ((client && lock) ? ' WITH (ROWLOCK)' : '') + (where ? (' WHERE ' + where) : '')
189
+ + (order ? (' ORDER BY ' + order) : '') + (offset !== undefined ? (' OFFSET ' + offset + ' ROWS') : '') + (limit !== undefined ? (' FETCH NEXT ' + limit + ' ROWS ONLY') : ''))
190
+ .then(q => q.recordsets[0].map(r => this.buildObject(r)));
191
+ }
89
192
  }
90
193
 
91
- getViewCountBy(order?: string, offset?: number, limit?: number, where?: string, inputs: any[] = [], client?: ClientBase, lock = true): Promise<ViewCount<T>> {
92
- return (client || this.pool).query('SELECT *, COUNT(*) OVER() AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : '') + (order ? (' ORDER BY ' + order) : '')
93
- + (offset ? (' OFFSET ' + offset) : '') + (limit !== undefined ? (' LIMIT ' + limit) : '') + ((client && lock) ? ' FOR UPDATE' : ''), inputs)
94
- .then(q => ({
95
- views: q.rows.map(r => this.buildObject(r)),
96
- count: q.rows.length ? parseInt(q.rows[0].cnt, 10) : 0
194
+ getViewCountBy(order?: string, offset?: number, limit?: number, where?: string, inputs: any[] = [], client?: ClientBase | Transaction, lock = true): Promise<ViewCount<T>> {
195
+ if(this.pool) {
196
+ return (<ClientBase | Pool>(client || this.pool)).query('SELECT *, COUNT(*) OVER() AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : '') + (order ? (' ORDER BY ' + order) : '')
197
+ + (offset ? (' OFFSET ' + offset) : '') + (limit !== undefined ? (' LIMIT ' + limit) : '') + ((client && lock) ? ' FOR UPDATE' : ''), inputs)
198
+ .then(q => ({
199
+ views: q.rows.map(r => this.buildObject(r)),
200
+ count: q.rows.length ? parseInt(q.rows[0].cnt, 10) : 0
201
+ }));
202
+ } else {
203
+ const request = (<Transaction | ConnectionPool>(client || this.poolMssql)).request();
204
+ if(where) where.match(/(@\d+)/g).forEach((match, i) => request.input(match.substr(1), inputs[i]));
205
+ return Promise.allConcurrent(1)([
206
+ () => request.query('SELECT * FROM ' + this.table + ((client && lock) ? ' WITH (ROWLOCK)' : '') + (where ? (' WHERE ' + where) : '')
207
+ + (order ? (' ORDER BY ' + order) : '') + (offset !== undefined ? (' OFFSET ' + offset + ' ROWS') : '') + (limit !== undefined ? (' FETCH NEXT ' + limit + ' ROWS ONLY') : '')),
208
+ () => request.query('SELECT COUNT(DISTINCT id) AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : ''))
209
+ ]).then(([q1, q2]: [any, any]) => ({
210
+ views: q1.recordsets[0].map((r: any) => this.buildObject(r)),
211
+ count: q2.recordsets.length ? q2.recordsets[0].reduce((p: number, n: any) => p + n.cnt, 0) : 0
97
212
  }));
213
+ }
98
214
  }
99
215
 
100
- delete(id: R, client: ClientBase): Promise<any> {
101
- return client.query('DELETE FROM ' + this.table + ' WHERE id=$1', [id]);
216
+ getRowsViewCountBy(rows: string[], order?: string, offset?: number, limit?: number, where?: string, inputs: any[] = [], client?: ClientBase | Transaction, lock = true): Promise<ViewCount<any>> {
217
+ if(this.pool) {
218
+ return (<ClientBase | Pool>(client || this.pool)).query('SELECT ' + rows.map(r => '"' + r + '"').join(',') + ', COUNT(*) OVER() AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : '') + (order ? (' ORDER BY ' + order) : '')
219
+ + (offset ? (' OFFSET ' + offset) : '') + (limit !== undefined ? (' LIMIT ' + limit) : '') + ((client && lock) ? ' FOR UPDATE' : ''), inputs)
220
+ .then(q => ({
221
+ views: q.rows.map(r => this.buildObject(r)),
222
+ count: q.rows.length ? parseInt(q.rows[0].cnt, 10) : 0
223
+ }));
224
+ } else {
225
+ const request = (<Transaction | ConnectionPool>(client || this.poolMssql)).request();
226
+ if(where) where.match(/(@\d+)/g).forEach((match, i) => request.input(match.substr(1), inputs[i]));
227
+ return Promise.allConcurrent(1)([
228
+ () => request.query('SELECT ' + rows.map(r => '"' + r + '"').join(',') + ' FROM ' + this.table
229
+ + ((client && lock) ? ' WITH (ROWLOCK)' : '') + (where ? (' WHERE ' + where) : '') + (order ? (' ORDER BY ' + order) : '')
230
+ + (offset !== undefined ? (' OFFSET ' + offset + ' ROWS') : '') + (limit !== undefined ? (' FETCH NEXT ' + limit + ' ROWS ONLY') : '')),
231
+ () => request.query('SELECT COUNT(DISTINCT id) AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : ''))
232
+ ]).then(([q1, q2]: [any, any]) => ({
233
+ views: q1.recordsets[0].map((r: any) => this.buildObject(r)),
234
+ count: q2.recordsets.length ? q2.recordsets[0].reduce((p: number, n: any) => p + n.cnt, 0) : 0
235
+ }));
236
+ }
237
+ }
238
+
239
+ archive(id: R, replacedById: R, client: ClientBase | Transaction): Promise<any> {
240
+ if(this.pool) {
241
+ return (<ClientBase>client).query('UPDATE ' + this.table + ' SET "archivedOn"=now(),replaced_by_id=$1 WHERE id=$2', [replacedById, id]);
242
+ } else {
243
+ const request = (<Transaction>client).request();
244
+ request.input('1', replacedById);
245
+ request.input('2', id);
246
+ return request.query('UPDATE ' + this.table + ' SET "archivedOn"=now(),replaced_by_id=@1 WHERE id=@2');
247
+ }
248
+ }
249
+
250
+ delete(id: R, client: ClientBase | Transaction): Promise<any> {
251
+ if(this.pool) {
252
+ return (<ClientBase>client).query('DELETE FROM ' + this.table + ' WHERE id=$1', [id]);
253
+ } else {
254
+ const request = (<Transaction>client).request();
255
+ request.input('1', id);
256
+ return request.query('DELETE FROM ' + this.table + ' WHERE id=@1');
257
+ }
102
258
  }
103
259
  }
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "anote-server-libs",
3
- "version": "0.0.5",
3
+ "version": "0.1.2",
4
4
  "description": "Helpers for express-TS servers",
5
5
  "scripts": {
6
- "test": "echo \"Error: no test specified\" && exit 1"
6
+ "test": "echo \"Error: no test specified\" && exit 1",
7
+ "build": "tsc -p tsconfig.json"
7
8
  },
8
9
  "repository": {
9
10
  "type": "git",
@@ -15,19 +16,23 @@
15
16
  "author": "Mathonet Gregoire",
16
17
  "license": "MIT",
17
18
  "dependencies": {
18
- "@types/express": "4.17.13",
19
- "@types/memcached": "2.2.7",
20
- "@types/node": "14.18.2",
21
- "@types/node-forge": "1.0.0",
22
- "@types/pg": "8.6.4",
19
+ "mssql": "8.0.2",
20
+ "pg": "8.7.3"
21
+ },
22
+ "peerDependencies": {
23
23
  "express": "4.17.2",
24
24
  "jsonschema": "1.4.0",
25
25
  "memcached": "2.2.2",
26
26
  "node-forge": "1.2.1",
27
- "pg": "8.7.3",
28
27
  "winston": "3.5.1"
29
28
  },
30
29
  "devDependencies": {
30
+ "@types/express": "4.17.13",
31
+ "@types/memcached": "2.2.7",
32
+ "@types/mssql": "7.1.4",
33
+ "@types/node": "14.18.2",
34
+ "@types/node-forge": "1.0.0",
35
+ "@types/pg": "8.6.4",
31
36
  "typescript": "4.5.5"
32
37
  }
33
38
  }
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WithBody = void 0;
4
+ const jsonschema_1 = require("jsonschema");
5
+ function WithBody(schema) {
6
+ const validator = new jsonschema_1.Validator();
7
+ validator.attributes.maxDigits = (instance, sc) => {
8
+ if (typeof instance !== 'number')
9
+ return undefined;
10
+ if (typeof sc.maxDigits !== 'number' || Math.floor(sc.maxDigits) !== sc.maxDigits) {
11
+ throw new jsonschema_1.SchemaError('"maxDigits" expects an integer', sc);
12
+ }
13
+ if (Math.round(instance * 10 ** sc.maxDigits) / (10 ** sc.maxDigits) !== instance) {
14
+ return 'has more precision than ' + sc.maxDigits + ' digits';
15
+ }
16
+ return undefined;
17
+ };
18
+ return function (_, __, descriptor) {
19
+ if (typeof descriptor.value === 'function') {
20
+ const previousMethod = descriptor.value;
21
+ descriptor.value = function (req, res) {
22
+ const keys = Object.getOwnPropertyNames(schema.properties);
23
+ keys.forEach(key => {
24
+ if (typeof schema.properties[key] === 'string') {
25
+ schema.properties[key] = this.config.app.endpointsSchemas[schema.properties[key]];
26
+ }
27
+ });
28
+ if (req.method.toUpperCase() !== 'POST' && req.method.toUpperCase() !== 'PUT') {
29
+ return previousMethod.call(this, req, res);
30
+ }
31
+ if (!req.body) {
32
+ res.status(400).json({
33
+ error: {
34
+ errorKey: 'client.body.missing',
35
+ additionalInfo: 'client.extended.badPayload'
36
+ }
37
+ });
38
+ }
39
+ else {
40
+ const result = validator.validate(req.body, schema);
41
+ if (result.valid) {
42
+ return previousMethod.call(this, req, res);
43
+ }
44
+ else {
45
+ res.status(400).json({
46
+ error: {
47
+ errorKey: 'client.body.missing',
48
+ additionalInfo: 'client.extended.badPayload',
49
+ detailedInfo: result.errors
50
+ }
51
+ });
52
+ }
53
+ }
54
+ };
55
+ return descriptor;
56
+ }
57
+ return undefined;
58
+ };
59
+ }
60
+ exports.WithBody = WithBody;