@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
|
@@ -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
|
+
}
|