pqb 0.0.1

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.
Files changed (122) hide show
  1. package/dist/index.d.ts +3630 -0
  2. package/dist/index.esm.js +4587 -0
  3. package/dist/index.esm.js.map +1 -0
  4. package/dist/index.js +4691 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +59 -0
  7. package/rollup.config.js +35 -0
  8. package/src/adapter.test.ts +10 -0
  9. package/src/adapter.ts +171 -0
  10. package/src/columnSchema/array.ts +21 -0
  11. package/src/columnSchema/boolean.ts +10 -0
  12. package/src/columnSchema/columnType.test.ts +129 -0
  13. package/src/columnSchema/columnType.ts +77 -0
  14. package/src/columnSchema/columnTypes.ts +145 -0
  15. package/src/columnSchema/columnsSchema.test.ts +32 -0
  16. package/src/columnSchema/columnsSchema.ts +100 -0
  17. package/src/columnSchema/commonMethods.ts +130 -0
  18. package/src/columnSchema/dateTime.ts +104 -0
  19. package/src/columnSchema/enum.ts +13 -0
  20. package/src/columnSchema/index.ts +11 -0
  21. package/src/columnSchema/json/array.ts +55 -0
  22. package/src/columnSchema/json/discriminatedUnion.ts +91 -0
  23. package/src/columnSchema/json/enum.ts +29 -0
  24. package/src/columnSchema/json/instanceOf.ts +16 -0
  25. package/src/columnSchema/json/intersection.ts +23 -0
  26. package/src/columnSchema/json/lazy.ts +22 -0
  27. package/src/columnSchema/json/literal.ts +12 -0
  28. package/src/columnSchema/json/map.ts +29 -0
  29. package/src/columnSchema/json/nativeEnum.ts +30 -0
  30. package/src/columnSchema/json/nullable.ts +33 -0
  31. package/src/columnSchema/json/nullish.ts +30 -0
  32. package/src/columnSchema/json/object.ts +206 -0
  33. package/src/columnSchema/json/optional.ts +28 -0
  34. package/src/columnSchema/json/record.ts +40 -0
  35. package/src/columnSchema/json/scalarTypes.ts +117 -0
  36. package/src/columnSchema/json/set.ts +34 -0
  37. package/src/columnSchema/json/tuple.ts +40 -0
  38. package/src/columnSchema/json/typeBase.ts +202 -0
  39. package/src/columnSchema/json/union.ts +16 -0
  40. package/src/columnSchema/json.ts +64 -0
  41. package/src/columnSchema/number.ts +122 -0
  42. package/src/columnSchema/string.ts +222 -0
  43. package/src/columnSchema/utils.ts +27 -0
  44. package/src/common.ts +86 -0
  45. package/src/db.test.ts +67 -0
  46. package/src/db.ts +212 -0
  47. package/src/errors.ts +7 -0
  48. package/src/index.ts +18 -0
  49. package/src/operators.test.ts +608 -0
  50. package/src/operators.ts +177 -0
  51. package/src/query.ts +292 -0
  52. package/src/queryDataUtils.ts +50 -0
  53. package/src/queryMethods/aggregate.test.ts +583 -0
  54. package/src/queryMethods/aggregate.ts +878 -0
  55. package/src/queryMethods/callbacks.test.ts +69 -0
  56. package/src/queryMethods/callbacks.ts +55 -0
  57. package/src/queryMethods/clear.test.ts +64 -0
  58. package/src/queryMethods/clear.ts +58 -0
  59. package/src/queryMethods/columnInfo.test.ts +45 -0
  60. package/src/queryMethods/columnInfo.ts +67 -0
  61. package/src/queryMethods/delete.test.ts +135 -0
  62. package/src/queryMethods/delete.ts +50 -0
  63. package/src/queryMethods/for.test.ts +57 -0
  64. package/src/queryMethods/for.ts +99 -0
  65. package/src/queryMethods/from.test.ts +66 -0
  66. package/src/queryMethods/from.ts +58 -0
  67. package/src/queryMethods/get.test.ts +66 -0
  68. package/src/queryMethods/get.ts +88 -0
  69. package/src/queryMethods/having.test.ts +247 -0
  70. package/src/queryMethods/having.ts +99 -0
  71. package/src/queryMethods/insert.test.ts +555 -0
  72. package/src/queryMethods/insert.ts +453 -0
  73. package/src/queryMethods/join.test.ts +150 -0
  74. package/src/queryMethods/join.ts +508 -0
  75. package/src/queryMethods/json.test.ts +398 -0
  76. package/src/queryMethods/json.ts +259 -0
  77. package/src/queryMethods/log.test.ts +172 -0
  78. package/src/queryMethods/log.ts +123 -0
  79. package/src/queryMethods/queryMethods.test.ts +629 -0
  80. package/src/queryMethods/queryMethods.ts +428 -0
  81. package/src/queryMethods/select.test.ts +479 -0
  82. package/src/queryMethods/select.ts +249 -0
  83. package/src/queryMethods/then.ts +236 -0
  84. package/src/queryMethods/transaction.test.ts +66 -0
  85. package/src/queryMethods/transaction.ts +66 -0
  86. package/src/queryMethods/union.test.ts +59 -0
  87. package/src/queryMethods/union.ts +89 -0
  88. package/src/queryMethods/update.test.ts +417 -0
  89. package/src/queryMethods/update.ts +350 -0
  90. package/src/queryMethods/upsert.test.ts +56 -0
  91. package/src/queryMethods/upsert.ts +43 -0
  92. package/src/queryMethods/where.test.ts +1594 -0
  93. package/src/queryMethods/where.ts +450 -0
  94. package/src/queryMethods/window.test.ts +66 -0
  95. package/src/queryMethods/window.ts +108 -0
  96. package/src/queryMethods/with.test.ts +191 -0
  97. package/src/queryMethods/with.ts +92 -0
  98. package/src/quote.ts +36 -0
  99. package/src/relations.ts +194 -0
  100. package/src/sql/aggregate.ts +80 -0
  101. package/src/sql/columnInfo.ts +22 -0
  102. package/src/sql/common.ts +42 -0
  103. package/src/sql/delete.ts +41 -0
  104. package/src/sql/distinct.ts +19 -0
  105. package/src/sql/fromAndAs.ts +51 -0
  106. package/src/sql/having.ts +140 -0
  107. package/src/sql/index.ts +2 -0
  108. package/src/sql/insert.ts +102 -0
  109. package/src/sql/join.ts +242 -0
  110. package/src/sql/orderBy.ts +41 -0
  111. package/src/sql/select.ts +153 -0
  112. package/src/sql/toSql.ts +153 -0
  113. package/src/sql/truncate.ts +13 -0
  114. package/src/sql/types.ts +355 -0
  115. package/src/sql/update.ts +62 -0
  116. package/src/sql/where.ts +314 -0
  117. package/src/sql/window.ts +38 -0
  118. package/src/sql/with.ts +32 -0
  119. package/src/test-utils.ts +172 -0
  120. package/src/utils.ts +140 -0
  121. package/tsconfig.build.json +6 -0
  122. package/tsconfig.json +8 -0
@@ -0,0 +1,62 @@
1
+ import { Query } from '../query';
2
+ import { UpdateQueryData } from './types';
3
+ import { addValue, q, quoteSchemaAndTable } from './common';
4
+ import { getRaw, isRaw, RawExpression } from '../common';
5
+ import { pushReturningSql } from './insert';
6
+ import { pushWhereSql } from './where';
7
+
8
+ export const pushUpdateSql = (
9
+ sql: string[],
10
+ values: unknown[],
11
+ model: Query,
12
+ query: UpdateQueryData,
13
+ quotedAs: string,
14
+ ) => {
15
+ const quotedTable = quoteSchemaAndTable(query.schema, model.table as string);
16
+ sql.push(`UPDATE ${quotedTable}`);
17
+
18
+ if (query.as && quotedTable !== quotedAs) {
19
+ sql.push(`AS ${quotedAs}`);
20
+ }
21
+
22
+ sql.push('SET');
23
+
24
+ query.data.forEach((item) => {
25
+ if (isRaw(item)) {
26
+ sql.push(getRaw(item, values));
27
+ } else {
28
+ const set: string[] = [];
29
+
30
+ for (const key in item) {
31
+ const value = item[key];
32
+ if (value !== undefined) {
33
+ set.push(`${q(key)} = ${processValue(values, key, value)}`);
34
+ }
35
+ }
36
+
37
+ sql.push(set.join(', '));
38
+ }
39
+ });
40
+
41
+ pushWhereSql(sql, model, query, values, quotedAs);
42
+ pushReturningSql(sql, model, query, values, quotedAs);
43
+ };
44
+
45
+ const processValue = (
46
+ values: unknown[],
47
+ key: string,
48
+ value: Exclude<UpdateQueryData['data'][number], RawExpression>[string],
49
+ ) => {
50
+ if (value && typeof value === 'object') {
51
+ if (isRaw(value)) {
52
+ return getRaw(value, values);
53
+ } else if ('op' in value && 'arg' in value) {
54
+ return `${q(key)} ${(value as { op: string }).op} ${addValue(
55
+ values,
56
+ (value as { arg: unknown }).arg,
57
+ )}`;
58
+ }
59
+ }
60
+
61
+ return addValue(values, value);
62
+ };
@@ -0,0 +1,314 @@
1
+ import { Query } from '../query';
2
+ import {
3
+ JoinItem,
4
+ QueryData,
5
+ WhereInItem,
6
+ WhereItem,
7
+ WhereJsonPathEqualsItem,
8
+ WhereOnItem,
9
+ } from './types';
10
+ import { addValue, q, qc, quoteFullColumn } from './common';
11
+ import { EMPTY_OBJECT, getRaw, isRaw, RawExpression } from '../common';
12
+ import { getQueryAs, MaybeArray, toArray } from '../utils';
13
+ import { processJoinItem } from './join';
14
+
15
+ export const pushWhereSql = (
16
+ sql: string[],
17
+ model: Pick<
18
+ Query,
19
+ 'whereQueryBuilder' | 'onQueryBuilder' | 'as' | 'shape' | 'relations'
20
+ >,
21
+ query: Pick<QueryData, 'as' | 'and' | 'or'>,
22
+ values: unknown[],
23
+ quotedAs?: string,
24
+ otherTableQuotedAs?: string,
25
+ ) => {
26
+ const whereConditions = whereToSql(
27
+ model,
28
+ query,
29
+ values,
30
+ quotedAs,
31
+ otherTableQuotedAs,
32
+ );
33
+ if (whereConditions) {
34
+ sql.push('WHERE', whereConditions);
35
+ }
36
+ };
37
+
38
+ export const whereToSql = (
39
+ model: Pick<
40
+ Query,
41
+ 'whereQueryBuilder' | 'onQueryBuilder' | 'table' | 'shape' | 'relations'
42
+ >,
43
+ query: Pick<QueryData, 'as' | 'and' | 'or'>,
44
+ values: unknown[],
45
+ quotedAs?: string,
46
+ otherTableQuotedAs?: string,
47
+ not?: boolean,
48
+ ): string => {
49
+ const or =
50
+ query.and && query.or
51
+ ? [query.and, ...query.or]
52
+ : query.and
53
+ ? [query.and]
54
+ : query.or;
55
+ if (!or?.length) return '';
56
+
57
+ const ors: string[] = [];
58
+ or.forEach((and) => {
59
+ const ands: string[] = [];
60
+ and.forEach((data) => {
61
+ const prefix = not ? 'NOT ' : '';
62
+
63
+ if (typeof data === 'function') {
64
+ const qb = data(new model.whereQueryBuilder(model.table, query.as));
65
+
66
+ const sql = whereToSql(
67
+ model,
68
+ {
69
+ as: query.as,
70
+ and: qb.query.and,
71
+ or: qb.query.or,
72
+ },
73
+ values,
74
+ quotedAs,
75
+ otherTableQuotedAs,
76
+ not,
77
+ );
78
+ if (sql) ands.push(sql);
79
+ return;
80
+ }
81
+
82
+ if ('prototype' in data || '__model' in data) {
83
+ const query = data as Query;
84
+ const sql = whereToSql(
85
+ query,
86
+ query.query || EMPTY_OBJECT,
87
+ values,
88
+ query.table && q(query.table),
89
+ );
90
+ if (sql) {
91
+ ands.push(`${prefix}(${sql})`);
92
+ }
93
+ return;
94
+ }
95
+
96
+ if (isRaw(data)) {
97
+ ands.push(`${prefix}(${getRaw(data, values)})`);
98
+ return;
99
+ }
100
+
101
+ for (const key in data) {
102
+ const value = (data as Record<string, unknown>)[key];
103
+ const handler = whereHandlers[key];
104
+ if (handler) {
105
+ handler(
106
+ value,
107
+ ands,
108
+ prefix,
109
+ model,
110
+ query,
111
+ values,
112
+ quotedAs,
113
+ otherTableQuotedAs,
114
+ not,
115
+ );
116
+ } else if (
117
+ typeof value === 'object' &&
118
+ value !== null &&
119
+ value !== undefined
120
+ ) {
121
+ if (isRaw(value)) {
122
+ ands.push(
123
+ `${prefix}${quoteFullColumn(key, quotedAs)} = ${getRaw(
124
+ value,
125
+ values,
126
+ )}`,
127
+ );
128
+ } else {
129
+ const column = model.shape[key];
130
+ if (!column) {
131
+ // TODO: custom error classes
132
+ throw new Error(`Unknown column ${key} provided to condition`);
133
+ }
134
+
135
+ for (const op in value) {
136
+ const operator = column.operators[op];
137
+ if (!operator) {
138
+ // TODO: custom error classes
139
+ throw new Error(`Unknown operator ${op} provided to condition`);
140
+ }
141
+
142
+ ands.push(
143
+ `${prefix}${operator(
144
+ qc(key, quotedAs),
145
+ value[op as keyof typeof value],
146
+ values,
147
+ )}`,
148
+ );
149
+ }
150
+ }
151
+ } else {
152
+ ands.push(
153
+ `${prefix}${quoteFullColumn(key, quotedAs)} ${
154
+ value === null ? 'IS NULL' : `= ${addValue(values, value)}`
155
+ }`,
156
+ );
157
+ }
158
+ }
159
+ });
160
+
161
+ ors.push(ands.join(' AND '));
162
+ });
163
+
164
+ return ors.join(' OR ');
165
+ };
166
+
167
+ const whereHandlers: Record<
168
+ string,
169
+ | ((
170
+ value: unknown,
171
+ ands: string[],
172
+ prefix: string,
173
+ ...params: Parameters<typeof whereToSql>
174
+ ) => void)
175
+ | undefined
176
+ > = {
177
+ AND(value, ands, _, model, _q, values, quotedAs, otherTableQuotedAs, not) {
178
+ const sql = whereToSql(
179
+ model,
180
+ {
181
+ and: toArray(value as MaybeArray<WhereItem>),
182
+ },
183
+ values,
184
+ quotedAs,
185
+ otherTableQuotedAs,
186
+ not,
187
+ );
188
+ if (sql) ands.push(sql);
189
+ },
190
+ OR(value, ands, _, model, _q, values, quotedAs, otherTableQuotedAs, not) {
191
+ const sql = whereToSql(
192
+ model,
193
+ {
194
+ or: (value as MaybeArray<WhereItem>[]).map(toArray),
195
+ },
196
+ values,
197
+ quotedAs,
198
+ otherTableQuotedAs,
199
+ not,
200
+ );
201
+ if (sql) ands.push(sql);
202
+ },
203
+ NOT(value, ands, _, model, _q, values, quotedAs, otherTableQuotedAs, not) {
204
+ const sql = whereToSql(
205
+ model,
206
+ {
207
+ and: toArray(value as MaybeArray<WhereItem>),
208
+ },
209
+ values,
210
+ quotedAs,
211
+ otherTableQuotedAs,
212
+ !not,
213
+ );
214
+ if (sql) ands.push(sql);
215
+ },
216
+ ON(value, ands, prefix, _, _q, values, quotedAs, otherTableQuotedAs) {
217
+ if (Array.isArray(value)) {
218
+ const item = value as WhereJsonPathEqualsItem;
219
+ const leftColumn = quoteFullColumn(item[0], quotedAs);
220
+ const leftPath = item[1];
221
+ const rightColumn = quoteFullColumn(item[2], otherTableQuotedAs);
222
+ const rightPath = item[3];
223
+
224
+ ands.push(
225
+ `${prefix}jsonb_path_query_first(${leftColumn}, ${addValue(
226
+ values,
227
+ leftPath,
228
+ )}) = jsonb_path_query_first(${rightColumn}, ${addValue(
229
+ values,
230
+ rightPath,
231
+ )})`,
232
+ );
233
+ } else {
234
+ const item = value as WhereOnItem;
235
+ const leftColumn = quoteFullColumn(
236
+ item.on[0],
237
+ typeof item.joinTo === 'string'
238
+ ? q(item.joinTo)
239
+ : q(getQueryAs(item.joinTo)),
240
+ );
241
+
242
+ const joinTo =
243
+ typeof item.joinFrom === 'string'
244
+ ? item.joinFrom
245
+ : q(getQueryAs(item.joinFrom));
246
+
247
+ const [op, rightColumn] =
248
+ item.on.length === 2
249
+ ? ['=', quoteFullColumn(item.on[1], joinTo)]
250
+ : [item.on[1], quoteFullColumn(item.on[2], joinTo)];
251
+
252
+ ands.push(`${prefix}${leftColumn} ${op} ${rightColumn}`);
253
+ }
254
+ },
255
+ IN(value, ands, prefix, _, _q, values, quotedAs) {
256
+ toArray(value as MaybeArray<WhereInItem>).forEach((item) => {
257
+ pushIn(ands, prefix, quotedAs, values, item);
258
+ });
259
+ },
260
+ EXISTS(value, ands, prefix, model, query, values, quotedAs) {
261
+ const joinItems = Array.isArray((value as unknown[])[0]) ? value : [value];
262
+ (joinItems as JoinItem['args'][]).forEach((item) => {
263
+ const { target, conditions } = processJoinItem(
264
+ model,
265
+ query,
266
+ values,
267
+ item,
268
+ quotedAs,
269
+ );
270
+
271
+ ands.push(
272
+ `${prefix}EXISTS (SELECT 1 FROM ${target} WHERE ${conditions} LIMIT 1)`,
273
+ );
274
+ });
275
+ },
276
+ };
277
+
278
+ const pushIn = (
279
+ ands: string[],
280
+ prefix: string,
281
+ quotedAs: string | undefined,
282
+ values: unknown[],
283
+ arg: {
284
+ columns: string[];
285
+ values: unknown[][] | Query | RawExpression;
286
+ },
287
+ ) => {
288
+ let value: string;
289
+
290
+ if (Array.isArray(arg.values)) {
291
+ value = `${arg.values
292
+ .map(
293
+ (arr) => `(${arr.map((value) => addValue(values, value)).join(', ')})`,
294
+ )
295
+ .join(', ')}`;
296
+
297
+ if (arg.columns.length > 1) value = `(${value})`;
298
+ } else if (isRaw(arg.values)) {
299
+ value = getRaw(arg.values, values);
300
+ } else {
301
+ const sql = arg.values.toSql(values);
302
+ value = `(${sql.text})`;
303
+ }
304
+
305
+ const columnsSql = arg.columns
306
+ .map((column) => quoteFullColumn(column, quotedAs))
307
+ .join(', ');
308
+
309
+ ands.push(
310
+ `${prefix}${
311
+ arg.columns.length > 1 ? `(${columnsSql})` : columnsSql
312
+ } IN ${value}`,
313
+ );
314
+ };
@@ -0,0 +1,38 @@
1
+ import { Query } from '../query';
2
+ import { WindowDeclaration } from './types';
3
+ import { getRaw, isRaw, RawExpression } from '../common';
4
+ import { expressionToSql, q } from './common';
5
+ import { orderByToSql } from './orderBy';
6
+
7
+ export const windowToSql = <T extends Query>(
8
+ window: T['windows'][number] | WindowDeclaration | RawExpression,
9
+ values: unknown[],
10
+ quotedAs?: string,
11
+ ) => {
12
+ if (typeof window === 'object') {
13
+ if (isRaw(window)) {
14
+ return `(${getRaw(window, values)})`;
15
+ } else {
16
+ const sql: string[] = [];
17
+ if (window.partitionBy) {
18
+ sql.push(
19
+ `PARTITION BY ${
20
+ Array.isArray(window.partitionBy)
21
+ ? window.partitionBy
22
+ .map((partitionBy) =>
23
+ expressionToSql(partitionBy, values, quotedAs),
24
+ )
25
+ .join(', ')
26
+ : expressionToSql(window.partitionBy, values, quotedAs)
27
+ }`,
28
+ );
29
+ }
30
+ if (window.order) {
31
+ sql.push(`ORDER BY ${orderByToSql(window.order, values, quotedAs)}`);
32
+ }
33
+ return `(${sql.join(' ')})`;
34
+ }
35
+ } else {
36
+ return q(window as string);
37
+ }
38
+ };
@@ -0,0 +1,32 @@
1
+ import { QueryData } from './types';
2
+ import { q } from './common';
3
+ import { isRaw, getRaw } from '../common';
4
+
5
+ export const pushWithSql = (
6
+ sql: string[],
7
+ values: unknown[],
8
+ withData: Exclude<QueryData['with'], undefined>,
9
+ ) => {
10
+ withData.forEach((withItem) => {
11
+ const [name, options, query] = withItem;
12
+
13
+ let inner: string;
14
+ if (isRaw(query)) {
15
+ inner = getRaw(query, values);
16
+ } else {
17
+ inner = query.toSql(values).text;
18
+ }
19
+
20
+ sql.push(
21
+ `WITH ${options.recursive ? 'RECURSIVE ' : ''}${q(name)}${
22
+ options.columns ? `(${options.columns.map(q).join(', ')})` : ''
23
+ } AS ${
24
+ options.materialized
25
+ ? 'MATERIALIZED '
26
+ : options.notMaterialized
27
+ ? 'NOT MATERIALIZED '
28
+ : ''
29
+ }(${inner})`,
30
+ );
31
+ });
32
+ };
@@ -0,0 +1,172 @@
1
+ import { Query } from './query';
2
+ import { createDb } from './db';
3
+ import {
4
+ patchPgForTransactions,
5
+ rollbackTransaction,
6
+ startTransaction,
7
+ unpatchPgForTransactions,
8
+ } from 'pg-transactional-tests';
9
+ import { Client } from 'pg';
10
+ import { quote } from './quote';
11
+ import { columnTypes } from './columnSchema';
12
+ import { MaybeArray, toArray } from './utils';
13
+ import { Adapter } from './adapter';
14
+
15
+ export const dbOptions = { connectionString: process.env.DATABASE_URL };
16
+
17
+ export const dbClient = new Client(dbOptions);
18
+
19
+ export const adapter = new Adapter(dbOptions);
20
+
21
+ export const db = createDb({
22
+ adapter,
23
+ columnTypes: {
24
+ ...columnTypes,
25
+ timestamp() {
26
+ return columnTypes.timestamp().parse((input) => new Date(input));
27
+ },
28
+ },
29
+ });
30
+
31
+ export const User = db('user', (t) => ({
32
+ id: t.serial().primaryKey(),
33
+ name: t.text(),
34
+ password: t.text(),
35
+ picture: t.text().nullable(),
36
+ data: t
37
+ .json((j) =>
38
+ j.object({
39
+ name: j.string(),
40
+ tags: j.string().array(),
41
+ }),
42
+ )
43
+ .nullable(),
44
+ age: t.integer().nullable(),
45
+ active: t.boolean().nullable(),
46
+ createdAt: t.timestamp(),
47
+ updatedAt: t.timestamp(),
48
+ }));
49
+
50
+ export const Profile = db('profile', (t) => ({
51
+ id: t.serial().primaryKey(),
52
+ userId: t.integer(),
53
+ bio: t.text().nullable(),
54
+ createdAt: t.timestamp(),
55
+ updatedAt: t.timestamp(),
56
+ }));
57
+
58
+ export const Chat = db('chat', (t) => ({
59
+ id: t.serial().primaryKey(),
60
+ title: t.text(),
61
+ createdAt: t.timestamp(),
62
+ updatedAt: t.timestamp(),
63
+ }));
64
+
65
+ export const Message = db('message', (t) => ({
66
+ id: t.serial().primaryKey(),
67
+ chatId: t.integer(),
68
+ authorId: t.integer(),
69
+ text: t.text(),
70
+ createdAt: t.timestamp(),
71
+ updatedAt: t.timestamp(),
72
+ }));
73
+
74
+ export const line = (s: string) =>
75
+ s.trim().replace(/\s+/g, ' ').replace(/\( /g, '(').replace(/ \)/g, ')');
76
+
77
+ export const expectSql = (
78
+ sql: MaybeArray<{ text: string; values: unknown[] }>,
79
+ text: string,
80
+ values: unknown[] = [],
81
+ ) => {
82
+ toArray(sql).forEach((item) => {
83
+ expect(item.text).toBe(line(text));
84
+ expect(item.values).toEqual(values);
85
+ });
86
+ };
87
+
88
+ export const expectQueryNotMutated = (q: Query) => {
89
+ expectSql(q.toSql(), `SELECT * FROM "${q.table}"`);
90
+ };
91
+
92
+ export const expectMatchObjectWithTimestamps = (
93
+ actual: { createdAt: Date; updatedAt: Date },
94
+ expected: { createdAt: Date; updatedAt: Date },
95
+ ) => {
96
+ expect({
97
+ ...actual,
98
+ createdAt: actual.createdAt.toISOString(),
99
+ updatedAt: actual.updatedAt.toISOString(),
100
+ }).toMatchObject({
101
+ ...expected,
102
+ createdAt: expected.createdAt.toISOString(),
103
+ updatedAt: expected.updatedAt.toISOString(),
104
+ });
105
+ };
106
+
107
+ export type AssertEqual<T, Expected> = [T] extends [Expected]
108
+ ? [Expected] extends [T]
109
+ ? true
110
+ : false
111
+ : false;
112
+
113
+ export const insert = async <
114
+ T extends Record<string, unknown> & { id: number },
115
+ >(
116
+ table: string,
117
+ record: T,
118
+ ): Promise<T> => {
119
+ const columns = Object.keys(record);
120
+ const result = await dbClient.query<{ id: number }>(
121
+ `INSERT INTO "${table}"(${columns
122
+ .map((column) => `"${column}"`)
123
+ .join(', ')}) VALUES (${columns
124
+ .map((column) => quote(record[column]))
125
+ .join(', ')}) RETURNING "id"`,
126
+ );
127
+
128
+ record.id = result.rows[0].id;
129
+ return record;
130
+ };
131
+
132
+ export const now = new Date();
133
+ export const userData = {
134
+ name: 'name',
135
+ password: 'password',
136
+ createdAt: now,
137
+ updatedAt: now,
138
+ };
139
+
140
+ export const profileData = {
141
+ bio: 'text',
142
+ createdAt: now,
143
+ updatedAt: now,
144
+ };
145
+
146
+ export const chatData = {
147
+ title: 'title',
148
+ createdAt: now,
149
+ updatedAt: now,
150
+ };
151
+
152
+ export const messageData = {
153
+ text: 'text',
154
+ createdAt: now,
155
+ updatedAt: now,
156
+ };
157
+
158
+ export const useTestDatabase = () => {
159
+ beforeAll(() => {
160
+ patchPgForTransactions();
161
+ });
162
+ beforeEach(async () => {
163
+ await startTransaction(dbClient);
164
+ });
165
+ afterEach(async () => {
166
+ await rollbackTransaction(dbClient);
167
+ });
168
+ afterAll(async () => {
169
+ unpatchPgForTransactions();
170
+ await dbClient.end();
171
+ });
172
+ };