-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathquery.ts
More file actions
127 lines (104 loc) · 2.97 KB
/
query.ts
File metadata and controls
127 lines (104 loc) · 2.97 KB
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
126
127
import pg, { type QueryArrayConfig } from "pg";
import { camelCase } from "./case.ts";
import { OnlyOneError, UniqueConstraintError } from "./errors.ts";
export type QueryResult<T> = {
length: number;
[Symbol.iterator]: () => Iterator<T>;
};
export function cursorRowConverter(
row: Record<string, unknown> | undefined,
): (row: Record<string, unknown>) => Record<string, unknown> {
if (row === undefined) {
return () => ({});
}
const keys = Object.keys(row);
const caseMap = keys.reduce<Record<string, string>>((acc, key) => {
acc[key] = camelCase(key);
return acc;
}, {});
return (row) =>
Object.fromEntries(Object.entries(row).map(([k, v]) => [caseMap[k], v]));
}
export function rowConverter(
fields: pg.FieldDef[],
): (row: unknown[]) => unknown {
const fieldNames = fields.map((field) => camelCase(field.name));
return (row) => {
return Object.fromEntries(
fieldNames.map((fieldName, i) => [fieldName, row[i]]),
);
};
}
function queryResult<T>(result: pg.QueryResult): QueryResult<T> {
const transform = rowConverter(result.fields);
return {
length: result.rows.length,
[Symbol.iterator]: () => {
let idx = -1;
return {
next: () => {
idx += 1;
if (idx < result.rows.length) {
return {
value: transform(result.rows[idx]) as T,
done: false,
};
}
return { done: true, value: undefined };
},
};
},
};
}
async function queryInternal<T extends object>(
sql: pg.QueryArrayConfig | string,
poolClient: pg.PoolClient,
): Promise<QueryResult<T>> {
try {
let query: QueryArrayConfig;
if (typeof sql === "string") {
query = { text: sql, rowMode: "array" };
} else {
query = sql;
}
let pgResult = await poolClient.query(query);
// if the query included multiple queries, only return the results of the
// last one
if (Array.isArray(pgResult)) {
pgResult = pgResult[pgResult.length - 1];
}
return queryResult<T>(pgResult);
} catch (e) {
if (e instanceof pg.DatabaseError && e.code === "23505") {
throw UniqueConstraintError.fromDBError(e);
}
throw e;
}
}
export async function query<T extends object>(
sql: pg.QueryArrayConfig | string,
poolClient: pg.PoolClient,
) {
return await queryInternal<T>(sql, poolClient);
}
export async function queryOne<T extends object>(
sql: pg.QueryArrayConfig | string,
poolClient: pg.PoolClient,
): Promise<T | undefined> {
const result = await queryInternal<T>(sql, poolClient);
const first = result[Symbol.iterator]().next();
if (first.value) {
return first.value as T;
}
return undefined;
}
export async function queryOnlyOne<T extends object>(
sql: pg.QueryArrayConfig | string,
poolClient: pg.PoolClient,
): Promise<T> {
const result = await queryOne<T>(sql, poolClient);
if (!result) {
throw new OnlyOneError("queryExactlyOne returned no rows");
}
return result;
}