-
Notifications
You must be signed in to change notification settings - Fork 180
/
Copy pathdb.ts
125 lines (111 loc) · 4.16 KB
/
db.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/*
This file just manages the database connection and provides a query method
*/
import { inspect } from 'util'
import { ClientBase, Client, ClientConfig, QueryArrayResult, QueryResult, QueryArrayConfig, QueryConfig } from 'pg'
import { Logger, DB } from './types'
/* eslint-disable @typescript-eslint/no-explicit-any */
export interface DBConnection extends DB {
createConnection(): Promise<void>
column(columnName: string, queryConfig: QueryArrayConfig, values?: any[]): Promise<any[]>
column(columnName: string, queryConfig: QueryConfig): Promise<any[]>
column(columnName: string, queryTextOrConfig: string | QueryConfig, values?: any[]): Promise<any[]>
connected: () => boolean
addBeforeCloseListener: (listener: any) => number
close(): Promise<void>
}
enum ConnectionStatus {
DISCONNECTED = 'DISCONNECTED',
CONNECTED = 'CONNECTED',
ERROR = 'ERROR',
}
const db = (connection: ClientBase | string | ClientConfig, logger: Logger = console): DBConnection => {
const isExternalClient =
typeof connection === 'object' && 'query' in connection && typeof connection.query === 'function'
let connectionStatus = ConnectionStatus.DISCONNECTED
const client: Client = isExternalClient ? (connection as Client) : new Client(connection as string | ClientConfig)
const beforeCloseListeners: any[] = []
const createConnection: () => Promise<void> = () =>
new Promise((resolve, reject) => {
if (isExternalClient || connectionStatus === ConnectionStatus.CONNECTED) {
resolve()
} else if (connectionStatus === ConnectionStatus.ERROR) {
reject(new Error('Connection already failed, do not try to connect again'))
} else {
client.connect((err) => {
if (err) {
connectionStatus = ConnectionStatus.ERROR
logger.error(`could not connect to postgres: ${inspect(err)}`)
return reject(err)
}
connectionStatus = ConnectionStatus.CONNECTED
return resolve()
})
}
})
const query: DBConnection['query'] = async (
queryTextOrConfig: string | QueryConfig | QueryArrayConfig,
values?: any[],
): Promise<QueryArrayResult | QueryResult> => {
await createConnection()
try {
return await client.query(queryTextOrConfig, values)
} catch (err) {
const { message, position }: { message: string; position: number } = err
const string: string = typeof queryTextOrConfig === 'string' ? queryTextOrConfig : queryTextOrConfig.text
if (message && position >= 1) {
const endLineWrapIndexOf = string.indexOf('\n', position)
const endLineWrapPos = endLineWrapIndexOf >= 0 ? endLineWrapIndexOf : string.length
const stringStart = string.substring(0, endLineWrapPos)
const stringEnd = string.substr(endLineWrapPos)
const startLineWrapPos = stringStart.lastIndexOf('\n') + 1
const padding = ' '.repeat(position - startLineWrapPos - 1)
logger.error(`Error executing:
${stringStart}
${padding}^^^^${stringEnd}
${message}
`)
} else {
logger.error(`Error executing:
${string}
${err}
`)
}
throw err
}
}
const select: DBConnection['select'] = async (
queryTextOrConfig: string | QueryConfig | QueryArrayConfig,
values?: any[],
) => {
const { rows } = await query(queryTextOrConfig, values)
return rows
}
const column: DBConnection['column'] = async (
columnName: string,
queryTextOrConfig: string | QueryConfig | QueryArrayConfig,
values?: any[],
) => {
const rows = await select(queryTextOrConfig, values)
return rows.map((r: { [key: string]: any }) => r[columnName])
}
return {
createConnection,
query,
select,
column,
connected: () => connectionStatus === ConnectionStatus.CONNECTED,
addBeforeCloseListener: (listener) => beforeCloseListeners.push(listener),
close: async () => {
await beforeCloseListeners.reduce(
(promise, listener) => promise.then(listener).catch((err: any) => logger.error(err.stack || err)),
Promise.resolve(),
)
if (!isExternalClient) {
connectionStatus = ConnectionStatus.DISCONNECTED
client.end()
}
},
}
}
export default db