wabe-postgres 0.5.3 → 0.5.5
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/dist/index.d.ts +74 -0
- package/dist/index.js +5373 -0
- package/package.json +5 -2
- package/src/index.test.ts +0 -2183
- package/src/index.ts +0 -766
- package/tsconfig.json +0 -31
- package/utils/preload.ts +0 -5
- package/utils/testHelper.ts +0 -126
package/src/index.ts
DELETED
|
@@ -1,766 +0,0 @@
|
|
|
1
|
-
import { Pool } from 'pg'
|
|
2
|
-
import type {
|
|
3
|
-
AdapterOptions,
|
|
4
|
-
DatabaseAdapter,
|
|
5
|
-
GetObjectOptions,
|
|
6
|
-
CreateObjectOptions,
|
|
7
|
-
UpdateObjectOptions,
|
|
8
|
-
GetObjectsOptions,
|
|
9
|
-
CreateObjectsOptions,
|
|
10
|
-
UpdateObjectsOptions,
|
|
11
|
-
DeleteObjectsOptions,
|
|
12
|
-
WhereType,
|
|
13
|
-
DeleteObjectOptions,
|
|
14
|
-
OutputType,
|
|
15
|
-
CountOptions,
|
|
16
|
-
OrderType,
|
|
17
|
-
WabeTypes,
|
|
18
|
-
TypeField,
|
|
19
|
-
SchemaInterface,
|
|
20
|
-
} from 'wabe'
|
|
21
|
-
|
|
22
|
-
const getSQLColumnCreateTableFromType = <T extends WabeTypes>(type: TypeField<T>) => {
|
|
23
|
-
switch (type.type) {
|
|
24
|
-
case 'String':
|
|
25
|
-
return `TEXT${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
|
|
26
|
-
case 'Int':
|
|
27
|
-
return `INT${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
|
|
28
|
-
case 'Float':
|
|
29
|
-
return `FLOAT${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
|
|
30
|
-
case 'Boolean':
|
|
31
|
-
return `BOOLEAN${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
|
|
32
|
-
case 'Email':
|
|
33
|
-
return `VARCHAR(255)${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
|
|
34
|
-
case 'Phone':
|
|
35
|
-
return `VARCHAR(255)${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
|
|
36
|
-
case 'Date':
|
|
37
|
-
// Because we store date in iso string in database
|
|
38
|
-
return `VARCHAR(255)${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
|
|
39
|
-
case 'File':
|
|
40
|
-
return `JSONB${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
|
|
41
|
-
case 'Object':
|
|
42
|
-
return `JSONB${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
|
|
43
|
-
case 'Array':
|
|
44
|
-
return `JSONB${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
|
|
45
|
-
case 'Pointer':
|
|
46
|
-
return `VARCHAR(255)${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
|
|
47
|
-
case 'Relation':
|
|
48
|
-
return `JSONB${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
|
|
49
|
-
default:
|
|
50
|
-
// Enum or scalar
|
|
51
|
-
return `VARCHAR(255)${type.required ? ' NOT NULL' : ' DEFAULT NULL'}`
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** Resolve where operators (e.g. { string: { equalTo: 'user1' } }) to plain values for JSON. */
|
|
56
|
-
const resolveWhereToPlain = (obj: unknown): unknown => {
|
|
57
|
-
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return obj
|
|
58
|
-
return Object.fromEntries(
|
|
59
|
-
Object.entries(obj).map(([key, value]) => [
|
|
60
|
-
key,
|
|
61
|
-
value && typeof value === 'object' && !Array.isArray(value) && 'equalTo' in value
|
|
62
|
-
? (value as { equalTo: unknown }).equalTo
|
|
63
|
-
: resolveWhereToPlain(value),
|
|
64
|
-
]),
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export const buildPostgresOrderQuery = <
|
|
69
|
-
T extends WabeTypes,
|
|
70
|
-
K extends keyof T['types'],
|
|
71
|
-
U extends keyof T['types'][K],
|
|
72
|
-
>(
|
|
73
|
-
order?: OrderType<T, K, U>,
|
|
74
|
-
): string => {
|
|
75
|
-
if (!order) return ''
|
|
76
|
-
|
|
77
|
-
const objectKeys = Object.keys(order) as Array<keyof OrderType<T, K, U>>
|
|
78
|
-
|
|
79
|
-
if (objectKeys.length === 0) return ''
|
|
80
|
-
|
|
81
|
-
const orderClauses = objectKeys.map(
|
|
82
|
-
(key) => `${key === 'id' ? '_id' : String(key)} ${order[key]}`,
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
return `ORDER BY ${orderClauses.join(', ')}`
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export const buildPostgresWhereQueryAndValues = <T extends WabeTypes, K extends keyof T['types']>(
|
|
89
|
-
where?: WhereType<T, K>,
|
|
90
|
-
startParamIndex = 1,
|
|
91
|
-
parentKey?: string,
|
|
92
|
-
): { query: string; values: any[]; paramIndex: number } => {
|
|
93
|
-
if (!where) return { query: '', values: [], paramIndex: startParamIndex }
|
|
94
|
-
|
|
95
|
-
const objectKeys = Object.keys(where) as Array<keyof WhereType<T, K>>
|
|
96
|
-
|
|
97
|
-
if (objectKeys.length === 0) return { query: '', values: [], paramIndex: startParamIndex }
|
|
98
|
-
|
|
99
|
-
const acc = objectKeys.reduce(
|
|
100
|
-
(acc, key) => {
|
|
101
|
-
const value = where[key]
|
|
102
|
-
const keyToWrite = key === 'id' ? '_id' : String(key)
|
|
103
|
-
|
|
104
|
-
const fullKey = parentKey ? `${parentKey}->>'${keyToWrite}'` : `"${keyToWrite}"`
|
|
105
|
-
|
|
106
|
-
const simpleFullKey = parentKey ? `${parentKey}->'${keyToWrite}'` : `"${keyToWrite}"`
|
|
107
|
-
|
|
108
|
-
if ('equalTo' in (value || {})) {
|
|
109
|
-
if (value?.equalTo === null || value?.equalTo === undefined) {
|
|
110
|
-
acc.conditions.push(`${fullKey} IS NULL`)
|
|
111
|
-
return acc
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
acc.conditions.push(`${fullKey} = $${acc.paramIndex}`)
|
|
115
|
-
acc.values.push(
|
|
116
|
-
Array.isArray(value.equalTo) ? JSON.stringify(value.equalTo) : value.equalTo,
|
|
117
|
-
)
|
|
118
|
-
acc.paramIndex++
|
|
119
|
-
return acc
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if ('notEqualTo' in (value || {})) {
|
|
123
|
-
if (value?.notEqualTo === null || value?.notEqualTo === undefined) {
|
|
124
|
-
acc.conditions.push(`${fullKey} IS NOT NULL`)
|
|
125
|
-
return acc
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
acc.conditions.push(`${fullKey} IS DISTINCT FROM $${acc.paramIndex}`)
|
|
129
|
-
acc.values.push(
|
|
130
|
-
Array.isArray(value.notEqualTo) ? JSON.stringify(value.notEqualTo) : value.notEqualTo,
|
|
131
|
-
)
|
|
132
|
-
acc.paramIndex++
|
|
133
|
-
return acc
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (value?.exists === true) {
|
|
137
|
-
if (parentKey) {
|
|
138
|
-
acc.conditions.push(
|
|
139
|
-
`${parentKey} IS NOT NULL
|
|
140
|
-
AND ${parentKey} ? '${keyToWrite}'
|
|
141
|
-
AND ${parentKey}->'${keyToWrite}' IS NOT NULL
|
|
142
|
-
AND ${parentKey}->'${keyToWrite}' <> 'null'::jsonb`,
|
|
143
|
-
)
|
|
144
|
-
} else {
|
|
145
|
-
acc.conditions.push(`"${keyToWrite}" IS NOT NULL`)
|
|
146
|
-
}
|
|
147
|
-
return acc
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (value?.exists === false) {
|
|
151
|
-
if (parentKey) {
|
|
152
|
-
acc.conditions.push(
|
|
153
|
-
`${parentKey} IS NULL
|
|
154
|
-
OR NOT (${parentKey} ? '${keyToWrite}')
|
|
155
|
-
OR ${parentKey}->'${keyToWrite}' IS NULL
|
|
156
|
-
OR ${parentKey}->'${keyToWrite}' = 'null'::jsonb`,
|
|
157
|
-
)
|
|
158
|
-
} else {
|
|
159
|
-
acc.conditions.push(`"${keyToWrite}" IS NULL`)
|
|
160
|
-
}
|
|
161
|
-
return acc
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (value?.greaterThan || value?.greaterThan === null) {
|
|
165
|
-
acc.conditions.push(`${fullKey} > $${acc.paramIndex}`)
|
|
166
|
-
acc.values.push(value.greaterThan)
|
|
167
|
-
acc.paramIndex++
|
|
168
|
-
return acc
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (value?.greaterThanOrEqualTo || value?.greaterThanOrEqualTo === null) {
|
|
172
|
-
acc.conditions.push(`${fullKey} >= $${acc.paramIndex}`)
|
|
173
|
-
acc.values.push(value.greaterThanOrEqualTo)
|
|
174
|
-
acc.paramIndex++
|
|
175
|
-
return acc
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (value?.lessThan || value?.lessThan === null) {
|
|
179
|
-
acc.conditions.push(`${fullKey} < $${acc.paramIndex}`)
|
|
180
|
-
acc.values.push(value.lessThan)
|
|
181
|
-
acc.paramIndex++
|
|
182
|
-
return acc
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (value?.lessThanOrEqualTo || value?.lessThanOrEqualTo === null) {
|
|
186
|
-
acc.conditions.push(`${fullKey} <= $${acc.paramIndex}`)
|
|
187
|
-
acc.values.push(value.lessThanOrEqualTo)
|
|
188
|
-
acc.paramIndex++
|
|
189
|
-
return acc
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (value?.in && Array.isArray(value.in) && value.in.length > 0) {
|
|
193
|
-
const placeholders = value.in.map(() => `$${acc.paramIndex++}`).join(', ')
|
|
194
|
-
|
|
195
|
-
acc.conditions.push(`${fullKey} IN (${placeholders})`)
|
|
196
|
-
|
|
197
|
-
acc.values.push(...value.in)
|
|
198
|
-
return acc
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (value?.notIn && Array.isArray(value.notIn) && value.notIn.length > 0) {
|
|
202
|
-
const placeholders = value.notIn.map(() => `$${acc.paramIndex++}`).join(', ')
|
|
203
|
-
acc.conditions.push(`${fullKey} NOT IN (${placeholders})`)
|
|
204
|
-
acc.values.push(...value.notIn)
|
|
205
|
-
return acc
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (value?.contains) {
|
|
209
|
-
// Simple access on json field because contains is use for array or object column
|
|
210
|
-
acc.conditions.push(`${simpleFullKey} @> $${acc.paramIndex}`)
|
|
211
|
-
acc.values.push(
|
|
212
|
-
Array.isArray(value.contains)
|
|
213
|
-
? JSON.stringify(value.contains)
|
|
214
|
-
: JSON.stringify([value.contains]),
|
|
215
|
-
)
|
|
216
|
-
acc.paramIndex++
|
|
217
|
-
return acc
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (value?.notContains) {
|
|
221
|
-
// Simple access on json field because contains is use for array or object column
|
|
222
|
-
acc.conditions.push(`NOT (${simpleFullKey} @> $${acc.paramIndex})`)
|
|
223
|
-
const toJson = (v: unknown) => (Array.isArray(v) ? JSON.stringify(v) : JSON.stringify([v]))
|
|
224
|
-
acc.values.push(
|
|
225
|
-
typeof value.notContains === 'object' && !Array.isArray(value.notContains)
|
|
226
|
-
? toJson(resolveWhereToPlain(value.notContains))
|
|
227
|
-
: toJson(value.notContains),
|
|
228
|
-
)
|
|
229
|
-
acc.paramIndex++
|
|
230
|
-
return acc
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (key === 'OR' && Array.isArray(value) && value.length > 0) {
|
|
234
|
-
const orConditions = value.map((orWhere) => {
|
|
235
|
-
const {
|
|
236
|
-
query,
|
|
237
|
-
values: orValues,
|
|
238
|
-
paramIndex: newParamIndex,
|
|
239
|
-
} = buildPostgresWhereQueryAndValues(orWhere, acc.paramIndex)
|
|
240
|
-
acc.paramIndex = newParamIndex
|
|
241
|
-
return { query, values: orValues }
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
const orQueries = orConditions.filter(({ query }) => query).map(({ query }) => `${query}`)
|
|
245
|
-
|
|
246
|
-
if (orQueries.length > 0) {
|
|
247
|
-
acc.conditions.push(`(${orQueries.join(' OR ')})`)
|
|
248
|
-
|
|
249
|
-
for (const orCondition of orConditions) {
|
|
250
|
-
acc.values.push(...orCondition.values)
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
return acc
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (key === 'AND' && Array.isArray(value) && value.length > 0) {
|
|
257
|
-
const andConditions = value.map((andWhere) => {
|
|
258
|
-
const {
|
|
259
|
-
query,
|
|
260
|
-
values: andValues,
|
|
261
|
-
paramIndex: newParamIndex,
|
|
262
|
-
} = buildPostgresWhereQueryAndValues(andWhere, acc.paramIndex)
|
|
263
|
-
|
|
264
|
-
acc.paramIndex = newParamIndex
|
|
265
|
-
|
|
266
|
-
return { query, values: andValues }
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
const andQueries = andConditions.filter(({ query }) => query).map(({ query }) => `${query}`)
|
|
270
|
-
|
|
271
|
-
if (andQueries.length > 0) {
|
|
272
|
-
acc.conditions.push(`(${andQueries.join(' AND ')})`)
|
|
273
|
-
|
|
274
|
-
for (const andCondition of andConditions) {
|
|
275
|
-
acc.values.push(...andCondition.values)
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
return acc
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (typeof value === 'object') {
|
|
282
|
-
const nestedResult = buildPostgresWhereQueryAndValues(
|
|
283
|
-
value as any,
|
|
284
|
-
acc.paramIndex,
|
|
285
|
-
simpleFullKey,
|
|
286
|
-
)
|
|
287
|
-
if (nestedResult.query) {
|
|
288
|
-
acc.conditions.push(`(${nestedResult.query})`)
|
|
289
|
-
acc.values.push(...nestedResult.values)
|
|
290
|
-
acc.paramIndex = nestedResult.paramIndex
|
|
291
|
-
}
|
|
292
|
-
return acc
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return acc
|
|
296
|
-
},
|
|
297
|
-
{
|
|
298
|
-
conditions: [] as string[],
|
|
299
|
-
values: [] as any[],
|
|
300
|
-
paramIndex: startParamIndex,
|
|
301
|
-
},
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
return {
|
|
305
|
-
query: acc.conditions.length > 0 ? acc.conditions.join(' AND ') : '',
|
|
306
|
-
values: acc.values,
|
|
307
|
-
paramIndex: acc.paramIndex,
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const computeValuesFromData = (data: Record<string, any>) => {
|
|
312
|
-
return Object.values(data).map((value) => {
|
|
313
|
-
if (Array.isArray(value)) return JSON.stringify(value)
|
|
314
|
-
|
|
315
|
-
return value
|
|
316
|
-
})
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
export class PostgresAdapter<T extends WabeTypes> implements DatabaseAdapter<T> {
|
|
320
|
-
public options: AdapterOptions
|
|
321
|
-
public postgresPool: Pool
|
|
322
|
-
public pool: Pool
|
|
323
|
-
|
|
324
|
-
constructor(options: AdapterOptions) {
|
|
325
|
-
this.options = options
|
|
326
|
-
this.postgresPool = new Pool({
|
|
327
|
-
connectionString: `${options.databaseUrl}/postgres`,
|
|
328
|
-
})
|
|
329
|
-
this.pool = new Pool({
|
|
330
|
-
// TODO: Improve this to support ending with a slash and more.
|
|
331
|
-
// Need to parse to detect if user already added the database name
|
|
332
|
-
connectionString: `${this.options.databaseUrl}/${this.options.databaseName}`,
|
|
333
|
-
})
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
async initializeDatabase(schema: SchemaInterface<T>) {
|
|
337
|
-
// We create the database with the pool on postgres database
|
|
338
|
-
const client = await this.postgresPool.connect()
|
|
339
|
-
|
|
340
|
-
try {
|
|
341
|
-
const res = await client.query('SELECT datname FROM pg_database WHERE datname = $1', [
|
|
342
|
-
this.options.databaseName,
|
|
343
|
-
])
|
|
344
|
-
|
|
345
|
-
if (res.rowCount === 0) await client.query(`CREATE DATABASE "${this.options.databaseName}"`)
|
|
346
|
-
|
|
347
|
-
await Promise.all(
|
|
348
|
-
(schema.classes || []).map((classSchema) => {
|
|
349
|
-
return this.createClassIfNotExist(classSchema.name, schema)
|
|
350
|
-
}),
|
|
351
|
-
)
|
|
352
|
-
} finally {
|
|
353
|
-
client.release()
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
async clearDatabase() {
|
|
358
|
-
const client = await this.pool.connect()
|
|
359
|
-
|
|
360
|
-
try {
|
|
361
|
-
const tablesResult = await client.query(`
|
|
362
|
-
SELECT tablename
|
|
363
|
-
FROM pg_catalog.pg_tables
|
|
364
|
-
WHERE schemaname = 'public' AND tablename != 'Role'
|
|
365
|
-
`)
|
|
366
|
-
|
|
367
|
-
await Promise.all(
|
|
368
|
-
tablesResult.rows.map((table) =>
|
|
369
|
-
client.query(`TRUNCATE TABLE "${table.tablename}" CASCADE`),
|
|
370
|
-
),
|
|
371
|
-
)
|
|
372
|
-
} finally {
|
|
373
|
-
client.release()
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
async close() {
|
|
378
|
-
if (!this.pool.ended) await this.pool.end()
|
|
379
|
-
if (!this.postgresPool.ended) await this.postgresPool.end()
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
async createClassIfNotExist(className: keyof T['types'], schema: SchemaInterface<T>) {
|
|
383
|
-
const schemaClass = schema?.classes?.find((currentClass) => currentClass.name === className)
|
|
384
|
-
|
|
385
|
-
if (!schemaClass) throw new Error(`${String(className)} is not defined in schema`)
|
|
386
|
-
|
|
387
|
-
const client = await this.pool.connect()
|
|
388
|
-
|
|
389
|
-
try {
|
|
390
|
-
const columns = Object.entries(schemaClass.fields)
|
|
391
|
-
|
|
392
|
-
const createTableParams = columns
|
|
393
|
-
.map(([fieldName, field]) => {
|
|
394
|
-
const sqlColumnCreateTable = getSQLColumnCreateTableFromType(field)
|
|
395
|
-
|
|
396
|
-
return `"${fieldName}" ${sqlColumnCreateTable}`
|
|
397
|
-
})
|
|
398
|
-
.join(', ')
|
|
399
|
-
|
|
400
|
-
// Create the table if it doesn't exist
|
|
401
|
-
await client.query(`
|
|
402
|
-
CREATE TABLE IF NOT EXISTS "${String(className)}" (
|
|
403
|
-
_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
404
|
-
${createTableParams}
|
|
405
|
-
)
|
|
406
|
-
`)
|
|
407
|
-
|
|
408
|
-
// Update the table if a column is added after the first launch
|
|
409
|
-
const res = await client.query(
|
|
410
|
-
`
|
|
411
|
-
SELECT column_name
|
|
412
|
-
FROM information_schema.columns
|
|
413
|
-
WHERE table_name = $1
|
|
414
|
-
`,
|
|
415
|
-
[String(className)],
|
|
416
|
-
)
|
|
417
|
-
|
|
418
|
-
const existingColumns = res.rows.map((row) => row.column_name)
|
|
419
|
-
|
|
420
|
-
// Add missing columns to the table
|
|
421
|
-
await Promise.all(
|
|
422
|
-
columns
|
|
423
|
-
.filter(([fieldName]) => !existingColumns.includes(fieldName))
|
|
424
|
-
.map(([fieldName, field]) => {
|
|
425
|
-
const sqlColumnCreateTable = getSQLColumnCreateTableFromType(field)
|
|
426
|
-
return client.query(`
|
|
427
|
-
ALTER TABLE "${String(className)}"
|
|
428
|
-
ADD COLUMN "${fieldName}" ${sqlColumnCreateTable}
|
|
429
|
-
`)
|
|
430
|
-
}),
|
|
431
|
-
)
|
|
432
|
-
|
|
433
|
-
// Create indexes if they don't exist
|
|
434
|
-
const indexes = schemaClass.indexes || []
|
|
435
|
-
|
|
436
|
-
await Promise.all(
|
|
437
|
-
indexes.map((index) => {
|
|
438
|
-
const indexType = index.unique ? 'UNIQUE' : ''
|
|
439
|
-
const indexDirection = index.order === 'ASC' ? 'ASC' : 'DESC'
|
|
440
|
-
|
|
441
|
-
return client.query(`
|
|
442
|
-
CREATE ${indexType} INDEX IF NOT EXISTS
|
|
443
|
-
idx_${String(className)}_${String(index.field)}
|
|
444
|
-
ON "${String(className)}" (${String(index.field)}' ${indexDirection})
|
|
445
|
-
`)
|
|
446
|
-
}),
|
|
447
|
-
)
|
|
448
|
-
|
|
449
|
-
return className
|
|
450
|
-
} finally {
|
|
451
|
-
client.release()
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
async count<K extends keyof T['types']>(params: CountOptions<T, K>) {
|
|
456
|
-
const { className, where } = params
|
|
457
|
-
|
|
458
|
-
const client = await this.pool.connect()
|
|
459
|
-
|
|
460
|
-
try {
|
|
461
|
-
const { query, values } = buildPostgresWhereQueryAndValues(where)
|
|
462
|
-
|
|
463
|
-
const whereClause = query ? `WHERE ${query}` : ''
|
|
464
|
-
|
|
465
|
-
const result = await client.query(
|
|
466
|
-
`SELECT COUNT(*) FROM "${String(className)}" ${whereClause}`,
|
|
467
|
-
values,
|
|
468
|
-
)
|
|
469
|
-
|
|
470
|
-
return Number(result.rows[0].count)
|
|
471
|
-
} finally {
|
|
472
|
-
client.release()
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
async getObject<K extends keyof T['types'], U extends keyof T['types'][K]>(
|
|
477
|
-
params: GetObjectOptions<T, K, U>,
|
|
478
|
-
): Promise<OutputType<T, K, U>> {
|
|
479
|
-
const { className, id, select, where } = params
|
|
480
|
-
|
|
481
|
-
const client = await this.pool.connect()
|
|
482
|
-
|
|
483
|
-
try {
|
|
484
|
-
// 2 because 1 is _id
|
|
485
|
-
const { query, values } = buildPostgresWhereQueryAndValues(where, 2)
|
|
486
|
-
|
|
487
|
-
const whereClause = query ? `AND ${query}` : ''
|
|
488
|
-
|
|
489
|
-
const selectFields = select
|
|
490
|
-
? Object.keys(select)
|
|
491
|
-
.filter((key) => select[key as keyof typeof select])
|
|
492
|
-
.map((key) => `"${key === 'id' ? '_id' : key}"`)
|
|
493
|
-
: []
|
|
494
|
-
|
|
495
|
-
const selectExpression = selectFields.length > 0 ? selectFields.join(', ') : '*'
|
|
496
|
-
|
|
497
|
-
const result = await client.query(
|
|
498
|
-
`SELECT ${selectExpression} FROM "${String(
|
|
499
|
-
className,
|
|
500
|
-
)}" WHERE _id = $1 ${whereClause} LIMIT 1`,
|
|
501
|
-
[id, ...values],
|
|
502
|
-
)
|
|
503
|
-
|
|
504
|
-
if (result.rows.length === 0) throw new Error('Object not found')
|
|
505
|
-
|
|
506
|
-
const row = result.rows[0]
|
|
507
|
-
|
|
508
|
-
const { _id, ...data } = row
|
|
509
|
-
|
|
510
|
-
return {
|
|
511
|
-
...data,
|
|
512
|
-
id: _id,
|
|
513
|
-
} as OutputType<T, K, U>
|
|
514
|
-
} finally {
|
|
515
|
-
client.release()
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
async getObjects<
|
|
520
|
-
K extends keyof T['types'],
|
|
521
|
-
U extends keyof T['types'][K],
|
|
522
|
-
W extends keyof T['types'][K],
|
|
523
|
-
>(params: GetObjectsOptions<T, K, U, W>): Promise<OutputType<T, K, W>[]> {
|
|
524
|
-
const { className, select, where, offset, first, order } = params
|
|
525
|
-
|
|
526
|
-
const client = await this.pool.connect()
|
|
527
|
-
|
|
528
|
-
try {
|
|
529
|
-
const { query, values } = buildPostgresWhereQueryAndValues(where)
|
|
530
|
-
|
|
531
|
-
const whereClause = query ? `WHERE ${query}` : ''
|
|
532
|
-
const orderClause = buildPostgresOrderQuery(order)
|
|
533
|
-
const limitClause = first ? `LIMIT ${first}` : ''
|
|
534
|
-
const offsetClause = offset ? `OFFSET ${offset}` : ''
|
|
535
|
-
|
|
536
|
-
const selectFields = select
|
|
537
|
-
? Object.keys(select)
|
|
538
|
-
.filter((key) => select[key as keyof typeof select])
|
|
539
|
-
.map((key) => `"${key === 'id' ? '_id' : key}"`)
|
|
540
|
-
: []
|
|
541
|
-
|
|
542
|
-
const selectExpression = selectFields.length > 0 ? selectFields.join(', ') : '*'
|
|
543
|
-
|
|
544
|
-
const result = await client.query(
|
|
545
|
-
`SELECT ${selectExpression} FROM "${String(
|
|
546
|
-
className,
|
|
547
|
-
)}" ${whereClause} ${orderClause} ${limitClause} ${offsetClause}`,
|
|
548
|
-
values,
|
|
549
|
-
)
|
|
550
|
-
|
|
551
|
-
const rows = result.rows as Array<Record<string, any>>
|
|
552
|
-
|
|
553
|
-
return rows.map((row) => {
|
|
554
|
-
const { _id, ...data } = row
|
|
555
|
-
|
|
556
|
-
return {
|
|
557
|
-
...data,
|
|
558
|
-
id: _id,
|
|
559
|
-
} as OutputType<T, K, W>
|
|
560
|
-
})
|
|
561
|
-
} finally {
|
|
562
|
-
client.release()
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
async createObject<
|
|
567
|
-
K extends keyof T['types'],
|
|
568
|
-
U extends keyof T['types'][K],
|
|
569
|
-
W extends keyof T['types'][K],
|
|
570
|
-
>(params: CreateObjectOptions<T, K, U, W>): Promise<{ id: string }> {
|
|
571
|
-
const { className, data } = params
|
|
572
|
-
|
|
573
|
-
const client = await this.pool.connect()
|
|
574
|
-
|
|
575
|
-
try {
|
|
576
|
-
const columns = Object.keys(data).map((column) => `"${column}"`)
|
|
577
|
-
const values = computeValuesFromData(data)
|
|
578
|
-
const placeholders = columns.map((_, index) => `$${index + 1}`)
|
|
579
|
-
|
|
580
|
-
const result = await client.query(
|
|
581
|
-
columns.length === 0
|
|
582
|
-
? `INSERT INTO "${String(className)}" DEFAULT VALUES RETURNING _id`
|
|
583
|
-
: `INSERT INTO "${String(className)}" (${columns.join(', ')}) VALUES (${placeholders.join(', ')}) RETURNING _id`,
|
|
584
|
-
values,
|
|
585
|
-
)
|
|
586
|
-
|
|
587
|
-
return { id: result.rows[0]._id }
|
|
588
|
-
} finally {
|
|
589
|
-
client.release()
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
async createObjects<
|
|
594
|
-
K extends keyof T['types'],
|
|
595
|
-
U extends keyof T['types'][K],
|
|
596
|
-
W extends keyof T['types'][K],
|
|
597
|
-
X extends keyof T['types'][K],
|
|
598
|
-
>(params: CreateObjectsOptions<T, K, U, W, X>): Promise<Array<{ id: string }>> {
|
|
599
|
-
const { className, data } = params
|
|
600
|
-
|
|
601
|
-
const client = await this.pool.connect()
|
|
602
|
-
|
|
603
|
-
try {
|
|
604
|
-
if (data.length === 0) return []
|
|
605
|
-
|
|
606
|
-
// Every line is empty
|
|
607
|
-
if (data.every((item) => Object.keys(item).length === 0)) {
|
|
608
|
-
const placeholders = data.map(() => 'DEFAULT VALUES').join(', ')
|
|
609
|
-
|
|
610
|
-
const result = await client.query(
|
|
611
|
-
`INSERT INTO "${String(className)}" ${placeholders} RETURNING _id`,
|
|
612
|
-
)
|
|
613
|
-
|
|
614
|
-
return result.rows.map((row) => ({ id: row._id }))
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
const allColumns = Array.from(new Set(data.flatMap((item) => Object.keys(item))))
|
|
618
|
-
|
|
619
|
-
const columns = allColumns.map((column) => `"${column}"`)
|
|
620
|
-
|
|
621
|
-
const values = data.flatMap((item: any) =>
|
|
622
|
-
allColumns.map((col) => {
|
|
623
|
-
const value = item[col]
|
|
624
|
-
return Array.isArray(value) ? JSON.stringify(value) : (value ?? null)
|
|
625
|
-
}),
|
|
626
|
-
)
|
|
627
|
-
|
|
628
|
-
const placeholders = data.map((_, rowIndex) => {
|
|
629
|
-
const offset = rowIndex * allColumns.length
|
|
630
|
-
return `(${allColumns.map((_, colIndex) => `$${offset + colIndex + 1}`).join(', ')})`
|
|
631
|
-
})
|
|
632
|
-
|
|
633
|
-
const result = await client.query(
|
|
634
|
-
`INSERT INTO "${String(className)}" (${columns.join(
|
|
635
|
-
', ',
|
|
636
|
-
)}) VALUES ${placeholders.join(', ')} RETURNING _id`,
|
|
637
|
-
values,
|
|
638
|
-
)
|
|
639
|
-
|
|
640
|
-
return result.rows.map((row) => ({ id: row._id }))
|
|
641
|
-
} finally {
|
|
642
|
-
client.release()
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
async updateObject<
|
|
647
|
-
K extends keyof T['types'],
|
|
648
|
-
U extends keyof T['types'][K],
|
|
649
|
-
W extends keyof T['types'][K],
|
|
650
|
-
>(params: UpdateObjectOptions<T, K, U, W>): Promise<{ id: string }> {
|
|
651
|
-
const { className, id, data, where } = params
|
|
652
|
-
|
|
653
|
-
const client = await this.pool.connect()
|
|
654
|
-
|
|
655
|
-
try {
|
|
656
|
-
const dataKeys = Object.keys(data)
|
|
657
|
-
const { query, values } = buildPostgresWhereQueryAndValues(where, dataKeys.length + 2)
|
|
658
|
-
|
|
659
|
-
const whereClause = query ? `AND ${query}` : ''
|
|
660
|
-
|
|
661
|
-
const setClause = dataKeys.map((key, index) => `"${key}" = $${index + 2}`).join(', ')
|
|
662
|
-
|
|
663
|
-
const result = await client.query(
|
|
664
|
-
`UPDATE "${String(className)}" SET ${setClause} WHERE _id = $1 ${whereClause} RETURNING _id`,
|
|
665
|
-
[id, ...computeValuesFromData(data), ...values],
|
|
666
|
-
)
|
|
667
|
-
|
|
668
|
-
if (result.rowCount === 0) throw new Error('Object not found')
|
|
669
|
-
|
|
670
|
-
return { id }
|
|
671
|
-
} finally {
|
|
672
|
-
client.release()
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
async updateObjects<
|
|
677
|
-
K extends keyof T['types'],
|
|
678
|
-
U extends keyof T['types'][K],
|
|
679
|
-
W extends keyof T['types'][K],
|
|
680
|
-
X extends keyof T['types'][K],
|
|
681
|
-
>(params: UpdateObjectsOptions<T, K, U, W, X>): Promise<Array<{ id: string }>> {
|
|
682
|
-
const { className, where, data, offset, first, context, order } = params
|
|
683
|
-
|
|
684
|
-
const client = await this.pool.connect()
|
|
685
|
-
|
|
686
|
-
try {
|
|
687
|
-
const objectsBeforeUpdate = await context.wabe.controllers.database.getObjects({
|
|
688
|
-
className,
|
|
689
|
-
where,
|
|
690
|
-
// @ts-expect-error
|
|
691
|
-
select: { id: true },
|
|
692
|
-
offset,
|
|
693
|
-
first,
|
|
694
|
-
context: {
|
|
695
|
-
...context,
|
|
696
|
-
isRoot: true,
|
|
697
|
-
},
|
|
698
|
-
order,
|
|
699
|
-
})
|
|
700
|
-
|
|
701
|
-
if (objectsBeforeUpdate.length === 0) return []
|
|
702
|
-
|
|
703
|
-
const objectIds = objectsBeforeUpdate.filter(Boolean).map((obj: any) => obj.id) as string[]
|
|
704
|
-
|
|
705
|
-
await Promise.all(
|
|
706
|
-
objectIds.map(async (id) => {
|
|
707
|
-
const setClause = Object.keys(data)
|
|
708
|
-
.map((key, index) => `"${key}" = $${index + 1}`)
|
|
709
|
-
.join(', ')
|
|
710
|
-
|
|
711
|
-
await client.query(
|
|
712
|
-
`UPDATE "${String(className)}" SET ${setClause} WHERE _id = $${Object.keys(data).length + 1}`,
|
|
713
|
-
[...computeValuesFromData(data), id],
|
|
714
|
-
)
|
|
715
|
-
}),
|
|
716
|
-
)
|
|
717
|
-
|
|
718
|
-
return objectsBeforeUpdate.filter(Boolean).map((obj: any) => ({ id: obj.id }))
|
|
719
|
-
} finally {
|
|
720
|
-
client.release()
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
async deleteObject<K extends keyof T['types'], U extends keyof T['types'][K]>(
|
|
725
|
-
params: DeleteObjectOptions<T, K, U>,
|
|
726
|
-
): Promise<void> {
|
|
727
|
-
const { className, id, where } = params
|
|
728
|
-
|
|
729
|
-
const client = await this.pool.connect()
|
|
730
|
-
|
|
731
|
-
try {
|
|
732
|
-
const { query, values } = buildPostgresWhereQueryAndValues(where, 2)
|
|
733
|
-
|
|
734
|
-
const whereClause = query ? `AND ${query}` : ''
|
|
735
|
-
|
|
736
|
-
const result = await client.query(
|
|
737
|
-
`DELETE FROM "${String(className)}" WHERE _id = $1 ${whereClause}`,
|
|
738
|
-
[id, ...values],
|
|
739
|
-
)
|
|
740
|
-
|
|
741
|
-
if (result.rowCount === 0) throw new Error('Object not found')
|
|
742
|
-
} finally {
|
|
743
|
-
client.release()
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
async deleteObjects<
|
|
748
|
-
K extends keyof T['types'],
|
|
749
|
-
U extends keyof T['types'][K],
|
|
750
|
-
W extends keyof T['types'][K],
|
|
751
|
-
>(params: DeleteObjectsOptions<T, K, U, W>): Promise<void> {
|
|
752
|
-
const { className, where } = params
|
|
753
|
-
|
|
754
|
-
const client = await this.pool.connect()
|
|
755
|
-
|
|
756
|
-
try {
|
|
757
|
-
const { query, values } = buildPostgresWhereQueryAndValues(where)
|
|
758
|
-
|
|
759
|
-
const whereClause = query ? `WHERE ${query}` : ''
|
|
760
|
-
|
|
761
|
-
await client.query(`DELETE FROM "${String(className)}" ${whereClause}`, values)
|
|
762
|
-
} finally {
|
|
763
|
-
client.release()
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
}
|