@oino-ts/db 0.21.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/cjs/OINODb.js +6 -144
  2. package/dist/cjs/OINODbApi.js +50 -318
  3. package/dist/cjs/OINODbConfig.js +10 -10
  4. package/dist/cjs/OINODbConstants.js +10 -0
  5. package/dist/cjs/OINODbDataField.js +28 -70
  6. package/dist/cjs/OINODbDataModel.js +29 -143
  7. package/dist/cjs/OINODbFactory.js +2 -2
  8. package/dist/cjs/OINODbModelSet.js +23 -23
  9. package/dist/cjs/OINODbQueryParams.js +201 -0
  10. package/dist/cjs/index.js +12 -41
  11. package/dist/esm/OINODb.js +6 -142
  12. package/dist/esm/OINODbApi.js +49 -314
  13. package/dist/esm/OINODbConstants.js +7 -0
  14. package/dist/esm/OINODbDataModel.js +29 -143
  15. package/dist/esm/OINODbFactory.js +1 -1
  16. package/dist/esm/OINODbQueryParams.js +194 -0
  17. package/dist/esm/index.js +4 -14
  18. package/dist/types/OINODb.d.ts +6 -173
  19. package/dist/types/OINODbApi.d.ts +18 -104
  20. package/dist/types/OINODbConstants.d.ts +23 -0
  21. package/dist/types/OINODbDataModel.d.ts +7 -61
  22. package/dist/types/OINODbFactory.d.ts +5 -2
  23. package/dist/types/OINODbQueryParams.d.ts +72 -0
  24. package/dist/types/index.d.ts +4 -108
  25. package/package.json +37 -37
  26. package/src/OINODb.ts +99 -348
  27. package/src/OINODbApi.test.ts +507 -498
  28. package/src/OINODbApi.ts +389 -667
  29. package/src/OINODbConstants.ts +32 -0
  30. package/src/OINODbDataModel.ts +191 -307
  31. package/src/OINODbFactory.ts +73 -68
  32. package/src/OINODbQueryParams.ts +203 -0
  33. package/src/index.ts +6 -118
  34. package/src/OINODbConfig.ts +0 -98
  35. package/src/OINODbDataField.ts +0 -405
  36. package/src/OINODbModelSet.ts +0 -353
  37. package/src/OINODbParser.ts +0 -438
  38. package/src/OINODbSqlParams.ts +0 -593
  39. package/src/OINODbSwagger.ts +0 -209
@@ -1,438 +0,0 @@
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 { Buffer } from "node:buffer"
8
- import { OINOContentType, OINOStr, OINOLog, OINO_ERROR_PREFIX } from "@oino-ts/common"
9
- import { OINODbDataModel, OINODbDataField, OINODataRow, OINONumberDataField } from "./index.js"
10
-
11
- /**
12
- * Static factory class for easily creating things based on data
13
- *
14
- */
15
- export class OINODbParser {
16
- /**
17
- * Create data rows from request body based on the datamodel.
18
- *
19
- * @param datamodel datamodel of the api
20
- * @param data data as either serialized string or unserialized JS object or OINODataRow-array or Buffer/Uint8Array binary data
21
- * @param contentType content type of the data
22
- * @param multipartBoundary multipart boundary for formdata parsing, if applicable
23
- *
24
- */
25
- static createRows(datamodel:OINODbDataModel, data:string|object|Buffer|Uint8Array, contentType:OINOContentType, multipartBoundary?:string ):OINODataRow[] {
26
- let result:OINODataRow[] = []
27
- if (typeof data == "string") {
28
- result = this._createRowsFromText(datamodel, data, contentType, multipartBoundary)
29
-
30
- } else if ((data instanceof Buffer) || (data instanceof Uint8Array)) {
31
- result = this._createRowsFromBlob(datamodel, data, contentType, multipartBoundary)
32
-
33
- } else if (typeof data == "object") {
34
- result = [this._createRowFromObject(datamodel, data)]
35
- }
36
- return result
37
- }
38
-
39
- private static _createRowsFromText(datamodel:OINODbDataModel, data:string, contentType:OINOContentType, multipartBoundary?:string ):OINODataRow[] {
40
- if ((contentType == OINOContentType.json) || (contentType == undefined)) {
41
- return this._createRowFromJson(datamodel, data)
42
-
43
- } else if (contentType == OINOContentType.csv) {
44
- return this._createRowFromCsv(datamodel, data)
45
-
46
- } else if (contentType == OINOContentType.formdata) {
47
- return this._createRowFromFormdata(datamodel, Buffer.from(data, "utf8"), multipartBoundary || "")
48
-
49
- } else if (contentType == OINOContentType.urlencode) {
50
- return this._createRowFromUrlencoded(datamodel, data)
51
-
52
- } else if (contentType == OINOContentType.html) {
53
- OINOLog.error("@oino-ts/db", "OINODbParser", "createRowsFromText", "HTML can't be used as an input content type!", {contentType:OINOContentType.html})
54
- return []
55
- } else {
56
- OINOLog.error("@oino-ts/db", "OINODbParser", "createRowsFromText", "Unrecognized input content type!", {contentType:contentType})
57
- return []
58
- }
59
- }
60
- private static _createRowsFromBlob(datamodel:OINODbDataModel, data:Buffer|Uint8Array, contentType:OINOContentType, multipartBoundary?:string ):OINODataRow[] {
61
- if (data instanceof Uint8Array && !(data instanceof Buffer)) {
62
- data = Buffer.from(data) as Buffer
63
- }
64
- if ((contentType == OINOContentType.json) || (contentType == undefined)) {
65
- return this._createRowFromJson(datamodel, data.toString()) // JSON is always a string
66
-
67
- } else if (contentType == OINOContentType.csv) {
68
- return this._createRowFromCsv(datamodel, data.toString()) // binary data has to be base64 encoded so it's a string
69
-
70
- } else if (contentType == OINOContentType.formdata) {
71
- return this._createRowFromFormdata(datamodel, data as Buffer, multipartBoundary || "")
72
-
73
- } else if (contentType == OINOContentType.urlencode) {
74
- return this._createRowFromUrlencoded(datamodel, data.toString()) // data is urlencoded so it's a string
75
-
76
- } else if (contentType == OINOContentType.html) {
77
- OINOLog.error("@oino-ts/db", "OINODbParser", "createRowsFromBlob", "HTML can't be used as an input content type!", {contentType:OINOContentType.html})
78
- return []
79
- } else {
80
- OINOLog.error("@oino-ts/db", "OINODbParser", "createRowsFromBlob", "Unrecognized input content type!", {contentType:contentType})
81
- return []
82
- }
83
- }
84
-
85
- /**
86
- * Create one data row from javascript object based on the datamodel.
87
- * NOTE! Data assumed to be unserialized i.e. of the native type (string, number, boolean, Buffer)
88
- *
89
- * @param datamodel datamodel of the api
90
- * @param data data as javascript object
91
- *
92
- */
93
- private static _createRowFromObject(datamodel:OINODbDataModel, data:any):OINODataRow {
94
- const fields:OINODbDataField[] = datamodel.fields
95
- let result:OINODataRow = new Array(fields.length)
96
- for (let i=0; i < fields.length; i++) {
97
- result[i] = data[fields[i].name]
98
- }
99
- return result
100
- }
101
-
102
- private static _findCsvLineEnd(csvData:string, start:number):number {
103
- const n:number = csvData.length
104
- if (start >= n) {
105
- return start
106
- }
107
- let end:number = start
108
- let quote_open:boolean = false
109
- while (end<n) {
110
- if (csvData[end] == "\"") {
111
- if (!quote_open) {
112
- quote_open = true
113
- } else if ((end < n-1) && (csvData[end+1] == "\"")) {
114
- end++
115
- } else {
116
- quote_open = false
117
- }
118
- } else if ((!quote_open) && (csvData[end] == "\r")) {
119
- return end
120
- }
121
- end++
122
- }
123
- return n
124
- }
125
-
126
- private static _parseCsvLine(csvLine:string):(string|null|undefined)[] {
127
- let result:(string|null|undefined)[] = []
128
- const n:number = csvLine.length
129
- let start:number = 0
130
- let end:number = 0
131
- let quote_open:boolean = false
132
- let has_quotes:boolean = false
133
- let has_escaped_quotes = false
134
- let found_field = false
135
- while (end<n) {
136
- if (csvLine[end] == "\"") {
137
- if (!quote_open) {
138
- quote_open = true
139
- } else if ((end < n-1) && (csvLine[end+1] == "\"")) {
140
- end++
141
- has_escaped_quotes = true
142
- } else {
143
- has_quotes = true
144
- quote_open = false
145
- }
146
- }
147
- if ((!quote_open) && ((end == n-1) || (csvLine[end] == ","))) {
148
- found_field = true
149
- if (end == n-1) {
150
- end++
151
- }
152
- }
153
- if (found_field) {
154
- // console.log("OINODB_csvParseLine: next field=" + csvLine.substring(start,end) + ", start="+start+", end="+end)
155
- let field_str:string|undefined|null
156
- if (has_quotes) {
157
- field_str = csvLine.substring(start+1,end-1)
158
- } else if (start == end) {
159
- field_str = undefined
160
- } else {
161
- field_str = csvLine.substring(start,end)
162
- if (field_str == "null") {
163
- field_str = null
164
- }
165
- }
166
- result.push(field_str)
167
- has_quotes = false
168
- has_escaped_quotes = true
169
- found_field = false
170
- start = end+1
171
- }
172
- end++
173
- }
174
- return result
175
- }
176
-
177
- private static _createRowFromCsv(datamodel:OINODbDataModel, data:string):OINODataRow[] {
178
- let result:OINODataRow[] = []
179
- const n = data.length
180
- let start:number = 0
181
- let end:number = this._findCsvLineEnd(data, start)
182
- const header_str = data.substring(start, end)
183
- const headers:(string|null|undefined)[] = this._parseCsvLine(header_str)
184
- let field_to_header_mapping:number[] = new Array(datamodel.fields.length)
185
- let headers_found:boolean = false
186
- for (let i=0; i<field_to_header_mapping.length; i++) {
187
- field_to_header_mapping[i] = headers.indexOf(datamodel.fields[i].name)
188
- headers_found = headers_found || (field_to_header_mapping[i] >= 0)
189
- }
190
- if (!headers_found) {
191
- return result
192
- }
193
- start = end + 1
194
- end = start
195
- while (end < n) {
196
- while ((start < n) && ((data[start] == "\r") || (data[start] == "\n"))) {
197
- start++
198
- }
199
- if (start >= n) {
200
- return result
201
- }
202
- end = this._findCsvLineEnd(data, start)
203
- const row_data:(string|null|undefined)[] = this._parseCsvLine(data.substring(start, end))
204
- const row:OINODataRow = new Array(field_to_header_mapping.length)
205
- let has_data:boolean = false
206
- for (let i=0; i<datamodel.fields.length; i++) {
207
- const field:OINODbDataField = datamodel.fields[i]
208
- let j:number = field_to_header_mapping[i]
209
- let value:string|null|undefined = row_data[j]
210
- if ((value === undefined) || (value === null)) { // null/undefined-decoding built into the parser
211
- row[i] = value
212
-
213
- } else if ((j >= 0) && (j < row_data.length)) {
214
- value = OINOStr.decode(value, OINOContentType.csv)
215
- if (value && (field.fieldParams.isPrimaryKey || field.fieldParams.isForeignKey) && (field instanceof OINONumberDataField) && (datamodel.api.hashid)) {
216
- value = datamodel.api.hashid.decode(value)
217
- }
218
- row[i] = field.deserializeCell(value)
219
-
220
- } else {
221
- row[i] = undefined
222
- }
223
- has_data = has_data || (row[i] !== undefined)
224
- }
225
- // console.log("createRowFromCsv: next row=" + row)
226
- if (has_data) {
227
- result.push(row)
228
- } else {
229
- OINOLog.warning("@oino-ts/db", "OINODbParser", "_createRowFromCsv", "Empty row skipped", {})
230
- }
231
- start = end
232
- end = start
233
- }
234
-
235
- return result
236
- }
237
-
238
- private static _createRowFromJsonObj(obj:any, datamodel:OINODbDataModel):OINODataRow|undefined {
239
- // console.log("createRowFromJsonObj: obj=" + JSON.stringify(obj))
240
- const fields:OINODbDataField[] = datamodel.fields
241
- let result:OINODataRow = new Array(fields.length)
242
- let has_data:boolean = false
243
- // console.log("createRowFromJsonObj: " + result)
244
- for (let i=0; i < fields.length; i++) {
245
- const field = fields[i]
246
- let value:any = obj[field.name]
247
- // console.log("createRowFromJsonObj: key=" + field.name + ", val=" + val)
248
- if ((value === null) || (value === undefined)) { // must be checed first as null is an object
249
- result[i] = value
250
-
251
- } else if (Array.isArray(value) || typeof value === "object") {
252
- result[i] = JSON.stringify(value).replaceAll("\"","\\\"") // only single level deep objects, rest is handled as JSON-strings
253
-
254
- } else if (typeof value === "string") {
255
- value = OINOStr.decode(value, OINOContentType.json)
256
- if (value && (field.fieldParams.isPrimaryKey || field.fieldParams.isForeignKey) && (field instanceof OINONumberDataField) && (datamodel.api.hashid)) {
257
- value = datamodel.api.hashid.decode(value)
258
- }
259
- result[i] = field.deserializeCell(value)
260
-
261
- } else {
262
- result[i] = value // value types are passed as-is
263
- }
264
- has_data = has_data || (result[i] !== undefined)
265
- // console.log("createRowFromJsonObj: result["+i+"]=" + result[i])
266
- }
267
- // console.log("createRowFromJsonObj: " + result)
268
- if (has_data) {
269
- return result
270
- } else {
271
- OINOLog.warning("@oino-ts/db", "OINODbParser", "_createRowFromJsonObj", "Empty row skipped", {})
272
- return undefined
273
- }
274
- }
275
-
276
- private static _createRowFromJson(datamodel:OINODbDataModel, data:string):OINODataRow[] {
277
- let result:OINODataRow[] = []
278
- // console.log("OINORowFactoryJson: data=" + data)
279
- const obj:object = JSON.parse(data)
280
- if (Array.isArray(obj)) {
281
- obj.forEach(row => {
282
- const data_row = this._createRowFromJsonObj(row, datamodel)
283
- if (data_row !== undefined) {
284
- result.push(data_row)
285
- }
286
- })
287
-
288
- } else {
289
- const data_row = this._createRowFromJsonObj(obj, datamodel)
290
- if (data_row !== undefined) {
291
- result.push(data_row)
292
- }
293
- }
294
- return result
295
- }
296
-
297
- private static _findMultipartBoundary(formData:Buffer, multipartBoundary:string, start:number):number {
298
- let n:number = formData.indexOf(multipartBoundary, start)
299
- if (n >= 0) {
300
- n += multipartBoundary.length + 2
301
- } else {
302
- n = formData.length
303
- }
304
- return n
305
- }
306
-
307
- private static _parseMultipartLine(data:Buffer, start:number):string {
308
- let line_end:number = data.indexOf('\r\n', start)
309
- if (line_end >= start) {
310
- return data.subarray(start, line_end).toString()
311
- } else {
312
- return ''
313
- }
314
- }
315
-
316
- private static _multipartHeaderRegex:RegExp = /Content-Disposition\: (form-data|file); name=\"([^\"]+)\"(; filename=.*)?/i
317
-
318
- private static _createRowFromFormdata(datamodel:OINODbDataModel, data:Buffer, multipartBoundary:string):OINODataRow[] {
319
- if (!multipartBoundary || (multipartBoundary.length == 0)) {
320
- OINOLog.error("@oino-ts/db", "OINODbParser", "_createRowFromFormdata", "Multipart boundary missing for formdata parsing!", {})
321
- throw new Error(OINO_ERROR_PREFIX + "Multipart boundary missing for formdata parsing!")
322
- }
323
- let result:OINODataRow[] = []
324
- try {
325
- const n = data.length
326
- let start:number = this._findMultipartBoundary(data, multipartBoundary, 0)
327
- let end:number = this._findMultipartBoundary(data, multipartBoundary, start)
328
- const row:OINODataRow = new Array(datamodel.fields.length)
329
- let has_data:boolean = false
330
- while (end < n) {
331
- let block_ok:boolean = true
332
- let l:string = this._parseMultipartLine(data, start)
333
- start += l.length+2
334
- const header_matches = OINODbParser._multipartHeaderRegex.exec(l)
335
- if (!header_matches) {
336
- OINOLog.warning("@oino-ts/db", "OINODbParser", "_createRowFromFormdata", "Unsupported block skipped", {header_line:l})
337
- block_ok = false
338
-
339
- } else {
340
- const field_name = header_matches[2]
341
- const is_file = header_matches[3] != null
342
- let is_base64:boolean = false
343
- const field_index:number = datamodel.findFieldIndexByName(field_name)
344
- if (field_index < 0) {
345
- OINOLog.warning("@oino-ts/db", "OINODbParser", "_createRowFromFormdata", "Form field not found and skipped!", {field_name:field_name})
346
- block_ok = false
347
-
348
- } else {
349
- const field:OINODbDataField = datamodel.fields[field_index]
350
- l = this._parseMultipartLine(data, start)
351
- while (block_ok && (l != '')) {
352
- if (l.startsWith('Content-Type:') && (l.indexOf('multipart/mixed')>=0)) {
353
- OINOLog.warning("@oino-ts/db", "OINODbParser", "_createRowFromFormdata", "Mixed multipart files not supported and skipped!", {header_line:l})
354
- block_ok = false
355
- } else if (l.startsWith('Content-Transfer-Encoding:') && (l.indexOf('BASE64')>=0)) {
356
- is_base64 = true
357
- }
358
- start += l.length+2
359
- l = this._parseMultipartLine(data, start)
360
- }
361
- start += 2
362
- if (!block_ok) {
363
- OINOLog.warning("@oino-ts/db", "OINODbParser", "_createRowFromFormdata", "Invalid block skipped", {field_name:field_name})
364
- } else if (start + multipartBoundary.length + 2 >= end) {
365
- row[field_index] = null
366
-
367
- } else if (is_file) {
368
- if (is_base64) {
369
- const value = this._parseMultipartLine(data, start).trim()
370
- row[field_index] = field.deserializeCell(OINOStr.decode(value, OINOContentType.formdata))
371
- } else {
372
- const e = this._findMultipartBoundary(data, multipartBoundary, start)
373
- const value = data.subarray(start, e-2)
374
- row[field_index] = value
375
- }
376
- } else {
377
- let value:string = OINOStr.decode(this._parseMultipartLine(data, start).trim(), OINOContentType.formdata)
378
- if (value && (field.fieldParams.isPrimaryKey || field.fieldParams.isForeignKey) && (field instanceof OINONumberDataField) && (datamodel.api.hashid)) {
379
- value = datamodel.api.hashid.decode(value)
380
- }
381
- row[field_index] = field.deserializeCell(value)
382
- }
383
- has_data = has_data || (row[field_index] !== undefined)
384
- }
385
- }
386
- start = end
387
- end = this._findMultipartBoundary(data, multipartBoundary, start)
388
- }
389
- if (has_data) {
390
- result.push(row)
391
- } else {
392
- OINOLog.warning("@oino-ts/db", "OINODbParser", "_createRowFromFormdata", "Empty row skipped", {})
393
- }
394
- } catch (e:any) {
395
- OINOLog.exception("@oino-ts/db", "OINODbParser", "_createRowFromFormdata", "Exception parsing formdata", {message:e.message, stack:e.stack})
396
- }
397
- return result
398
- }
399
- private static _createRowFromUrlencoded(datamodel:OINODbDataModel, data:string):OINODataRow[] {
400
- let result:OINODataRow[] = []
401
- const row:OINODataRow = new Array(datamodel.fields.length)
402
- let has_data:boolean = false
403
- const data_parts:string[] = data.trim().split('&')
404
- try {
405
- for (let i=0; i<data_parts.length; i++) {
406
- const param_parts = data_parts[i].split('=')
407
- if (param_parts.length == 2) {
408
- const key=OINOStr.decodeUrlencode(param_parts[0]) || ""
409
- const field_index:number = datamodel.findFieldIndexByName(key)
410
- if (field_index < 0) {
411
- OINOLog.info("@oino-ts/db", "OINODbParser", "_createRowFromUrlencoded", "Param field not found", {field:key})
412
-
413
- } else {
414
- const field:OINODbDataField = datamodel.fields[field_index]
415
- let value:string=OINOStr.decode(param_parts[1], OINOContentType.urlencode)
416
- if (value && (field.fieldParams.isPrimaryKey || field.fieldParams.isForeignKey) && (field instanceof OINONumberDataField) && (datamodel.api.hashid)) {
417
- value = datamodel.api.hashid.decode(value)
418
- }
419
- row[field_index] = field.deserializeCell(value)
420
- has_data = has_data || (row[field_index] !== undefined)
421
- }
422
- }
423
-
424
- // const value = requestParams[]
425
-
426
- }
427
- if (has_data) {
428
- result.push(row)
429
- } else {
430
- OINOLog.warning("@oino-ts/db", "OINODbParser", "_createRowFromUrlencoded", "Empty row skipped", {})
431
- }
432
- } catch (e:any) {
433
- OINOLog.exception("@oino-ts/db", "OINODbParser", "_createRowFromUrlencoded", "Exception parsing urlencoded data", {message:e.message, stack:e.stack})
434
- }
435
- return result
436
- }
437
-
438
- }