@dockstat/sqlite-wrapper 1.2.7 → 1.2.8
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/LICENSE +373 -373
- package/README.md +99 -99
- package/index.ts +858 -840
- package/package.json +54 -54
- package/query-builder/base.ts +221 -221
- package/query-builder/delete.ts +352 -352
- package/query-builder/index.ts +431 -431
- package/query-builder/insert.ts +249 -249
- package/query-builder/select.ts +358 -358
- package/query-builder/update.ts +278 -278
- package/query-builder/where.ts +307 -307
- package/types.ts +623 -623
package/package.json
CHANGED
|
@@ -1,54 +1,54 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@dockstat/sqlite-wrapper",
|
|
3
|
-
"version": "1.2.
|
|
4
|
-
"description": "A TypeScript wrapper around bun:sqlite with type-safe query building",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./index.ts",
|
|
7
|
-
"types": "./index.ts",
|
|
8
|
-
"files": [
|
|
9
|
-
"index.ts",
|
|
10
|
-
"query-builder/**/*",
|
|
11
|
-
"README.md",
|
|
12
|
-
"types.ts"
|
|
13
|
-
],
|
|
14
|
-
"scripts": {
|
|
15
|
-
"dev": "bun build index.ts",
|
|
16
|
-
"lint": "biome lint .",
|
|
17
|
-
"lint:fix": "biome lint --write .",
|
|
18
|
-
"lint:gh": "turbo run lint -- --reporter=github",
|
|
19
|
-
"check-types": "bunx tsc --noEmit",
|
|
20
|
-
"test": "bun run test.ts"
|
|
21
|
-
},
|
|
22
|
-
"keywords": [
|
|
23
|
-
"sqlite",
|
|
24
|
-
"database",
|
|
25
|
-
"typescript",
|
|
26
|
-
"bun",
|
|
27
|
-
"query-builder",
|
|
28
|
-
"orm"
|
|
29
|
-
],
|
|
30
|
-
"author": "Its4Nik",
|
|
31
|
-
"license": "MPL-2.0",
|
|
32
|
-
"repository": {
|
|
33
|
-
"type": "git",
|
|
34
|
-
"url": "https://github.com/Its4Nik/DockStat/tree/main/packages/sqlite-wrapper",
|
|
35
|
-
"directory": "packages/sqlite-wrapper"
|
|
36
|
-
},
|
|
37
|
-
"bugs": {
|
|
38
|
-
"url": "https://github.com/Its4Nik/DockStat/issues"
|
|
39
|
-
},
|
|
40
|
-
"homepage": "https://outline.itsnik.de/s/9d88c471-373e-4ef2-a955-b1058eb7dc99/doc/dockstatsqlite-wrapper-Lxt4IphXI5",
|
|
41
|
-
"engines": {
|
|
42
|
-
"bun": ">=1.0.0"
|
|
43
|
-
},
|
|
44
|
-
"dependencies": {
|
|
45
|
-
"@dockstat/logger": "
|
|
46
|
-
},
|
|
47
|
-
"peerDependencies": {
|
|
48
|
-
"typescript": "^5"
|
|
49
|
-
},
|
|
50
|
-
"devDependencies": {
|
|
51
|
-
"@types/bun": "latest",
|
|
52
|
-
"@types/dockerode": "^3.3.
|
|
53
|
-
}
|
|
54
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@dockstat/sqlite-wrapper",
|
|
3
|
+
"version": "1.2.8",
|
|
4
|
+
"description": "A TypeScript wrapper around bun:sqlite with type-safe query building",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.ts",
|
|
7
|
+
"types": "./index.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"index.ts",
|
|
10
|
+
"query-builder/**/*",
|
|
11
|
+
"README.md",
|
|
12
|
+
"types.ts"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"dev": "bun build index.ts",
|
|
16
|
+
"lint": "biome lint .",
|
|
17
|
+
"lint:fix": "biome lint --write .",
|
|
18
|
+
"lint:gh": "turbo run lint -- --reporter=github",
|
|
19
|
+
"check-types": "bunx tsc --noEmit",
|
|
20
|
+
"test": "bun run test.ts"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"sqlite",
|
|
24
|
+
"database",
|
|
25
|
+
"typescript",
|
|
26
|
+
"bun",
|
|
27
|
+
"query-builder",
|
|
28
|
+
"orm"
|
|
29
|
+
],
|
|
30
|
+
"author": "Its4Nik",
|
|
31
|
+
"license": "MPL-2.0",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/Its4Nik/DockStat/tree/main/packages/sqlite-wrapper",
|
|
35
|
+
"directory": "packages/sqlite-wrapper"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/Its4Nik/DockStat/issues"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://outline.itsnik.de/s/9d88c471-373e-4ef2-a955-b1058eb7dc99/doc/dockstatsqlite-wrapper-Lxt4IphXI5",
|
|
41
|
+
"engines": {
|
|
42
|
+
"bun": ">=1.0.0"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@dockstat/logger": "latest"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"typescript": "^5.9.3"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/bun": "latest",
|
|
52
|
+
"@types/dockerode": "^3.3.44"
|
|
53
|
+
}
|
|
54
|
+
}
|
package/query-builder/base.ts
CHANGED
|
@@ -1,221 +1,221 @@
|
|
|
1
|
-
import type { Database, SQLQueryBindings } from 'bun:sqlite'
|
|
2
|
-
import { createLogger } from '@dockstat/logger'
|
|
3
|
-
import type {
|
|
4
|
-
ColumnNames,
|
|
5
|
-
DatabaseRowData,
|
|
6
|
-
JsonColumnConfig,
|
|
7
|
-
OrderDirection,
|
|
8
|
-
QueryBuilderState,
|
|
9
|
-
} from '../types'
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Base QueryBuilder class that manages core state and shared functionality.
|
|
13
|
-
* This class provides the foundation for all query operations.
|
|
14
|
-
*/
|
|
15
|
-
export abstract class BaseQueryBuilder<T extends Record<string, unknown>> {
|
|
16
|
-
private logger = createLogger('BaseQueryBuilder')
|
|
17
|
-
protected state: QueryBuilderState<T>
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Get the logger instance
|
|
21
|
-
*/
|
|
22
|
-
protected getLogger() {
|
|
23
|
-
return this.logger
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
constructor(
|
|
27
|
-
db: Database,
|
|
28
|
-
tableName: string,
|
|
29
|
-
jsonConfig?: JsonColumnConfig<T>
|
|
30
|
-
) {
|
|
31
|
-
this.state = {
|
|
32
|
-
db,
|
|
33
|
-
tableName,
|
|
34
|
-
whereConditions: [],
|
|
35
|
-
whereParams: [],
|
|
36
|
-
regexConditions: [],
|
|
37
|
-
jsonColumns: jsonConfig,
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Reset query builder state
|
|
43
|
-
*/
|
|
44
|
-
protected reset(): void {
|
|
45
|
-
this.state.whereConditions = []
|
|
46
|
-
this.state.whereParams = []
|
|
47
|
-
this.state.regexConditions = []
|
|
48
|
-
// Reset any ordering, limit, offset, selected columns if present
|
|
49
|
-
if ('orderColumn' in this) (this as any).orderColumn = undefined
|
|
50
|
-
if ('orderDirection' in this) (this as any).orderDirection = 'ASC'
|
|
51
|
-
if ('limitValue' in this) (this as any).limitValue = undefined
|
|
52
|
-
if ('offsetValue' in this) (this as any).offsetValue = undefined
|
|
53
|
-
if ('selectedColumns' in this) (this as any).selectedColumns = ['*']
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Get the database instance
|
|
58
|
-
*/
|
|
59
|
-
protected getDb(): Database {
|
|
60
|
-
return this.state.db
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Get the table name
|
|
65
|
-
*/
|
|
66
|
-
protected getTableName(): string {
|
|
67
|
-
return this.state.tableName
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Build the WHERE clause portion of a SQL query.
|
|
72
|
-
* @returns Tuple of [whereClause, parameters] where whereClause includes "WHERE" prefix
|
|
73
|
-
*/
|
|
74
|
-
protected buildWhereClause(): [string, SQLQueryBindings[]] {
|
|
75
|
-
if (this.state.whereConditions.length === 0) {
|
|
76
|
-
return ['', []]
|
|
77
|
-
}
|
|
78
|
-
return [
|
|
79
|
-
` WHERE ${this.state.whereConditions.join(' AND ')}`,
|
|
80
|
-
this.state.whereParams.slice(),
|
|
81
|
-
]
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Check if there are any regex conditions that require client-side filtering.
|
|
86
|
-
*/
|
|
87
|
-
protected hasRegexConditions(): boolean {
|
|
88
|
-
return this.state.regexConditions.length > 0
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Apply client-side regex filtering to a set of rows.
|
|
93
|
-
* This is used when regex conditions are present.
|
|
94
|
-
*/
|
|
95
|
-
protected applyRegexFiltering(rows: T[]): T[] {
|
|
96
|
-
if (this.state.regexConditions.length === 0) {
|
|
97
|
-
return rows
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return rows.filter((row) =>
|
|
101
|
-
this.state.regexConditions.every(({ column, regex }) => {
|
|
102
|
-
const value = row[String(column)]
|
|
103
|
-
if (value === null || value === undefined) return false
|
|
104
|
-
return regex.test(String(value))
|
|
105
|
-
})
|
|
106
|
-
)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Validate that WHERE conditions exist for operations that require them.
|
|
111
|
-
* Throws an error if no WHERE conditions are present.
|
|
112
|
-
*/
|
|
113
|
-
protected requireWhereClause(operation: string): void {
|
|
114
|
-
if (
|
|
115
|
-
this.state.whereConditions.length === 0 &&
|
|
116
|
-
this.state.regexConditions.length === 0
|
|
117
|
-
) {
|
|
118
|
-
const error = `${operation} operation requires at least one WHERE condition. Use where(), whereRaw(), whereIn(), whereOp(), or whereRgx() to add conditions.`
|
|
119
|
-
this.logger.error(error)
|
|
120
|
-
throw new Error(error)
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Quote SQL identifiers to prevent injection and handle special characters.
|
|
126
|
-
*/
|
|
127
|
-
protected quoteIdentifier(identifier: string): string {
|
|
128
|
-
return `"${identifier.replace(/"/g, '""')}"`
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Reset all WHERE conditions and parameters.
|
|
133
|
-
* Useful for reusing the same builder instance.
|
|
134
|
-
*/
|
|
135
|
-
protected resetWhereConditions(): void {
|
|
136
|
-
this.state.whereConditions = []
|
|
137
|
-
this.state.whereParams = []
|
|
138
|
-
this.state.regexConditions = []
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Transform row data after fetching from database (deserialize JSON columns).
|
|
143
|
-
*/
|
|
144
|
-
protected transformRowFromDb(row: unknown): T {
|
|
145
|
-
if (!this.state.jsonColumns || !row) return row as T
|
|
146
|
-
|
|
147
|
-
try {
|
|
148
|
-
const transformed = { ...row } as DatabaseRowData
|
|
149
|
-
for (const column of this.state.jsonColumns) {
|
|
150
|
-
const columnKey = String(column)
|
|
151
|
-
if (
|
|
152
|
-
transformed[columnKey] !== null &&
|
|
153
|
-
transformed[columnKey] !== undefined &&
|
|
154
|
-
typeof transformed[columnKey] === 'string'
|
|
155
|
-
) {
|
|
156
|
-
try {
|
|
157
|
-
transformed[columnKey] = JSON.parse(
|
|
158
|
-
transformed[columnKey] as string
|
|
159
|
-
)
|
|
160
|
-
} catch (parseError) {
|
|
161
|
-
// Keep original value if JSON parsing fails
|
|
162
|
-
this.logger.warn(
|
|
163
|
-
`JSON parse failed for column ${columnKey}: ${parseError}`
|
|
164
|
-
)
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
return transformed as T
|
|
169
|
-
} catch (error) {
|
|
170
|
-
this.logger.error(`Error in transformRowFromDb: ${error}`)
|
|
171
|
-
return row as T
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Transform multiple rows after fetching from database.
|
|
177
|
-
*/
|
|
178
|
-
protected transformRowsFromDb(rows: unknown[]): T[] {
|
|
179
|
-
if (!this.state.jsonColumns || !Array.isArray(rows)) return rows as T[]
|
|
180
|
-
|
|
181
|
-
try {
|
|
182
|
-
return rows.map((row, index) => {
|
|
183
|
-
try {
|
|
184
|
-
return this.transformRowFromDb(row)
|
|
185
|
-
} catch (error) {
|
|
186
|
-
this.logger.error(`Error transforming row ${index}: ${error}`)
|
|
187
|
-
return row as T
|
|
188
|
-
}
|
|
189
|
-
})
|
|
190
|
-
} catch (error) {
|
|
191
|
-
this.logger.error(`Error in transformRowsFromDb: ${error}`)
|
|
192
|
-
return rows as T[]
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Transform row data before inserting/updating to database (serialize JSON columns).
|
|
198
|
-
*/
|
|
199
|
-
protected transformRowToDb(row: Partial<T>): DatabaseRowData {
|
|
200
|
-
this.logger.debug(`Transforming row (${JSON.stringify(row)}) to row Data`)
|
|
201
|
-
if (!this.state.jsonColumns || !row) {
|
|
202
|
-
return row as DatabaseRowData
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const transformed: DatabaseRowData = { ...row } as DatabaseRowData
|
|
206
|
-
|
|
207
|
-
for (const column of this.state.jsonColumns) {
|
|
208
|
-
const columnKey = String(column)
|
|
209
|
-
this.logger.debug(`Checking: ${columnKey}`)
|
|
210
|
-
if (
|
|
211
|
-
transformed[columnKey] !== undefined &&
|
|
212
|
-
transformed[columnKey] !== null
|
|
213
|
-
) {
|
|
214
|
-
if (typeof transformed[columnKey] === 'object') {
|
|
215
|
-
transformed[columnKey] = JSON.stringify(transformed[columnKey])
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
return transformed
|
|
220
|
-
}
|
|
221
|
-
}
|
|
1
|
+
import type { Database, SQLQueryBindings } from 'bun:sqlite'
|
|
2
|
+
import { createLogger } from '@dockstat/logger'
|
|
3
|
+
import type {
|
|
4
|
+
ColumnNames,
|
|
5
|
+
DatabaseRowData,
|
|
6
|
+
JsonColumnConfig,
|
|
7
|
+
OrderDirection,
|
|
8
|
+
QueryBuilderState,
|
|
9
|
+
} from '../types'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Base QueryBuilder class that manages core state and shared functionality.
|
|
13
|
+
* This class provides the foundation for all query operations.
|
|
14
|
+
*/
|
|
15
|
+
export abstract class BaseQueryBuilder<T extends Record<string, unknown>> {
|
|
16
|
+
private logger = createLogger('BaseQueryBuilder')
|
|
17
|
+
protected state: QueryBuilderState<T>
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get the logger instance
|
|
21
|
+
*/
|
|
22
|
+
protected getLogger() {
|
|
23
|
+
return this.logger
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
db: Database,
|
|
28
|
+
tableName: string,
|
|
29
|
+
jsonConfig?: JsonColumnConfig<T>
|
|
30
|
+
) {
|
|
31
|
+
this.state = {
|
|
32
|
+
db,
|
|
33
|
+
tableName,
|
|
34
|
+
whereConditions: [],
|
|
35
|
+
whereParams: [],
|
|
36
|
+
regexConditions: [],
|
|
37
|
+
jsonColumns: jsonConfig,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Reset query builder state
|
|
43
|
+
*/
|
|
44
|
+
protected reset(): void {
|
|
45
|
+
this.state.whereConditions = []
|
|
46
|
+
this.state.whereParams = []
|
|
47
|
+
this.state.regexConditions = []
|
|
48
|
+
// Reset any ordering, limit, offset, selected columns if present
|
|
49
|
+
if ('orderColumn' in this) (this as any).orderColumn = undefined
|
|
50
|
+
if ('orderDirection' in this) (this as any).orderDirection = 'ASC'
|
|
51
|
+
if ('limitValue' in this) (this as any).limitValue = undefined
|
|
52
|
+
if ('offsetValue' in this) (this as any).offsetValue = undefined
|
|
53
|
+
if ('selectedColumns' in this) (this as any).selectedColumns = ['*']
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get the database instance
|
|
58
|
+
*/
|
|
59
|
+
protected getDb(): Database {
|
|
60
|
+
return this.state.db
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get the table name
|
|
65
|
+
*/
|
|
66
|
+
protected getTableName(): string {
|
|
67
|
+
return this.state.tableName
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Build the WHERE clause portion of a SQL query.
|
|
72
|
+
* @returns Tuple of [whereClause, parameters] where whereClause includes "WHERE" prefix
|
|
73
|
+
*/
|
|
74
|
+
protected buildWhereClause(): [string, SQLQueryBindings[]] {
|
|
75
|
+
if (this.state.whereConditions.length === 0) {
|
|
76
|
+
return ['', []]
|
|
77
|
+
}
|
|
78
|
+
return [
|
|
79
|
+
` WHERE ${this.state.whereConditions.join(' AND ')}`,
|
|
80
|
+
this.state.whereParams.slice(),
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Check if there are any regex conditions that require client-side filtering.
|
|
86
|
+
*/
|
|
87
|
+
protected hasRegexConditions(): boolean {
|
|
88
|
+
return this.state.regexConditions.length > 0
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Apply client-side regex filtering to a set of rows.
|
|
93
|
+
* This is used when regex conditions are present.
|
|
94
|
+
*/
|
|
95
|
+
protected applyRegexFiltering(rows: T[]): T[] {
|
|
96
|
+
if (this.state.regexConditions.length === 0) {
|
|
97
|
+
return rows
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return rows.filter((row) =>
|
|
101
|
+
this.state.regexConditions.every(({ column, regex }) => {
|
|
102
|
+
const value = row[String(column)]
|
|
103
|
+
if (value === null || value === undefined) return false
|
|
104
|
+
return regex.test(String(value))
|
|
105
|
+
})
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Validate that WHERE conditions exist for operations that require them.
|
|
111
|
+
* Throws an error if no WHERE conditions are present.
|
|
112
|
+
*/
|
|
113
|
+
protected requireWhereClause(operation: string): void {
|
|
114
|
+
if (
|
|
115
|
+
this.state.whereConditions.length === 0 &&
|
|
116
|
+
this.state.regexConditions.length === 0
|
|
117
|
+
) {
|
|
118
|
+
const error = `${operation} operation requires at least one WHERE condition. Use where(), whereRaw(), whereIn(), whereOp(), or whereRgx() to add conditions.`
|
|
119
|
+
this.logger.error(error)
|
|
120
|
+
throw new Error(error)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Quote SQL identifiers to prevent injection and handle special characters.
|
|
126
|
+
*/
|
|
127
|
+
protected quoteIdentifier(identifier: string): string {
|
|
128
|
+
return `"${identifier.replace(/"/g, '""')}"`
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Reset all WHERE conditions and parameters.
|
|
133
|
+
* Useful for reusing the same builder instance.
|
|
134
|
+
*/
|
|
135
|
+
protected resetWhereConditions(): void {
|
|
136
|
+
this.state.whereConditions = []
|
|
137
|
+
this.state.whereParams = []
|
|
138
|
+
this.state.regexConditions = []
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Transform row data after fetching from database (deserialize JSON columns).
|
|
143
|
+
*/
|
|
144
|
+
protected transformRowFromDb(row: unknown): T {
|
|
145
|
+
if (!this.state.jsonColumns || !row) return row as T
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const transformed = { ...row } as DatabaseRowData
|
|
149
|
+
for (const column of this.state.jsonColumns) {
|
|
150
|
+
const columnKey = String(column)
|
|
151
|
+
if (
|
|
152
|
+
transformed[columnKey] !== null &&
|
|
153
|
+
transformed[columnKey] !== undefined &&
|
|
154
|
+
typeof transformed[columnKey] === 'string'
|
|
155
|
+
) {
|
|
156
|
+
try {
|
|
157
|
+
transformed[columnKey] = JSON.parse(
|
|
158
|
+
transformed[columnKey] as string
|
|
159
|
+
)
|
|
160
|
+
} catch (parseError) {
|
|
161
|
+
// Keep original value if JSON parsing fails
|
|
162
|
+
this.logger.warn(
|
|
163
|
+
`JSON parse failed for column ${columnKey}: ${parseError}`
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return transformed as T
|
|
169
|
+
} catch (error) {
|
|
170
|
+
this.logger.error(`Error in transformRowFromDb: ${error}`)
|
|
171
|
+
return row as T
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Transform multiple rows after fetching from database.
|
|
177
|
+
*/
|
|
178
|
+
protected transformRowsFromDb(rows: unknown[]): T[] {
|
|
179
|
+
if (!this.state.jsonColumns || !Array.isArray(rows)) return rows as T[]
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
return rows.map((row, index) => {
|
|
183
|
+
try {
|
|
184
|
+
return this.transformRowFromDb(row)
|
|
185
|
+
} catch (error) {
|
|
186
|
+
this.logger.error(`Error transforming row ${index}: ${error}`)
|
|
187
|
+
return row as T
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
} catch (error) {
|
|
191
|
+
this.logger.error(`Error in transformRowsFromDb: ${error}`)
|
|
192
|
+
return rows as T[]
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Transform row data before inserting/updating to database (serialize JSON columns).
|
|
198
|
+
*/
|
|
199
|
+
protected transformRowToDb(row: Partial<T>): DatabaseRowData {
|
|
200
|
+
this.logger.debug(`Transforming row (${JSON.stringify(row)}) to row Data`)
|
|
201
|
+
if (!this.state.jsonColumns || !row) {
|
|
202
|
+
return row as DatabaseRowData
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const transformed: DatabaseRowData = { ...row } as DatabaseRowData
|
|
206
|
+
|
|
207
|
+
for (const column of this.state.jsonColumns) {
|
|
208
|
+
const columnKey = String(column)
|
|
209
|
+
this.logger.debug(`Checking: ${columnKey}`)
|
|
210
|
+
if (
|
|
211
|
+
transformed[columnKey] !== undefined &&
|
|
212
|
+
transformed[columnKey] !== null
|
|
213
|
+
) {
|
|
214
|
+
if (typeof transformed[columnKey] === 'object') {
|
|
215
|
+
transformed[columnKey] = JSON.stringify(transformed[columnKey])
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return transformed
|
|
220
|
+
}
|
|
221
|
+
}
|