@juit/pgproxy-persister 1.0.0
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/README.md +228 -0
- package/dist/index.cjs +27 -0
- package/dist/index.cjs.map +6 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.mjs +4 -0
- package/dist/index.mjs.map +6 -0
- package/dist/model.cjs +211 -0
- package/dist/model.cjs.map +6 -0
- package/dist/model.d.ts +95 -0
- package/dist/model.mjs +185 -0
- package/dist/model.mjs.map +6 -0
- package/dist/persister.cjs +81 -0
- package/dist/persister.cjs.map +6 -0
- package/dist/persister.d.ts +49 -0
- package/dist/persister.mjs +56 -0
- package/dist/persister.mjs.map +6 -0
- package/package.json +58 -0
- package/src/index.ts +3 -0
- package/src/model.ts +462 -0
- package/src/persister.ts +147 -0
package/README.md
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# PostgreSQL Proxy Client (Persister Interface)
|
|
2
|
+
|
|
3
|
+
The persister interface for PostgreSQL Proxy is a higher-level interface
|
|
4
|
+
offering (on top of the usual connection and query interface) a CRUD
|
|
5
|
+
abstraction over database tables and few utility methods.
|
|
6
|
+
|
|
7
|
+
* [Connecting](#connecting)
|
|
8
|
+
* [Schema Definition](#schema-defintion)
|
|
9
|
+
* [Model Views](#model-views)
|
|
10
|
+
* [Create](#create)
|
|
11
|
+
* [Upsert](#upsert)
|
|
12
|
+
* [Read](#read)
|
|
13
|
+
* [Find](#find)
|
|
14
|
+
* [Update](#update)
|
|
15
|
+
* [Delete](#delete)
|
|
16
|
+
* [Pinging the database](#pinging-the-database)
|
|
17
|
+
* [PGProxy](https://github.com/juitnow/juit-pgproxy/blob/main/README.md)
|
|
18
|
+
* [Copyright Notice](https://github.com/juitnow/juit-pgproxy/blob/main/NOTICE.md)
|
|
19
|
+
* [License](https://github.com/juitnow/juit-pgproxy/blob/main/NOTICE.md)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Connecting
|
|
24
|
+
|
|
25
|
+
In the code, you can simply depend on the `Persister` class:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { Persister } from '@juit/pgproxy-persister'
|
|
29
|
+
|
|
30
|
+
const client = new Persister()
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
As with the standard client (`PGClient`) persisters can be constructed with a
|
|
34
|
+
`url` as a parameter, indicating the endpoint of the connection _and_
|
|
35
|
+
the specific client to be used.
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
### Schema Definition
|
|
40
|
+
|
|
41
|
+
The `Persister` interface (and the `Model`s bound to it) is a _generic_
|
|
42
|
+
interface. The `Schema` type parameter can be used to provide a fully typed
|
|
43
|
+
view over the columns (and related `Model`s) it manages.
|
|
44
|
+
|
|
45
|
+
Formally, the `Schema` is a type mapping table and column names to column
|
|
46
|
+
definitions. Each column definition is a type containing the following
|
|
47
|
+
properties:
|
|
48
|
+
|
|
49
|
+
* `type`: the _type_ of the column
|
|
50
|
+
* `isNullable` _(optional)_: if `true` the column is _nullable_ and henceforth
|
|
51
|
+
the `null` value can be used in lieu of the `type` above.
|
|
52
|
+
* `hasDefault` _(optional)_: if `true` the column _specifies a default value_
|
|
53
|
+
and therefore can be omitted in create operations.
|
|
54
|
+
|
|
55
|
+
An example of a `Schema` is as follows:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
/** Definition for all modelable columns */
|
|
59
|
+
export interface MySchema {
|
|
60
|
+
/** Columns for the `users` table */
|
|
61
|
+
users: {
|
|
62
|
+
/** Definition for the `id` column in `users` */
|
|
63
|
+
id: { type: number, hasDefault: true } // not nullable, but has default
|
|
64
|
+
/** Definition for the `email` column in `users` */
|
|
65
|
+
email: { type: string } // not nullable, no default, required creating
|
|
66
|
+
/** Definition for the `age` column in `users` */
|
|
67
|
+
age: { type: number, isNullable: true, hasDefault: false }
|
|
68
|
+
|
|
69
|
+
// ... all other columns
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
// ... all other tables
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The `@juit/pgproxy-utils` comes with a useful schema generator, querying a
|
|
77
|
+
database for all of its tables and generating a proper TypeScript interface.
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
### Model views
|
|
82
|
+
|
|
83
|
+
Model views offer a very basic interface to **C**reate, **R**ead, **U**pdate
|
|
84
|
+
and **D**elete data from a table.
|
|
85
|
+
|
|
86
|
+
A _CRUD_ model can be obtained by calling the `in(tableName)` on a `Persister`
|
|
87
|
+
or `connection` object, for example:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
const model = persister.in('myTable')
|
|
91
|
+
model.create({ ... })
|
|
92
|
+
model.delete({ ... })
|
|
93
|
+
|
|
94
|
+
persister.connect(async (connection) => {
|
|
95
|
+
const model = connection.in('myTable')
|
|
96
|
+
await model.create({ ... })
|
|
97
|
+
await model.delete({ ... })
|
|
98
|
+
})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
#### Create
|
|
102
|
+
|
|
103
|
+
The model's `create(object)` function will create `INSERT INTO ... RETURNING *`
|
|
104
|
+
statements based on the specified object.
|
|
105
|
+
|
|
106
|
+
Each key in the object will represent a _column name_ and its associated value
|
|
107
|
+
will be inserted in place.
|
|
108
|
+
|
|
109
|
+
This function will return (obviously) the values inserted, including any default
|
|
110
|
+
value calculated by the database.
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
persisterOrConnection.in('myTable').create({ myString: 'foo', myNumber: 123 })
|
|
114
|
+
// INSERT INTO "myTable" ("myString", "myNumber") VALUES ('foo', 123) RETURNING *
|
|
115
|
+
|
|
116
|
+
persisterOrConnection.in('myTable').create({})
|
|
117
|
+
// INSERT INTO "myTable" DEFAULT VALUES RETURNING *
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### Upsert
|
|
121
|
+
|
|
122
|
+
The model's `upsert(keys, data)` function will create upsert statements like
|
|
123
|
+
`INSERT INTO ... ON CONFLICT (...) DO UPDATE ... RETURNING *`.
|
|
124
|
+
|
|
125
|
+
The `keys` object passed as a first argument indicates the columns (and values
|
|
126
|
+
to set) for which conflicts are to be detected, while `data` is an object
|
|
127
|
+
containing other columns to update.
|
|
128
|
+
|
|
129
|
+
This function will return the values inserted and/or updated.
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
persisterOrConnection.in('myTable').upsert({
|
|
133
|
+
myKey: 'myValue', anotherKey: 'anotherValue'
|
|
134
|
+
}, {
|
|
135
|
+
myString: 'foo', myNumber: 123
|
|
136
|
+
})
|
|
137
|
+
// INSERT INTO "myTable" ("myKey", "anotherKey", "myString", "myNumber")
|
|
138
|
+
// VALUES ('myValue', 'anotherValue', 'foo', 123)
|
|
139
|
+
// ON CONFLICT ("myKey", "anotherKey") DO UPDATE
|
|
140
|
+
// SET "myString"='foo',
|
|
141
|
+
// "myNumber"=123
|
|
142
|
+
// RETURNING *
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
#### Read
|
|
146
|
+
|
|
147
|
+
The model's `read(query, sort)` function will create `SELECT * FROM ...`
|
|
148
|
+
statements based on the specified query and sort parameters.
|
|
149
|
+
|
|
150
|
+
Each key/value mapping in the query object will be mapped to a `WHERE key=value`
|
|
151
|
+
statement part.
|
|
152
|
+
|
|
153
|
+
The sort parameter must be an `Array` of `string`(s) containing the column name
|
|
154
|
+
and (optionally) the keywords `ASC` or `DESC`:
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
persisterOrConnection.in('myTable').read({ myString: 'foo', myNumber: 123 }, [
|
|
158
|
+
'mySortColumn',
|
|
159
|
+
'anotherSortColumn ASC',
|
|
160
|
+
'yetAnotherSortColumn DESC',
|
|
161
|
+
])
|
|
162
|
+
// SELECT * FROM "myTable" WHERE "myString"='foo' AND "myNumber"=123
|
|
163
|
+
// ORDER BY "mySortColumn", "anotherSortColumn" ASC, "yetAnotherSortColumn" DESC
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
#### Find
|
|
167
|
+
|
|
168
|
+
Similar to `read(...)` this method will return the _first_ result of the
|
|
169
|
+
generated `SELECT` query, or _undefined_ in case of no results:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
persisterOrConnection.in('myTable').find({ myString: 'foo', myNumber: 123 }, [
|
|
173
|
+
'mySortColumn',
|
|
174
|
+
'anotherSortColumn ASC',
|
|
175
|
+
'yetAnotherSortColumn DESC',
|
|
176
|
+
])
|
|
177
|
+
// SELECT * FROM "myTable" WHERE "myString"='foo' AND "myNumber"=123
|
|
178
|
+
// ORDER BY "mySortColumn", "anotherSortColumn" ASC, "yetAnotherSortColumn" DESC
|
|
179
|
+
// LIMIT 1
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
#### Update
|
|
183
|
+
|
|
184
|
+
The model's `update(query, patch)` function will create
|
|
185
|
+
`UPDATE ... WHERE ... SET ... RETURNING *` statements.
|
|
186
|
+
|
|
187
|
+
* the `query` parameter will work as in [read](#read), generating `WHERE ...`
|
|
188
|
+
statement parts.
|
|
189
|
+
* the `patch` parameter will work similarly to [create](#create), generating
|
|
190
|
+
`SET ...=...` statement parts.
|
|
191
|
+
|
|
192
|
+
This function will _cowardly_ fail when the `query` parameter is an empty object
|
|
193
|
+
(by design, we don't allow modification of _all_ rows in a database).
|
|
194
|
+
|
|
195
|
+
This function will return an `Array` of all rows modified by this call.
|
|
196
|
+
|
|
197
|
+
```javascript
|
|
198
|
+
persisterOrConnection.in('myTable').update({ myString: 'foo'}, { myNumber: 123 })
|
|
199
|
+
// UPDATE "myTable" SET "myNumber=123 WHERE "myString"='foo' RETURNING *
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
#### Delete
|
|
203
|
+
|
|
204
|
+
The model's `delete(query)` function will create
|
|
205
|
+
`DELETE FROM ... WHERE ... RETURNING *` statements.
|
|
206
|
+
|
|
207
|
+
* the `query` parameter will work as in [read](#read), generating `WHERE ...`
|
|
208
|
+
statement parts.
|
|
209
|
+
* the `patch` parameter will work similarly to [create](#create), generating
|
|
210
|
+
`SET ...=...` statement parts.
|
|
211
|
+
|
|
212
|
+
This function will _cowardly_ fail when the `query` parameter is an empty object
|
|
213
|
+
(by design, we don't allow deletion of _all_ rows in a database).
|
|
214
|
+
|
|
215
|
+
This function will return the _number of rows_ deleted by the query.
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
persisterOrConnection.in('myTable').delete({ myString: 'foo'})
|
|
219
|
+
// DELETE FROM "myTable" WHERE "myString"='foo' RETURNING *
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
### Pinging the database
|
|
225
|
+
|
|
226
|
+
The `ping()` method on `Persister` is a simple shortcut to
|
|
227
|
+
`void query('SELECT now()')` and can be used to ping the database (for health
|
|
228
|
+
checks, connectivity checks, keepalives, ...).
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __copyProps = (to, from, except, desc) => {
|
|
7
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
|
+
for (let key of __getOwnPropNames(from))
|
|
9
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
10
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
11
|
+
}
|
|
12
|
+
return to;
|
|
13
|
+
};
|
|
14
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
15
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
16
|
+
|
|
17
|
+
// index.ts
|
|
18
|
+
var src_exports = {};
|
|
19
|
+
module.exports = __toCommonJS(src_exports);
|
|
20
|
+
__reExport(src_exports, require("./model.cjs"), module.exports);
|
|
21
|
+
__reExport(src_exports, require("./persister.cjs"), module.exports);
|
|
22
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
23
|
+
0 && (module.exports = {
|
|
24
|
+
...require("./model.cjs"),
|
|
25
|
+
...require("./persister.cjs")
|
|
26
|
+
});
|
|
27
|
+
//# sourceMappingURL=index.cjs.map
|
package/dist/index.d.ts
ADDED
package/dist/index.mjs
ADDED
package/dist/model.cjs
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// model.ts
|
|
21
|
+
var model_exports = {};
|
|
22
|
+
__export(model_exports, {
|
|
23
|
+
Model: () => Model,
|
|
24
|
+
escape: () => escape
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(model_exports);
|
|
27
|
+
function assert(assertion, message) {
|
|
28
|
+
if (!assertion)
|
|
29
|
+
throw new Error(message);
|
|
30
|
+
}
|
|
31
|
+
function assertArray(value, message) {
|
|
32
|
+
assert(Array.isArray(value), message);
|
|
33
|
+
}
|
|
34
|
+
function assertObject(value, message) {
|
|
35
|
+
assert(value && typeof value === "object", message);
|
|
36
|
+
}
|
|
37
|
+
function where(query, params) {
|
|
38
|
+
const conditions = [];
|
|
39
|
+
let count = 0;
|
|
40
|
+
for (const [column, value] of Object.entries(query)) {
|
|
41
|
+
if (value === void 0)
|
|
42
|
+
continue;
|
|
43
|
+
if (value === null) {
|
|
44
|
+
conditions.push(`${escape(column)} IS NULL`);
|
|
45
|
+
} else {
|
|
46
|
+
const index = params.push(value);
|
|
47
|
+
conditions.push(`${escape(column)}=$${index}`);
|
|
48
|
+
}
|
|
49
|
+
count++;
|
|
50
|
+
}
|
|
51
|
+
return [
|
|
52
|
+
conditions.length ? ` WHERE ${conditions.join(" AND ")}` : "",
|
|
53
|
+
params,
|
|
54
|
+
count
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
function insert(schema, table, query) {
|
|
58
|
+
assertObject(query, "Called INSERT with a non-object");
|
|
59
|
+
const columns = [];
|
|
60
|
+
const placeholders = [];
|
|
61
|
+
const values = [];
|
|
62
|
+
for (const [column, value] of Object.entries(query)) {
|
|
63
|
+
if (value === void 0)
|
|
64
|
+
continue;
|
|
65
|
+
const index = columns.push(`${escape(column)}`);
|
|
66
|
+
placeholders.push(`$${index}`);
|
|
67
|
+
values.push(value);
|
|
68
|
+
}
|
|
69
|
+
return [
|
|
70
|
+
columns.length == 0 ? `INSERT INTO ${escape(schema)}.${escape(table)} DEFAULT VALUES RETURNING *` : `INSERT INTO ${escape(schema)}.${escape(table)} (${columns.join()}) VALUES (${placeholders.join()}) RETURNING *`,
|
|
71
|
+
values
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
function upsert(schema, table, keys, data) {
|
|
75
|
+
assertObject(keys, "Called UPSERT with a non-object for keys");
|
|
76
|
+
assertObject(data, "Called UPSERT with a non-object for data");
|
|
77
|
+
assert(Object.keys(keys).length > 0, "Called UPSERT with no conflict keys");
|
|
78
|
+
assert(Object.keys(data).length > 0, "Called UPSERT with no updateable data");
|
|
79
|
+
const object = { ...keys, ...data, ...keys };
|
|
80
|
+
const columns = [];
|
|
81
|
+
const placeholders = [];
|
|
82
|
+
const values = [];
|
|
83
|
+
for (const [column, value] of Object.entries(object)) {
|
|
84
|
+
if (value === void 0)
|
|
85
|
+
continue;
|
|
86
|
+
const index = columns.push(`${escape(column)}`);
|
|
87
|
+
placeholders.push(`$${index}`);
|
|
88
|
+
values.push(value);
|
|
89
|
+
}
|
|
90
|
+
const conflictKeys = [];
|
|
91
|
+
for (const [column, value] of Object.entries(keys)) {
|
|
92
|
+
if (value !== void 0)
|
|
93
|
+
conflictKeys.push(escape(column));
|
|
94
|
+
}
|
|
95
|
+
const updates = [];
|
|
96
|
+
for (const [column, value] of Object.entries(data)) {
|
|
97
|
+
if (value === void 0)
|
|
98
|
+
continue;
|
|
99
|
+
updates.push(`${escape(column)}=$${updates.length + columns.length + 1}`);
|
|
100
|
+
values.push(value);
|
|
101
|
+
}
|
|
102
|
+
return [
|
|
103
|
+
`INSERT INTO ${escape(schema)}.${escape(table)} (${columns.join()}) VALUES (${placeholders.join()}) ON CONFLICT (${conflictKeys.join(",")}) DO UPDATE SET ${updates.join(",")} RETURNING *`,
|
|
104
|
+
values
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
function select(schema, table, query, sort, offset, limit) {
|
|
108
|
+
if (typeof sort === "string")
|
|
109
|
+
sort = [sort];
|
|
110
|
+
assertObject(query, "Called SELECT with a non-object query");
|
|
111
|
+
assertArray(sort, "Called SELECT with a non-array sort");
|
|
112
|
+
const [conditions, values] = where(query, []);
|
|
113
|
+
const order = [];
|
|
114
|
+
for (const field of sort) {
|
|
115
|
+
if (field.toLowerCase().endsWith(" desc")) {
|
|
116
|
+
order.push(`${escape(field.slice(0, -5))} DESC`);
|
|
117
|
+
} else if (field.toLowerCase().endsWith(" asc")) {
|
|
118
|
+
order.push(`${escape(field.slice(0, -4))} ASC`);
|
|
119
|
+
} else {
|
|
120
|
+
order.push(escape(field));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const orderby = order.length == 0 ? "" : ` ORDER BY ${order.join(",")}`;
|
|
124
|
+
let sql = `SELECT * FROM ${escape(schema)}.${escape(table)}${conditions}${orderby}`;
|
|
125
|
+
if (offset && offset > 0) {
|
|
126
|
+
sql += ` OFFSET $${values.length + 1}`;
|
|
127
|
+
values.push(Math.floor(offset));
|
|
128
|
+
}
|
|
129
|
+
if (limit && limit > 0) {
|
|
130
|
+
sql += ` LIMIT $${values.length + 1}`;
|
|
131
|
+
values.push(Math.floor(limit));
|
|
132
|
+
}
|
|
133
|
+
return [sql, values];
|
|
134
|
+
}
|
|
135
|
+
function update(schema, table, query, patch) {
|
|
136
|
+
assertObject(query, "Called UPDATE with a non-object query");
|
|
137
|
+
assertObject(patch, "Called UPDATE with a non-object patch");
|
|
138
|
+
const patches = [];
|
|
139
|
+
const values = [];
|
|
140
|
+
for (const [column, value] of Object.entries(patch)) {
|
|
141
|
+
if (value === void 0)
|
|
142
|
+
continue;
|
|
143
|
+
const index = values.push(value);
|
|
144
|
+
patches.push(`${escape(column)}=$${index}`);
|
|
145
|
+
}
|
|
146
|
+
if (patches.length === 0)
|
|
147
|
+
return select(schema, table, query, [], 0, 0);
|
|
148
|
+
const [conditions, , count] = where(query, values);
|
|
149
|
+
assert(count > 0, "Cowardly refusing to run UPDATE with empty query");
|
|
150
|
+
const statement = `UPDATE ${escape(schema)}.${escape(table)} SET ${patches.join()}${conditions} RETURNING *`;
|
|
151
|
+
return [statement, values];
|
|
152
|
+
}
|
|
153
|
+
function del(schema, table, query) {
|
|
154
|
+
assertObject(query, "Called DELETE with a non-object query");
|
|
155
|
+
const [conditions, values, count] = where(query, []);
|
|
156
|
+
assert(count > 0, "Cowardly refusing to run DELETE with empty query");
|
|
157
|
+
return [`DELETE FROM ${escape(schema)}.${escape(table)}${conditions} RETURNING *`, values];
|
|
158
|
+
}
|
|
159
|
+
var ModelImpl = class {
|
|
160
|
+
_connection;
|
|
161
|
+
_schema;
|
|
162
|
+
_table;
|
|
163
|
+
constructor(connection, name) {
|
|
164
|
+
this._connection = connection;
|
|
165
|
+
const [schemaOrTable, maybeTable, ...extra] = name.split(".");
|
|
166
|
+
assert(extra.length === 0, `Invalid table name "${name}"`);
|
|
167
|
+
const [schema, table] = maybeTable ? [schemaOrTable, maybeTable] : ["public", schemaOrTable];
|
|
168
|
+
assert(table, `Invalid table name "${name}"`);
|
|
169
|
+
this._schema = schema || "public";
|
|
170
|
+
this._table = table;
|
|
171
|
+
}
|
|
172
|
+
async create(data) {
|
|
173
|
+
const [sql, params] = insert(this._schema, this._table, data);
|
|
174
|
+
const result = await this._connection.query(sql, params);
|
|
175
|
+
return result.rows[0];
|
|
176
|
+
}
|
|
177
|
+
async upsert(keys, data) {
|
|
178
|
+
const [sql, params] = upsert(this._schema, this._table, keys, data);
|
|
179
|
+
const result = await this._connection.query(sql, params);
|
|
180
|
+
return result.rows[0];
|
|
181
|
+
}
|
|
182
|
+
async read(query = {}, sort = [], offset = 0, limit = 0) {
|
|
183
|
+
const [sql, params] = select(this._schema, this._table, query, sort, offset, limit);
|
|
184
|
+
const result = await this._connection.query(sql, params);
|
|
185
|
+
return result.rows;
|
|
186
|
+
}
|
|
187
|
+
async find(query, sort) {
|
|
188
|
+
const result = await this.read(query, sort, 0, 1);
|
|
189
|
+
return result[0];
|
|
190
|
+
}
|
|
191
|
+
async update(query, patch) {
|
|
192
|
+
const [sql, params] = update(this._schema, this._table, query, patch);
|
|
193
|
+
const result = await this._connection.query(sql, params);
|
|
194
|
+
return result.rows;
|
|
195
|
+
}
|
|
196
|
+
async delete(query) {
|
|
197
|
+
const [sql, params] = del(this._schema, this._table, query);
|
|
198
|
+
const result = await this._connection.query(sql, params);
|
|
199
|
+
return result.rowCount;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
function escape(str) {
|
|
203
|
+
return `"${str.replaceAll('"', '""').trim()}"`;
|
|
204
|
+
}
|
|
205
|
+
var Model = ModelImpl;
|
|
206
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
207
|
+
0 && (module.exports = {
|
|
208
|
+
Model,
|
|
209
|
+
escape
|
|
210
|
+
});
|
|
211
|
+
//# sourceMappingURL=model.cjs.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/model.ts"],
|
|
4
|
+
"mappings": "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",
|
|
5
|
+
"names": []
|
|
6
|
+
}
|
package/dist/model.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { PGQueryable } from '@juit/pgproxy-client';
|
|
2
|
+
type SimplifyIntersection<T> = {
|
|
3
|
+
[K in keyof T]: T[K];
|
|
4
|
+
};
|
|
5
|
+
type OnlyStrings<T> = T extends string ? T : never;
|
|
6
|
+
/** The definition of a column */
|
|
7
|
+
export interface ColumnDefinition<T = any> {
|
|
8
|
+
/** The TypeScript type of the column (from the type parser) */
|
|
9
|
+
type: T;
|
|
10
|
+
/** Whether the column is _nulable_ or not */
|
|
11
|
+
isNullable?: boolean;
|
|
12
|
+
/** Whether the column _specifies a default value_ or not */
|
|
13
|
+
hasDefault?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** Infer the TypeScript type suitable for an `INSERT` in a table */
|
|
16
|
+
export type InferInsertType<Table extends Record<string, ColumnDefinition>> = SimplifyIntersection<{
|
|
17
|
+
[Column in keyof Table as Column extends string ? Table[Column]['isNullable'] extends true ? Column : Table[Column]['hasDefault'] extends true ? Column : never : never]?: Table[Column]['isNullable'] extends true ? Table[Column]['type'] | null : Table[Column]['type'];
|
|
18
|
+
} & {
|
|
19
|
+
[Column in keyof Table as Column extends string ? Table[Column]['isNullable'] extends true ? never : Table[Column]['hasDefault'] extends true ? never : Column : never]-?: Table[Column]['isNullable'] extends true ? Table[Column]['type'] | null : Table[Column]['type'];
|
|
20
|
+
}>;
|
|
21
|
+
/** Infer the TypeScript type suitable for a `SELECT` from a table */
|
|
22
|
+
export type InferSelectType<Table extends Record<string, ColumnDefinition>> = {
|
|
23
|
+
[Column in keyof Table as Column extends string ? Column : never]-?: Table[Column]['isNullable'] extends true ? Table[Column]['type'] | null : Table[Column]['type'];
|
|
24
|
+
};
|
|
25
|
+
/** Infer the TypeScript type suitable for a `UPDATE` in a table */
|
|
26
|
+
export type InferUpdateType<Table extends Record<string, ColumnDefinition>> = {
|
|
27
|
+
[Column in keyof Table as Column extends string ? Column : never]?: Table[Column]['isNullable'] extends true ? Table[Column]['type'] | null : Table[Column]['type'];
|
|
28
|
+
};
|
|
29
|
+
/** Infer the available sort values for a table (as required by `ORDER BY`) */
|
|
30
|
+
export type InferSort<Table extends Record<string, ColumnDefinition>> = `${OnlyStrings<keyof Table>}${' ASC' | ' asc' | ' DESC' | ' desc' | ''}`;
|
|
31
|
+
/** The model interface defines a CRUD interface to PosgreSQL tables */
|
|
32
|
+
export interface Model<Table extends Record<string, ColumnDefinition>> {
|
|
33
|
+
/**
|
|
34
|
+
* Create a row in the table.
|
|
35
|
+
*
|
|
36
|
+
* @param data - The data to insert in the table
|
|
37
|
+
* @returns A record containing all colums from the table (including defaults)
|
|
38
|
+
*/
|
|
39
|
+
create(data: InferInsertType<Table>): Promise<InferSelectType<Table>>;
|
|
40
|
+
/**
|
|
41
|
+
* Insert a row in the database or update its contents on conflict.
|
|
42
|
+
*
|
|
43
|
+
* @param keys - The data uniquely identifying the row to upsert (primary key)
|
|
44
|
+
* @param data - The data to associate with the given key (all extra columns)
|
|
45
|
+
* @returns A record containing all colums from the table (including defaults)
|
|
46
|
+
*/
|
|
47
|
+
upsert<K extends InferUpdateType<Table>>(keys: K, data: Omit<InferInsertType<Table>, keyof K>): Promise<InferSelectType<Table>>;
|
|
48
|
+
/**
|
|
49
|
+
* Read all rows in the table associated with the specified query
|
|
50
|
+
*
|
|
51
|
+
* @param query - The columns whose values need to be queried (for equality)
|
|
52
|
+
* @param sort - Any sort criteria to order the data
|
|
53
|
+
* @param offset - The offset of the results to return
|
|
54
|
+
* @param length - The maximum number of rows to return
|
|
55
|
+
* @returns An array of records containing all columns from the table
|
|
56
|
+
*/
|
|
57
|
+
read(query?: InferUpdateType<Table>, sort?: InferSort<Table> | InferSort<Table>[], offset?: number, limit?: number): Promise<InferSelectType<Table>[]>;
|
|
58
|
+
/**
|
|
59
|
+
* Find the _first_ rows in the table associated with the specified query
|
|
60
|
+
*
|
|
61
|
+
* @param query - The columns whose values need to be queried (for equality)
|
|
62
|
+
* @param sort - Any sort criteria to order the data
|
|
63
|
+
* @returns The first records matching the query or `undefined`
|
|
64
|
+
*/
|
|
65
|
+
find(query?: InferUpdateType<Table>, sort?: InferSort<Table> | InferSort<Table>[]): Promise<InferSelectType<Table> | undefined>;
|
|
66
|
+
/**
|
|
67
|
+
* Update all rows in the table matching the specified query.
|
|
68
|
+
*
|
|
69
|
+
* This method _will fail_ when query is the empty object `{}` as we cowardly
|
|
70
|
+
* refuse to update all records in a table (by design).
|
|
71
|
+
*
|
|
72
|
+
* @param query - The columns whose values need to be queried (for equality)
|
|
73
|
+
* @param patch - The updated data to persist in the table
|
|
74
|
+
* @returns An array of updated records containing all columns from the table
|
|
75
|
+
*/
|
|
76
|
+
update(query: InferUpdateType<Table>, patch: InferUpdateType<Table>): Promise<InferSelectType<Table>[]>;
|
|
77
|
+
/**
|
|
78
|
+
* Delete all rows in the table matching the specified query.
|
|
79
|
+
*
|
|
80
|
+
* This method _will fail_ when query is the empty object `{}` as we cowardly
|
|
81
|
+
* refuse to delete all records in a table (by design).
|
|
82
|
+
*
|
|
83
|
+
* @param query - The columns whose values need to be queried (for equality)
|
|
84
|
+
* @returns The number of rows deleted
|
|
85
|
+
*/
|
|
86
|
+
delete(query: InferUpdateType<Table>): Promise<number>;
|
|
87
|
+
}
|
|
88
|
+
/** Constructor for model instances */
|
|
89
|
+
export interface ModelConstructor {
|
|
90
|
+
new <Schema extends Record<string, ColumnDefinition>>(queryable: PGQueryable, table: string): Model<Schema>;
|
|
91
|
+
}
|
|
92
|
+
/** Escape a PostgreSQL identifier (table, column, ... names) */
|
|
93
|
+
export declare function escape(str: string): string;
|
|
94
|
+
export declare const Model: ModelConstructor;
|
|
95
|
+
export {};
|