@oino-ts/db 0.6.1 → 0.7.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/dist/cjs/OINODb.js +7 -0
- package/dist/cjs/OINODbApi.js +149 -48
- package/dist/cjs/OINODbDataModel.js +2 -0
- package/dist/esm/OINODb.js +7 -0
- package/dist/esm/OINODbApi.js +149 -48
- package/dist/esm/OINODbDataModel.js +2 -0
- package/dist/types/OINODb.d.ts +11 -0
- package/dist/types/OINODbApi.d.ts +23 -3
- package/dist/types/index.d.ts +2 -0
- package/package.json +3 -3
- package/src/OINODb.ts +15 -0
- package/src/OINODbApi.test.ts +108 -58
- package/src/OINODbApi.ts +150 -47
- package/src/OINODbDataModel.ts +2 -0
- package/src/index.ts +2 -0
package/src/OINODbApi.ts
CHANGED
|
@@ -162,6 +162,9 @@ export class OINODbHtmlTemplate extends OINOHtmlTemplate {
|
|
|
162
162
|
*
|
|
163
163
|
*/
|
|
164
164
|
export class OINODbApi {
|
|
165
|
+
/** Enable debug output on errors */
|
|
166
|
+
private _debugOnError:boolean = false
|
|
167
|
+
|
|
165
168
|
/** API database reference */
|
|
166
169
|
readonly db: OINODb
|
|
167
170
|
|
|
@@ -197,7 +200,19 @@ export class OINODbApi {
|
|
|
197
200
|
}
|
|
198
201
|
}
|
|
199
202
|
|
|
200
|
-
private
|
|
203
|
+
private _printSql(result: OINODbApiResult, rows: OINODataRow[], requirePrimaryKey: boolean, printer: (row: OINODataRow) => string): string {
|
|
204
|
+
let sql = "";
|
|
205
|
+
for (let i = 0; i < rows.length; i++) {
|
|
206
|
+
this._validateRow(result, rows[i], requirePrimaryKey);
|
|
207
|
+
if (result.success) {
|
|
208
|
+
sql += printer(rows[i]);
|
|
209
|
+
} else if (this.params.failOnAnyInvalidRows === false) {
|
|
210
|
+
result.setOk(); // individual rows may fail and will just be messages in response similar to executing multiple sql statements
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return sql;
|
|
214
|
+
}
|
|
215
|
+
private _validateRow(result:OINODbApiResult, row:OINODataRow, requirePrimaryKey:boolean):void {
|
|
201
216
|
let field:OINODbDataField
|
|
202
217
|
for (let i=0; i<this.datamodel.fields.length; i++) {
|
|
203
218
|
field = this.datamodel.fields[i]
|
|
@@ -205,13 +220,13 @@ export class OINODbApi {
|
|
|
205
220
|
const val:OINODataCell = row[i]
|
|
206
221
|
// OINOLog.debug("OINODbApi.validateHttpValues", {val:val})
|
|
207
222
|
if ((val === null) && ((field.fieldParams.isNotNull)||(field.fieldParams.isPrimaryKey))) { // null is a valid SQL value except if it's not allowed
|
|
208
|
-
|
|
223
|
+
result.setError(405, "Field '" + field.name + "' is not allowed to be NULL!", "ValidateRowValues")
|
|
209
224
|
|
|
210
225
|
} else if ((val === undefined) && (requirePrimaryKey) && (field.fieldParams.isPrimaryKey) && (!field.fieldParams.isAutoInc)) {
|
|
211
|
-
|
|
226
|
+
result.setError(405, "Primary key '" + field.name + "' is not autoinc and missing from the data!", "ValidateRowValues")
|
|
212
227
|
|
|
213
228
|
} else if ((val !== undefined) && (this.params.failOnUpdateOnAutoinc) && (field.fieldParams.isAutoInc)) {
|
|
214
|
-
|
|
229
|
+
result.setError(405, "Autoinc field '" + field.name + "' can't be updated!", "ValidateRowValues")
|
|
215
230
|
|
|
216
231
|
} else {
|
|
217
232
|
if ((field instanceof OINOStringDataField) && ((field.maxLength > 0))){
|
|
@@ -219,9 +234,9 @@ export class OINODbApi {
|
|
|
219
234
|
// OINOLog.debug("OINODbApi.validateHttpValues", {f:str_field, val:val})
|
|
220
235
|
if (str_val.length > field.maxLength) {
|
|
221
236
|
if (this.params.failOnOversizedValues) {
|
|
222
|
-
|
|
237
|
+
result.setError(405, "Field '" + field.name + "' length (" + str_val.length + ") exceeds maximum (" + field.maxLength + ") and can't be set!", "ValidateRowValues")
|
|
223
238
|
} else {
|
|
224
|
-
|
|
239
|
+
result.addWarning("Field '" + field.name + "' length (" + str_val.length + ") exceeds maximum (" + field.maxLength + ") and might truncate or fail.", "ValidateRowValues")
|
|
225
240
|
}
|
|
226
241
|
}
|
|
227
242
|
}
|
|
@@ -231,6 +246,21 @@ export class OINODbApi {
|
|
|
231
246
|
//logDebug("OINODbApi.validateHttpValues", {result:result})
|
|
232
247
|
}
|
|
233
248
|
|
|
249
|
+
private _parseData(httpResult:OINODbApiResult, body:string|OINODataRow[]|Buffer|any, params:OINODbApiRequestParams):OINODataRow[] {
|
|
250
|
+
let rows:OINODataRow[] = []
|
|
251
|
+
try {
|
|
252
|
+
if (Array.isArray(body)) {
|
|
253
|
+
rows = body as OINODataRow[]
|
|
254
|
+
} else {
|
|
255
|
+
rows = OINODbParser.createRows(this.datamodel, body, params)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
} catch (e:any) {
|
|
259
|
+
httpResult.setError(400, "Invalid data: " + e.message, "DoRequest")
|
|
260
|
+
}
|
|
261
|
+
return rows
|
|
262
|
+
}
|
|
263
|
+
|
|
234
264
|
private async _doGet(result:OINODbApiResult, id:string, params:OINODbApiRequestParams):Promise<void> {
|
|
235
265
|
let sql:string = ""
|
|
236
266
|
try {
|
|
@@ -253,27 +283,28 @@ export class OINODbApi {
|
|
|
253
283
|
private async _doPost(result:OINODbApiResult, rows:OINODataRow[]):Promise<void> {
|
|
254
284
|
let sql:string = ""
|
|
255
285
|
try {
|
|
256
|
-
let i
|
|
257
|
-
|
|
258
|
-
this._validateRowValues(result, rows[i], this.params.failOnInsertWithoutKey||false)
|
|
286
|
+
for (let i=0; i<rows.length; i++) {
|
|
287
|
+
this._validateRow(result, rows[i], this.params.failOnInsertWithoutKey||false)
|
|
259
288
|
if (result.success) {
|
|
260
289
|
sql += this.datamodel.printSqlInsert(rows[i])
|
|
290
|
+
|
|
291
|
+
} else if (this.params.failOnAnyInvalidRows == false) {
|
|
292
|
+
result.setOk() // individual rows may fail and will just be messages in response similar to executing multiple sql statements
|
|
261
293
|
}
|
|
262
|
-
result.setOk() // individual rows may fail and will just be messages in response similar to executing multiple sql statements
|
|
263
|
-
i++
|
|
264
294
|
}
|
|
265
|
-
if (sql == "") {
|
|
295
|
+
if ((sql == "") && result.success) {
|
|
266
296
|
result.setError(405, "No valid rows for POST!", "DoPost")
|
|
267
|
-
result.addDebug("OINO POST DATA [" + rows.join("|") + "]", "DoPost")
|
|
268
297
|
|
|
269
|
-
} else {
|
|
298
|
+
} else if (result.success) {
|
|
270
299
|
// OINOLog.debug("OINODbApi.doPost sql", {sql:sql})
|
|
271
300
|
const sql_res:OINODbDataSet = await this.db.sqlExec(sql)
|
|
272
301
|
// OINOLog.debug("OINODbApi.doPost sql_res", {sql_res:sql_res})
|
|
273
302
|
if (sql_res.hasErrors()) {
|
|
274
303
|
result.setError(500, sql_res.getFirstError(), "DoPost")
|
|
275
|
-
|
|
276
|
-
|
|
304
|
+
if (this._debugOnError) {
|
|
305
|
+
result.addDebug("OINO POST MESSAGES [" + sql_res.messages.join('|') + "]", "DoPost")
|
|
306
|
+
result.addDebug("OINO POST SQL [" + sql + "]", "DoPost")
|
|
307
|
+
}
|
|
277
308
|
}
|
|
278
309
|
}
|
|
279
310
|
} catch (e:any) {
|
|
@@ -282,19 +313,33 @@ export class OINODbApi {
|
|
|
282
313
|
}
|
|
283
314
|
}
|
|
284
315
|
|
|
285
|
-
private async _doPut(result:OINODbApiResult, id:string,
|
|
316
|
+
private async _doPut(result:OINODbApiResult, id:string|null, rows:OINODataRow[]):Promise<void> {
|
|
286
317
|
let sql:string = ""
|
|
287
318
|
try {
|
|
288
|
-
this._validateRowValues(result, row, false)
|
|
289
|
-
|
|
290
|
-
|
|
319
|
+
// this._validateRowValues(result, row, false)
|
|
320
|
+
for (let i=0; i<rows.length; i++) {
|
|
321
|
+
const row_id = id || OINODbConfig.printOINOId(this.datamodel.getRowPrimarykeyValues(rows[i], this.hashid != null))
|
|
322
|
+
this._validateRow(result, rows[i], this.params.failOnInsertWithoutKey||false)
|
|
323
|
+
if (result.success) {
|
|
324
|
+
sql += this.datamodel.printSqlUpdate(row_id, rows[i])
|
|
325
|
+
|
|
326
|
+
} else if (this.params.failOnAnyInvalidRows == false) {
|
|
327
|
+
result.setOk() // individual rows may fail and will just be messages in response similar to executing multiple sql statements
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if ((sql == "") && result.success) {
|
|
331
|
+
result.setError(405, "No valid rows for PUT!", "DoPut") // only set error if there are multiple rows and no valid sql was created
|
|
332
|
+
|
|
333
|
+
} else if (result.success) {
|
|
291
334
|
// OINOLog.debug("OINODbApi.doPut sql", {sql:sql})
|
|
292
335
|
const sql_res:OINODbDataSet = await this.db.sqlExec(sql)
|
|
293
336
|
// OINOLog.debug("OINODbApi.doPut sql_res", {sql_res:sql_res})
|
|
294
337
|
if (sql_res.hasErrors()) {
|
|
295
338
|
result.setError(500, sql_res.getFirstError(), "DoPut")
|
|
296
|
-
|
|
297
|
-
|
|
339
|
+
if (this._debugOnError) {
|
|
340
|
+
result.addDebug("OINO PUT MESSAGES [" + sql_res.messages.join('|') + "]", "DoPut")
|
|
341
|
+
result.addDebug("OINO PUT SQL [" + sql + "]", "DoPut")
|
|
342
|
+
}
|
|
298
343
|
}
|
|
299
344
|
}
|
|
300
345
|
} catch (e:any) {
|
|
@@ -303,17 +348,36 @@ export class OINODbApi {
|
|
|
303
348
|
}
|
|
304
349
|
}
|
|
305
350
|
|
|
306
|
-
private async _doDelete(result:OINODbApiResult, id:string):Promise<void> {
|
|
351
|
+
private async _doDelete(result:OINODbApiResult, id:string|null, rows:OINODataRow[]|null):Promise<void> {
|
|
307
352
|
let sql:string = ""
|
|
308
353
|
try {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
354
|
+
if (rows != null) {
|
|
355
|
+
for (let i=0; i<rows.length; i++) {
|
|
356
|
+
const row_id = OINODbConfig.printOINOId(this.datamodel.getRowPrimarykeyValues(rows[i], this.hashid != null))
|
|
357
|
+
if (row_id) {
|
|
358
|
+
sql += this.datamodel.printSqlDelete(row_id)
|
|
359
|
+
} else if (this.params.failOnAnyInvalidRows == false) {
|
|
360
|
+
result.setOk() // individual rows may fail and will just be messages in response similar to executing multiple sql statements
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
} else if (id) {
|
|
364
|
+
sql = this.datamodel.printSqlDelete(id)
|
|
365
|
+
}
|
|
366
|
+
if ((sql == "") && result.success) {
|
|
367
|
+
result.setError(405, "No valid rows for DELETE!", "DoDelete") // only set error if there are multiple rows and no valid sql was created
|
|
368
|
+
|
|
369
|
+
} else if (result.success) {
|
|
370
|
+
|
|
371
|
+
// OINOLog.debug("OINODbApi.doDelete sql", {sql:sql})
|
|
372
|
+
const sql_res:OINODbDataSet = await this.db.sqlExec(sql)
|
|
373
|
+
// OINOLog.debug("OINODbApi.doDelete sql_res", {sql_res:sql_res})
|
|
374
|
+
if (sql_res.hasErrors()) {
|
|
375
|
+
result.setError(500, sql_res.getFirstError(), "DoDelete")
|
|
376
|
+
if (this._debugOnError) {
|
|
377
|
+
result.addDebug("OINO DELETE MESSAGES [" + sql_res.messages.join('|') + "]", "DoDelete")
|
|
378
|
+
result.addDebug("OINO DELETE SQL [" + sql + "]", "DoDelete")
|
|
379
|
+
}
|
|
380
|
+
}
|
|
317
381
|
}
|
|
318
382
|
} catch (e:any) {
|
|
319
383
|
result.setError(500, "Unhandled exception: " + e.message, "DoDelete")
|
|
@@ -321,33 +385,32 @@ export class OINODbApi {
|
|
|
321
385
|
}
|
|
322
386
|
}
|
|
323
387
|
|
|
388
|
+
/**
|
|
389
|
+
* Enable or disable debug output on errors.
|
|
390
|
+
*
|
|
391
|
+
* @param debugOnError true to enable debug output on errors, false to disable
|
|
392
|
+
*/
|
|
393
|
+
setDebugOnError(debugOnError:boolean) {
|
|
394
|
+
this._debugOnError = debugOnError
|
|
395
|
+
}
|
|
396
|
+
|
|
324
397
|
/**
|
|
325
398
|
* Method for handlind a HTTP REST request with GET, POST, PUT, DELETE corresponding to
|
|
326
399
|
* SQL select, insert, update and delete.
|
|
327
400
|
*
|
|
328
401
|
* @param method HTTP verb (uppercase)
|
|
329
402
|
* @param id URL id of the REST request
|
|
330
|
-
* @param
|
|
403
|
+
* @param data HTTP body data as either serialized string or unserialized JS object / OINODataRow-array
|
|
331
404
|
* @param params HTTP URL parameters as key-value-pairs
|
|
332
405
|
*
|
|
333
406
|
*/
|
|
334
|
-
async doRequest(method:string, id: string,
|
|
407
|
+
async doRequest(method:string, id: string, data:string|OINODataRow[]|Buffer|any, params:OINODbApiRequestParams = API_EMPTY_PARAMS):Promise<OINODbApiResult> {
|
|
335
408
|
OINOBenchmark.start("OINODbApi", "doRequest")
|
|
336
409
|
// OINOLog.debug("OINODbApi.doRequest enter", {method:method, id:id, body:body, params:params})
|
|
337
410
|
let result:OINODbApiResult = new OINODbApiResult(params)
|
|
338
411
|
let rows:OINODataRow[] = []
|
|
339
412
|
if ((method == "POST") || (method == "PUT")) {
|
|
340
|
-
|
|
341
|
-
if (Array.isArray(body)) {
|
|
342
|
-
rows = body as OINODataRow[]
|
|
343
|
-
} else {
|
|
344
|
-
rows = OINODbParser.createRows(this.datamodel, body, params)
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
} catch (e:any) {
|
|
348
|
-
result.setError(400, "Invalid data: " + e.message, "DoRequest")
|
|
349
|
-
}
|
|
350
|
-
// OINOLog.debug("OINODbApi.doRequest - OINODataRow rows", {rows:rows})
|
|
413
|
+
rows = this._parseData(result, data, params)
|
|
351
414
|
}
|
|
352
415
|
if (method == "GET") {
|
|
353
416
|
await this._doGet(result, id, params)
|
|
@@ -361,7 +424,7 @@ export class OINODbApi {
|
|
|
361
424
|
|
|
362
425
|
} else {
|
|
363
426
|
try {
|
|
364
|
-
await this._doPut(result, id, rows
|
|
427
|
+
await this._doPut(result, id, rows)
|
|
365
428
|
|
|
366
429
|
} catch (e:any) {
|
|
367
430
|
result.setError(500, "Unhandled exception in HTTP PUT doRequest: " + e.message, "DoRequest")
|
|
@@ -389,19 +452,59 @@ export class OINODbApi {
|
|
|
389
452
|
|
|
390
453
|
} else {
|
|
391
454
|
try {
|
|
392
|
-
await this._doDelete(result, id)
|
|
455
|
+
await this._doDelete(result, id, null)
|
|
393
456
|
|
|
394
457
|
} catch (e:any) {
|
|
395
458
|
result.setError(500, "Unhandled exception in HTTP DELETE doRequest: " + e.message, "DoRequest")
|
|
396
459
|
}
|
|
397
460
|
}
|
|
398
461
|
} else {
|
|
399
|
-
result.setError(405, "Unsupported HTTP method '" + method + "'", "DoRequest")
|
|
462
|
+
result.setError(405, "Unsupported HTTP method '" + method + "' for REST request", "DoRequest")
|
|
400
463
|
}
|
|
401
464
|
OINOBenchmark.end("OINODbApi", "doRequest", method)
|
|
402
465
|
return Promise.resolve(result)
|
|
403
466
|
}
|
|
404
467
|
|
|
468
|
+
/**
|
|
469
|
+
* Method for handlind a HTTP REST request with GET, POST, PUT, DELETE corresponding to
|
|
470
|
+
* SQL select, insert, update and delete.
|
|
471
|
+
*
|
|
472
|
+
* @param method HTTP verb (uppercase)
|
|
473
|
+
* @param data HTTP body data as either serialized string or unserialized JS object / OINODataRow-array
|
|
474
|
+
* @param params HTTP URL parameters as key-value-pairs
|
|
475
|
+
*
|
|
476
|
+
*/
|
|
477
|
+
async doBatchUpdate(method:string, data:string|OINODataRow[]|Buffer|any, params:OINODbApiRequestParams = API_EMPTY_PARAMS):Promise<OINODbApiResult> {
|
|
478
|
+
OINOBenchmark.start("OINODbApi", "doBatchUpdate")
|
|
479
|
+
// OINOLog.debug("OINODbApi.doRequest enter", {method:method, id:id, body:body, params:params})
|
|
480
|
+
let result:OINODbApiResult = new OINODbApiResult(params)
|
|
481
|
+
let rows:OINODataRow[] = []
|
|
482
|
+
if ((method == "PUT")) {
|
|
483
|
+
rows = this._parseData(result, data, params)
|
|
484
|
+
}
|
|
485
|
+
if (method == "PUT") {
|
|
486
|
+
|
|
487
|
+
try {
|
|
488
|
+
await this._doPut(result, null, rows)
|
|
489
|
+
|
|
490
|
+
} catch (e:any) {
|
|
491
|
+
result.setError(500, "Unhandled exception in HTTP PUT doRequest: " + e.message, "DoBatchUpdate")
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
} else if (method == "DELETE") {
|
|
495
|
+
try {
|
|
496
|
+
await this._doDelete(result, null, rows)
|
|
497
|
+
|
|
498
|
+
} catch (e:any) {
|
|
499
|
+
result.setError(500, "Unhandled exception in HTTP DELETE doRequest: " + e.message, "DoBatchUpdate")
|
|
500
|
+
}
|
|
501
|
+
} else {
|
|
502
|
+
result.setError(405, "Unsupported HTTP method '" + method + "' for batch update", "DoBatchUpdate")
|
|
503
|
+
}
|
|
504
|
+
OINOBenchmark.end("OINODbApi", "doBatchUpdate", method)
|
|
505
|
+
return Promise.resolve(result)
|
|
506
|
+
}
|
|
507
|
+
|
|
405
508
|
/**
|
|
406
509
|
* Method to check if a field is included in the API params.
|
|
407
510
|
*
|
package/src/OINODbDataModel.ts
CHANGED
|
@@ -103,6 +103,7 @@ export class OINODbDataModel {
|
|
|
103
103
|
if ((f instanceof OINONumberDataField) && (this.api.hashid)) {
|
|
104
104
|
value = this.api.hashid.decode(value)
|
|
105
105
|
}
|
|
106
|
+
// OINOLog.debug("OINODbDataModel._printSqlPrimaryKeyCondition", {field:f.name, value:value, id_value:id_value})
|
|
106
107
|
result += f.printSqlColumnName() + "=" + f.printCellAsSqlValue(value);
|
|
107
108
|
i = i + 1
|
|
108
109
|
}
|
|
@@ -279,6 +280,7 @@ export class OINODbDataModel {
|
|
|
279
280
|
*/
|
|
280
281
|
printSqlUpdate(id: string, row: OINODataRow): string {
|
|
281
282
|
let result: string = "UPDATE " + this.api.db.printSqlTablename(this.api.params.tableName) + " SET " + this._printSqlUpdateValues(row) + " WHERE " + this._printSqlPrimaryKeyCondition(id) + ";";
|
|
283
|
+
// OINOLog.debug("OINODbDataModel.printSqlUpdate", {result:result, id:id, row:row})
|
|
282
284
|
return result;
|
|
283
285
|
}
|
|
284
286
|
|
package/src/index.ts
CHANGED
|
@@ -30,6 +30,8 @@ export type OINODbApiParams = {
|
|
|
30
30
|
failOnUpdateOnAutoinc?: boolean
|
|
31
31
|
/** Reject POST-requests without primary key value (can work if DB-side ) */
|
|
32
32
|
failOnInsertWithoutKey?: boolean
|
|
33
|
+
/** Reject POST-requests without primary key value (can work if DB-side ) */
|
|
34
|
+
failOnAnyInvalidRows?: boolean
|
|
33
35
|
/** Treat date type fields as just strings and use the native formatting instead of the ISO 8601 format */
|
|
34
36
|
useDatesAsString?: Boolean
|
|
35
37
|
/** Include given fields from the API and exclude rest (if defined) */
|