@carbonorm/carbonnode 4.0.1 → 5.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 +158 -49
- package/dist/api/executors/SqlExecutor.d.ts +6 -0
- package/dist/api/handlers/ExpressHandler.d.ts +2 -1
- package/dist/api/types/ormInterfaces.d.ts +12 -0
- package/dist/api/utils/sqlAllowList.d.ts +2 -0
- package/dist/index.cjs.js +247 -10
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.js +246 -11
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
- package/scripts/assets/handlebars/C6.test.ts.handlebars +578 -32
- package/scripts/generateRestBindings.cjs +5 -5
- package/scripts/generateRestBindings.ts +5 -5
- package/src/__tests__/fixtures/createTestServer.ts +11 -3
- package/src/__tests__/fixtures/sqlResponses/actor.get.json +13 -0
- package/src/__tests__/fixtures/sqlResponses/sqlAllowList.blocked.json +3 -0
- package/src/__tests__/fixtures/sqlResponses/sqlAllowList.json +3 -0
- package/src/__tests__/sakila-db/C6.js +1 -1
- package/src/__tests__/sakila-db/C6.mysql.cnf +6 -0
- package/src/__tests__/sakila-db/C6.mysqldump.json +1 -0
- package/src/__tests__/sakila-db/C6.mysqldump.sql +720 -0
- package/src/__tests__/sakila-db/C6.sqlAllowList.json +94 -0
- package/src/__tests__/sakila-db/C6.test.ts +578 -32
- package/src/__tests__/sakila-db/C6.ts +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.delete.json +10 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.delete.lookup.json +9 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.get.json +14 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.join.json +15 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.post.json +12 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.post.latest.json +14 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.put.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.put.lookup.json +16 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.seed.json +14 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.address.delete.json +10 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.address.delete.lookup.json +9 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.address.fk.current.json +358 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.address.fk.referenced.json +158 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.address.get.json +22 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.address.join.json +24 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.address.post.json +16 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.address.post.latest.json +22 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.address.put.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.address.put.lookup.json +24 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.address.seed.json +22 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.category.delete.json +10 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.category.delete.lookup.json +9 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.category.get.json +13 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.category.join.json +14 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.category.post.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.category.post.latest.json +13 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.category.put.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.category.put.lookup.json +15 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.category.seed.json +13 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.city.delete.json +10 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.city.delete.lookup.json +9 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.city.fk.current.json +158 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.city.fk.referenced.json +133 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.city.get.json +14 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.city.join.json +15 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.city.post.json +12 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.city.post.latest.json +14 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.city.put.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.city.put.lookup.json +16 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.city.seed.json +14 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.country.delete.json +10 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.country.delete.lookup.json +9 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.country.get.json +13 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.country.join.json +15 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.country.post.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.country.post.latest.json +13 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.country.put.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.country.put.lookup.json +15 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.country.seed.json +13 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.delete.json +10 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.delete.lookup.json +9 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.fk.current.json +283 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.fk.referenced.json +358 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.get.json +19 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.join.json +29 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.post.json +17 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.post.latest.json +19 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.put.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.put.lookup.json +21 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.seed.json +19 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.film.delete.json +10 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.film.delete.lookup.json +9 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.film.fk.current.json +383 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.film.fk.referenced.json +38 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.film.get.json +23 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.film.join.json +24 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.film.post.json +20 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.film.post.latest.json +23 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.film.put.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.film.put.lookup.json +25 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.film.seed.json +23 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.delete.json +10 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.delete.lookup.json +9 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.fk.current.json +158 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.fk.referenced.json +20 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.get.json +14 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.join.json +25 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.post.json +12 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.post.latest.json +14 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.put.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.put.lookup.json +16 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.seed.json +14 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.language.delete.json +10 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.language.delete.lookup.json +9 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.language.get.json +13 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.language.join.json +24 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.language.post.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.language.post.latest.json +13 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.language.put.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.language.put.lookup.json +15 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.language.seed.json +13 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.delete.json +10 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.delete.lookup.json +9 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.fk.current.json +233 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.fk.referenced.json +233 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.get.json +17 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.join.json +24 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.post.json +15 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.post.latest.json +17 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.put.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.put.lookup.json +19 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.seed.json +17 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.delete.json +10 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.delete.lookup.json +9 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.fk.current.json +233 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.fk.referenced.json +34 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.get.json +17 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.join.json +24 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.post.json +15 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.post.latest.json +17 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.put.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.put.lookup.json +19 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.seed.json +17 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.staff.fk.current.json +34 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.staff.fk.referenced.json +20 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.staff.get.json +21 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.staff.join.json +31 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.staff.seed.json +21 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.store.fk.current.json +20 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.store.fk.referenced.json +34 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.store.get.json +14 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.store.join.json +24 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.store.seed.json +14 -0
- package/src/__tests__/sakila.generated.test.ts +31 -0
- package/src/__tests__/sqlAllowList.test.ts +135 -0
- package/src/api/executors/SqlExecutor.ts +156 -0
- package/src/api/handlers/ExpressHandler.ts +10 -1
- package/src/api/types/ormInterfaces.ts +15 -0
- package/src/api/utils/sqlAllowList.ts +54 -0
- package/src/index.ts +1 -0
package/README.md
CHANGED
|
@@ -47,45 +47,104 @@ npm install @carbonorm/carbonnode
|
|
|
47
47
|
|
|
48
48
|
## Generate Models
|
|
49
49
|
|
|
50
|
-
The
|
|
51
|
-
|
|
52
|
-
control system. All arguments are optional. If you do not provide them the defaults will be used. The example arguments
|
|
53
|
-
below are the defaults.
|
|
50
|
+
The generator produces a single `C6.ts` file containing all tables, types, and REST bindings. Keep this file in version
|
|
51
|
+
control and share it between server and client. All arguments are optional; the example below shows the defaults.
|
|
54
52
|
|
|
55
53
|
```bash
|
|
56
|
-
npx generateRestBindings --user root --pass password --host 127.0.0.1 --port 3306 --dbname carbonPHP --prefix carbon_ --output /
|
|
54
|
+
npx generateRestBindings --user root --pass password --host 127.0.0.1 --port 3306 --dbname carbonPHP --prefix carbon_ --output ./shared/rest/C6.ts
|
|
57
55
|
```
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
The generated file exports `C6`, `GLOBAL_REST_PARAMETERS`, `TABLES`, `ORM`, and per-table bindings (e.g. `Users`):
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { C6, GLOBAL_REST_PARAMETERS, Users } from "./shared/rest/C6";
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
You can view the generator source in
|
|
64
|
+
[CarbonNode](https://github.com/CarbonORM/CarbonNode/blob/main/scripts/generateRestBindings.ts). We use
|
|
65
|
+
[Handlebars templates](https://mustache.github.io/) to generate the code.
|
|
66
|
+
|
|
67
|
+
### Runtime Setup
|
|
68
|
+
|
|
69
|
+
CarbonNode executes SQL directly when `GLOBAL_REST_PARAMETERS.mysqlPool` is provided. If no pool is set, it will use
|
|
70
|
+
the HTTP executor (useful for frontends or non-Node runtimes).
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import mysql from "mysql2/promise";
|
|
74
|
+
import { GLOBAL_REST_PARAMETERS } from "./shared/rest/C6";
|
|
75
|
+
|
|
76
|
+
GLOBAL_REST_PARAMETERS.mysqlPool = mysql.createPool({
|
|
77
|
+
host: "127.0.0.1",
|
|
78
|
+
user: "root",
|
|
79
|
+
password: "password",
|
|
80
|
+
database: "carbonPHP",
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Optional HTTP path:
|
|
84
|
+
// GLOBAL_REST_PARAMETERS.axios = axiosInstance;
|
|
85
|
+
// GLOBAL_REST_PARAMETERS.restURL = "/rest/";
|
|
86
|
+
|
|
87
|
+
// Optional websocket broadcast on writes:
|
|
88
|
+
// GLOBAL_REST_PARAMETERS.websocketBroadcast = (payload) => wsServer.broadcast(JSON.stringify(payload));
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Request Flow
|
|
92
|
+
|
|
93
|
+
```mermaid
|
|
94
|
+
flowchart LR
|
|
95
|
+
Client["App code\nActor.Get(...)" ] --> RestRequest["restOrm + restRequest"]
|
|
96
|
+
RestRequest -->|"Node + mysqlPool"| SqlExec["SqlExecutor"] --> MySQL[("MySQL")]
|
|
97
|
+
RestRequest -->|"No pool"| HttpExec["HttpExecutor"] --> RestApi["/rest/:table"]
|
|
98
|
+
RestApi --> Express["ExpressHandler"] --> SqlExec
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### SQL Allowlist
|
|
102
|
+
|
|
103
|
+
To restrict which SQL statements can run in production, set `GLOBAL_REST_PARAMETERS.sqlAllowListPath` to a JSON file
|
|
104
|
+
containing allowed SQL strings. When the path is set, `SqlExecutor` normalizes whitespace and validates each query against
|
|
105
|
+
the allowlist. If the file is missing, an error is thrown; if the SQL is not listed, execution is blocked.
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
GLOBAL_REST_PARAMETERS.sqlAllowListPath = "/path/to/sqlAllowList.json";
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Allowlist format:
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
[
|
|
115
|
+
"SELECT * FROM `actor` LIMIT 1"
|
|
116
|
+
]
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Generated tests in `src/__tests__/sakila-db/C6.test.ts` write response fixtures into `src/__tests__/sakila-db/sqlResponses/`
|
|
120
|
+
and compile `src/__tests__/sakila-db/C6.sqlAllowList.json` after the suite finishes. Pass that file path to enable
|
|
121
|
+
validation.
|
|
122
|
+
|
|
123
|
+
When using the REST handler directly, forward the path as well:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
app.all("/rest/:table", ExpressHandler({ C6, mysqlPool, sqlAllowListPath }));
|
|
127
|
+
```
|
|
63
128
|
|
|
64
129
|
### Generated Tests
|
|
65
130
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
*describe*. Once a test does not have xdescribe it will no longer be updated with new generation changes.
|
|
131
|
+
The generator also writes `C6.test.ts` alongside `C6.ts`. Tests use Vitest and the generated bindings. Keep or delete the
|
|
132
|
+
file depending on your workflow.
|
|
69
133
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
[VSCode](https://code.visualstudio.com/updates/v1_67#_explorer-file-nesting).
|
|
134
|
+
The generator also writes `C6.MySqlDump.json`, `C6.mysqldump.sql`, and `C6.mysql.cnf` into the same output directory for
|
|
135
|
+
debugging and inspection.
|
|
73
136
|
|
|
74
137
|
### Templates
|
|
75
138
|
|
|
76
|
-
|
|
77
|
-
database consisting of your GET PUT POST and DELETE methods and a Jest test file, a C6.tsx file which contains all
|
|
78
|
-
table information and TypeScript types, and finally a websocket file which contains references to methods that are
|
|
79
|
-
generate. Here are the templates used to generate the code:
|
|
139
|
+
Two templates are used to generate the output:
|
|
80
140
|
|
|
81
141
|
1) [C6.ts.handlebars](https://github.com/CarbonORM/CarbonNode/blob/main/scripts/assets/handlebars/C6.ts.handlebars)
|
|
82
|
-
2) [
|
|
83
|
-
3) [Websocket.ts.handlebars](https://github.com/CarbonORM/CarbonNode/blob/main/scripts/assets/handlebars/C6RestApi.ts.handlebars)
|
|
142
|
+
2) [C6.test.ts.handlebars](https://github.com/CarbonORM/CarbonNode/blob/main/scripts/assets/handlebars/C6.test.ts.handlebars)
|
|
84
143
|
|
|
85
144
|
#### Generation Example
|
|
86
145
|
|
|
87
146
|
0) **npx generateRestBindings** is executed.
|
|
88
|
-
1) **The MySQL dump tool** outputs a
|
|
147
|
+
1) **The MySQL dump tool** outputs a structure for every table.
|
|
89
148
|
|
|
90
149
|
```mysql
|
|
91
150
|
CREATE TABLE actor (
|
|
@@ -98,6 +157,7 @@ CREATE TABLE actor (
|
|
|
98
157
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
99
158
|
```
|
|
100
159
|
|
|
160
|
+
2) **The generator** parses the table structure and creates an internal representation.
|
|
101
161
|
```typescript
|
|
102
162
|
export interface iActor {
|
|
103
163
|
'actor_id'?: number;
|
|
@@ -191,52 +251,102 @@ export const Actor = {
|
|
|
191
251
|
```
|
|
192
252
|
|
|
193
253
|
3) **Profit**
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
Allowing you to do:
|
|
254
|
+
You import from the frontend or backend using the same syntax:
|
|
197
255
|
|
|
198
256
|
```typescript
|
|
199
|
-
import { Actor,
|
|
257
|
+
import { Actor, C6 } from "./shared/rest/C6";
|
|
200
258
|
|
|
201
259
|
// GET
|
|
202
|
-
const actors = await Actor.
|
|
203
|
-
[
|
|
260
|
+
const actors = await Actor.Get({
|
|
261
|
+
[C6.SELECT]: [
|
|
204
262
|
Actor.ACTOR_ID,
|
|
205
263
|
Actor.FIRST_NAME,
|
|
206
264
|
Actor.LAST_NAME,
|
|
207
265
|
],
|
|
208
|
-
[
|
|
266
|
+
[C6.WHERE]: {
|
|
209
267
|
[Actor.LAST_NAME]: { like: "%PITT%" },
|
|
210
268
|
},
|
|
211
|
-
[
|
|
269
|
+
[C6.PAGINATION]: { [C6.LIMIT]: 10 },
|
|
212
270
|
});
|
|
213
271
|
|
|
214
272
|
// POST
|
|
215
|
-
await Actor.
|
|
216
|
-
[
|
|
217
|
-
|
|
218
|
-
[Actor.LAST_NAME]: "Pitt",
|
|
219
|
-
},
|
|
273
|
+
await Actor.Post({
|
|
274
|
+
[Actor.FIRST_NAME]: "Brad",
|
|
275
|
+
[Actor.LAST_NAME]: "Pitt",
|
|
220
276
|
});
|
|
221
277
|
|
|
222
|
-
// PUT
|
|
223
|
-
await Actor.
|
|
224
|
-
[
|
|
225
|
-
|
|
226
|
-
},
|
|
227
|
-
[C6C.DATA]: {
|
|
228
|
-
[Actor.LAST_NAME]: "Updated",
|
|
229
|
-
},
|
|
278
|
+
// PUT (singular)
|
|
279
|
+
await Actor.Put({
|
|
280
|
+
[Actor.ACTOR_ID]: 42,
|
|
281
|
+
[Actor.LAST_NAME]: "Updated",
|
|
230
282
|
});
|
|
231
283
|
|
|
232
|
-
// DELETE
|
|
233
|
-
await Actor.
|
|
234
|
-
[
|
|
235
|
-
[Actor.ACTOR_ID]: 42,
|
|
236
|
-
},
|
|
284
|
+
// DELETE (singular)
|
|
285
|
+
await Actor.Delete({
|
|
286
|
+
[Actor.ACTOR_ID]: 42,
|
|
237
287
|
});
|
|
238
288
|
```
|
|
239
289
|
|
|
290
|
+
Example response payloads (HTTP executor):
|
|
291
|
+
|
|
292
|
+
GET
|
|
293
|
+
|
|
294
|
+
```json
|
|
295
|
+
{
|
|
296
|
+
"success": true,
|
|
297
|
+
"rest": [
|
|
298
|
+
{ "actor_id": 1, "first_name": "PENELOPE", "last_name": "GUINESS" }
|
|
299
|
+
],
|
|
300
|
+
"next": "Function"
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
POST
|
|
305
|
+
|
|
306
|
+
```json
|
|
307
|
+
{
|
|
308
|
+
"success": true,
|
|
309
|
+
"created": 201,
|
|
310
|
+
"rest": { "actor_id": 201, "first_name": "Brad", "last_name": "Pitt" }
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
PUT
|
|
315
|
+
|
|
316
|
+
```json
|
|
317
|
+
{
|
|
318
|
+
"success": true,
|
|
319
|
+
"updated": true,
|
|
320
|
+
"rest": { "actor_id": 42, "last_name": "Updated" }
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
DELETE
|
|
325
|
+
|
|
326
|
+
```json
|
|
327
|
+
{
|
|
328
|
+
"success": true,
|
|
329
|
+
"deleted": true,
|
|
330
|
+
"rest": { "actor_id": 42 }
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
SQL executor responses omit `success` and include `sql` for GETs plus `affected` for writes. Express responses from `ExpressHandler` add `success: true`.
|
|
335
|
+
|
|
336
|
+
SQL executor example (GET):
|
|
337
|
+
|
|
338
|
+
```json
|
|
339
|
+
{
|
|
340
|
+
"rest": [
|
|
341
|
+
{ "actor_id": 1, "first_name": "PENELOPE", "last_name": "GUINESS" }
|
|
342
|
+
],
|
|
343
|
+
"sql": {
|
|
344
|
+
"sql": "SELECT * FROM `actor` LIMIT 10",
|
|
345
|
+
"values": []
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
240
350
|
Our CarbonReact extends this solution for automatic state and pagination management.
|
|
241
351
|
|
|
242
352
|
|
|
@@ -258,4 +368,3 @@ This will configure Git to use the hooks in the `.githooks` directory. The hooks
|
|
|
258
368
|
# Support and Issues
|
|
259
369
|
|
|
260
370
|
Any issues found should be reported on [GitHub](https://github.com/CarbonORM/CarbonNode/issues).
|
|
261
|
-
|
|
@@ -11,6 +11,11 @@ export declare class SqlExecutor<G extends OrmGenerics> extends Executor<G> {
|
|
|
11
11
|
[key: string]: any;
|
|
12
12
|
}): string;
|
|
13
13
|
private formatValue;
|
|
14
|
+
private stripRequestMetadata;
|
|
15
|
+
private normalizeRequestPayload;
|
|
16
|
+
private extractRequestBody;
|
|
17
|
+
private extractPrimaryKeyValues;
|
|
18
|
+
private broadcastWebsocketIfConfigured;
|
|
14
19
|
runQuery(): Promise<{
|
|
15
20
|
rest: any;
|
|
16
21
|
sql: {
|
|
@@ -26,4 +31,5 @@ export declare class SqlExecutor<G extends OrmGenerics> extends Executor<G> {
|
|
|
26
31
|
values: any;
|
|
27
32
|
};
|
|
28
33
|
}>;
|
|
34
|
+
private validateSqlAllowList;
|
|
29
35
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Request, Response, NextFunction } from "express";
|
|
2
2
|
import { Pool } from "mysql2/promise";
|
|
3
3
|
import { iC6Object } from "../types/ormInterfaces";
|
|
4
|
-
export declare function ExpressHandler({ C6, mysqlPool }: {
|
|
4
|
+
export declare function ExpressHandler({ C6, mysqlPool, sqlAllowListPath, }: {
|
|
5
5
|
C6: iC6Object;
|
|
6
6
|
mysqlPool: Pool;
|
|
7
|
+
sqlAllowListPath?: string;
|
|
7
8
|
}): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
@@ -135,6 +135,16 @@ export interface iGetC6RestResponse<ResponseDataType extends {
|
|
|
135
135
|
export type DetermineResponseDataType<Method extends iRestMethods, RestTableInterface extends {
|
|
136
136
|
[key: string]: any;
|
|
137
137
|
}, ResponseDataOverrides = {}> = (Method extends 'POST' ? iPostC6RestResponse<RestTableInterface> : Method extends 'GET' ? iGetC6RestResponse<RestTableInterface, ResponseDataOverrides> : Method extends 'PUT' ? iPutC6RestResponse<RestTableInterface> : Method extends 'DELETE' ? iDeleteC6RestResponse<RestTableInterface> : never);
|
|
138
|
+
export type iRestWebsocketPayload = {
|
|
139
|
+
REST: {
|
|
140
|
+
TABLE_NAME: string;
|
|
141
|
+
TABLE_PREFIX: string;
|
|
142
|
+
METHOD: iRestMethods;
|
|
143
|
+
REQUEST: Record<string, any>;
|
|
144
|
+
REQUEST_PRIMARY_KEY: Record<string, any> | null;
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
export type tWebsocketBroadcast = (payload: iRestWebsocketPayload) => void | Promise<void>;
|
|
138
148
|
export interface iRest<RestShortTableName extends string = any, RestTableInterface extends Record<string, any> = any, PrimaryKey extends keyof RestTableInterface & string = keyof RestTableInterface & string> {
|
|
139
149
|
C6: iC6Object;
|
|
140
150
|
axios?: AxiosInstance;
|
|
@@ -146,7 +156,9 @@ export interface iRest<RestShortTableName extends string = any, RestTableInterfa
|
|
|
146
156
|
requestMethod: iRestMethods;
|
|
147
157
|
clearCache?: () => void;
|
|
148
158
|
skipPrimaryCheck?: boolean;
|
|
159
|
+
websocketBroadcast?: tWebsocketBroadcast;
|
|
149
160
|
verbose?: boolean;
|
|
161
|
+
sqlAllowListPath?: string;
|
|
150
162
|
}
|
|
151
163
|
export interface iConstraint {
|
|
152
164
|
TABLE: string;
|
package/dist/index.cjs.js
CHANGED
|
@@ -2963,6 +2963,63 @@ function normalizeSingularRequest(requestMethod, request, restModel, removedPrim
|
|
|
2963
2963
|
return tslib.__assign(tslib.__assign({}, normalized), { dataInsertMultipleRows: dataInsertMultipleRows, cacheResults: cacheResults, fetchDependencies: fetchDependencies, debug: debug, success: success, error: error });
|
|
2964
2964
|
}
|
|
2965
2965
|
|
|
2966
|
+
var allowListCache = new Map();
|
|
2967
|
+
var normalizeSql = function (sql) {
|
|
2968
|
+
return sql.replace(/\s+/g, " ").trim();
|
|
2969
|
+
};
|
|
2970
|
+
var parseAllowList = function (raw, sourcePath) {
|
|
2971
|
+
var parsed;
|
|
2972
|
+
try {
|
|
2973
|
+
parsed = JSON.parse(raw);
|
|
2974
|
+
}
|
|
2975
|
+
catch (error) {
|
|
2976
|
+
throw new Error("SQL allowlist at ".concat(sourcePath, " is not valid JSON."));
|
|
2977
|
+
}
|
|
2978
|
+
if (!Array.isArray(parsed)) {
|
|
2979
|
+
throw new Error("SQL allowlist at ".concat(sourcePath, " must be a JSON array of strings."));
|
|
2980
|
+
}
|
|
2981
|
+
var sqlEntries = parsed
|
|
2982
|
+
.filter(function (entry) { return typeof entry === "string"; })
|
|
2983
|
+
.map(normalizeSql)
|
|
2984
|
+
.filter(function (entry) { return entry.length > 0; });
|
|
2985
|
+
if (sqlEntries.length !== parsed.length) {
|
|
2986
|
+
throw new Error("SQL allowlist at ".concat(sourcePath, " must contain only string entries."));
|
|
2987
|
+
}
|
|
2988
|
+
return sqlEntries;
|
|
2989
|
+
};
|
|
2990
|
+
var loadSqlAllowList = function (allowListPath) { return tslib.__awaiter(void 0, void 0, void 0, function () {
|
|
2991
|
+
var readFile, raw, sqlEntries, allowList;
|
|
2992
|
+
return tslib.__generator(this, function (_a) {
|
|
2993
|
+
switch (_a.label) {
|
|
2994
|
+
case 0:
|
|
2995
|
+
if (allowListCache.has(allowListPath)) {
|
|
2996
|
+
return [2 /*return*/, allowListCache.get(allowListPath)];
|
|
2997
|
+
}
|
|
2998
|
+
if (!isNode()) {
|
|
2999
|
+
throw new Error("SQL allowlist validation requires a Node runtime.");
|
|
3000
|
+
}
|
|
3001
|
+
return [4 /*yield*/, import('node:fs/promises')];
|
|
3002
|
+
case 1:
|
|
3003
|
+
readFile = (_a.sent()).readFile;
|
|
3004
|
+
_a.label = 2;
|
|
3005
|
+
case 2:
|
|
3006
|
+
_a.trys.push([2, 4, , 5]);
|
|
3007
|
+
return [4 /*yield*/, readFile(allowListPath, "utf-8")];
|
|
3008
|
+
case 3:
|
|
3009
|
+
raw = _a.sent();
|
|
3010
|
+
return [3 /*break*/, 5];
|
|
3011
|
+
case 4:
|
|
3012
|
+
_a.sent();
|
|
3013
|
+
throw new Error("SQL allowlist file not found at ".concat(allowListPath, "."));
|
|
3014
|
+
case 5:
|
|
3015
|
+
sqlEntries = parseAllowList(raw, allowListPath);
|
|
3016
|
+
allowList = new Set(sqlEntries);
|
|
3017
|
+
allowListCache.set(allowListPath, allowList);
|
|
3018
|
+
return [2 /*return*/, allowList];
|
|
3019
|
+
}
|
|
3020
|
+
});
|
|
3021
|
+
}); };
|
|
3022
|
+
|
|
2966
3023
|
var SqlExecutor = /** @class */ (function (_super) {
|
|
2967
3024
|
tslib.__extends(SqlExecutor, _super);
|
|
2968
3025
|
function SqlExecutor() {
|
|
@@ -2995,10 +3052,10 @@ var SqlExecutor = /** @class */ (function (_super) {
|
|
|
2995
3052
|
switch (_a) {
|
|
2996
3053
|
case 'GET': return [3 /*break*/, 1];
|
|
2997
3054
|
case 'POST': return [3 /*break*/, 3];
|
|
2998
|
-
case 'PUT': return [3 /*break*/,
|
|
2999
|
-
case 'DELETE': return [3 /*break*/,
|
|
3055
|
+
case 'PUT': return [3 /*break*/, 6];
|
|
3056
|
+
case 'DELETE': return [3 /*break*/, 9];
|
|
3000
3057
|
}
|
|
3001
|
-
return [3 /*break*/,
|
|
3058
|
+
return [3 /*break*/, 12];
|
|
3002
3059
|
case 1: return [4 /*yield*/, this.runQuery()];
|
|
3003
3060
|
case 2:
|
|
3004
3061
|
rest = _b.sent();
|
|
@@ -3006,16 +3063,25 @@ var SqlExecutor = /** @class */ (function (_super) {
|
|
|
3006
3063
|
case 3: return [4 /*yield*/, this.runQuery()];
|
|
3007
3064
|
case 4:
|
|
3008
3065
|
result = _b.sent();
|
|
3066
|
+
return [4 /*yield*/, this.broadcastWebsocketIfConfigured()];
|
|
3067
|
+
case 5:
|
|
3068
|
+
_b.sent();
|
|
3009
3069
|
return [2 /*return*/, result];
|
|
3010
|
-
case
|
|
3011
|
-
case
|
|
3070
|
+
case 6: return [4 /*yield*/, this.runQuery()];
|
|
3071
|
+
case 7:
|
|
3012
3072
|
result = _b.sent();
|
|
3013
|
-
return [
|
|
3014
|
-
case 7: return [4 /*yield*/, this.runQuery()];
|
|
3073
|
+
return [4 /*yield*/, this.broadcastWebsocketIfConfigured()];
|
|
3015
3074
|
case 8:
|
|
3075
|
+
_b.sent();
|
|
3076
|
+
return [2 /*return*/, result];
|
|
3077
|
+
case 9: return [4 /*yield*/, this.runQuery()];
|
|
3078
|
+
case 10:
|
|
3016
3079
|
result = _b.sent();
|
|
3080
|
+
return [4 /*yield*/, this.broadcastWebsocketIfConfigured()];
|
|
3081
|
+
case 11:
|
|
3082
|
+
_b.sent();
|
|
3017
3083
|
return [2 /*return*/, result];
|
|
3018
|
-
case
|
|
3084
|
+
case 12: throw new Error("Unsupported request method: ".concat(method));
|
|
3019
3085
|
}
|
|
3020
3086
|
});
|
|
3021
3087
|
});
|
|
@@ -3076,6 +3142,149 @@ var SqlExecutor = /** @class */ (function (_super) {
|
|
|
3076
3142
|
return "'".concat(val.toISOString().slice(0, 19).replace('T', ' '), "'");
|
|
3077
3143
|
return "'".concat(JSON.stringify(val), "'");
|
|
3078
3144
|
};
|
|
3145
|
+
SqlExecutor.prototype.stripRequestMetadata = function (source) {
|
|
3146
|
+
var ignoredKeys = new Set([
|
|
3147
|
+
C6Constants.SELECT,
|
|
3148
|
+
C6Constants.UPDATE,
|
|
3149
|
+
C6Constants.DELETE,
|
|
3150
|
+
C6Constants.WHERE,
|
|
3151
|
+
C6Constants.JOIN,
|
|
3152
|
+
C6Constants.PAGINATION,
|
|
3153
|
+
C6Constants.INSERT,
|
|
3154
|
+
C6Constants.REPLACE,
|
|
3155
|
+
"dataInsertMultipleRows",
|
|
3156
|
+
"cacheResults",
|
|
3157
|
+
"fetchDependencies",
|
|
3158
|
+
"debug",
|
|
3159
|
+
"success",
|
|
3160
|
+
"error",
|
|
3161
|
+
]);
|
|
3162
|
+
var filtered = {};
|
|
3163
|
+
for (var _i = 0, _a = Object.entries(source); _i < _a.length; _i++) {
|
|
3164
|
+
var _b = _a[_i], key = _b[0], value = _b[1];
|
|
3165
|
+
if (!ignoredKeys.has(key)) {
|
|
3166
|
+
filtered[key] = value;
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
return filtered;
|
|
3170
|
+
};
|
|
3171
|
+
SqlExecutor.prototype.normalizeRequestPayload = function (source) {
|
|
3172
|
+
var _a;
|
|
3173
|
+
var columns = this.config.restModel.COLUMNS;
|
|
3174
|
+
var validColumns = new Set(Object.values(columns));
|
|
3175
|
+
var normalized = {};
|
|
3176
|
+
for (var _i = 0, _b = Object.entries(source); _i < _b.length; _i++) {
|
|
3177
|
+
var _c = _b[_i], key = _c[0], value = _c[1];
|
|
3178
|
+
var shortKey = (_a = columns[key]) !== null && _a !== void 0 ? _a : (key.includes(".") ? key.split(".").pop() : key);
|
|
3179
|
+
if (validColumns.has(shortKey)) {
|
|
3180
|
+
normalized[shortKey] = value;
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
return normalized;
|
|
3184
|
+
};
|
|
3185
|
+
SqlExecutor.prototype.extractRequestBody = function () {
|
|
3186
|
+
var _a, _b;
|
|
3187
|
+
var request = this.request;
|
|
3188
|
+
if (this.config.requestMethod === C6Constants.POST) {
|
|
3189
|
+
if (Array.isArray(request.dataInsertMultipleRows) && request.dataInsertMultipleRows.length > 0) {
|
|
3190
|
+
return request.dataInsertMultipleRows[0];
|
|
3191
|
+
}
|
|
3192
|
+
if (C6Constants.INSERT in request) {
|
|
3193
|
+
return (_a = request[C6Constants.INSERT]) !== null && _a !== void 0 ? _a : {};
|
|
3194
|
+
}
|
|
3195
|
+
if (C6Constants.REPLACE in request) {
|
|
3196
|
+
return (_b = request[C6Constants.REPLACE]) !== null && _b !== void 0 ? _b : {};
|
|
3197
|
+
}
|
|
3198
|
+
return this.stripRequestMetadata(request);
|
|
3199
|
+
}
|
|
3200
|
+
if (this.config.requestMethod === C6Constants.PUT) {
|
|
3201
|
+
if (request[C6Constants.UPDATE] && typeof request[C6Constants.UPDATE] === "object") {
|
|
3202
|
+
return request[C6Constants.UPDATE];
|
|
3203
|
+
}
|
|
3204
|
+
return this.stripRequestMetadata(request);
|
|
3205
|
+
}
|
|
3206
|
+
return {};
|
|
3207
|
+
};
|
|
3208
|
+
SqlExecutor.prototype.extractPrimaryKeyValues = function () {
|
|
3209
|
+
var _a, _b, _c;
|
|
3210
|
+
var request = this.request;
|
|
3211
|
+
var where = request === null || request === void 0 ? void 0 : request[C6Constants.WHERE];
|
|
3212
|
+
var sources = [request, (where && typeof where === "object" && !Array.isArray(where)) ? where : undefined];
|
|
3213
|
+
var columns = this.config.restModel.COLUMNS;
|
|
3214
|
+
var primaryShorts = (_a = this.config.restModel.PRIMARY_SHORT) !== null && _a !== void 0 ? _a : [];
|
|
3215
|
+
var primaryFulls = (_b = this.config.restModel.PRIMARY) !== null && _b !== void 0 ? _b : [];
|
|
3216
|
+
var pkValues = {};
|
|
3217
|
+
var _loop_1 = function (pkShort) {
|
|
3218
|
+
var value = undefined;
|
|
3219
|
+
for (var _d = 0, sources_1 = sources; _d < sources_1.length; _d++) {
|
|
3220
|
+
var source = sources_1[_d];
|
|
3221
|
+
if (source && pkShort in source) {
|
|
3222
|
+
value = source[pkShort];
|
|
3223
|
+
break;
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
if (value === undefined) {
|
|
3227
|
+
var fullKey = (_c = primaryFulls.find(function (key) { return key.endsWith("." + pkShort); })) !== null && _c !== void 0 ? _c : Object.keys(columns).find(function (key) { return columns[key] === pkShort; });
|
|
3228
|
+
if (fullKey) {
|
|
3229
|
+
for (var _e = 0, sources_2 = sources; _e < sources_2.length; _e++) {
|
|
3230
|
+
var source = sources_2[_e];
|
|
3231
|
+
if (source && fullKey in source) {
|
|
3232
|
+
value = source[fullKey];
|
|
3233
|
+
break;
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
}
|
|
3238
|
+
if (value !== undefined) {
|
|
3239
|
+
pkValues[pkShort] = value;
|
|
3240
|
+
}
|
|
3241
|
+
};
|
|
3242
|
+
for (var _i = 0, primaryShorts_1 = primaryShorts; _i < primaryShorts_1.length; _i++) {
|
|
3243
|
+
var pkShort = primaryShorts_1[_i];
|
|
3244
|
+
_loop_1(pkShort);
|
|
3245
|
+
}
|
|
3246
|
+
if (primaryShorts.length > 0 && Object.keys(pkValues).length < primaryShorts.length) {
|
|
3247
|
+
return null;
|
|
3248
|
+
}
|
|
3249
|
+
return Object.keys(pkValues).length > 0 ? pkValues : null;
|
|
3250
|
+
};
|
|
3251
|
+
SqlExecutor.prototype.broadcastWebsocketIfConfigured = function () {
|
|
3252
|
+
return tslib.__awaiter(this, void 0, void 0, function () {
|
|
3253
|
+
var broadcast, payload, error_1;
|
|
3254
|
+
var _a, _b;
|
|
3255
|
+
return tslib.__generator(this, function (_c) {
|
|
3256
|
+
switch (_c.label) {
|
|
3257
|
+
case 0:
|
|
3258
|
+
broadcast = this.config.websocketBroadcast;
|
|
3259
|
+
if (!broadcast || this.config.requestMethod === C6Constants.GET)
|
|
3260
|
+
return [2 /*return*/];
|
|
3261
|
+
payload = {
|
|
3262
|
+
REST: {
|
|
3263
|
+
TABLE_NAME: this.config.restModel.TABLE_NAME,
|
|
3264
|
+
TABLE_PREFIX: (_b = (_a = this.config.C6) === null || _a === void 0 ? void 0 : _a.PREFIX) !== null && _b !== void 0 ? _b : "",
|
|
3265
|
+
METHOD: this.config.requestMethod,
|
|
3266
|
+
REQUEST: this.normalizeRequestPayload(this.extractRequestBody()),
|
|
3267
|
+
REQUEST_PRIMARY_KEY: this.extractPrimaryKeyValues(),
|
|
3268
|
+
},
|
|
3269
|
+
};
|
|
3270
|
+
_c.label = 1;
|
|
3271
|
+
case 1:
|
|
3272
|
+
_c.trys.push([1, 3, , 4]);
|
|
3273
|
+
return [4 /*yield*/, broadcast(payload)];
|
|
3274
|
+
case 2:
|
|
3275
|
+
_c.sent();
|
|
3276
|
+
return [3 /*break*/, 4];
|
|
3277
|
+
case 3:
|
|
3278
|
+
error_1 = _c.sent();
|
|
3279
|
+
if (this.config.verbose) {
|
|
3280
|
+
console.error("[SQL EXECUTOR] websocketBroadcast failed", error_1);
|
|
3281
|
+
}
|
|
3282
|
+
return [3 /*break*/, 4];
|
|
3283
|
+
case 4: return [2 /*return*/];
|
|
3284
|
+
}
|
|
3285
|
+
});
|
|
3286
|
+
});
|
|
3287
|
+
};
|
|
3079
3288
|
SqlExecutor.prototype.runQuery = function () {
|
|
3080
3289
|
return tslib.__awaiter(this, void 0, void 0, function () {
|
|
3081
3290
|
var TABLE_NAME, method, builder, QueryResult, formatted, toUnnamed, _a, sql, values;
|
|
@@ -3107,6 +3316,9 @@ var SqlExecutor = /** @class */ (function (_super) {
|
|
|
3107
3316
|
this.config.verbose && console.log("[SQL EXECUTOR] \uD83E\uDDE0 Formatted ".concat(method.toUpperCase(), " SQL:"), formatted);
|
|
3108
3317
|
toUnnamed = namedPlaceholders();
|
|
3109
3318
|
_a = toUnnamed(QueryResult.sql, QueryResult.params), sql = _a[0], values = _a[1];
|
|
3319
|
+
return [4 /*yield*/, this.validateSqlAllowList(sql)];
|
|
3320
|
+
case 1:
|
|
3321
|
+
_b.sent();
|
|
3110
3322
|
return [4 /*yield*/, this.withConnection(function (conn) { return tslib.__awaiter(_this, void 0, void 0, function () {
|
|
3111
3323
|
var result;
|
|
3112
3324
|
return tslib.__generator(this, function (_a) {
|
|
@@ -3131,7 +3343,29 @@ var SqlExecutor = /** @class */ (function (_super) {
|
|
|
3131
3343
|
}
|
|
3132
3344
|
});
|
|
3133
3345
|
}); })];
|
|
3134
|
-
case
|
|
3346
|
+
case 2: return [2 /*return*/, _b.sent()];
|
|
3347
|
+
}
|
|
3348
|
+
});
|
|
3349
|
+
});
|
|
3350
|
+
};
|
|
3351
|
+
SqlExecutor.prototype.validateSqlAllowList = function (sql) {
|
|
3352
|
+
return tslib.__awaiter(this, void 0, void 0, function () {
|
|
3353
|
+
var allowListPath, allowList, normalized;
|
|
3354
|
+
return tslib.__generator(this, function (_a) {
|
|
3355
|
+
switch (_a.label) {
|
|
3356
|
+
case 0:
|
|
3357
|
+
allowListPath = this.config.sqlAllowListPath;
|
|
3358
|
+
if (!allowListPath) {
|
|
3359
|
+
return [2 /*return*/];
|
|
3360
|
+
}
|
|
3361
|
+
return [4 /*yield*/, loadSqlAllowList(allowListPath)];
|
|
3362
|
+
case 1:
|
|
3363
|
+
allowList = _a.sent();
|
|
3364
|
+
normalized = normalizeSql(sql);
|
|
3365
|
+
if (!allowList.has(normalized)) {
|
|
3366
|
+
throw new Error("SQL statement is not permitted by allowlist (".concat(allowListPath, ")."));
|
|
3367
|
+
}
|
|
3368
|
+
return [2 /*return*/];
|
|
3135
3369
|
}
|
|
3136
3370
|
});
|
|
3137
3371
|
});
|
|
@@ -3148,7 +3382,7 @@ var SqlExecutor$1 = /*#__PURE__*/Object.freeze({
|
|
|
3148
3382
|
// note sure how it would help anyone actually...
|
|
3149
3383
|
function ExpressHandler(_a) {
|
|
3150
3384
|
var _this = this;
|
|
3151
|
-
var C6 = _a.C6, mysqlPool = _a.mysqlPool;
|
|
3385
|
+
var C6 = _a.C6, mysqlPool = _a.mysqlPool, sqlAllowListPath = _a.sqlAllowListPath;
|
|
3152
3386
|
return function (req, res, next) { return tslib.__awaiter(_this, void 0, void 0, function () {
|
|
3153
3387
|
var incomingMethod, table, primary, methodOverrideRaw, methodOverride, treatAsGet, method, payload, restModel, primaryKeys_1, primaryShortKeys_1, columnMap_1, resolveShortKey_1, hasPrimaryKeyValues, primaryKeyName, response, err_1;
|
|
3154
3388
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
@@ -3234,6 +3468,7 @@ function ExpressHandler(_a) {
|
|
|
3234
3468
|
return [4 /*yield*/, restRequest({
|
|
3235
3469
|
C6: C6,
|
|
3236
3470
|
mysqlPool: mysqlPool,
|
|
3471
|
+
sqlAllowListPath: sqlAllowListPath,
|
|
3237
3472
|
requestMethod: method,
|
|
3238
3473
|
restModel: C6.TABLES[table]
|
|
3239
3474
|
})(payload)];
|
|
@@ -3396,7 +3631,9 @@ exports.isLocal = isLocal;
|
|
|
3396
3631
|
exports.isNode = isNode;
|
|
3397
3632
|
exports.isTest = isTest;
|
|
3398
3633
|
exports.isVerbose = isVerbose;
|
|
3634
|
+
exports.loadSqlAllowList = loadSqlAllowList;
|
|
3399
3635
|
exports.normalizeSingularRequest = normalizeSingularRequest;
|
|
3636
|
+
exports.normalizeSql = normalizeSql;
|
|
3400
3637
|
exports.onError = onError;
|
|
3401
3638
|
exports.onSuccess = onSuccess;
|
|
3402
3639
|
exports.removeInvalidKeys = removeInvalidKeys;
|