@smuzi/db-postgres 0.0.2 → 0.0.4
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/package.json +10 -9
- package/src/entityRepository.ts +0 -11
- package/src/index.ts +0 -160
- package/src/migrationsLogRepository.ts +0 -69
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smuzi/db-postgres",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "PostgreSQL driver for @smuzi/database",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"access": "public"
|
|
17
17
|
},
|
|
18
18
|
"files": [
|
|
19
|
-
"
|
|
20
|
-
"
|
|
19
|
+
"build/**/*.js",
|
|
20
|
+
"build/**/*.d.ts"
|
|
21
21
|
],
|
|
22
22
|
"exports": {
|
|
23
23
|
"./package.json": "./package.json",
|
|
@@ -34,14 +34,14 @@
|
|
|
34
34
|
"#lib/*": "./src/*"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@smuzi/database": "0.0.
|
|
38
|
-
"@smuzi/std": "0.2.
|
|
39
|
-
"@smuzi/schema": "0.0.
|
|
37
|
+
"@smuzi/database": "0.0.5",
|
|
38
|
+
"@smuzi/std": "0.2.8",
|
|
39
|
+
"@smuzi/schema": "0.0.7",
|
|
40
40
|
"pg": "^8.16.3"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@smuzi/faker": "0.0.
|
|
44
|
-
"@smuzi/tests": "0.0.
|
|
43
|
+
"@smuzi/faker": "0.0.6",
|
|
44
|
+
"@smuzi/tests": "0.0.5",
|
|
45
45
|
"@types/node": "^22.15.21",
|
|
46
46
|
"@types/pg": "^8.11.10",
|
|
47
47
|
"typescript": "^7.0.2"
|
|
@@ -49,5 +49,6 @@
|
|
|
49
49
|
"scripts": {
|
|
50
50
|
"test": "tsx --env-file=.env tests/index.ts",
|
|
51
51
|
"build": "tsc --project tsconfig.build.json"
|
|
52
|
-
}
|
|
52
|
+
},
|
|
53
|
+
"main": "./build/index.js"
|
|
53
54
|
}
|
package/src/entityRepository.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import {TDatabaseClient} from "@smuzi/database";
|
|
2
|
-
import {Option} from "@smuzi/std";
|
|
3
|
-
|
|
4
|
-
export const buildPostgresEntityRepository = (client: TDatabaseClient) => <Entity>(table: string) => {
|
|
5
|
-
return {
|
|
6
|
-
async find(id: number, { columns = ['*'], idColumn = 'id'}) {
|
|
7
|
-
return (await client.query(`SELECT ${columns.join(',')} FROM ${table} where ${idColumn} = $1`, [id]))
|
|
8
|
-
.mapOk(res => res.rows.get(0) as Option<Entity>);
|
|
9
|
-
},
|
|
10
|
-
}
|
|
11
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { Pool } from 'pg'
|
|
2
|
-
import {
|
|
3
|
-
preparedSqlFromObjectToArrayParams, TableRows,
|
|
4
|
-
TDatabaseClient, TInsertManyRowResult, TInsertRow, TInsertRowResult, DBQueryError,
|
|
5
|
-
TQueryParams,
|
|
6
|
-
TQueryResult
|
|
7
|
-
} from "@smuzi/database";
|
|
8
|
-
import {
|
|
9
|
-
isEmpty,
|
|
10
|
-
asArray,
|
|
11
|
-
asObject, dump,
|
|
12
|
-
Err,
|
|
13
|
-
isArray, isOption,
|
|
14
|
-
None,
|
|
15
|
-
Ok, Option,
|
|
16
|
-
OptionFromNullable,
|
|
17
|
-
RecordFromKeys, Result,
|
|
18
|
-
Simplify,
|
|
19
|
-
Some, StdList, StdRecord
|
|
20
|
-
} from "@smuzi/std";
|
|
21
|
-
import {SchemaObject} from "@smuzi/schema";
|
|
22
|
-
export * from "./migrationsLogRepository.js"
|
|
23
|
-
export * from "./entityRepository.js"
|
|
24
|
-
|
|
25
|
-
export type Config = {
|
|
26
|
-
user: string,
|
|
27
|
-
password: string,
|
|
28
|
-
host: string,
|
|
29
|
-
port: number,
|
|
30
|
-
database: string,
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
export class PostgresClient implements TDatabaseClient {
|
|
37
|
-
readonly #pool: Pool;
|
|
38
|
-
|
|
39
|
-
constructor(private readonly config: Config) {
|
|
40
|
-
this.#pool = new Pool(config)
|
|
41
|
-
|
|
42
|
-
this.#pool.on('error', (err, config) => {
|
|
43
|
-
console.error('Unexpected error on idle client', err)
|
|
44
|
-
process.exit(-1)
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async query<S extends SchemaObject>( sql: string, params: TQueryParams = [], schema: Option<S> = None()): Promise<TQueryResult<S>> {
|
|
50
|
-
let preparedSql = sql;
|
|
51
|
-
let preparedParams: unknown[] = asArray(params) ? params : [];
|
|
52
|
-
|
|
53
|
-
if (asObject(params)) {
|
|
54
|
-
const preparedRes = preparedSqlFromObjectToArrayParams(preparedSql, params).unwrap();
|
|
55
|
-
preparedSql = preparedRes.sql;
|
|
56
|
-
preparedParams = preparedRes.params;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
const res = await this.#pool.query({
|
|
61
|
-
text: preparedSql,
|
|
62
|
-
values: preparedParams,
|
|
63
|
-
},
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
return Ok({
|
|
67
|
-
rows: new TableRows(schema, res.rows),
|
|
68
|
-
rowCount: OptionFromNullable(res.rowCount),
|
|
69
|
-
})
|
|
70
|
-
} catch (err) {
|
|
71
|
-
return Err(new DBQueryError({
|
|
72
|
-
sql: preparedSql.substring(0, 200) + (preparedSql.length > 200 ? " ..." : ""),
|
|
73
|
-
message: err.message,
|
|
74
|
-
code: OptionFromNullable(err.code),
|
|
75
|
-
detail: OptionFromNullable(err.detail),
|
|
76
|
-
table: OptionFromNullable(err.table),
|
|
77
|
-
}));
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async insertRow<S extends SchemaObject<any>, const RC extends string[]>(
|
|
82
|
-
table: string,
|
|
83
|
-
schema: S,
|
|
84
|
-
row: TInsertRow<S>,
|
|
85
|
-
returningColumns: RC = Array<string>() as RC
|
|
86
|
-
): Promise<TInsertRowResult<S, RC>> {
|
|
87
|
-
//TODO: protected for injections
|
|
88
|
-
const columns = Object.keys(row);
|
|
89
|
-
const values = Object.values(row).map(val => isOption(val) ? val.someOr(null) : val);
|
|
90
|
-
const placeholders = values.map((_, index) => `$${index + 1}`).join(', ');
|
|
91
|
-
|
|
92
|
-
let sql = `INSERT INTO ${table} (${columns}) VALUES (${placeholders})`;
|
|
93
|
-
if (!isEmpty(returningColumns)) sql += ` RETURNING ${returningColumns.join(',')}` ;
|
|
94
|
-
|
|
95
|
-
return (await this.query(sql, values, Some(schema))).mapOk(res => res.rows.get(0)) as TInsertRowResult<S, RC>;
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async insertManyRows<S extends SchemaObject<any>, const RC extends string[]>(
|
|
100
|
-
table: string,
|
|
101
|
-
schema: S,
|
|
102
|
-
rows: TInsertRow<S>[],
|
|
103
|
-
returningColumns: RC = Array<string>() as RC
|
|
104
|
-
): Promise<TInsertManyRowResult<S, RC>> {
|
|
105
|
-
if (rows.length === 0) return Ok(new TableRows(Some(schema), [])) as any;
|
|
106
|
-
|
|
107
|
-
//TODO: protected for injections
|
|
108
|
-
|
|
109
|
-
const columns = Object.keys(rows[0]);
|
|
110
|
-
const values: any[] = [];
|
|
111
|
-
const placeholders = rows.map((row, rowIndex) => {
|
|
112
|
-
return `(${columns.map((_, colIndex) => {
|
|
113
|
-
const placeholderIndex = rowIndex * columns.length + colIndex + 1;
|
|
114
|
-
return `$${placeholderIndex}`;
|
|
115
|
-
}).join(', ')})`;
|
|
116
|
-
}).join(', ');
|
|
117
|
-
|
|
118
|
-
rows.forEach(row => values.push(...Object.values(row).map(val => isOption(val) ? val.someOr(null) : val)));
|
|
119
|
-
|
|
120
|
-
return (await this.query(`INSERT INTO ${table} (${columns.join(', ')}) VALUES ${placeholders} RETURNING ${returningColumns.join(',')}`, values, Some(schema))).mapOk(result => result.rows) as any;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async updateRowById<S extends SchemaObject<any>>(
|
|
124
|
-
table: string,
|
|
125
|
-
schema: S,
|
|
126
|
-
id: number | string,
|
|
127
|
-
row: Partial<TInsertRow<S>>,
|
|
128
|
-
idColumn: string = 'id'
|
|
129
|
-
): Promise<TQueryResult<S>>
|
|
130
|
-
{
|
|
131
|
-
//TODO: protected for injections
|
|
132
|
-
return await this.updateManyRows(table, row, `${idColumn} = ${id}`);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async updateManyRows<S extends SchemaObject<any>>(table: string, values: Partial<TInsertRow<S>>, where): Promise<TQueryResult<S>>
|
|
136
|
-
{
|
|
137
|
-
//TODO: protected for injections
|
|
138
|
-
const entries = Object.entries(values);
|
|
139
|
-
|
|
140
|
-
const setClause = entries
|
|
141
|
-
.map(([key], i) => `${key} = $${i + 1}`)
|
|
142
|
-
.join(", ");
|
|
143
|
-
|
|
144
|
-
const sql = `UPDATE ${table} SET ${setClause} WHERE ${where};`;
|
|
145
|
-
|
|
146
|
-
const params = entries.map(([, val]) => val);
|
|
147
|
-
|
|
148
|
-
return (await this.query(sql, params));
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
export function postgresClient(config: Config): TDatabaseClient {
|
|
156
|
-
return new PostgresClient(config);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import {isEmpty, None, Option, Some} from "@smuzi/std";
|
|
2
|
-
import {
|
|
3
|
-
migrationLogRowSchema,
|
|
4
|
-
TDatabaseClient,
|
|
5
|
-
TMigrationLogAction,
|
|
6
|
-
TMigrationLogRowSchema,
|
|
7
|
-
TMigrationsLogRepository
|
|
8
|
-
} from "@smuzi/database";
|
|
9
|
-
|
|
10
|
-
const table = 'migrations_log';
|
|
11
|
-
|
|
12
|
-
export const buildPostgresMigrationsLogRepository = (client: TDatabaseClient): TMigrationsLogRepository => {
|
|
13
|
-
return {
|
|
14
|
-
getTable: () => table,
|
|
15
|
-
createTableIfNotExists() {
|
|
16
|
-
return client.query(`CREATE TABLE IF NOT EXISTS ${table} (
|
|
17
|
-
id SERIAL PRIMARY KEY,
|
|
18
|
-
name VARCHAR(255) NOT NULL ,
|
|
19
|
-
branch INTEGER NOT NULL,
|
|
20
|
-
action VARCHAR(20),
|
|
21
|
-
sql_source TEXT NOT NULL,
|
|
22
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
23
|
-
);
|
|
24
|
-
|
|
25
|
-
CREATE INDEX IF NOT EXISTS idx_branch_name_created ON ${table} (branch, name, created_at DESC);
|
|
26
|
-
`)
|
|
27
|
-
},
|
|
28
|
-
listRuned() {
|
|
29
|
-
return client.query<TMigrationLogRowSchema>(`SELECT * FROM ( SELECT DISTINCT ON (name) * FROM ${table} ORDER BY name, created_at DESC ) last_records WHERE action = '${TMigrationLogAction.up}'`);
|
|
30
|
-
},
|
|
31
|
-
listRunedByBranch(branch: number) {
|
|
32
|
-
return client.query<TMigrationLogRowSchema>(`SELECT * FROM ( SELECT DISTINCT ON (name) * FROM ${table} WHERE branch = ${branch} ORDER BY name, created_at DESC ) last_records WHERE action = '${TMigrationLogAction.up}'`);
|
|
33
|
-
},
|
|
34
|
-
async getLastBranch(): Promise<Option<number>> {
|
|
35
|
-
const res = (await client.query(`SELECT MAX(branch) as last_branch FROM ${table}`)).unwrap();
|
|
36
|
-
if (res.rowCount.isZero()) return None();
|
|
37
|
-
|
|
38
|
-
return res.rows.get(0).unwrap().get("last_branch");
|
|
39
|
-
},
|
|
40
|
-
create(row) {
|
|
41
|
-
return client.insertRow(table, migrationLogRowSchema, row);
|
|
42
|
-
},
|
|
43
|
-
async migrationLastAction(name: string) {
|
|
44
|
-
const res = (await client.query(`SELECT action FROM ${table} WHERE name = $1 ORDER BY created_at DESC LIMIT 1`, [name])).unwrap();
|
|
45
|
-
|
|
46
|
-
if (res.rowCount.isZero()) return None();
|
|
47
|
-
|
|
48
|
-
return res.rows.get(0).unwrap().get("action");
|
|
49
|
-
},
|
|
50
|
-
async migrationWillBeRuned(name: string)
|
|
51
|
-
{
|
|
52
|
-
const lastAction = await this.migrationLastAction(name);
|
|
53
|
-
|
|
54
|
-
return lastAction.match({
|
|
55
|
-
Some: (v) => v === TMigrationLogAction.up,
|
|
56
|
-
None: () => false,
|
|
57
|
-
})
|
|
58
|
-
},
|
|
59
|
-
async freshSchema() {
|
|
60
|
-
return client.query(`DO $$ DECLARE
|
|
61
|
-
r RECORD;
|
|
62
|
-
BEGIN
|
|
63
|
-
FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = 'public') LOOP
|
|
64
|
-
EXECUTE 'DROP TABLE IF EXISTS public.' || quote_ident(r.tablename) || ' CASCADE';
|
|
65
|
-
END LOOP;
|
|
66
|
-
END $$;`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|