@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.
Files changed (48) hide show
  1. package/README.md +222 -0
  2. package/dist/cjs/OINODb.js +27 -0
  3. package/dist/cjs/OINODbApi.js +270 -0
  4. package/dist/cjs/OINODbConfig.js +86 -0
  5. package/dist/cjs/OINODbDataField.js +354 -0
  6. package/dist/cjs/OINODbDataModel.js +279 -0
  7. package/dist/cjs/OINODbDataSet.js +139 -0
  8. package/dist/cjs/OINODbFactory.js +563 -0
  9. package/dist/cjs/OINODbModelSet.js +267 -0
  10. package/dist/cjs/OINODbParams.js +280 -0
  11. package/dist/cjs/OINODbRequestParams.js +280 -0
  12. package/dist/cjs/OINODbSwagger.js +201 -0
  13. package/dist/cjs/index.js +51 -0
  14. package/dist/esm/OINODb.js +23 -0
  15. package/dist/esm/OINODbApi.js +265 -0
  16. package/dist/esm/OINODbConfig.js +82 -0
  17. package/dist/esm/OINODbDataField.js +345 -0
  18. package/dist/esm/OINODbDataModel.js +275 -0
  19. package/dist/esm/OINODbDataSet.js +134 -0
  20. package/dist/esm/OINODbFactory.js +559 -0
  21. package/dist/esm/OINODbModelSet.js +263 -0
  22. package/dist/esm/OINODbRequestParams.js +274 -0
  23. package/dist/esm/OINODbSwagger.js +197 -0
  24. package/dist/esm/index.js +17 -0
  25. package/dist/types/OINODb.d.ts +75 -0
  26. package/dist/types/OINODbApi.d.ts +57 -0
  27. package/dist/types/OINODbConfig.d.ts +52 -0
  28. package/dist/types/OINODbDataField.d.ts +202 -0
  29. package/dist/types/OINODbDataModel.d.ts +108 -0
  30. package/dist/types/OINODbDataSet.d.ts +95 -0
  31. package/dist/types/OINODbFactory.d.ts +99 -0
  32. package/dist/types/OINODbModelSet.d.ts +50 -0
  33. package/dist/types/OINODbRequestParams.d.ts +130 -0
  34. package/dist/types/OINODbSwagger.d.ts +25 -0
  35. package/dist/types/index.d.ts +103 -0
  36. package/package.json +35 -0
  37. package/src/OINODb.ts +98 -0
  38. package/src/OINODbApi.test.ts +243 -0
  39. package/src/OINODbApi.ts +270 -0
  40. package/src/OINODbConfig.ts +92 -0
  41. package/src/OINODbDataField.ts +372 -0
  42. package/src/OINODbDataModel.ts +290 -0
  43. package/src/OINODbDataSet.ts +170 -0
  44. package/src/OINODbFactory.ts +570 -0
  45. package/src/OINODbModelSet.ts +286 -0
  46. package/src/OINODbRequestParams.ts +281 -0
  47. package/src/OINODbSwagger.ts +209 -0
  48. package/src/index.ts +116 -0
@@ -0,0 +1,570 @@
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+
7
+ import { OINODbApi, OINODbApiParams, OINODbParams, OINOContentType, OINODbDataModel, OINODbDataField, OINODb, OINODataRow, OINODbConstructor, OINORequestParams, OINODbSqlFilter, OINOStr, OINOBlobDataField, OINODbApiResult, OINODbDataSet, OINODbModelSet, OINODbConfig, OINONumberDataField, OINODataCell, OINODbSqlOrder, OINODbSqlLimit, OINO_ERROR_PREFIX, OINO_WARNING_PREFIX, OINO_INFO_PREFIX, OINO_DEBUG_PREFIX, OINOLog } from "./index.js"
8
+
9
+ /**
10
+ * Static factory class for easily creating things based on data
11
+ *
12
+ */
13
+ export class OINODbFactory {
14
+ private static _dbRegistry:Record<string, OINODbConstructor> = {}
15
+
16
+ /**
17
+ * Register a supported database class. Used to enable those that are installed in the factory
18
+ * instead of forcing everyone to install all database libraries.
19
+ *
20
+ * @param dbName name of the database implementation class
21
+ * @param dbTypeClass constructor for creating a database of that type
22
+ */
23
+ static registerDb(dbName:string, dbTypeClass: OINODbConstructor):void {
24
+ // OINOLog.debug("OINODbFactory.registerDb", {dbType:dbName})
25
+ this._dbRegistry[dbName] = dbTypeClass
26
+
27
+ }
28
+
29
+ /**
30
+ * Create database from parameters from the registered classes.
31
+ *
32
+ * @param params database connection parameters
33
+ */
34
+ static async createDb(params:OINODbParams):Promise<OINODb> {
35
+ let result:OINODb
36
+ let db_type = this._dbRegistry[params.type]
37
+ if (db_type) {
38
+ result = new db_type(params)
39
+ } else {
40
+ throw new Error("Unsupported database type: " + params.type)
41
+ }
42
+ await result.connect()
43
+ return result
44
+ }
45
+
46
+ /**
47
+ * Create API from parameters and calls initDatamodel on the datamodel.
48
+ *
49
+ * @param db databased used in API
50
+ * @param params parameters of the API
51
+ */
52
+ static async createApi(db: OINODb, params: OINODbApiParams):Promise<OINODbApi> {
53
+ let result:OINODbApi = new OINODbApi(db, params)
54
+ await db.initializeApiDatamodel(result)
55
+ return result
56
+ }
57
+
58
+ /**
59
+ * Creates a key-value-collection from Javascript URL parameters.
60
+ *
61
+ * @param request HTTP Request
62
+ */
63
+ static createParamsFromRequest(request:Request):OINORequestParams {
64
+ let result:OINORequestParams = { sqlParams: {}}
65
+ const url:URL = new URL(request.url)
66
+ const content_type = request.headers.get("content-type")
67
+ if (content_type == OINOContentType.csv) {
68
+ result.requestType = OINOContentType.csv
69
+
70
+ } else if (content_type == OINOContentType.urlencode) {
71
+ result.requestType = OINOContentType.urlencode
72
+
73
+ } else if (content_type?.startsWith(OINOContentType.formdata)) {
74
+ result.requestType = OINOContentType.formdata
75
+ result.multipartBoundary = content_type.split('boundary=')[1] || ""
76
+
77
+ } else {
78
+ result.requestType = OINOContentType.json
79
+ }
80
+ const accept = request.headers.get("accept")
81
+ // OINOLog.debug("createParamsFromRequest: accept headers", {accept:accept})
82
+ const accept_types = accept?.split(', ') || []
83
+ for (let i=0; i<accept_types.length; i++) {
84
+ if (Object.values(OINOContentType).includes(accept_types[i] as OINOContentType)) {
85
+ result.responseType = accept_types[i] as OINOContentType
86
+ // OINOLog.debug("createParamsFromRequest: response type found", {respnse_type:result.responseType})
87
+ break
88
+ }
89
+ }
90
+ if (result.responseType === undefined) {
91
+ result.responseType = OINOContentType.json
92
+ }
93
+
94
+ const filter = url.searchParams.get(OINODbConfig.OINODB_SQL_FILTER_PARAM)
95
+ if (filter) {
96
+ result.sqlParams.filter = OINODbSqlFilter.parse(filter)
97
+ }
98
+ const order = url.searchParams.get(OINODbConfig.OINODB_SQL_ORDER_PARAM)
99
+ if (order) {
100
+ result.sqlParams.order = new OINODbSqlOrder(order)
101
+ }
102
+ const limit = url.searchParams.get(OINODbConfig.OINODB_SQL_LIMIT_PARAM)
103
+ if (limit) {
104
+ result.sqlParams.limit = new OINODbSqlLimit(limit)
105
+ }
106
+ // OINOLog.debug("createParamsFromRequest", {params:result})
107
+ return result
108
+ }
109
+
110
+ /**
111
+ * Creates a HTTP Response from API results.
112
+ *
113
+ * @param apiResult API results
114
+ * @param requestParams API request parameters
115
+ * @param responseHeaders Headers to include in the response
116
+ *
117
+ */
118
+ static createResponseFromApiResult(apiResult:OINODbApiResult, requestParams:OINORequestParams, responseHeaders:Record<string, string> = {}):Response {
119
+ let response:Response|null = null
120
+ if (apiResult.success && apiResult.data) {
121
+ response = new Response(apiResult.data.writeString(requestParams.responseType), {status:apiResult.statusCode, statusText: apiResult.statusMessage, headers: responseHeaders })
122
+ } else {
123
+ response = new Response(JSON.stringify(apiResult), {status:apiResult.statusCode, statusText: apiResult.statusMessage, headers: responseHeaders })
124
+ }
125
+ for (let i=0; i<apiResult.messages.length; i++) {
126
+ response.headers.set('X-OINO-MESSAGE-' + i, apiResult.messages[i])
127
+ }
128
+ return response
129
+ }
130
+
131
+ /**
132
+ * Creates HTML Response from API modelset.
133
+ *
134
+ * @param modelset OINO API dataset
135
+ * @param template HTML template
136
+ *
137
+ */
138
+ static createHtmlFromData(modelset:OINODbModelSet, template:string):string {
139
+ let result:string = ""
140
+ const dataset:OINODbDataSet = modelset.dataset
141
+ const datamodel:OINODbDataModel = modelset.datamodel
142
+ while (!dataset.isEof()) {
143
+ const row:OINODataRow = dataset.getRow()
144
+ let row_id_seed:string = datamodel.getRowPrimarykeyValues(row).join(' ')
145
+ let primary_key_values:string[] = []
146
+ let html_row:string = template.replaceAll('###' + OINODbConfig.OINODB_ID_FIELD + '###', '###createHtmlFromData_temporary_oinoid###')
147
+ for (let i=0; i<datamodel.fields.length; i++) {
148
+ const f:OINODbDataField = datamodel.fields[i]
149
+ let value:string|null|undefined = f.serializeCell(row[i])
150
+ if (f.fieldParams.isPrimaryKey) {
151
+ if (value && (f instanceof OINONumberDataField) && (datamodel.api.hashid)) {
152
+ value = datamodel.api.hashid.encode(value, f.name + " " + row_id_seed)
153
+ }
154
+ primary_key_values.push(value || "")
155
+ }
156
+ html_row = html_row.replaceAll('###' + f.name + '###', OINOStr.encode(value, OINOContentType.html))
157
+ }
158
+ html_row = html_row.replaceAll('###createHtmlFromData_temporary_oinoid###', OINOStr.encode(OINODbConfig.printOINOId(primary_key_values), OINOContentType.html))
159
+ result += html_row + "\r\n"
160
+ dataset.next()
161
+ }
162
+ return result
163
+ }
164
+
165
+ /**
166
+ * Creates HTML Response from a row id.
167
+ *
168
+ * @param oinoId OINO id
169
+ * @param template HTML template
170
+ *
171
+ */
172
+ static createHtmlFromOinoId(oinoId:string, template:string):string {
173
+ let result:string = template.replaceAll('###' + OINODbConfig.OINODB_ID_FIELD + '###', OINOStr.encode(oinoId, OINOContentType.html))
174
+ return result
175
+ }
176
+
177
+ /**
178
+ * Creates HTML Response from object properties.
179
+ *
180
+ * @param object object
181
+ * @param template HTML template
182
+ *
183
+ */
184
+ static createHtmlFromObject(object:any, template:string):string {
185
+ let result:string = template
186
+ for (let key in object) {
187
+ const value = object[key]
188
+ if (value) {
189
+ result = result.replaceAll('###' + key + '###', OINOStr.encode(value.toString(), OINOContentType.html))
190
+ }
191
+ }
192
+ result = result.replace(/###[^#]*###/g, "")
193
+ return result
194
+ }
195
+
196
+ /**
197
+ * Creates HTML Response from API result.
198
+ *
199
+ * @param apiResult object
200
+ * @param template HTML template
201
+ * @param includeErrorMessages include debug messages in result
202
+ * @param includeWarningMessages include debug messages in result
203
+ * @param includeInfoMessages include debug messages in result
204
+ * @param includeDebugMessages include debug messages in result
205
+ *
206
+ */
207
+ static createHtmlFromApiResult(apiResult:OINODbApiResult, template:string, includeErrorMessages:boolean=false, includeWarningMessages:boolean=false, includeInfoMessages:boolean=false, includeDebugMessages:boolean=false):string {
208
+ let result:string = template
209
+ result = result.replaceAll('###statusCode###', OINOStr.encode(apiResult.statusCode.toString(), OINOContentType.html))
210
+ result = result.replaceAll('###statusMessage###', OINOStr.encode(apiResult.statusMessage.toString(), OINOContentType.html))
211
+ let messages = ""
212
+ for (let i:number = 0; i<apiResult.messages.length; i++) {
213
+ if (includeErrorMessages && apiResult.messages[i].startsWith(OINO_ERROR_PREFIX)) {
214
+ messages += "<li>" + OINOStr.encode(apiResult.messages[i], OINOContentType.html) + "</li>"
215
+ }
216
+ if (includeWarningMessages && apiResult.messages[i].startsWith(OINO_WARNING_PREFIX)) {
217
+ messages += "<li>" + OINOStr.encode(apiResult.messages[i], OINOContentType.html) + "</li>"
218
+ }
219
+ if (includeInfoMessages && apiResult.messages[i].startsWith(OINO_INFO_PREFIX)) {
220
+ messages += "<li>" + OINOStr.encode(apiResult.messages[i], OINOContentType.html) + "</li>"
221
+ }
222
+ if (includeDebugMessages && apiResult.messages[i].startsWith(OINO_DEBUG_PREFIX)) {
223
+ messages += "<li>" + OINOStr.encode(apiResult.messages[i], OINOContentType.html) + "</li>"
224
+ }
225
+
226
+ }
227
+ if (messages) {
228
+ result = result.replaceAll('###messages###', "<ul>" + messages + "</ul>")
229
+ }
230
+ result = result.replace(/###[^#]*###/g, "")
231
+ return result
232
+ }
233
+
234
+ private static _findCsvLineEnd(csvData:string, start:number):number {
235
+ const n:number = csvData.length
236
+ if (start >= n) {
237
+ return start
238
+ }
239
+ let end:number = start
240
+ let quote_open:boolean = false
241
+ while (end<n) {
242
+ if (csvData[end] == "\"") {
243
+ if (!quote_open) {
244
+ quote_open = true
245
+ } else if ((end < n-1) && (csvData[end+1] == "\"")) {
246
+ end++
247
+ } else {
248
+ quote_open = false
249
+ }
250
+ } else if ((!quote_open) && (csvData[end] == "\r")) {
251
+ return end
252
+ }
253
+ end++
254
+ }
255
+ return n
256
+ }
257
+
258
+ private static _parseCsvLine(csvLine:string):(string|null|undefined)[] {
259
+ let result:(string|null|undefined)[] = []
260
+ const n:number = csvLine.length
261
+ let start:number = 0
262
+ let end:number = 0
263
+ let quote_open:boolean = false
264
+ let has_quotes:boolean = false
265
+ let has_escaped_quotes = false
266
+ let found_field = false
267
+ while (end<n) {
268
+ if (csvLine[end] == "\"") {
269
+ if (!quote_open) {
270
+ quote_open = true
271
+ } else if ((end < n-1) && (csvLine[end+1] == "\"")) {
272
+ end++
273
+ has_escaped_quotes = true
274
+ } else {
275
+ has_quotes = true
276
+ quote_open = false
277
+ }
278
+ }
279
+ if ((!quote_open) && ((end == n-1) || (csvLine[end] == ","))) {
280
+ found_field = true
281
+ if (end == n-1) {
282
+ end++
283
+ }
284
+ }
285
+ if (found_field) {
286
+ // console.log("OINODB_csvParseLine: next field=" + csvLine.substring(start,end) + ", start="+start+", end="+end)
287
+ let field_str:string|undefined|null
288
+ if (has_quotes) {
289
+ field_str = csvLine.substring(start+1,end-1)
290
+ } else if (start == end) {
291
+ field_str = undefined
292
+ } else {
293
+ field_str = csvLine.substring(start,end)
294
+ if (field_str == "null") {
295
+ field_str = null
296
+ }
297
+ }
298
+ result.push(field_str)
299
+ has_quotes = false
300
+ has_escaped_quotes = true
301
+ found_field = false
302
+ start = end+1
303
+ }
304
+ end++
305
+ }
306
+ return result
307
+ }
308
+
309
+ private static createRowFromCsv(datamodel:OINODbDataModel, data:string):OINODataRow[] {
310
+ let result:OINODataRow[] = []
311
+ const n = data.length
312
+ let start:number = 0
313
+ let end:number = this._findCsvLineEnd(data, start)
314
+ const header_str = data.substring(start, end)
315
+ const headers:(string|null|undefined)[] = this._parseCsvLine(header_str)
316
+ let field_to_header_mapping:number[] = new Array(datamodel.fields.length)
317
+ let headers_found:boolean = false
318
+ for (let i=0; i<field_to_header_mapping.length; i++) {
319
+ field_to_header_mapping[i] = headers.indexOf(datamodel.fields[i].name)
320
+ headers_found = headers_found || (field_to_header_mapping[i] >= 0)
321
+ }
322
+ // OINOLog.debug("createRowFromCsv", {headers:headers, field_to_header_mapping:field_to_header_mapping})
323
+ if (!headers_found) {
324
+ return result
325
+ }
326
+ start = end + 1
327
+ end = start
328
+ while (end < n) {
329
+ while ((start < n) && ((data[start] == "\r") || (data[start] == "\n"))) {
330
+ start++
331
+ }
332
+ if (start >= n) {
333
+ return result
334
+ }
335
+ end = this._findCsvLineEnd(data, start)
336
+ const row_data:(string|null|undefined)[] = this._parseCsvLine(data.substring(start, end))
337
+ const row:OINODataRow = new Array(field_to_header_mapping.length)
338
+ for (let i=0; i<datamodel.fields.length; i++) {
339
+ const field:OINODbDataField = datamodel.fields[i]
340
+ let j:number = field_to_header_mapping[i]
341
+ let value:OINODataCell = row_data[j]
342
+ if ((value === undefined) || (value === null)) { // null/undefined-decoding built into the parser
343
+ row[i] = value
344
+
345
+ } else if ((j >= 0) && (j < row_data.length)) {
346
+ value = OINOStr.decode(value, OINOContentType.csv)
347
+ if (value && field.fieldParams.isPrimaryKey && (field instanceof OINONumberDataField) && (datamodel.api.hashid)) {
348
+ value = datamodel.api.hashid.decode(value)
349
+ }
350
+ row[i] = field.deserializeCell(value)
351
+
352
+ } else {
353
+ row[i] = undefined
354
+ }
355
+ }
356
+ // console.log("createRowFromCsv: next row=" + row)
357
+ result.push(row)
358
+ start = end
359
+ end = start
360
+ }
361
+
362
+ return result
363
+ }
364
+
365
+ private static _createRowFromJsonObj(obj:any, datamodel:OINODbDataModel):OINODataRow {
366
+ // console.log("createRowFromJsonObj: obj=" + JSON.stringify(obj))
367
+ const fields:OINODbDataField[] = datamodel.fields
368
+ let result:OINODataRow = new Array(fields.length)
369
+ // console.log("createRowFromJsonObj: " + result)
370
+ for (let i=0; i < fields.length; i++) {
371
+ const field = fields[i]
372
+ let value:OINODataCell = OINOStr.decode(obj[field.name], OINOContentType.json)
373
+ // console.log("createRowFromJsonObj: key=" + field.name + ", val=" + val)
374
+ if ((value === undefined) || (value === null)) {
375
+ result[i] = value
376
+ } else {
377
+ if (Array.isArray(value) || typeof value === "object") { // only single level deep object, rest is handled as JSON-strings
378
+ result[i] = JSON.stringify(value).replaceAll("\"","\\\"")
379
+
380
+ } else {
381
+ if (value && field.fieldParams.isPrimaryKey && (field instanceof OINONumberDataField) && (datamodel.api.hashid)) {
382
+ value = datamodel.api.hashid.decode(value)
383
+ }
384
+ result[i] = field.deserializeCell(value)
385
+ }
386
+ }
387
+ // console.log("createRowFromJsonObj: result["+i+"]=" + result[i])
388
+ }
389
+ // console.log("createRowFromJsonObj: " + result)
390
+ return result
391
+ }
392
+
393
+ private static _createRowFromJson(datamodel:OINODbDataModel, data:string):OINODataRow[] {
394
+ try {
395
+ let result:OINODataRow[] = []
396
+ // console.log("OINORowFactoryJson: data=" + data)
397
+ const obj:object = JSON.parse(data)
398
+ if (Array.isArray(obj)) {
399
+ obj.forEach(row => {
400
+ result.push(this._createRowFromJsonObj(row, datamodel))
401
+ });
402
+
403
+ } else {
404
+ result.push(this._createRowFromJsonObj(obj, datamodel))
405
+ }
406
+ return result
407
+
408
+ } catch (e:any) {
409
+ return []
410
+ }
411
+ }
412
+
413
+ private static _findMultipartBoundary(formData:string, multipartBoundary:string, start:number):number {
414
+ let n:number = formData.indexOf(multipartBoundary, start)
415
+ if (n >= 0) {
416
+ n += multipartBoundary.length + 2
417
+ } else {
418
+ n = formData.length
419
+ }
420
+ return n
421
+ }
422
+
423
+ private static _parseMultipartLine(csvData:string, start:number):string {
424
+ let line_end:number = csvData.indexOf('\r\n', start)
425
+ if (line_end >= start) {
426
+ return csvData.substring(start, line_end)
427
+ } else {
428
+ return ''
429
+ }
430
+ }
431
+
432
+ private static _multipartHeaderRegex:RegExp = /Content-Disposition\: (form-data|file); name=\"([^\"]+)\"(; filename=.*)?/i
433
+
434
+ private static createRowFromFormdata(datamodel:OINODbDataModel, data:string, multipartBoundary:string):OINODataRow[] {
435
+ let result:OINODataRow[] = []
436
+ const n = data.length
437
+ let start:number = this._findMultipartBoundary(data, multipartBoundary, 0)
438
+ let end:number = this._findMultipartBoundary(data, multipartBoundary, start)
439
+ // OINOLog.debug("createRowFromFormdata: enter", {start:start, end:end, multipartBoundary:multipartBoundary})
440
+ const row:OINODataRow = new Array(datamodel.fields.length)
441
+ while (end < n) {
442
+ // OINOLog.debug("createRowFromFormdata: next block", {start:start, end:end, block:data.substring(start, end)})
443
+ let block_ok:boolean = true
444
+ let l:string = this._parseMultipartLine(data, start)
445
+ // OINOLog.debug("createRowFromFormdata: next line", {start:start, end:end, line:l})
446
+ start += l.length+2
447
+ const header_matches = OINODbFactory._multipartHeaderRegex.exec(l)
448
+ if (!header_matches) {
449
+ OINOLog.warning("OINODbFactory.createRowFromFormdata: unsupported block skipped!", {header_line:l})
450
+ block_ok = false
451
+
452
+ } else {
453
+ const field_name = header_matches[2]
454
+ const is_file = header_matches[3] != null
455
+ let is_base64:boolean = false
456
+ const field_index:number = datamodel.findFieldIndexByName(field_name)
457
+ // OINOLog.debug("createRowFromFormdata: header", {field_name:field_name, field_index:field_index, is_file:is_file})
458
+ if (field_index < 0) {
459
+ OINOLog.warning("OINODbFactory.createRowFromFormdata: form field not found and skipped!", {field_name:field_name})
460
+ block_ok = false
461
+
462
+ } else {
463
+ const field:OINODbDataField = datamodel.fields[field_index]
464
+ l = this._parseMultipartLine(data, start)
465
+ // OINOLog.debug("createRowFromFormdata: next line", {start:start, end:end, line:l})
466
+ while (block_ok && (l != '')) {
467
+ if (l.startsWith('Content-Type:') && (l.indexOf('multipart/mixed')>=0)) {
468
+ OINOLog.warning("OINODbFactory.createRowFromFormdata: mixed multipart files not supported and skipped!", {header_line:l})
469
+ block_ok = false
470
+ } else if (l.startsWith('Content-Transfer-Encoding:') && (l.indexOf('BASE64')>=0)) {
471
+ is_base64 = true
472
+ }
473
+ start += l.length+2
474
+ l = this._parseMultipartLine(data, start)
475
+ // OINOLog.debug("createRowFromFormdata: next line", {start:start, end:end, line:l})
476
+ }
477
+ start += 2
478
+ if (!block_ok) {
479
+ OINOLog.warning("OINODbFactory.createRowFromFormdata: invalid block skipped", {field_name:field_name})
480
+ } else if (start + multipartBoundary.length + 2 >= end) {
481
+ // OINOLog.debug("OINODbFactory.createRowFromFormdata: null value", {field_name:field_name})
482
+ row[field_index] = null
483
+
484
+ } else if (is_file) {
485
+ const value = this._parseMultipartLine(data, start).trim()
486
+ if (is_base64) {
487
+ row[field_index] = field.deserializeCell(OINOStr.decode(value, OINOContentType.formdata))
488
+ } else {
489
+ row[field_index] = Buffer.from(value, "binary")
490
+ }
491
+ } else {
492
+ let value:OINODataCell = OINOStr.decode(this._parseMultipartLine(data, start).trim(), OINOContentType.formdata)
493
+ // OINOLog.debug("OINODbFactory.createRowFromFormdata: parse form field", {field_name:field_name, value:value})
494
+ if (value && field.fieldParams.isPrimaryKey && (field instanceof OINONumberDataField) && (datamodel.api.hashid)) {
495
+ value = datamodel.api.hashid.decode(value)
496
+ }
497
+ row[field_index] = field.deserializeCell(value)
498
+ }
499
+ }
500
+ }
501
+ start = end
502
+ end = this._findMultipartBoundary(data, multipartBoundary, start)
503
+ }
504
+ OINOLog.debug("createRowFromFormdata: next row", {row:row})
505
+ result.push(row)
506
+
507
+ return result
508
+ }
509
+ private static createRowFromUrlencoded(datamodel:OINODbDataModel, data:string):OINODataRow[] {
510
+ // OINOLog.debug("createRowFromUrlencoded: enter", {data:data})
511
+ let result:OINODataRow[] = []
512
+ const row:OINODataRow = new Array(datamodel.fields.length)
513
+ const data_parts:string[] = data.trim().split('&')
514
+ for (let i=0; i<data_parts.length; i++) {
515
+ const param_parts = data_parts[i].split('=')
516
+ OINOLog.debug("createRowFromUrlencoded: next param", {param_parts:param_parts})
517
+ if (param_parts.length == 2) {
518
+ const key=OINOStr.decodeUrlencode(param_parts[0]) || ""
519
+ const field_index:number = datamodel.findFieldIndexByName(key)
520
+ if (field_index < 0) {
521
+ OINOLog.info("createRowFromUrlencoded: param field not found", {field:key})
522
+
523
+ } else {
524
+ const field:OINODbDataField = datamodel.fields[field_index]
525
+ let value:OINODataCell=OINOStr.decode(param_parts[1], OINOContentType.urlencode)
526
+ if (value && field.fieldParams.isPrimaryKey && (field instanceof OINONumberDataField) && (datamodel.api.hashid)) {
527
+ value = datamodel.api.hashid.decode(value)
528
+ }
529
+ row[field_index] = field.deserializeCell(value)
530
+ }
531
+ }
532
+
533
+ // const value = requestParams[]
534
+
535
+ }
536
+ console.log("createRowFromUrlencoded: next row=" + row)
537
+ result.push(row)
538
+ return result
539
+ }
540
+
541
+ /**
542
+ * Create data rows from request body based on the datamodel.
543
+ *
544
+ * @param datamodel datamodel of the api
545
+ * @param data data as a string
546
+ * @param requestParams parameters
547
+ *
548
+ */
549
+ static createRows(datamodel:OINODbDataModel, data:string, requestParams:OINORequestParams ):OINODataRow[] {
550
+ if ((requestParams.requestType == OINOContentType.json) || (requestParams.requestType == undefined)) {
551
+ return this._createRowFromJson(datamodel, data)
552
+
553
+ } else if (requestParams.requestType == OINOContentType.csv) {
554
+ return this.createRowFromCsv(datamodel, data)
555
+
556
+ } else if (requestParams.requestType == OINOContentType.formdata) {
557
+ return this.createRowFromFormdata(datamodel, data, requestParams.multipartBoundary || "")
558
+
559
+ } else if (requestParams.requestType == OINOContentType.urlencode) {
560
+ return this.createRowFromUrlencoded(datamodel, data)
561
+
562
+ } else if (requestParams.requestType == OINOContentType.html) {
563
+ OINOLog.error("HTML can't be used as an input content type!", {contentType:OINOContentType.html})
564
+ return []
565
+ } else {
566
+ OINOLog.error("Unrecognized input content type!", {contentType:requestParams.requestType})
567
+ return []
568
+ }
569
+ }
570
+ }