@oino-ts/db 0.0.11
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 +222 -0
- package/dist/cjs/OINODb.js +27 -0
- package/dist/cjs/OINODbApi.js +270 -0
- package/dist/cjs/OINODbConfig.js +86 -0
- package/dist/cjs/OINODbDataField.js +354 -0
- package/dist/cjs/OINODbDataModel.js +279 -0
- package/dist/cjs/OINODbDataSet.js +139 -0
- package/dist/cjs/OINODbFactory.js +563 -0
- package/dist/cjs/OINODbModelSet.js +267 -0
- package/dist/cjs/OINODbParams.js +280 -0
- package/dist/cjs/OINODbRequestParams.js +280 -0
- package/dist/cjs/OINODbSwagger.js +201 -0
- package/dist/cjs/index.js +51 -0
- package/dist/esm/OINODb.js +23 -0
- package/dist/esm/OINODbApi.js +265 -0
- package/dist/esm/OINODbConfig.js +82 -0
- package/dist/esm/OINODbDataField.js +345 -0
- package/dist/esm/OINODbDataModel.js +275 -0
- package/dist/esm/OINODbDataSet.js +134 -0
- package/dist/esm/OINODbFactory.js +559 -0
- package/dist/esm/OINODbModelSet.js +263 -0
- package/dist/esm/OINODbRequestParams.js +274 -0
- package/dist/esm/OINODbSwagger.js +197 -0
- package/dist/esm/index.js +17 -0
- package/dist/types/OINODb.d.ts +75 -0
- package/dist/types/OINODbApi.d.ts +57 -0
- package/dist/types/OINODbConfig.d.ts +52 -0
- package/dist/types/OINODbDataField.d.ts +202 -0
- package/dist/types/OINODbDataModel.d.ts +108 -0
- package/dist/types/OINODbDataSet.d.ts +95 -0
- package/dist/types/OINODbFactory.d.ts +99 -0
- package/dist/types/OINODbModelSet.d.ts +50 -0
- package/dist/types/OINODbRequestParams.d.ts +130 -0
- package/dist/types/OINODbSwagger.d.ts +25 -0
- package/dist/types/index.d.ts +103 -0
- package/package.json +35 -0
- package/src/OINODb.ts +98 -0
- package/src/OINODbApi.test.ts +243 -0
- package/src/OINODbApi.ts +270 -0
- package/src/OINODbConfig.ts +92 -0
- package/src/OINODbDataField.ts +372 -0
- package/src/OINODbDataModel.ts +290 -0
- package/src/OINODbDataSet.ts +170 -0
- package/src/OINODbFactory.ts +570 -0
- package/src/OINODbModelSet.ts +286 -0
- package/src/OINODbRequestParams.ts +281 -0
- package/src/OINODbSwagger.ts +209 -0
- package/src/index.ts +116 -0
package/README.md
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# OINO TS
|
|
2
|
+
OINO Is Not an ORM but it's trying to solve a similar problem for API development. Instead of mirroring your DB schema in code that needs manual updates, OINO will get the data schema from DBMS using SQL in real time. Every time your app starts, it has an updated data model which enables automatic (de)serialize SQL results to JSON/CSV and back. OINO works on the level below routing where you pass the method, URL ID, body and request parameters to the API-object. OINO will parse and validate the data against the data model and generate proper SQL for your DB. Because OINO knows how data is serialized (e.g. JSON), what column it belongs to (e.g. floating point number) and what the target database is, it knows how to parse, format and escape the value as valid SQL.
|
|
3
|
+
|
|
4
|
+
```
|
|
5
|
+
const result:OINOApiResult = await api_orderdetails.doRequest("GET", id, body, params)
|
|
6
|
+
return new Response(result.modelset.writeString(OINOContentType.json))
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# GETTING STARTED
|
|
11
|
+
|
|
12
|
+
### Setup
|
|
13
|
+
Install the `@oino-ts/core` npm package and necessary database packages and import them in your code.
|
|
14
|
+
```
|
|
15
|
+
bun install @oino-ts/core
|
|
16
|
+
bun install @oino-ts/bunsqlite
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
import { OINODb, OINOApi, OINOFactory } from "@oino-ts/core";
|
|
21
|
+
import { OINODbBunSqlite } from "@oino-ts/bunsqlite"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Register database and logger
|
|
25
|
+
Register your database implementation and logger (see [`OINOConsoleLog`](https://pragmatta.github.io/oino-ts/classes/core_src.OINOConsoleLog.html) how to implement your own)
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
OINOLog.setLogger(new OINOConsoleLog())
|
|
29
|
+
OINOFactory.registerDb("OINODbBunSqlite", OINODbBunSqlite)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Create a database
|
|
33
|
+
Creating a database connection [`OINODb`](https://pragmatta.github.io/oino-ts/classes/core_src_OINODb.OINODb.html) is done by passing [`OINODbParams`](https://pragmatta.github.io/oino-ts/types/core_src.OINODbParams.html) to the factory method. For [`OINODbBunSqlite`](https://pragmatta.github.io/oino-ts/classes/bunsqlite_OINODbBunSqlite.OINODbBunSqlite.html) that means a file url for the database file, for others network host, port, credentials etc.
|
|
34
|
+
```
|
|
35
|
+
const db:OINODb = await OINOFactory.createDb( { type: "OINODbBunSqlite", url: "file://../localDb/northwind.sqlite" } )
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Create an API
|
|
39
|
+
From a database you can create an [`OINOApi`](https://pragmatta.github.io/oino-ts/classes/core_src_OINOApi.OINOApi.html) by passing [`OINOApiParams`](https://pragmatta.github.io/oino-ts/types/core_src.OINOApiParams.html) with table name and preferences to the factory method.
|
|
40
|
+
```
|
|
41
|
+
const api_employees:OINOApi = await OINOFactory.createApi(db, { tableName: "Employees", excludeFields:["BirthDate"] })
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Pass HTTP requests to API
|
|
45
|
+
When you receive a HTTP request, just pass the method, URL ID, body and params to the correct API, which will parse and validate input and return results.
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
const result:OINOApiResult = await api_orderdetails.doRequest("GET", id, body, params)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Write results back to HTTP Response
|
|
52
|
+
The results for a GET request will contain [`OINOModelSet`](https://pragmatta.github.io/oino-ts/classes/core_src_OINOModelSet.OINOModelSet.html) data that can be written out as JSON or CSV as needed. For other requests result is just success or error with messages.
|
|
53
|
+
```
|
|
54
|
+
return new Response(result.modelset.writeString(OINOContentType.json))
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# FEATURES
|
|
59
|
+
|
|
60
|
+
## RESTfull
|
|
61
|
+
OINO maps HTTP methods GET/POST/PUT/DELETE to SQL operations SELECT/INSERT/UPDATE/DELETE. The GET/POST requests can be made without URL ID to get all rows or insert new ones and others target a single row using URL ID.
|
|
62
|
+
|
|
63
|
+
### HTTP GET
|
|
64
|
+
```
|
|
65
|
+
Request and response:
|
|
66
|
+
> curl.exe -X GET http://localhost:3001/orderdetails/11077:77
|
|
67
|
+
[
|
|
68
|
+
{"_OINOID_":"11077:77","OrderID":11077,"ProductID":77,"UnitPrice":13,"Quantity":2,"Discount":0}
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
SQL:
|
|
72
|
+
SELECT "OrderID","ProductID","UnitPrice","Quantity","Discount" FROM [OrderDetails] WHERE ("OrderID"=11077 AND "ProductID"=77);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### HTTP POST
|
|
76
|
+
```
|
|
77
|
+
Request and response:
|
|
78
|
+
> curl.exe -X POST http://localhost:3001/orderdetails -H "Content-Type: application/json" --data '[{\"OrderID\":11077,\"ProductID\":99,\"UnitPrice\":19,\"Quantity\":1,\"Discount\":0}]'
|
|
79
|
+
{"success":true,"statusCode":200,"statusMessage":"OK","messages":[]}
|
|
80
|
+
|
|
81
|
+
SQL:
|
|
82
|
+
INSERT INTO [OrderDetails] ("OrderID","ProductID","UnitPrice","Quantity","Discount") VALUES (11077,99,19,1,0);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### HTTP PUT
|
|
86
|
+
```
|
|
87
|
+
Request and response:
|
|
88
|
+
> curl.exe -X PUT http://localhost:3001/orderdetails/11077:99 -H "Content-Type: application/json" --data '[{\"UnitPrice\":20}]'
|
|
89
|
+
{"success":true,"statusCode":200,"statusMessage":"OK","messages":[]}
|
|
90
|
+
|
|
91
|
+
SQL:
|
|
92
|
+
UPDATE [OrderDetails] SET "UnitPrice"=20 WHERE ("OrderID"=11077 AND "ProductID"=99);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### HTTP DELETE
|
|
96
|
+
```
|
|
97
|
+
Request and response:
|
|
98
|
+
> curl.exe -X DELETE http://localhost:3001/orderdetails/11077:99
|
|
99
|
+
{"success":true,"statusCode":200,"statusMessage":"OK","messages":[]}
|
|
100
|
+
|
|
101
|
+
SQL:
|
|
102
|
+
DELETE FROM [OrderDetails] WHERE ("OrderID"=11077 AND "ProductID"=99);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Universal Serialization
|
|
106
|
+
OINO handles serialization of data to JSON/CSV/etc. and back based on the data model. It knows what columns exist, what is their data type and how to convert each to JSON/CSV and back. This allows also partial data to be sent, i.e. you can send only columns that need updating or even send extra columns and have them ignored.
|
|
107
|
+
|
|
108
|
+
### Features
|
|
109
|
+
- Files can be sent to BLOB fields using BASE64 encoding.
|
|
110
|
+
- Datetimes are (optionally) normalized to ISO 8601 format.
|
|
111
|
+
- Extended JSON-encoding
|
|
112
|
+
- Unquoted literal `undefined` can be used to represent non-existent values (leaving property out works too but preserving structure might be easier e.g. when translating data).
|
|
113
|
+
- CSV
|
|
114
|
+
- Comma-separated, doublequotes.
|
|
115
|
+
- Unquoted literal `null` represents null values.
|
|
116
|
+
- Unquoted empty string represents undefined values.
|
|
117
|
+
- Form data
|
|
118
|
+
- Multipart-mixed and binary files not supported.
|
|
119
|
+
- Non-existent value line (i.e. nothing after the empty line) treated as a null value.
|
|
120
|
+
- Url-encoded
|
|
121
|
+
- No null values, missing properties treated as undefined.
|
|
122
|
+
- Multiple lines could be used to post multiple rows.
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
## Database Abstraction
|
|
126
|
+
OINO functions as a database abstraction, providing a consistent interface for working with different databases. It abstracts out different conventions in connecting, making queries and formatting data.
|
|
127
|
+
|
|
128
|
+
Currently supported databases:
|
|
129
|
+
- Bun Sqlite through Bun native implementation
|
|
130
|
+
- Postgresql through [pg](https://www.npmjs.com/package/pg)-package
|
|
131
|
+
- Mariadb / Mysql-support through [mariadb](https://www.npmjs.com/package/mariadb)-package
|
|
132
|
+
|
|
133
|
+
## Complex Keys
|
|
134
|
+
To support tables with multipart primary keys OINO generates a composite key `_OINOID_` that is included in the result and can be used as the REST ID. For example in the example above table `OrderDetails` has two primary keys `OrderID` and `ProductID` making the `_OINOID_` of form `11077:99`.
|
|
135
|
+
|
|
136
|
+
## Power Of SQL
|
|
137
|
+
Since OINO controls the SQL, WHERE-conditions can be defined with [`OINOSqlFilter`](https://pragmatta.github.io/oino-ts/classes/core_src.OINOSqlFilter.html) and order with [`OINOSqlOrder`](https://pragmatta.github.io/oino-ts/classes/core_src.OINOSqlOrder.html) that are passed as HTTP request parameters. No more API development where you make unique API endpoints for each filter that fetch all data with original API and filter in backend code. Every API can be filtered when and as needed without unnessecary data tranfer and utilizing SQL indexing when available.
|
|
138
|
+
|
|
139
|
+
## Swagger Support
|
|
140
|
+
Swagger is great as long as the definitions are updated and with OINO you can automatically get a Swagger definition including a data model schema.
|
|
141
|
+
```
|
|
142
|
+
if (url.pathname == "/swagger.json") {
|
|
143
|
+
return new Response(JSON.stringify(OINOSwagger.getApiDefinition(api_array)))
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+

|
|
147
|
+
|
|
148
|
+
## Node support
|
|
149
|
+
OINO is developped Typescript first but compiles to standard CommonJS and the NPM packages should work on either ESM / CommonJS. Checkout sample apps `readmeApp` (ESM) and `nodeApp` (CommonJS).
|
|
150
|
+
|
|
151
|
+
## HTMX support
|
|
152
|
+
OINO is [htmx.org](https://htmx.org) friendly, allowing easy translation of [`OINODataRow`](https://pragmatta.github.io/oino-ts/types/core_src.OINODataRow.html) to HTML output using templates (cf. the [htmx sample app](https://github.com/pragmatta/oino-ts/tree/main/samples/htmxApp)).
|
|
153
|
+
|
|
154
|
+
### Hashids
|
|
155
|
+
Autoinc numeric id's are very pragmatic and fit well with OINO (e.g. using a form without primary key fields to insert new rows with database assigned ids). However it's not always sensible to share information about the sequence. Hashids solve this by masking the original values by encrypting the ids using AES-128 and some randomness. Length of the hashid can be chosen from 12-32 characters where longer ids provide more security. However this should not be considereded a cryptographic solution for keeping ids secret but rather making it infeasible to iterate all ids.
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# STATUS
|
|
159
|
+
OINO is currently a hobby project which should and should considered in alpha status. That also means compatibility breaking changes can be made without prior notice when architectual issues are discovered.
|
|
160
|
+
|
|
161
|
+
## Beta
|
|
162
|
+
For a beta status following milestones are planned:
|
|
163
|
+
|
|
164
|
+
### Realistic app
|
|
165
|
+
There needs to be a realistic app built on top of OINO to get a better grasp of the edge cases.
|
|
166
|
+
|
|
167
|
+
### Security review
|
|
168
|
+
Handling of SQL-injection attacks needs a thorough review, what are the relevant attack vectors are for OINO and what protections are still needed.
|
|
169
|
+
|
|
170
|
+
## Roadmap
|
|
171
|
+
Things that need to happen in some order before beta-status are at least following:
|
|
172
|
+
|
|
173
|
+
### Support for views
|
|
174
|
+
Simple cases of views would work already in some databases but edge cases might get complicated. For example
|
|
175
|
+
- How to handle a view which does not have a complete private key?
|
|
176
|
+
- What edge cases exist in updating views?
|
|
177
|
+
|
|
178
|
+
### Batch updates
|
|
179
|
+
Supporting batch updates similar to batch inserts is slightly bending the RESTfull principles but would still be a useful optional feature.
|
|
180
|
+
|
|
181
|
+
### Aggregation and limits
|
|
182
|
+
Similar to filtering and ordering, aggregation and limits can be implemented as HTTP request parameters telling what column is aggregated or used for ordering or how many results to return.
|
|
183
|
+
|
|
184
|
+
### Streaming
|
|
185
|
+
One core idea is to be efficient in not making unnecessary copies of the data and minimizing garbage collection debt. This can be taken further by implementing streaming, allowing large dataset to be written to HTTP response as SQL result rows are received.
|
|
186
|
+
|
|
187
|
+
### SQL generation callbacks
|
|
188
|
+
It would be useful to allow developer to validate / override SQL generation to cover cases OINO does not support or even workaround issues.
|
|
189
|
+
|
|
190
|
+
### Transactions
|
|
191
|
+
Even though the basic case for OINO is executing SQL operations on individual rows, having an option to use SQL transactions could make sense at least for batch operations.
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# HELP
|
|
195
|
+
|
|
196
|
+
## Bug reports
|
|
197
|
+
Fixing bugs is a priority and getting good quality bug reports helps. It's recommended to use the sample Northwind database included with project to replicate issues or make an SQL script export of the relevant table.
|
|
198
|
+
|
|
199
|
+
## Feedback
|
|
200
|
+
Understanding and prioritizing the use cases for OINO is also important and feedback about how you'd use OINO is interesting. Feel free to raise issues and feature requests in Github, but understand that short term most of the effort goes towards reaching the beta stage.
|
|
201
|
+
|
|
202
|
+
## Typescript / Javascript architecture
|
|
203
|
+
Typescript building with different targets and module-systemts and a ton of configuration is a complex domain and something I have little experience un so help in fixing problems and how thing ought to be done is appreciated.
|
|
204
|
+
|
|
205
|
+
# LINKS
|
|
206
|
+
- [Github repository](https://github.com/pragmatta/oino-ts)
|
|
207
|
+
- [NPM repository](https://www.npmjs.com/org/oino-ts)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# ACKNOWLEDGEMENTS
|
|
211
|
+
|
|
212
|
+
## Libraries
|
|
213
|
+
OINO uses the following open source libraries and npm packages and I would like to thank everyone for their contributions:
|
|
214
|
+
- Postgresql support by [node-postgres package](https://www.npmjs.com/package/pg)
|
|
215
|
+
- Mariadb / Mysql-support by [mariadb package](https://www.npmjs.com/package/mariadb)
|
|
216
|
+
|
|
217
|
+
## Bun
|
|
218
|
+
OINO has been developed using the Bun runtime, not because of the speed improvements but for the first class Typescript support and integrated developper experience. Kudos on the bun team for making Typescript work more exiting again.
|
|
219
|
+
|
|
220
|
+
## SQL Scripts
|
|
221
|
+
The SQL scripts for creating the sample Northwind database are based on [Google Code archive](https://code.google.com/archive/p/northwindextended/downloads) and have been further customized to ensure they would have identical data (in the scope of the automated testing).
|
|
222
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
4
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
5
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.OINODb = void 0;
|
|
9
|
+
/**
|
|
10
|
+
* Base class for database abstraction, implementing methods for connecting, making queries and parsing/formatting data
|
|
11
|
+
* between SQL and serialization formats.
|
|
12
|
+
*
|
|
13
|
+
*/
|
|
14
|
+
class OINODb {
|
|
15
|
+
_params;
|
|
16
|
+
/** Name of the database */
|
|
17
|
+
name;
|
|
18
|
+
/**
|
|
19
|
+
* Constructor for `OINODb`.
|
|
20
|
+
* @param params database parameters
|
|
21
|
+
*/
|
|
22
|
+
constructor(params) {
|
|
23
|
+
this._params = params;
|
|
24
|
+
this.name = params.database;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.OINODb = OINODb;
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
4
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
5
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.OINODbApi = exports.OINODbApiResult = void 0;
|
|
9
|
+
const index_js_1 = require("./index.js");
|
|
10
|
+
const hashid_1 = require("@oino-ts/hashid");
|
|
11
|
+
/**
|
|
12
|
+
* OINO API request result object with returned data and/or http status code/message and
|
|
13
|
+
* error / warning messages.
|
|
14
|
+
*
|
|
15
|
+
*/
|
|
16
|
+
class OINODbApiResult extends index_js_1.OINOResult {
|
|
17
|
+
/** Returned data if any */
|
|
18
|
+
data;
|
|
19
|
+
/**
|
|
20
|
+
* Constructor of OINODbApiResult.
|
|
21
|
+
*
|
|
22
|
+
* @param data result data
|
|
23
|
+
*
|
|
24
|
+
*/
|
|
25
|
+
constructor(data) {
|
|
26
|
+
super();
|
|
27
|
+
this.data = data;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.OINODbApiResult = OINODbApiResult;
|
|
31
|
+
/**
|
|
32
|
+
* API class with method to process HTTP REST requests.
|
|
33
|
+
*
|
|
34
|
+
*/
|
|
35
|
+
class OINODbApi {
|
|
36
|
+
/** API database reference */
|
|
37
|
+
db;
|
|
38
|
+
/** API datamodel */
|
|
39
|
+
datamodel;
|
|
40
|
+
/** API parameters */
|
|
41
|
+
params;
|
|
42
|
+
/** API hashid */
|
|
43
|
+
hashid;
|
|
44
|
+
/**
|
|
45
|
+
* Constructor of API object.
|
|
46
|
+
* NOTE! OINODb.initDatamodel must be called if created manually instead of the factory.
|
|
47
|
+
*
|
|
48
|
+
* @param db database for the API
|
|
49
|
+
* @param params parameters for the API
|
|
50
|
+
*
|
|
51
|
+
*/
|
|
52
|
+
constructor(db, params) {
|
|
53
|
+
// OINOLog.debug("OINODbApi.constructor", {db:db, tableName:tableName, params:params})
|
|
54
|
+
if (!params.tableName) {
|
|
55
|
+
throw new Error(index_js_1.OINO_ERROR_PREFIX + ": OINODbApiParams needs to define a table name!");
|
|
56
|
+
}
|
|
57
|
+
this.db = db;
|
|
58
|
+
this.params = params;
|
|
59
|
+
this.datamodel = new index_js_1.OINODbDataModel(this);
|
|
60
|
+
if (this.params.hashidKey) {
|
|
61
|
+
this.hashid = new hashid_1.OINOHashid(this.params.hashidKey, this.db.name, this.params.hashidLength, this.params.hashidRandomIds);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
this.hashid = null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
_validateRowValues(httpResult, row, requirePrimaryKey) {
|
|
68
|
+
let field;
|
|
69
|
+
for (let i = 0; i < this.datamodel.fields.length; i++) {
|
|
70
|
+
field = this.datamodel.fields[i];
|
|
71
|
+
// OINOLog.debug("OINODbApi.validateHttpValues", {field:field})
|
|
72
|
+
const val = row[i];
|
|
73
|
+
// OINOLog.debug("OINODbApi.validateHttpValues", {val:val})
|
|
74
|
+
if ((val === null) && ((field.fieldParams.isNotNull) || (field.fieldParams.isPrimaryKey))) { // null is a valid SQL value except if it's not allowed
|
|
75
|
+
httpResult.setError(405, "Field '" + field.name + "' is not allowed to be NULL!", "ValidateRowValues");
|
|
76
|
+
}
|
|
77
|
+
else if ((val === undefined) && (requirePrimaryKey) && (field.fieldParams.isPrimaryKey) && (!field.fieldParams.isAutoInc)) {
|
|
78
|
+
httpResult.setError(405, "Primary key '" + field.name + "' is not autoinc and missing from the data!", "ValidateRowValues");
|
|
79
|
+
}
|
|
80
|
+
else if ((val !== undefined) && (this.params.failOnUpdateOnAutoinc) && (field.fieldParams.isAutoInc)) {
|
|
81
|
+
httpResult.setError(405, "Autoinc field '" + field.name + "' can't be updated!", "ValidateRowValues");
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
if ((field instanceof index_js_1.OINOStringDataField) && ((field.maxLength > 0))) {
|
|
85
|
+
const str_val = val?.toString() || "";
|
|
86
|
+
// OINOLog.debug("OINODbApi.validateHttpValues", {f:str_field, val:val})
|
|
87
|
+
if (str_val.length > field.maxLength) {
|
|
88
|
+
if (this.params.failOnOversizedValues) {
|
|
89
|
+
httpResult.setError(405, "Field '" + field.name + "' length (" + str_val.length + ") exceeds maximum (" + field.maxLength + ") and can't be set!", "ValidateRowValues");
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
httpResult.addWarning("Field '" + field.name + "' length (" + str_val.length + ") exceeds maximum (" + field.maxLength + ") and might truncate or fail.", "ValidateRowValues");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//logDebug("OINODbApi.validateHttpValues", {result:result})
|
|
99
|
+
}
|
|
100
|
+
async _doGet(result, id, params) {
|
|
101
|
+
index_js_1.OINOBenchmark.start("doGet");
|
|
102
|
+
const sql = this.datamodel.printSqlSelect(id, params.sqlParams);
|
|
103
|
+
// OINOLog.debug("OINODbApi.doGet sql", {sql:sql})
|
|
104
|
+
try {
|
|
105
|
+
const sql_res = await this.db.sqlSelect(sql);
|
|
106
|
+
// OINOLog.debug("OINODbApi.doGet sql_res", {sql_res:sql_res})
|
|
107
|
+
if (sql_res.hasErrors()) {
|
|
108
|
+
result.setError(500, sql_res.getFirstError(), "DoGet");
|
|
109
|
+
result.addDebug("OINO GET SQL [" + sql + "]", "DoPut");
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
result.data = new index_js_1.OINODbModelSet(this.datamodel, sql_res);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
result.setError(500, "Unhandled exception in doGet: " + e.message, "DoGet");
|
|
117
|
+
result.addDebug("OINO GET SQL [" + sql + "]", "DoGet");
|
|
118
|
+
}
|
|
119
|
+
index_js_1.OINOBenchmark.end("doGet");
|
|
120
|
+
}
|
|
121
|
+
async _doPost(result, rows) {
|
|
122
|
+
index_js_1.OINOBenchmark.start("doPost");
|
|
123
|
+
let sql = "";
|
|
124
|
+
try {
|
|
125
|
+
let i = 0;
|
|
126
|
+
while (i < rows.length) {
|
|
127
|
+
this._validateRowValues(result, rows[i], this.params.failOnInsertWithoutKey || false);
|
|
128
|
+
if (result.success) {
|
|
129
|
+
sql += this.datamodel.printSqlInsert(rows[i]);
|
|
130
|
+
}
|
|
131
|
+
result.setOk(); // individual rows may fail and will just be messages in response similar to executing multiple sql statements
|
|
132
|
+
i++;
|
|
133
|
+
}
|
|
134
|
+
if (sql == "") {
|
|
135
|
+
result.setError(405, "No valid rows for POST!", "DoPost");
|
|
136
|
+
result.addDebug("OINO POST DATA [" + rows.join("|") + "]", "DoPost");
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
// OINOLog.debug("OINODbApi.doPost sql", {sql:sql})
|
|
140
|
+
const sql_res = await this.db.sqlExec(sql);
|
|
141
|
+
// OINOLog.debug("OINODbApi.doPost sql_res", {sql_res:sql_res})
|
|
142
|
+
if (sql_res.hasErrors()) {
|
|
143
|
+
result.setError(500, sql_res.getFirstError(), "DoPost");
|
|
144
|
+
result.addDebug("OINO POST MESSAGES [" + sql_res.messages.join('|') + "]", "DoPost");
|
|
145
|
+
result.addDebug("OINO POST SQL [" + sql + "]", "DoPost");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch (e) {
|
|
150
|
+
result.setError(500, "Unhandled exception in doPost: " + e.message, "DoPost");
|
|
151
|
+
result.addDebug("OINO POST SQL [" + sql + "]", "DoPost");
|
|
152
|
+
}
|
|
153
|
+
index_js_1.OINOBenchmark.end("doPost");
|
|
154
|
+
}
|
|
155
|
+
async _doPut(result, id, row) {
|
|
156
|
+
index_js_1.OINOBenchmark.start("doPut");
|
|
157
|
+
let sql = "";
|
|
158
|
+
try {
|
|
159
|
+
this._validateRowValues(result, row, false);
|
|
160
|
+
if (result.success) {
|
|
161
|
+
sql = this.datamodel.printSqlUpdate(id, row);
|
|
162
|
+
// OINOLog.debug("OINODbApi.doPut sql", {sql:sql})
|
|
163
|
+
const sql_res = await this.db.sqlExec(sql);
|
|
164
|
+
// OINOLog.debug("OINODbApi.doPut sql_res", {sql_res:sql_res})
|
|
165
|
+
if (sql_res.hasErrors()) {
|
|
166
|
+
result.setError(500, sql_res.getFirstError(), "DoPut");
|
|
167
|
+
result.addDebug("OINO PUT MESSAGES [" + sql_res.messages.join('|') + "]", "DoPut");
|
|
168
|
+
result.addDebug("OINO PUT SQL [" + sql + "]", "DoPut");
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch (e) {
|
|
173
|
+
result.setError(500, "Unhandled exception: " + e.message, "DoPut");
|
|
174
|
+
result.addDebug("OINO POST SQL [" + sql + "]", "DoPut");
|
|
175
|
+
}
|
|
176
|
+
index_js_1.OINOBenchmark.end("doPut");
|
|
177
|
+
}
|
|
178
|
+
async _doDelete(result, id) {
|
|
179
|
+
index_js_1.OINOBenchmark.start("doDelete");
|
|
180
|
+
let sql = "";
|
|
181
|
+
try {
|
|
182
|
+
sql = this.datamodel.printSqlDelete(id);
|
|
183
|
+
// OINOLog.debug("OINODbApi.doDelete sql", {sql:sql})
|
|
184
|
+
const sql_res = await this.db.sqlExec(sql);
|
|
185
|
+
// OINOLog.debug("OINODbApi.doDelete sql_res", {sql_res:sql_res})
|
|
186
|
+
if (sql_res.hasErrors()) {
|
|
187
|
+
result.setError(500, sql_res.getFirstError(), "DoDelete");
|
|
188
|
+
result.addDebug("OINO DELETE MESSAGES [" + sql_res.messages.join('|') + "]", "DoDelete");
|
|
189
|
+
result.addDebug("OINO DELETE SQL [" + sql + "]", "DoDelete");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
catch (e) {
|
|
193
|
+
result.setError(500, "Unhandled exception: " + e.message, "DoDelete");
|
|
194
|
+
result.addDebug("OINO DELETE SQL [" + sql + "]", "DoDelete");
|
|
195
|
+
}
|
|
196
|
+
index_js_1.OINOBenchmark.end("doDelete");
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Method for handlind a HTTP REST request with GET, POST, PUT, DELETE corresponding to
|
|
200
|
+
* SQL select, insert, update and delete.
|
|
201
|
+
*
|
|
202
|
+
* @param method HTTP verb (uppercase)
|
|
203
|
+
* @param id URL id of the REST request
|
|
204
|
+
* @param body HTTP body data as string
|
|
205
|
+
* @param params HTTP URL parameters as key-value-pairs
|
|
206
|
+
*
|
|
207
|
+
*/
|
|
208
|
+
async doRequest(method, id, body, params) {
|
|
209
|
+
index_js_1.OINOBenchmark.start("doRequest");
|
|
210
|
+
let result = new OINODbApiResult();
|
|
211
|
+
index_js_1.OINOLog.debug("OINODbApi.doRequest enter", { method: method, id: id, body: body, searchParams: params });
|
|
212
|
+
if (method == "GET") {
|
|
213
|
+
await this._doGet(result, id, params);
|
|
214
|
+
}
|
|
215
|
+
else if (method == "PUT") {
|
|
216
|
+
const rows = index_js_1.OINODbFactory.createRows(this.datamodel, body, params);
|
|
217
|
+
if (!id) {
|
|
218
|
+
result.setError(400, "HTTP PUT method requires an URL ID for the row that is updated!", "DoRequest");
|
|
219
|
+
}
|
|
220
|
+
else if (rows.length != 1) {
|
|
221
|
+
result.setError(400, "HTTP PUT method requires exactly one row in the body data!", "DoRequest");
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
try {
|
|
225
|
+
await this._doPut(result, id, rows[0]);
|
|
226
|
+
}
|
|
227
|
+
catch (e) {
|
|
228
|
+
result.setError(500, "Unhandled exception in HTTP PUT doRequest: " + e.message, "DoRequest");
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
else if (method == "POST") {
|
|
233
|
+
const rows = index_js_1.OINODbFactory.createRows(this.datamodel, body, params);
|
|
234
|
+
if (id) {
|
|
235
|
+
result.setError(400, "HTTP POST method must not have an URL ID as it does not target an existing row but creates a new one!", "DoRequest");
|
|
236
|
+
}
|
|
237
|
+
else if (rows.length == 0) {
|
|
238
|
+
result.setError(400, "HTTP POST method requires at least one row in the body data!", "DoRequest");
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
try {
|
|
242
|
+
index_js_1.OINOLog.debug("OINODbApi.doRequest / POST", { rows: rows });
|
|
243
|
+
await this._doPost(result, rows);
|
|
244
|
+
}
|
|
245
|
+
catch (e) {
|
|
246
|
+
result.setError(500, "Unhandled exception in HTTP POST doRequest: " + e.message, "DoRequest");
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
else if (method == "DELETE") {
|
|
251
|
+
if (!id) {
|
|
252
|
+
result.setError(400, "HTTP DELETE method requires an id!", "DoRequest");
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
try {
|
|
256
|
+
await this._doDelete(result, id);
|
|
257
|
+
}
|
|
258
|
+
catch (e) {
|
|
259
|
+
result.setError(500, "Unhandled exception in HTTP DELETE doRequest: " + e.message, "DoRequest");
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
result.setError(405, "Unsupported HTTP method '" + method + "'", "DoRequest");
|
|
265
|
+
}
|
|
266
|
+
index_js_1.OINOBenchmark.end("doRequest");
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
exports.OINODbApi = OINODbApi;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Set the name of the OINO ID field (default \_OINOID\_) */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.OINODbConfig = void 0;
|
|
5
|
+
class OINODbConfig {
|
|
6
|
+
/** Name of the synthetic OINO ID field */
|
|
7
|
+
static OINODB_ID_FIELD = "_OINOID_";
|
|
8
|
+
/** Private key separator of the synthetic OINO ID field */
|
|
9
|
+
static OINODB_ID_SEPARATOR = "_";
|
|
10
|
+
static OINODB_ID_SEPARATOR_ESCAPED = "%";
|
|
11
|
+
/** Name of the OINODbSqlFilter-parameter in request */
|
|
12
|
+
static OINODB_SQL_FILTER_PARAM = "oinosqlfilter";
|
|
13
|
+
/** Name of the OINODbSqlOrder-parameter in request */
|
|
14
|
+
static OINODB_SQL_ORDER_PARAM = "oinosqlorder";
|
|
15
|
+
/** Name of the OINODbSqlLimit-parameter in request */
|
|
16
|
+
static OINODB_SQL_LIMIT_PARAM = "oinosqllimit";
|
|
17
|
+
/**
|
|
18
|
+
* Set the name of the OINO ID field
|
|
19
|
+
* @param idField name of the OINO ID field
|
|
20
|
+
*/
|
|
21
|
+
static setOinoIdField(idField) {
|
|
22
|
+
if (idField) {
|
|
23
|
+
OINODbConfig.OINODB_ID_FIELD = idField;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Set the separator character of the OINO ID field
|
|
28
|
+
* @param idSeparator character to use as separator of id parts
|
|
29
|
+
*/
|
|
30
|
+
static setOinoIdSeparator(idSeparator) {
|
|
31
|
+
if (idSeparator && (idSeparator.length == 1)) {
|
|
32
|
+
OINODbConfig.OINODB_ID_SEPARATOR = idSeparator;
|
|
33
|
+
OINODbConfig.OINODB_ID_SEPARATOR_ESCAPED = '%' + idSeparator.charCodeAt(0).toString(16);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Print OINO ID for primary key values.
|
|
38
|
+
*
|
|
39
|
+
* @param primaryKeys an array of primary key values.
|
|
40
|
+
*
|
|
41
|
+
*/
|
|
42
|
+
static printOINOId(primaryKeys) {
|
|
43
|
+
let result = "";
|
|
44
|
+
for (let i = 0; i < primaryKeys.length; i++) {
|
|
45
|
+
if (i > 0) {
|
|
46
|
+
result += OINODbConfig.OINODB_ID_SEPARATOR;
|
|
47
|
+
}
|
|
48
|
+
result += encodeURIComponent(primaryKeys[i]).replaceAll(OINODbConfig.OINODB_ID_SEPARATOR, OINODbConfig.OINODB_ID_SEPARATOR_ESCAPED);
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Set the name of the OINODbSqlFilter-param field
|
|
54
|
+
*
|
|
55
|
+
* @param sqlFilterParam name of the http parameter with `OINODbSqlFilter` definition
|
|
56
|
+
*
|
|
57
|
+
*/
|
|
58
|
+
static setOinoSqlFilterParam(sqlFilterParam) {
|
|
59
|
+
if (sqlFilterParam) {
|
|
60
|
+
OINODbConfig.OINODB_SQL_FILTER_PARAM = sqlFilterParam;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Set the name of the OINODbSqlOrder-param field
|
|
65
|
+
*
|
|
66
|
+
* @param sqlOrderParam name of the http parameter with `OINODbSqlOrder` definition
|
|
67
|
+
*
|
|
68
|
+
*/
|
|
69
|
+
static setOinoSqlOrderParam(sqlOrderParam) {
|
|
70
|
+
if (sqlOrderParam) {
|
|
71
|
+
OINODbConfig.OINODB_SQL_ORDER_PARAM = sqlOrderParam;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Set the name of the OINODbSqlLimit-param field
|
|
76
|
+
*
|
|
77
|
+
* @param sqlLimitParam name of the http parameter with `OINODbSqlLimit` definition
|
|
78
|
+
*
|
|
79
|
+
*/
|
|
80
|
+
static setOinoSqlLimitParam(sqlLimitParam) {
|
|
81
|
+
if (sqlLimitParam) {
|
|
82
|
+
OINODbConfig.OINODB_SQL_LIMIT_PARAM = sqlLimitParam;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
exports.OINODbConfig = OINODbConfig;
|