@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,290 @@
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 { OINODbDataField, OINODbApi, OINODataRow, OINO_ERROR_PREFIX, OINODbDataFieldFilter, OINODbConfig, OINODbSqlParams, OINONumberDataField, OINOLog } from "./index.js";
8
+
9
+ /**
10
+ * OINO Datamodel object for representing one database table and it's columns.
11
+ *
12
+ */
13
+ export class OINODbDataModel {
14
+ private _columnLookup:Record<string, number>;
15
+
16
+ /** Database refererence of the table */
17
+ readonly api:OINODbApi
18
+
19
+ /** Field refererences of the API */
20
+ readonly fields: OINODbDataField[]
21
+
22
+ /**
23
+ * Constructor of the data model.
24
+ * NOTE! OINODbDataModel.initialize must be called after constructor to populate fields.
25
+ *
26
+ * @param api api of the data model
27
+ *
28
+ */
29
+ constructor(api:OINODbApi) {
30
+ this._columnLookup = {}
31
+ this.api = api
32
+ this.fields = []
33
+
34
+ // OINOLog_debug("OINODbDataModel (" + tableName + "):\n" + this._printTableDebug("\n"))
35
+ }
36
+ /**
37
+ * Initialize datamodel from SQL schema.
38
+ *
39
+ */
40
+ async initialize() {
41
+ await this.api.db.initializeApiDatamodel(this.api)
42
+ }
43
+
44
+ private _printSqlColumnNames(): string {
45
+ let result: string = "";
46
+ for (let f of this.fields) {
47
+ if (result != "") {
48
+ result += ",";
49
+ }
50
+ result += f.printSqlColumnName();
51
+ }
52
+ return result;
53
+ }
54
+
55
+ private _printSqlInsertColumnsAndValues(row: OINODataRow): string {
56
+ let columns: string = "";
57
+ let values: string = "";
58
+ for (let i=0; i< this.fields.length; i++) {
59
+ const val = row[i];
60
+ // console.log("_printSqlInsertColumnsAndValues: row[" + i + "]=" + val)
61
+ if (val !== undefined) {
62
+ const f = this.fields[i]
63
+ if (values != "") {
64
+ columns += ",";
65
+ values += ",";
66
+ }
67
+ columns += f.printSqlColumnName();
68
+ values += f.printCellAsSqlValue(val);
69
+ }
70
+ }
71
+ // console.log("_printSqlInsertColumnsAndValues: columns=" + columns + ", values=" + values)
72
+ return "(" + columns + ") VALUES (" + values + ")";
73
+ }
74
+
75
+ private _printSqlUpdateValues(row: OINODataRow): string {
76
+ let result: string = "";
77
+ for (let i=0; i< this.fields.length; i++) {
78
+ const f = this.fields[i]
79
+ const val = row[i];
80
+ // OINOLog_debug("OINODbDataModel._printSqlUpdateValues", {field:f.name, primary_key:f.fieldParams.isPrimaryKey, val:val})
81
+ if ((!f.fieldParams.isPrimaryKey) && (val !== undefined)) {
82
+ if (result != "") {
83
+ result += ",";
84
+ }
85
+ result += f.printSqlColumnName() + "=" + f.printCellAsSqlValue(val);
86
+ }
87
+ }
88
+ return result;
89
+ }
90
+
91
+ private _printSqlPrimaryKeyCondition(id_value: string): string {
92
+ let result: string = ""
93
+ let i:number = 0
94
+ const id_parts = id_value.split(OINODbConfig.OINODB_ID_SEPARATOR)
95
+ for (let f of this.fields) {
96
+ if (f.fieldParams.isPrimaryKey) {
97
+ if (result != "") {
98
+ result += " AND "
99
+ }
100
+ let value = decodeURIComponent(id_parts[i])
101
+ if ((f instanceof OINONumberDataField) && (this.api.hashid)) {
102
+ value = this.api.hashid.decode(value)
103
+ }
104
+ result += f.printSqlColumnName() + "=" + f.printCellAsSqlValue(value);
105
+ i = i + 1
106
+ }
107
+ }
108
+ if (i != id_parts.length) {
109
+ throw new Error(OINO_ERROR_PREFIX + ": id '" + id_value + "' is not a valid key for table " + this.api.params.tableName)
110
+ }
111
+ return "(" + result + ")";
112
+ }
113
+
114
+ /**
115
+ * Add a field to the datamodel.
116
+ *
117
+ * @param field dataset field
118
+ *
119
+ */
120
+ addField(field:OINODbDataField) {
121
+ this.fields.push(field)
122
+ this._columnLookup[field.name] = this.fields.length-1
123
+ }
124
+
125
+ /**
126
+ * Find a field of a given name if any.
127
+ *
128
+ * @param name name of the field to find
129
+ *
130
+ */
131
+ findFieldByName(name:string):OINODbDataField|null {
132
+ // OINOLog.debug("OINODbDataModel.findFieldByName", {_columnLookup:this._columnLookup})
133
+ const i:number = this._columnLookup[name]
134
+ if (i >= 0) {
135
+ return this.fields[i]
136
+ } else {
137
+ return null
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Find index of a field of a given name if any.
143
+ *
144
+ * @param name name of the field to find
145
+ *
146
+ */
147
+ findFieldIndexByName(name:string):number {
148
+ // OINOLog.debug("OINODbDataModel.findFieldIndexByName", {_columnLookup:this._columnLookup})
149
+ const i:number = this._columnLookup[name]
150
+ if (i >= 0) {
151
+ return i
152
+ } else {
153
+ return -1
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Find all fields based of given filter callback criteria (e.g. fields of certain data type, primary keys etc.)
159
+ *
160
+ * @param filter callback called for each field to include or not
161
+ *
162
+ */
163
+ filterFields(filter:OINODbDataFieldFilter):OINODbDataField[] {
164
+ let result:OINODbDataField[] = []
165
+ for (let f of this.fields) {
166
+ if (filter(f)) {
167
+ result.push(f)
168
+ }
169
+ }
170
+ return result
171
+ }
172
+
173
+ /**
174
+ * Return the primary key values of one row in order of the data model
175
+ *
176
+ * @param row data row
177
+ * @param hashidValues apply hashid when applicable
178
+ *
179
+ */
180
+ getRowPrimarykeyValues(row: OINODataRow, hashidValues:boolean = false): string[] {
181
+ let values: string[] = [];
182
+ for (let i=0; i< this.fields.length; i++) {
183
+ const f = this.fields[i]
184
+ if (f.fieldParams.isPrimaryKey) {
185
+ const value:string = row[i]?.toString() || ""
186
+ if (hashidValues && value && (f instanceof OINONumberDataField) && this.api.hashid) {
187
+ values.push(this.api.hashid.encode(value))
188
+ } else {
189
+ values.push(value)
190
+ }
191
+ }
192
+ }
193
+ return values
194
+ }
195
+
196
+ /**
197
+ * Print debug information about the fields.
198
+ *
199
+ * @param separator string to separate field prints
200
+ *
201
+ */
202
+ printDebug(separator:string = ""): string {
203
+ let result: string = this.api.params.tableName + ":" + separator;
204
+ for (let f of this.fields) {
205
+ result += f.printColumnDebug() + separator;
206
+ }
207
+ return result;
208
+ }
209
+
210
+ /**
211
+ * Print all public properties (db, table name, fields) of the datamodel. Used
212
+ * in automated testing validate schema has stayed the same.
213
+ *
214
+ */
215
+ printFieldPublicPropertiesJson():string {
216
+ const result:string = JSON.stringify(this.fields, (key:any, value:any) => {
217
+ if (key.startsWith("_")) {
218
+ return undefined
219
+ } else {
220
+ return value
221
+ }
222
+ })
223
+ return result
224
+ }
225
+
226
+ /**
227
+ * Print SQL select statement using optional id and filter.
228
+ *
229
+ * @param id OINO ID (i.e. combined primary key values)
230
+ * @param params OINO reqest params
231
+ *
232
+ */
233
+ printSqlSelect(id: string, params:OINODbSqlParams): string {
234
+ let result:string = "SELECT " + this._printSqlColumnNames() + " FROM " + this.api.db.printSqlTablename(this.api.params.tableName);
235
+ const filter_sql = params.filter?.toSql(this) || ""
236
+ const order_sql = params.order?.toSql(this) || ""
237
+ const limit_sql = params.limit?.toSql(this) || ""
238
+ // OINOLog.debug("OINODbDataModel.printSqlSelect", {select_sql:result, filter_sql:filter_sql, order_sql:order_sql})
239
+ if ((id != "") && (filter_sql != "")) {
240
+ result += "\nWHERE " + this._printSqlPrimaryKeyCondition(id) + " AND " + filter_sql;
241
+ } else if (id != "") {
242
+ result += "\nWHERE " + this._printSqlPrimaryKeyCondition(id);
243
+ } else if (filter_sql != "") {
244
+ result += "\nWHERE " + filter_sql;
245
+ }
246
+ if (order_sql) {
247
+ result += "\nORDER BY " + order_sql
248
+ }
249
+ if (limit_sql) {
250
+ result += "\nLIMIT " + limit_sql
251
+ }
252
+ result += ";"
253
+ OINOLog.debug("OINODbDataModel.printSqlSelect", {result:result})
254
+ return result;
255
+ }
256
+
257
+ /**
258
+ * Print SQL insert statement from one data row.
259
+ *
260
+ * @param row one row of data in the data model
261
+ *
262
+ */
263
+ printSqlInsert(row: OINODataRow): string {
264
+ let result: string = "INSERT INTO " + this.api.db.printSqlTablename(this.api.params.tableName) + " " + this._printSqlInsertColumnsAndValues(row) + ";";
265
+ return result;
266
+ }
267
+
268
+ /**
269
+ * Print SQL insert statement from one data row.
270
+ *
271
+ * @param id OINO ID (i.e. combined primary key values)
272
+ * @param row one row of data in the data model
273
+ *
274
+ */
275
+ printSqlUpdate(id: string, row: OINODataRow): string {
276
+ let result: string = "UPDATE " + this.api.db.printSqlTablename(this.api.params.tableName) + " SET " + this._printSqlUpdateValues(row) + " WHERE " + this._printSqlPrimaryKeyCondition(id) + ";";
277
+ return result;
278
+ }
279
+
280
+ /**
281
+ * Print SQL delete statement for id.
282
+ *
283
+ * @param id OINO ID (i.e. combined primary key values)
284
+ *
285
+ */
286
+ printSqlDelete(id: string): string {
287
+ let result: string = "DELETE FROM " + this.api.db.printSqlTablename(this.api.params.tableName) + " WHERE " + this._printSqlPrimaryKeyCondition(id) + ";";
288
+ return result;
289
+ }
290
+ }
@@ -0,0 +1,170 @@
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 { OINODataRow, OINO_ERROR_PREFIX, OINODB_EMPTY_ROW } from "./index.js";
8
+
9
+ /**
10
+ * Base class for SQL results that can be asynchronously iterated (but
11
+ * not necessarity rewinded). Idea is to handle database specific mechanisms
12
+ * for returning and formatting conventions in the database specific
13
+ * implementation. Data might be in memory or streamed in chunks and
14
+ * `OINODbDataSet` will serve it out consistently.
15
+ *
16
+ */
17
+ export abstract class OINODbDataSet {
18
+ private _data: unknown;
19
+
20
+ /** Error messages */
21
+ readonly messages: string[];
22
+
23
+ /**
24
+ * Constructor for `OINODbDataSet`.
25
+ *
26
+ * @param data internal database specific data type (constructor will throw if invalid)
27
+ * @param messages error messages from SQL-query
28
+ *
29
+ */
30
+ constructor(data: unknown, messages: string[] = []) {
31
+ this._data = data;
32
+ this.messages = messages;
33
+ }
34
+
35
+ /**
36
+ * Is data set empty.
37
+ *
38
+ */
39
+ abstract isEmpty(): boolean;
40
+
41
+ /**
42
+ * Is there no more content, i.e. either dataset is empty or we have moved beyond last line
43
+ *
44
+ */
45
+ abstract isEof(): boolean;
46
+
47
+ /**
48
+ * Moves dataset to the next row, returns !isEof().
49
+ *
50
+ */
51
+ abstract next(): boolean;
52
+
53
+ /**
54
+ * Gets current row of data.
55
+ *
56
+ */
57
+ abstract getRow(): OINODataRow;
58
+
59
+ /**
60
+ * Checks if the messages contain errors.
61
+ *
62
+ */
63
+ hasErrors(): boolean {
64
+ for (let i=0; i<this.messages.length; i++) {
65
+ if (this.messages[i].startsWith(OINO_ERROR_PREFIX)) {
66
+ return true
67
+ }
68
+ }
69
+ return false
70
+ }
71
+ /**
72
+ * Checks if the messages contain errors.
73
+ *
74
+ */
75
+ getFirstError(): string {
76
+ for (let i=0; i<this.messages.length; i++) {
77
+ if (this.messages[i].startsWith(OINO_ERROR_PREFIX)) {
78
+ return this.messages[i]
79
+ }
80
+ }
81
+ return ""
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Generic in memory implementation of a data set where data is an array of rows. Used
87
+ * by BunSqlite and automated testing. Can be rewinded.
88
+ *
89
+ */
90
+ export class OINODbMemoryDataSet extends OINODbDataSet {
91
+ private _rows: OINODataRow[];
92
+ private _currentRow: number;
93
+ private _eof: boolean;
94
+
95
+ /**
96
+ * Constructor of `OINODbMemoryDataSet`.
97
+ *
98
+ * @param data data as OINODataRow[] (constructor will throw if invalid)
99
+ * @param errors error messages from SQL-query
100
+ *
101
+ */
102
+ constructor(data: unknown, errors: string[] = []) {
103
+ super(data, errors);
104
+ if ((data == null) || !(Array.isArray(data))) {
105
+ throw new Error(OINO_ERROR_PREFIX + ": Data needs to be compatible with OINORow[]!"); // TODO: maybe check all rows
106
+ }
107
+ this._rows = data as OINODataRow[];
108
+ if (this.isEmpty()) {
109
+ this._currentRow = -1;
110
+ this._eof = true;
111
+ } else {
112
+ this._currentRow = 0;
113
+ this._eof = false;
114
+ }
115
+ }
116
+
117
+
118
+
119
+
120
+ /**
121
+ * Is data set empty.
122
+ *
123
+ */
124
+ isEmpty(): boolean {
125
+ return (this._rows.length == 0);
126
+ }
127
+
128
+ /**
129
+ * Is there no more content, i.e. either dataset is empty or we have moved beyond last line
130
+ *
131
+ */
132
+ isEof(): boolean {
133
+ return (this._eof);
134
+ }
135
+
136
+ /**
137
+ * Moves dataset to the next row, returns !isEof().
138
+ *
139
+ */
140
+ next(): boolean {
141
+ if (this._currentRow < this._rows.length - 1) {
142
+ this._currentRow = this._currentRow + 1;
143
+ } else {
144
+ this._eof = true;
145
+ }
146
+ return !this._eof;
147
+ }
148
+
149
+ /**
150
+ * Gets current row of data.
151
+ *
152
+ */
153
+ getRow(): OINODataRow {
154
+ if ((this._currentRow >= 0) && (this._currentRow < this._rows.length)) {
155
+ return this._rows[this._currentRow];
156
+ } else {
157
+ return OINODB_EMPTY_ROW;
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Rewinds data set to the first row, returns !isEof().
163
+ *
164
+ */
165
+ first(): boolean {
166
+ this._currentRow = 0;
167
+ this._eof = this._rows.length == 0;
168
+ return !this._eof;
169
+ }
170
+ }