anote-server-libs 0.0.6 → 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/models/ApiCall.js +17 -4
- package/models/ApiCall.ts +16 -4
- package/models/Migration.js +16 -4
- package/models/Migration.ts +15 -4
- package/models/repository/BaseModelRepository.js +36 -19
- package/models/repository/BaseModelRepository.ts +37 -20
- package/models/repository/ModelDao.js +186 -28
- package/models/repository/ModelDao.ts +192 -36
- package/package.json +5 -2
- package/services/WithTransaction.js +22 -29
- package/services/WithTransaction.ts +17 -29
- package/services/utils.js +20 -2
- package/services/utils.ts +18 -1
package/models/ApiCall.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ApiCallRepository = void 0;
|
|
4
|
+
const mssql_1 = require("mssql");
|
|
4
5
|
const ModelDao_1 = require("./repository/ModelDao");
|
|
5
6
|
class ApiCallRepository extends ModelDao_1.ModelDao {
|
|
6
|
-
constructor(pool, logger) {
|
|
7
|
-
super(pool, logger, 'api_call', 5, 'id=$1,"updatedOn"=$2,"responseCode"=$3,"responseJson"=$4,"expiresAt"=$5'
|
|
7
|
+
constructor(pool, poolMssql, logger) {
|
|
8
|
+
super(pool, poolMssql, logger, 'api_call', 5, pool ? 'id=$1,"updatedOn"=$2,"responseCode"=$3,"responseJson"=$4,"expiresAt"=$5' :
|
|
9
|
+
'id=@1,"updatedOn"=@2,"responseCode"=@3,"responseJson"=@4,"expiresAt"=@5');
|
|
8
10
|
this.pool = pool;
|
|
11
|
+
this.poolMssql = poolMssql;
|
|
9
12
|
this.logger = logger;
|
|
10
13
|
}
|
|
11
14
|
buildObject(q) {
|
|
@@ -17,8 +20,18 @@ class ApiCallRepository extends ModelDao_1.ModelDao {
|
|
|
17
20
|
q.expiresAt = q.expiresAt && new Date(q.expiresAt);
|
|
18
21
|
return q;
|
|
19
22
|
}
|
|
20
|
-
serialize(instance) {
|
|
21
|
-
|
|
23
|
+
serialize(instance, request) {
|
|
24
|
+
if (request) {
|
|
25
|
+
request.input('1', instance.id);
|
|
26
|
+
request.input('2', mssql_1.DateTimeOffset, instance.updatedOn);
|
|
27
|
+
request.input('3', instance.responseCode);
|
|
28
|
+
request.input('4', instance.responseJson);
|
|
29
|
+
request.input('5', mssql_1.DateTimeOffset, instance.expiresAt);
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
return [instance.id, instance.updatedOn, instance.responseCode, instance.responseJson, instance.expiresAt];
|
|
34
|
+
}
|
|
22
35
|
}
|
|
23
36
|
}
|
|
24
37
|
exports.ApiCallRepository = ApiCallRepository;
|
package/models/ApiCall.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ConnectionPool, DateTimeOffset, Request } from 'mssql';
|
|
1
2
|
import {Pool} from 'pg';
|
|
2
3
|
import {Logger} from 'winston';
|
|
3
4
|
import {Model, ModelDao} from './repository/ModelDao';
|
|
@@ -9,8 +10,9 @@ export interface ApiCall extends Model<string> {
|
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export class ApiCallRepository extends ModelDao<string, ApiCall> {
|
|
12
|
-
constructor(protected pool: Pool, protected logger: Logger) {
|
|
13
|
-
super(pool, logger, 'api_call', 5, 'id=$1,"updatedOn"=$2,"responseCode"=$3,"responseJson"=$4,"expiresAt"=$5'
|
|
13
|
+
constructor(protected pool: Pool, protected poolMssql: ConnectionPool, protected logger: Logger) {
|
|
14
|
+
super(pool, poolMssql, logger, 'api_call', 5, pool ? 'id=$1,"updatedOn"=$2,"responseCode"=$3,"responseJson"=$4,"expiresAt"=$5' :
|
|
15
|
+
'id=@1,"updatedOn"=@2,"responseCode"=@3,"responseJson"=@4,"expiresAt"=@5');
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
buildObject(q: any): ApiCall {
|
|
@@ -22,7 +24,17 @@ export class ApiCallRepository extends ModelDao<string, ApiCall> {
|
|
|
22
24
|
return q;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
serialize(instance: ApiCall)
|
|
26
|
-
|
|
27
|
+
serialize(instance: ApiCall, request?: Request) {
|
|
28
|
+
if(request) {
|
|
29
|
+
request.input('1', instance.id);
|
|
30
|
+
request.input('2', DateTimeOffset, instance.updatedOn);
|
|
31
|
+
request.input('3', instance.responseCode);
|
|
32
|
+
request.input('4', instance.responseJson);
|
|
33
|
+
request.input('5', DateTimeOffset, instance.expiresAt);
|
|
34
|
+
return undefined;
|
|
35
|
+
} else {
|
|
36
|
+
return [instance.id, instance.updatedOn, instance.responseCode, instance.responseJson, instance.expiresAt];
|
|
37
|
+
}
|
|
27
38
|
}
|
|
39
|
+
|
|
28
40
|
}
|
package/models/Migration.js
CHANGED
|
@@ -3,9 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.MigrationRepository = void 0;
|
|
4
4
|
const ModelDao_1 = require("./repository/ModelDao");
|
|
5
5
|
class MigrationRepository extends ModelDao_1.ModelDao {
|
|
6
|
-
constructor(pool, logger) {
|
|
7
|
-
super(pool, logger, 'migration', 5, 'id=$1,hash=$2,"sqlUp"=$3,"sqlDown"=$4,state=$5'
|
|
6
|
+
constructor(pool, poolMssql, logger) {
|
|
7
|
+
super(pool, poolMssql, logger, 'migration', 5, pool ? 'id=$1,hash=$2,"sqlUp"=$3,"sqlDown"=$4,state=$5' :
|
|
8
|
+
'id=@1,hash=@2,"sqlUp"=@3,"sqlDown"=@4,state=@5');
|
|
8
9
|
this.pool = pool;
|
|
10
|
+
this.poolMssql = poolMssql;
|
|
9
11
|
this.logger = logger;
|
|
10
12
|
}
|
|
11
13
|
buildObject(q) {
|
|
@@ -16,8 +18,18 @@ class MigrationRepository extends ModelDao_1.ModelDao {
|
|
|
16
18
|
q.state = parseInt(q.state, 10);
|
|
17
19
|
return q;
|
|
18
20
|
}
|
|
19
|
-
serialize(instance) {
|
|
20
|
-
|
|
21
|
+
serialize(instance, request) {
|
|
22
|
+
if (request) {
|
|
23
|
+
request.input('1', instance.id);
|
|
24
|
+
request.input('2', instance.hash);
|
|
25
|
+
request.input('3', instance.sqlUp);
|
|
26
|
+
request.input('4', instance.sqlDown);
|
|
27
|
+
request.input('5', instance.state);
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
return [instance.id, instance.hash, instance.sqlUp, instance.sqlDown, instance.state];
|
|
32
|
+
}
|
|
21
33
|
}
|
|
22
34
|
}
|
|
23
35
|
exports.MigrationRepository = MigrationRepository;
|
package/models/Migration.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ConnectionPool, Request } from 'mssql';
|
|
1
2
|
import {Pool} from 'pg';
|
|
2
3
|
import {Logger} from 'winston';
|
|
3
4
|
import {ModelDao} from './repository/ModelDao';
|
|
@@ -11,8 +12,9 @@ export interface Migration {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export class MigrationRepository extends ModelDao<number, Migration> {
|
|
14
|
-
constructor(protected pool: Pool, protected logger: Logger) {
|
|
15
|
-
super(pool, logger, 'migration', 5, 'id=$1,hash=$2,"sqlUp"=$3,"sqlDown"=$4,state=$5'
|
|
15
|
+
constructor(protected pool: Pool, protected poolMssql: ConnectionPool, protected logger: Logger) {
|
|
16
|
+
super(pool, poolMssql, logger, 'migration', 5, pool ? 'id=$1,hash=$2,"sqlUp"=$3,"sqlDown"=$4,state=$5' :
|
|
17
|
+
'id=@1,hash=@2,"sqlUp"=@3,"sqlDown"=@4,state=@5');
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
buildObject(q: any): Migration {
|
|
@@ -23,7 +25,16 @@ export class MigrationRepository extends ModelDao<number, Migration> {
|
|
|
23
25
|
return q;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
serialize(instance: Migration)
|
|
27
|
-
|
|
28
|
+
serialize(instance: Migration, request?: Request) {
|
|
29
|
+
if(request) {
|
|
30
|
+
request.input('1', instance.id);
|
|
31
|
+
request.input('2', instance.hash);
|
|
32
|
+
request.input('3', instance.sqlUp);
|
|
33
|
+
request.input('4', instance.sqlDown);
|
|
34
|
+
request.input('5', instance.state);
|
|
35
|
+
return undefined;
|
|
36
|
+
} else {
|
|
37
|
+
return [instance.id, instance.hash, instance.sqlUp, instance.sqlDown, instance.state];
|
|
38
|
+
}
|
|
28
39
|
}
|
|
29
40
|
}
|
|
@@ -6,28 +6,37 @@ const node_forge_1 = require("node-forge");
|
|
|
6
6
|
const ApiCall_1 = require("../ApiCall");
|
|
7
7
|
const Migration_1 = require("../Migration");
|
|
8
8
|
class BaseModelRepository {
|
|
9
|
-
constructor(db, dbSpare, logger, cache) {
|
|
9
|
+
constructor(db, dbSpare, dbMssql, logger, cache) {
|
|
10
10
|
this.db = db;
|
|
11
11
|
this.dbSpare = dbSpare;
|
|
12
|
+
this.dbMssql = dbMssql;
|
|
12
13
|
this.logger = logger;
|
|
13
14
|
this.cache = cache;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
if (db && dbSpare) {
|
|
16
|
+
const dbQuery = db.query.bind(db);
|
|
17
|
+
db.query = (function (text, values, cb) {
|
|
18
|
+
if ((this.idleCount + this.waitingCount) >= this.totalCount && this.totalCount === this.options.max)
|
|
19
|
+
return dbSpare.query(text, values, cb);
|
|
20
|
+
return dbQuery(text, values, cb);
|
|
21
|
+
}).bind(db);
|
|
22
|
+
}
|
|
23
|
+
this.Migration = new Migration_1.MigrationRepository(db, dbMssql, logger);
|
|
24
|
+
this.ApiCall = new ApiCall_1.ApiCallRepository(db, dbMssql, logger);
|
|
22
25
|
}
|
|
23
26
|
migrate(migrationsPath, callback) {
|
|
24
|
-
this.db.query('CREATE TABLE IF NOT EXISTS migration (' +
|
|
27
|
+
(this.db ? this.db.query('CREATE TABLE IF NOT EXISTS migration (' +
|
|
25
28
|
'id integer PRIMARY KEY,' +
|
|
26
29
|
'hash text NOT NULL,' +
|
|
27
30
|
'"sqlUp" text NOT NULL,' +
|
|
28
31
|
'"sqlDown" text NOT NULL,' +
|
|
29
32
|
'state integer NOT NULL' +
|
|
30
|
-
')').
|
|
33
|
+
')') : this.dbMssql.query('if not exists (select * from sysobjects where name=\'migration\' and xtype=\'U\') CREATE TABLE migration (' +
|
|
34
|
+
'id int PRIMARY KEY,' +
|
|
35
|
+
'hash text NOT NULL,' +
|
|
36
|
+
'"sqlUp" text NOT NULL,' +
|
|
37
|
+
'"sqlDown" text NOT NULL,' +
|
|
38
|
+
'state int NOT NULL' +
|
|
39
|
+
')')).then(() => {
|
|
31
40
|
this.Migration.getAllBy('id').then((migrations) => {
|
|
32
41
|
if (migrations.find(migration => migration.state !== 0))
|
|
33
42
|
process.exit(4);
|
|
@@ -61,7 +70,9 @@ class BaseModelRepository {
|
|
|
61
70
|
}, () => process.exit(2));
|
|
62
71
|
}
|
|
63
72
|
lockTables(tables, client) {
|
|
64
|
-
|
|
73
|
+
if (this.db)
|
|
74
|
+
return Promise.all(tables.map(t => client.query('LOCK TABLE ' + t.table + ' IN EXCLUSIVE MODE')));
|
|
75
|
+
return Promise.all(tables.map(t => client.request().query('SELECT id FROM ' + t.table + ' WITH (UPDLOCK)')));
|
|
65
76
|
}
|
|
66
77
|
applyUpUntil(migrations, current, until) {
|
|
67
78
|
if (current < until)
|
|
@@ -78,13 +89,13 @@ class BaseModelRepository {
|
|
|
78
89
|
sqlDown: sqlParts[1],
|
|
79
90
|
state: 2
|
|
80
91
|
}).then(() => {
|
|
81
|
-
this.db.query(sqlParts[0], (err) => {
|
|
92
|
+
(this.db || this.dbMssql).query(sqlParts[0], (err) => {
|
|
82
93
|
if (err) {
|
|
83
94
|
console.error(err);
|
|
84
95
|
reject(10);
|
|
85
96
|
}
|
|
86
97
|
else {
|
|
87
|
-
this.db.query('UPDATE "migration" SET "state"=0 WHERE "id"=' + migration.id, (err2) => {
|
|
98
|
+
(this.db || this.dbMssql).query('UPDATE "migration" SET "state"=0 WHERE "id"=' + migration.id, (err2) => {
|
|
88
99
|
if (err2)
|
|
89
100
|
reject(11);
|
|
90
101
|
else
|
|
@@ -104,28 +115,34 @@ class BaseModelRepository {
|
|
|
104
115
|
}
|
|
105
116
|
applyDown(migration) {
|
|
106
117
|
return new Promise((resolve, reject) => {
|
|
107
|
-
this.db.query('UPDATE "migration" SET "state"=1 WHERE "id"=' + migration.id, (err) => {
|
|
118
|
+
(this.db || this.dbMssql).query('UPDATE "migration" SET "state"=1 WHERE "id"=' + migration.id, (err) => {
|
|
108
119
|
if (err)
|
|
109
120
|
reject(6);
|
|
110
121
|
else
|
|
111
|
-
this.db.query(migration.sqlDown, (err2) => {
|
|
122
|
+
(this.db || this.dbMssql).query(migration.sqlDown, (err2) => {
|
|
112
123
|
if (err2) {
|
|
113
124
|
console.error(err2);
|
|
114
125
|
reject(7);
|
|
115
126
|
}
|
|
116
127
|
else
|
|
117
|
-
this.db.query('DELETE FROM "migration" WHERE "id"=' + migration.id, (err3) => {
|
|
128
|
+
(this.db || this.dbMssql).query('DELETE FROM "migration" WHERE "id"=' + migration.id, (err3) => {
|
|
118
129
|
if (err3 && migration.id !== 1)
|
|
119
130
|
reject(8);
|
|
120
131
|
else {
|
|
121
132
|
if (migration.id === 1) {
|
|
122
|
-
this.db.query('CREATE TABLE IF NOT EXISTS migration (' +
|
|
133
|
+
(this.db ? this.db.query('CREATE TABLE IF NOT EXISTS migration (' +
|
|
123
134
|
'id integer PRIMARY KEY,' +
|
|
124
135
|
'hash text NOT NULL,' +
|
|
125
136
|
'"sqlUp" text NOT NULL,' +
|
|
126
137
|
'"sqlDown" text NOT NULL,' +
|
|
127
138
|
'state integer NOT NULL' +
|
|
128
|
-
')').
|
|
139
|
+
')') : this.dbMssql.query('if not exists (select * from sysobjects where name=\'migration\' and xtype=\'U\') CREATE TABLE migration (' +
|
|
140
|
+
'id int PRIMARY KEY,' +
|
|
141
|
+
'hash text NOT NULL,' +
|
|
142
|
+
'"sqlUp" text NOT NULL,' +
|
|
143
|
+
'"sqlDown" text NOT NULL,' +
|
|
144
|
+
'state int NOT NULL' +
|
|
145
|
+
')')).then(() => resolve(), reject);
|
|
129
146
|
}
|
|
130
147
|
else
|
|
131
148
|
resolve();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as Memcached from 'memcached';
|
|
3
|
+
import {ConnectionPool, Transaction} from 'mssql';
|
|
3
4
|
import {md} from 'node-forge';
|
|
4
5
|
import {ClientBase, Pool} from 'pg';
|
|
5
6
|
import {Logger} from 'winston';
|
|
@@ -11,25 +12,33 @@ export class BaseModelRepository {
|
|
|
11
12
|
Migration: MigrationRepository;
|
|
12
13
|
ApiCall: ApiCallRepository;
|
|
13
14
|
|
|
14
|
-
constructor(public db: Pool, public dbSpare: Pool, public logger: Logger, public cache: Memcached) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
constructor(public db: Pool, public dbSpare: Pool, public dbMssql: ConnectionPool, public logger: Logger, public cache: Memcached) {
|
|
16
|
+
if(db && dbSpare) {
|
|
17
|
+
const dbQuery = db.query.bind(db);
|
|
18
|
+
db.query = (function(text: any, values: any, cb: any) {
|
|
19
|
+
if((this.idleCount + this.waitingCount) >= this.totalCount && this.totalCount === this.options.max)
|
|
20
|
+
return dbSpare.query(text, values, cb);
|
|
21
|
+
return dbQuery(text, values, cb);
|
|
22
|
+
}).bind(db);
|
|
23
|
+
}
|
|
24
|
+
this.Migration = new MigrationRepository(db, dbMssql, logger);
|
|
25
|
+
this.ApiCall = new ApiCallRepository(db, dbMssql, logger);
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
migrate(migrationsPath: string, callback: (() => void)) {
|
|
26
|
-
this.db.query('CREATE TABLE IF NOT EXISTS migration (' +
|
|
29
|
+
(this.db ? this.db.query('CREATE TABLE IF NOT EXISTS migration (' +
|
|
27
30
|
'id integer PRIMARY KEY,' +
|
|
28
31
|
'hash text NOT NULL,' +
|
|
29
32
|
'"sqlUp" text NOT NULL,' +
|
|
30
33
|
'"sqlDown" text NOT NULL,' +
|
|
31
34
|
'state integer NOT NULL' +
|
|
32
|
-
')').
|
|
35
|
+
')') : this.dbMssql.query('if not exists (select * from sysobjects where name=\'migration\' and xtype=\'U\') CREATE TABLE migration (' +
|
|
36
|
+
'id int PRIMARY KEY,' +
|
|
37
|
+
'hash text NOT NULL,' +
|
|
38
|
+
'"sqlUp" text NOT NULL,' +
|
|
39
|
+
'"sqlDown" text NOT NULL,' +
|
|
40
|
+
'state int NOT NULL' +
|
|
41
|
+
')')).then(() => {
|
|
33
42
|
this.Migration.getAllBy('id').then((migrations: Migration[]) => {
|
|
34
43
|
if(migrations.find(migration => migration.state !== 0)) process.exit(4); // Have to fix manually
|
|
35
44
|
// Read the new ones
|
|
@@ -62,8 +71,10 @@ export class BaseModelRepository {
|
|
|
62
71
|
}, () => process.exit(2));
|
|
63
72
|
}
|
|
64
73
|
|
|
65
|
-
lockTables(tables: ModelRepr[], client: ClientBase): Promise<any> {
|
|
66
|
-
|
|
74
|
+
lockTables(tables: ModelRepr[], client: ClientBase | Transaction): Promise<any> {
|
|
75
|
+
if(this.db)
|
|
76
|
+
return Promise.all(tables.map(t => (<ClientBase>client).query('LOCK TABLE ' + t.table + ' IN EXCLUSIVE MODE')));
|
|
77
|
+
return Promise.all(tables.map(t => (<Transaction>client).request().query('SELECT id FROM ' + t.table + ' WITH (UPDLOCK)')));
|
|
67
78
|
}
|
|
68
79
|
|
|
69
80
|
private applyUpUntil(migrations: {id: number, content: string, hash: string}[], current: number, until: number): Promise<void> {
|
|
@@ -82,12 +93,12 @@ export class BaseModelRepository {
|
|
|
82
93
|
sqlDown: sqlParts[1],
|
|
83
94
|
state: 2
|
|
84
95
|
}).then(() => {
|
|
85
|
-
this.db.query(sqlParts[0], (err: any) => {
|
|
96
|
+
(<any>(this.db|| this.dbMssql)).query(sqlParts[0], (err: any) => {
|
|
86
97
|
if(err) {
|
|
87
98
|
console.error(err);
|
|
88
99
|
reject(10);
|
|
89
100
|
} else {
|
|
90
|
-
this.db.query('UPDATE "migration" SET "state"=0 WHERE "id"=' + migration.id, (err2: any) => {
|
|
101
|
+
(<any>(this.db|| this.dbMssql)).query('UPDATE "migration" SET "state"=0 WHERE "id"=' + migration.id, (err2: any) => {
|
|
91
102
|
if(err2) reject(11); // No cleanup
|
|
92
103
|
else resolve();
|
|
93
104
|
});
|
|
@@ -107,24 +118,30 @@ export class BaseModelRepository {
|
|
|
107
118
|
|
|
108
119
|
private applyDown(migration: Migration): Promise<void> {
|
|
109
120
|
return new Promise((resolve, reject) => {
|
|
110
|
-
this.db.query('UPDATE "migration" SET "state"=1 WHERE "id"=' + migration.id, (err: any) => {
|
|
121
|
+
(<any>(this.db|| this.dbMssql)).query('UPDATE "migration" SET "state"=1 WHERE "id"=' + migration.id, (err: any) => {
|
|
111
122
|
if(err) reject(6); // No required change
|
|
112
|
-
else this.db.query(migration.sqlDown, (err2: any) => {
|
|
123
|
+
else (<any>(this.db|| this.dbMssql)).query(migration.sqlDown, (err2: any) => {
|
|
113
124
|
if(err2) {
|
|
114
125
|
console.error(err2);
|
|
115
126
|
reject(7);
|
|
116
127
|
} // No apply down
|
|
117
|
-
else this.db.query('DELETE FROM "migration" WHERE "id"=' + migration.id, (err3: any) => {
|
|
128
|
+
else (<any>(this.db|| this.dbMssql)).query('DELETE FROM "migration" WHERE "id"=' + migration.id, (err3: any) => {
|
|
118
129
|
if(err3 && migration.id !== 1) reject(8); // No cleanup for not base migration
|
|
119
130
|
else {
|
|
120
131
|
if(migration.id === 1) {
|
|
121
|
-
this.db.query('CREATE TABLE IF NOT EXISTS migration (' +
|
|
132
|
+
(this.db ? this.db.query('CREATE TABLE IF NOT EXISTS migration (' +
|
|
122
133
|
'id integer PRIMARY KEY,' +
|
|
123
134
|
'hash text NOT NULL,' +
|
|
124
135
|
'"sqlUp" text NOT NULL,' +
|
|
125
136
|
'"sqlDown" text NOT NULL,' +
|
|
126
137
|
'state integer NOT NULL' +
|
|
127
|
-
')').
|
|
138
|
+
')') : this.dbMssql.query('if not exists (select * from sysobjects where name=\'migration\' and xtype=\'U\') CREATE TABLE migration (' +
|
|
139
|
+
'id int PRIMARY KEY,' +
|
|
140
|
+
'hash text NOT NULL,' +
|
|
141
|
+
'"sqlUp" text NOT NULL,' +
|
|
142
|
+
'"sqlDown" text NOT NULL,' +
|
|
143
|
+
'state int NOT NULL' +
|
|
144
|
+
')')).then(() => resolve(), reject);
|
|
128
145
|
} else resolve();
|
|
129
146
|
}
|
|
130
147
|
});
|
|
@@ -1,15 +1,56 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ModelDao = void 0;
|
|
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);
|
|
4
36
|
class Dao {
|
|
5
|
-
constructor(pool, logger, table, nFields, updateDefinition) {
|
|
37
|
+
constructor(pool, poolMssql, logger, table, nFields, updateDefinition) {
|
|
6
38
|
this.pool = pool;
|
|
39
|
+
this.poolMssql = poolMssql;
|
|
7
40
|
this.logger = logger;
|
|
8
41
|
this.table = table;
|
|
9
42
|
this.nFields = nFields;
|
|
10
43
|
this.updateDefinition = updateDefinition;
|
|
11
44
|
}
|
|
12
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) {
|
|
13
54
|
const storage = {};
|
|
14
55
|
for (let i = 0; i < q.length; i++) {
|
|
15
56
|
storage[key(q[i])] = storage[key(q[i])] || [];
|
|
@@ -18,22 +59,49 @@ class Dao {
|
|
|
18
59
|
return Object.getOwnPropertyNames(storage).map(k => storage[k]);
|
|
19
60
|
}
|
|
20
61
|
update(instance, client) {
|
|
62
|
+
if (instance.archivedOn)
|
|
63
|
+
return Promise.reject('Record archived!');
|
|
21
64
|
instance.updatedOn = new Date();
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
+
}
|
|
25
76
|
}
|
|
26
|
-
|
|
27
|
-
instance.
|
|
28
|
-
|
|
29
|
-
return
|
|
30
|
-
|
|
31
|
-
const idNum = parseInt(q.rows[0].id, 10);
|
|
32
|
-
if (String(idNum) !== q.rows[0].id)
|
|
33
|
-
return q.rows[0].id;
|
|
34
|
-
return idNum;
|
|
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);
|
|
35
82
|
});
|
|
36
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
|
+
}
|
|
37
105
|
createSeveral(instances, client) {
|
|
38
106
|
if (!instances.length)
|
|
39
107
|
return Promise.resolve([]);
|
|
@@ -51,30 +119,120 @@ class Dao {
|
|
|
51
119
|
}
|
|
52
120
|
class ModelDao extends Dao {
|
|
53
121
|
get(id, client, lock = true) {
|
|
54
|
-
|
|
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
|
+
}
|
|
55
130
|
}
|
|
56
131
|
count(where, inputs = [], client) {
|
|
57
|
-
|
|
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
|
+
}
|
|
58
141
|
}
|
|
59
142
|
getList(ids, client, lock = true) {
|
|
60
|
-
|
|
61
|
-
|
|
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
|
+
}
|
|
62
153
|
}
|
|
63
154
|
getAllBy(order, offset, limit, where, inputs = [], client, lock = true) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
+
}
|
|
67
168
|
}
|
|
68
169
|
getViewCountBy(order, offset, limit, where, inputs = [], client, lock = true) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
+
}
|
|
75
226
|
}
|
|
76
227
|
delete(id, client) {
|
|
77
|
-
|
|
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
|
+
}
|
|
78
236
|
}
|
|
79
237
|
}
|
|
80
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:
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
40
|
-
instance.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
82
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
101
|
-
|
|
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
|
|
3
|
+
"version": "0.1.0",
|
|
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",
|
|
@@ -17,12 +18,14 @@
|
|
|
17
18
|
"dependencies": {
|
|
18
19
|
"@types/express": "4.17.13",
|
|
19
20
|
"@types/memcached": "2.2.7",
|
|
21
|
+
"@types/mssql": "7.1.4",
|
|
20
22
|
"@types/node": "14.18.2",
|
|
21
23
|
"@types/node-forge": "1.0.0",
|
|
22
24
|
"@types/pg": "8.6.4",
|
|
23
25
|
"express": "4.17.2",
|
|
24
26
|
"jsonschema": "1.4.0",
|
|
25
27
|
"memcached": "2.2.2",
|
|
28
|
+
"mssql": "8.0.2",
|
|
26
29
|
"node-forge": "1.2.1",
|
|
27
30
|
"pg": "8.7.3",
|
|
28
31
|
"winston": "3.5.1"
|
|
@@ -2,35 +2,22 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.withTransaction = void 0;
|
|
4
4
|
const utils_1 = require("./utils");
|
|
5
|
-
function jsonStringify(obj) {
|
|
6
|
-
const cache = {};
|
|
7
|
-
return JSON.stringify(obj, function (_, value) {
|
|
8
|
-
if (typeof value === 'object' && value !== null) {
|
|
9
|
-
if (cache[value] !== -1) {
|
|
10
|
-
try {
|
|
11
|
-
return JSON.parse(JSON.stringify(value));
|
|
12
|
-
}
|
|
13
|
-
catch (error) {
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
cache[value] = true;
|
|
18
|
-
}
|
|
19
|
-
return value;
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
5
|
function withTransaction(repo, logger, previousMethod, lock) {
|
|
23
6
|
return function (req, res, next) {
|
|
24
7
|
const endTerminator = res.end.bind(res);
|
|
25
8
|
const jsonTerminator = (obj) => {
|
|
26
|
-
res.write(jsonStringify(obj) || '{}');
|
|
9
|
+
res.write((0, utils_1.jsonStringify)(obj) || '{}');
|
|
27
10
|
endTerminator();
|
|
28
11
|
};
|
|
29
12
|
const connectTimeoutHandler = setTimeout(() => {
|
|
30
13
|
logger.error('Error timed out getting a client, exiting...');
|
|
31
14
|
process.exit(22);
|
|
32
15
|
}, 3000);
|
|
33
|
-
|
|
16
|
+
Promise.all([
|
|
17
|
+
repo.db ? repo.db.connect() : Promise.resolve(undefined),
|
|
18
|
+
repo.dbMssql ? Promise.resolve(repo.dbMssql.transaction()) : Promise.resolve(undefined)
|
|
19
|
+
]).then(([c1, c2]) => {
|
|
20
|
+
const dbClient = c1 || c2;
|
|
34
21
|
clearTimeout(connectTimeoutHandler);
|
|
35
22
|
utils_1.utils.logger = logger;
|
|
36
23
|
dbClient.removeListener('error', utils_1.utils.clientErrorHandler);
|
|
@@ -40,8 +27,9 @@ function withTransaction(repo, logger, previousMethod, lock) {
|
|
|
40
27
|
res.locals.dbClientCommit = (cb) => {
|
|
41
28
|
if (!res.locals.dbClientCommited) {
|
|
42
29
|
res.locals.dbClientCommited = true;
|
|
43
|
-
dbClient.query('COMMIT').catch(err => err).then((err) => {
|
|
44
|
-
|
|
30
|
+
(repo.db ? dbClient.query('COMMIT') : dbClient.commit()).catch((err) => err).then((err) => {
|
|
31
|
+
if (repo.db)
|
|
32
|
+
dbClient.release();
|
|
45
33
|
if (!(err instanceof Error)) {
|
|
46
34
|
for (let i = 0; i < res.locals.dbClientOnCommit.length; i++) {
|
|
47
35
|
res.locals.dbClientOnCommit[i]();
|
|
@@ -55,15 +43,16 @@ function withTransaction(repo, logger, previousMethod, lock) {
|
|
|
55
43
|
}
|
|
56
44
|
};
|
|
57
45
|
res.locals.dbClientOnCommit = [];
|
|
58
|
-
return dbClient.query('BEGIN').then(() => {
|
|
46
|
+
return (repo.db ? dbClient.query('BEGIN') : dbClient.begin()).then(() => {
|
|
59
47
|
const finish = () => {
|
|
60
48
|
res.json = (obj) => {
|
|
61
49
|
if (res.statusCode > 303 && res.statusCode !== 412) {
|
|
62
50
|
if (logger && res.statusCode > 499) {
|
|
63
51
|
logger.error('Uncaught 500: %j', obj.error.additionalInfo);
|
|
64
52
|
}
|
|
65
|
-
dbClient.query('ROLLBACK').catch(err => obj.error.additionalInfo2 = { message: err.message }).then(() => {
|
|
66
|
-
|
|
53
|
+
(repo.db ? dbClient.query('ROLLBACK') : dbClient.rollback()).catch((err) => obj.error.additionalInfo2 = { message: err.message }).then(() => {
|
|
54
|
+
if (repo.db)
|
|
55
|
+
dbClient.release();
|
|
67
56
|
jsonTerminator(obj);
|
|
68
57
|
});
|
|
69
58
|
}
|
|
@@ -89,8 +78,9 @@ function withTransaction(repo, logger, previousMethod, lock) {
|
|
|
89
78
|
if (logger && res.statusCode > 499) {
|
|
90
79
|
logger.error('Uncaught 500 with no details...');
|
|
91
80
|
}
|
|
92
|
-
dbClient.query('ROLLBACK').catch(() => undefined).then(() => {
|
|
93
|
-
|
|
81
|
+
(repo.db ? dbClient.query('ROLLBACK') : dbClient.rollback()).catch(() => undefined).then(() => {
|
|
82
|
+
if (repo.db)
|
|
83
|
+
dbClient.release();
|
|
94
84
|
endTerminator();
|
|
95
85
|
});
|
|
96
86
|
}
|
|
@@ -114,7 +104,7 @@ function withTransaction(repo, logger, previousMethod, lock) {
|
|
|
114
104
|
return previousMethod.call(this, req, res, next);
|
|
115
105
|
};
|
|
116
106
|
if (lock) {
|
|
117
|
-
dbClient.query('SELECT pg_advisory_xact_lock(' + lock + ')').then(() => finish()).catch(err => {
|
|
107
|
+
dbClient.query('SELECT pg_advisory_xact_lock(' + lock + ')').then(() => finish()).catch((err) => {
|
|
118
108
|
res.status(500).json({
|
|
119
109
|
error: {
|
|
120
110
|
errorKey: 'internal.db',
|
|
@@ -127,8 +117,11 @@ function withTransaction(repo, logger, previousMethod, lock) {
|
|
|
127
117
|
else {
|
|
128
118
|
finish();
|
|
129
119
|
}
|
|
130
|
-
}).catch(err => {
|
|
131
|
-
|
|
120
|
+
}).catch((err) => {
|
|
121
|
+
if (repo.db)
|
|
122
|
+
dbClient.release();
|
|
123
|
+
else
|
|
124
|
+
dbClient.rollback();
|
|
132
125
|
throw err;
|
|
133
126
|
});
|
|
134
127
|
}).catch(err => {
|
|
@@ -1,30 +1,13 @@
|
|
|
1
1
|
import {NextFunction, Request, Response} from 'express';
|
|
2
2
|
import {Logger} from 'winston';
|
|
3
3
|
import {BaseModelRepository} from '../models/repository/BaseModelRepository';
|
|
4
|
-
import {utils} from './utils';
|
|
4
|
+
import {jsonStringify, utils} from './utils';
|
|
5
5
|
|
|
6
6
|
export const enum SystemLock {
|
|
7
7
|
CHECK_CROSSING = 1,
|
|
8
8
|
FLUSH_CALLS = 2
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
function jsonStringify(obj: any): string {
|
|
12
|
-
const cache: any = {};
|
|
13
|
-
return JSON.stringify(obj, function(_, value) {
|
|
14
|
-
if(typeof value === 'object' && value !== null) {
|
|
15
|
-
if(cache[value] !== -1) {
|
|
16
|
-
try {
|
|
17
|
-
return JSON.parse(JSON.stringify(value));
|
|
18
|
-
} catch(error) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
cache[value] = true;
|
|
23
|
-
}
|
|
24
|
-
return value;
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
11
|
export function withTransaction(repo: BaseModelRepository, logger: Logger, previousMethod: (req: Request, res: Response, next: NextFunction) => void, lock?: SystemLock) {
|
|
29
12
|
return function(req: Request, res: Response, next: NextFunction) {
|
|
30
13
|
const endTerminator = res.end.bind(res);
|
|
@@ -37,7 +20,11 @@ export function withTransaction(repo: BaseModelRepository, logger: Logger, previ
|
|
|
37
20
|
logger.error('Error timed out getting a client, exiting...');
|
|
38
21
|
process.exit(22);
|
|
39
22
|
}, 3000);
|
|
40
|
-
|
|
23
|
+
Promise.all([
|
|
24
|
+
repo.db ? repo.db.connect() : Promise.resolve(undefined),
|
|
25
|
+
repo.dbMssql ? Promise.resolve(repo.dbMssql.transaction()) : Promise.resolve(undefined)
|
|
26
|
+
]).then(([c1, c2]) => {
|
|
27
|
+
const dbClient = c1 || c2;
|
|
41
28
|
clearTimeout(connectTimeoutHandler);
|
|
42
29
|
// On error, will rollback...
|
|
43
30
|
utils.logger = logger;
|
|
@@ -49,8 +36,8 @@ export function withTransaction(repo: BaseModelRepository, logger: Logger, previ
|
|
|
49
36
|
res.locals.dbClientCommit = (cb: (err: any) => any) => {
|
|
50
37
|
if(!res.locals.dbClientCommited) {
|
|
51
38
|
res.locals.dbClientCommited = true;
|
|
52
|
-
dbClient.query('COMMIT').catch(err => err).then((err: any) => {
|
|
53
|
-
dbClient.release();
|
|
39
|
+
(repo.db ? dbClient.query('COMMIT') : dbClient.commit()).catch((err: any) => err).then((err: any) => {
|
|
40
|
+
if(repo.db) dbClient.release();
|
|
54
41
|
if(!(err instanceof Error)) {
|
|
55
42
|
for(let i = 0; i < res.locals.dbClientOnCommit.length; i++) {
|
|
56
43
|
res.locals.dbClientOnCommit[i]();
|
|
@@ -63,15 +50,15 @@ export function withTransaction(repo: BaseModelRepository, logger: Logger, previ
|
|
|
63
50
|
}
|
|
64
51
|
};
|
|
65
52
|
res.locals.dbClientOnCommit = [];
|
|
66
|
-
return dbClient.query('BEGIN').then(() => {
|
|
53
|
+
return (repo.db ? dbClient.query('BEGIN') : dbClient.begin()).then(() => {
|
|
67
54
|
const finish = () => {
|
|
68
55
|
res.json = (obj: any) => {
|
|
69
56
|
if(res.statusCode > 303 && res.statusCode !== 412) {
|
|
70
57
|
if(logger && res.statusCode > 499) {
|
|
71
58
|
logger.error('Uncaught 500: %j', obj.error.additionalInfo);
|
|
72
59
|
}
|
|
73
|
-
dbClient.query('ROLLBACK').catch(err => obj.error.additionalInfo2 = {message: err.message}).then(() => {
|
|
74
|
-
dbClient.release();
|
|
60
|
+
(repo.db ? dbClient.query('ROLLBACK') : dbClient.rollback()).catch((err: any) => obj.error.additionalInfo2 = {message: err.message}).then(() => {
|
|
61
|
+
if(repo.db) dbClient.release();
|
|
75
62
|
jsonTerminator(obj);
|
|
76
63
|
});
|
|
77
64
|
} else {
|
|
@@ -94,8 +81,8 @@ export function withTransaction(repo: BaseModelRepository, logger: Logger, previ
|
|
|
94
81
|
if(logger && res.statusCode > 499) {
|
|
95
82
|
logger.error('Uncaught 500 with no details...');
|
|
96
83
|
}
|
|
97
|
-
dbClient.query('ROLLBACK').catch((): any => undefined).then(() => {
|
|
98
|
-
dbClient.release();
|
|
84
|
+
(repo.db ? dbClient.query('ROLLBACK') : dbClient.rollback()).catch((): any => undefined).then(() => {
|
|
85
|
+
if(repo.db) dbClient.release();
|
|
99
86
|
endTerminator();
|
|
100
87
|
});
|
|
101
88
|
} else {
|
|
@@ -117,7 +104,7 @@ export function withTransaction(repo: BaseModelRepository, logger: Logger, previ
|
|
|
117
104
|
};
|
|
118
105
|
|
|
119
106
|
if(lock) {
|
|
120
|
-
dbClient.query('SELECT pg_advisory_xact_lock(' + lock + ')').then(() => finish()).catch(err => {
|
|
107
|
+
dbClient.query('SELECT pg_advisory_xact_lock(' + lock + ')').then(() => finish()).catch((err: any) => {
|
|
121
108
|
res.status(500).json({
|
|
122
109
|
error: {
|
|
123
110
|
errorKey: 'internal.db',
|
|
@@ -129,8 +116,9 @@ export function withTransaction(repo: BaseModelRepository, logger: Logger, previ
|
|
|
129
116
|
} else {
|
|
130
117
|
finish();
|
|
131
118
|
}
|
|
132
|
-
}).catch(err => {
|
|
133
|
-
dbClient.release();
|
|
119
|
+
}).catch((err: any) => {
|
|
120
|
+
if(repo.db) dbClient.release();
|
|
121
|
+
else dbClient.rollback();
|
|
134
122
|
throw err;
|
|
135
123
|
});
|
|
136
124
|
}).catch(err => {
|
package/services/utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.digitize = exports.fpEuros = exports.sendSelfPostableMessage = exports.idempotent = exports.lcm = exports.gcd = exports.utils = exports.clientErrorHandle = exports.btoa = exports.atob = void 0;
|
|
3
|
+
exports.digitize = exports.fpEuros = exports.sendSelfPostableMessage = exports.idempotent = exports.jsonStringify = exports.lcm = exports.gcd = exports.utils = exports.clientErrorHandle = exports.btoa = exports.atob = void 0;
|
|
4
4
|
function atob(str) {
|
|
5
5
|
return Buffer.from(str, 'base64').toString('binary');
|
|
6
6
|
}
|
|
@@ -60,6 +60,24 @@ function lcm(values) {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
exports.lcm = lcm;
|
|
63
|
+
function jsonStringify(obj) {
|
|
64
|
+
const cache = {};
|
|
65
|
+
return JSON.stringify(obj, function (_, value) {
|
|
66
|
+
if (typeof value === 'object' && value !== null) {
|
|
67
|
+
if (cache[value] !== -1) {
|
|
68
|
+
try {
|
|
69
|
+
return JSON.parse(JSON.stringify(value));
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
cache[value] = true;
|
|
76
|
+
}
|
|
77
|
+
return value;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
exports.jsonStringify = jsonStringify;
|
|
63
81
|
function idempotent(repo, debug, logger) {
|
|
64
82
|
return function (req, res, next) {
|
|
65
83
|
let idempotenceKey = req.header('x-idempotent-key');
|
|
@@ -73,7 +91,7 @@ function idempotent(repo, debug, logger) {
|
|
|
73
91
|
repo.ApiCall.create({
|
|
74
92
|
id: idempotenceKey,
|
|
75
93
|
responseCode: res.statusCode,
|
|
76
|
-
responseJson:
|
|
94
|
+
responseJson: jsonStringify(obj),
|
|
77
95
|
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000)
|
|
78
96
|
}).then(() => jsonTerminator(obj), err => {
|
|
79
97
|
if (err)
|
package/services/utils.ts
CHANGED
|
@@ -60,6 +60,23 @@ export function lcm(values: number[]): number {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
export function jsonStringify(obj: any): string {
|
|
64
|
+
const cache: any = {};
|
|
65
|
+
return JSON.stringify(obj, function(_, value) {
|
|
66
|
+
if(typeof value === 'object' && value !== null) {
|
|
67
|
+
if(cache[value] !== -1) {
|
|
68
|
+
try {
|
|
69
|
+
return JSON.parse(JSON.stringify(value));
|
|
70
|
+
} catch(error) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
cache[value] = true;
|
|
75
|
+
}
|
|
76
|
+
return value;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
63
80
|
export function idempotent(repo: BaseModelRepository, debug: boolean, logger: Logger) {
|
|
64
81
|
return function(req: Request, res: Response, next: NextFunction) {
|
|
65
82
|
let idempotenceKey = req.header('x-idempotent-key');
|
|
@@ -73,7 +90,7 @@ export function idempotent(repo: BaseModelRepository, debug: boolean, logger: Lo
|
|
|
73
90
|
repo.ApiCall.create({
|
|
74
91
|
id: idempotenceKey,
|
|
75
92
|
responseCode: res.statusCode,
|
|
76
|
-
responseJson:
|
|
93
|
+
responseJson: jsonStringify(obj),
|
|
77
94
|
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000)
|
|
78
95
|
}).then(() => jsonTerminator(obj), err => {
|
|
79
96
|
if(err) logger.warn('Cannot save idempotent key: %j', err);
|