@dockstat/sqlite-wrapper 1.2.8 → 1.3.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.
- package/LICENSE +373 -373
- package/README.md +553 -99
- package/index.ts +1120 -858
- package/package.json +60 -54
- package/query-builder/base.ts +183 -221
- package/query-builder/delete.ts +441 -352
- package/query-builder/index.ts +409 -431
- package/query-builder/insert.ts +280 -249
- package/query-builder/select.ts +333 -358
- package/query-builder/update.ts +308 -278
- package/query-builder/where.ts +272 -307
- package/types.ts +608 -623
- package/utils/index.ts +44 -0
- package/utils/logger.ts +184 -0
- package/utils/sql.ts +241 -0
- package/utils/transformer.ts +256 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import type { SQLQueryBindings } from "bun:sqlite"
|
|
2
|
+
import type { Parser } from "../types"
|
|
3
|
+
import { createLogger } from "./logger"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Row Transformer for sqlite-wrapper
|
|
7
|
+
*
|
|
8
|
+
* Handles serialization (to DB) and deserialization (from DB) of row data,
|
|
9
|
+
* including JSON columns, Boolean columns, and Module columns.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const logger = createLogger("transformer")
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Generic row data type
|
|
16
|
+
*/
|
|
17
|
+
export type RowData = Record<string, SQLQueryBindings>
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Transform options
|
|
21
|
+
*/
|
|
22
|
+
export interface TransformOptions<T> {
|
|
23
|
+
parser?: Parser<T>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Transform a row FROM the database (deserialization)
|
|
28
|
+
*
|
|
29
|
+
* - JSON columns: Parse JSON strings back to objects
|
|
30
|
+
* - BOOLEAN columns: Convert 0/1 to true/false
|
|
31
|
+
* - MODULE columns: Transpile and create importable URLs
|
|
32
|
+
*/
|
|
33
|
+
export function transformFromDb<T extends Record<string, unknown>>(
|
|
34
|
+
row: unknown,
|
|
35
|
+
options?: TransformOptions<T>
|
|
36
|
+
): T {
|
|
37
|
+
if (!row || typeof row !== "object") {
|
|
38
|
+
return row as T
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const parser = options?.parser
|
|
42
|
+
if (!parser) {
|
|
43
|
+
return row as T
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const transformed = { ...row } as RowData
|
|
47
|
+
const transformedColumns: string[] = []
|
|
48
|
+
|
|
49
|
+
// Transform JSON columns
|
|
50
|
+
if (parser.JSON && parser.JSON.length > 0) {
|
|
51
|
+
for (const column of parser.JSON) {
|
|
52
|
+
const columnKey = String(column)
|
|
53
|
+
const value = transformed[columnKey]
|
|
54
|
+
|
|
55
|
+
if (value !== null && value !== undefined && typeof value === "string") {
|
|
56
|
+
try {
|
|
57
|
+
transformed[columnKey] = JSON.parse(value)
|
|
58
|
+
transformedColumns.push(`JSON:${columnKey}`)
|
|
59
|
+
} catch {
|
|
60
|
+
// Keep original value if JSON parsing fails
|
|
61
|
+
logger.warn(`Failed to parse JSON column: ${columnKey}`)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Transform BOOLEAN columns
|
|
68
|
+
if (parser.BOOLEAN && parser.BOOLEAN.length > 0) {
|
|
69
|
+
for (const column of parser.BOOLEAN) {
|
|
70
|
+
const columnKey = String(column)
|
|
71
|
+
const value = transformed[columnKey]
|
|
72
|
+
|
|
73
|
+
if (value === null || value === undefined) {
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Already a boolean - no transformation needed
|
|
78
|
+
if (typeof value === "boolean") {
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Convert number (0/1) to boolean
|
|
83
|
+
if (typeof value === "number") {
|
|
84
|
+
transformed[columnKey] = value === 1
|
|
85
|
+
transformedColumns.push(`BOOL:${columnKey}`)
|
|
86
|
+
continue
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Convert string representations
|
|
90
|
+
if (typeof value === "string") {
|
|
91
|
+
const normalized = value.trim().toLowerCase()
|
|
92
|
+
if (["1", "true", "t", "yes"].includes(normalized)) {
|
|
93
|
+
transformed[columnKey] = true
|
|
94
|
+
transformedColumns.push(`BOOL:${columnKey}`)
|
|
95
|
+
} else if (["0", "false", "f", "no"].includes(normalized)) {
|
|
96
|
+
transformed[columnKey] = false
|
|
97
|
+
transformedColumns.push(`BOOL:${columnKey}`)
|
|
98
|
+
} else {
|
|
99
|
+
// Try numeric conversion
|
|
100
|
+
const num = Number(normalized)
|
|
101
|
+
if (!Number.isNaN(num)) {
|
|
102
|
+
transformed[columnKey] = num === 1
|
|
103
|
+
transformedColumns.push(`BOOL:${columnKey}`)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Transform MODULE columns
|
|
111
|
+
if (parser.MODULE && Object.keys(parser.MODULE).length > 0) {
|
|
112
|
+
for (const [funcKey, options] of Object.entries(parser.MODULE)) {
|
|
113
|
+
const value = transformed[funcKey]
|
|
114
|
+
|
|
115
|
+
if (value !== undefined && value !== null && typeof value === "string") {
|
|
116
|
+
try {
|
|
117
|
+
const transpiler = new Bun.Transpiler(options)
|
|
118
|
+
const compiled = transpiler.transformSync(value)
|
|
119
|
+
const blob = new Blob([compiled], { type: "text/javascript" })
|
|
120
|
+
transformed[funcKey] = URL.createObjectURL(blob)
|
|
121
|
+
transformedColumns.push(`MODULE:${funcKey}`)
|
|
122
|
+
} catch (_error) {
|
|
123
|
+
logger.warn(`Failed to transpile MODULE column: ${funcKey}`)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (transformedColumns.length > 0) {
|
|
130
|
+
logger.transform("deserialize", transformedColumns)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return transformed as T
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Transform multiple rows FROM the database
|
|
138
|
+
*/
|
|
139
|
+
export function transformRowsFromDb<T extends Record<string, unknown>>(
|
|
140
|
+
rows: unknown[],
|
|
141
|
+
options?: TransformOptions<T>
|
|
142
|
+
): T[] {
|
|
143
|
+
if (!rows || !Array.isArray(rows)) {
|
|
144
|
+
return []
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return rows.map((row) => transformFromDb<T>(row, options))
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Transform a row TO the database (serialization)
|
|
152
|
+
*
|
|
153
|
+
* - JSON columns: Stringify objects to JSON strings
|
|
154
|
+
* - MODULE columns: Stringify functions
|
|
155
|
+
*/
|
|
156
|
+
export function transformToDb<T extends Record<string, unknown>>(
|
|
157
|
+
row: Partial<T>,
|
|
158
|
+
options?: TransformOptions<T>
|
|
159
|
+
): RowData {
|
|
160
|
+
if (!row || typeof row !== "object") {
|
|
161
|
+
return row as RowData
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const parser = options?.parser
|
|
165
|
+
if (!parser) {
|
|
166
|
+
return row as RowData
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const transformed = { ...row } as RowData
|
|
170
|
+
const transformedColumns: string[] = []
|
|
171
|
+
|
|
172
|
+
// Serialize JSON columns
|
|
173
|
+
if (parser.JSON && parser.JSON.length > 0) {
|
|
174
|
+
for (const column of parser.JSON) {
|
|
175
|
+
const columnKey = String(column)
|
|
176
|
+
const value = transformed[columnKey]
|
|
177
|
+
|
|
178
|
+
if (value !== undefined && value !== null && typeof value === "object") {
|
|
179
|
+
transformed[columnKey] = JSON.stringify(value)
|
|
180
|
+
transformedColumns.push(`JSON:${columnKey}`)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Serialize MODULE columns (functions to strings)
|
|
186
|
+
if (parser.MODULE && Object.keys(parser.MODULE).length > 0) {
|
|
187
|
+
for (const [funcKey, options] of Object.entries(parser.MODULE)) {
|
|
188
|
+
const value = transformed[funcKey]
|
|
189
|
+
|
|
190
|
+
if (value !== undefined && value !== null && typeof value === "function") {
|
|
191
|
+
try {
|
|
192
|
+
const transpiler = new Bun.Transpiler(options)
|
|
193
|
+
const fnValue = value as () => unknown
|
|
194
|
+
transformed[funcKey] = transpiler.transformSync(fnValue.toString())
|
|
195
|
+
transformedColumns.push(`MODULE:${funcKey}`)
|
|
196
|
+
} catch {
|
|
197
|
+
logger.warn(`Failed to serialize MODULE column: ${funcKey}`)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (transformedColumns.length > 0) {
|
|
204
|
+
logger.transform("serialize", transformedColumns)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return transformed
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Transform multiple rows TO the database
|
|
212
|
+
*/
|
|
213
|
+
export function transformRowsToDb<T extends Record<string, unknown>>(
|
|
214
|
+
rows: Partial<T>[],
|
|
215
|
+
options?: TransformOptions<T>
|
|
216
|
+
): RowData[] {
|
|
217
|
+
if (!rows || !Array.isArray(rows)) {
|
|
218
|
+
return []
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return rows.map((row) => transformToDb<T>(row, options))
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Check if a parser has any transformations configured
|
|
226
|
+
*/
|
|
227
|
+
export function hasTransformations<T>(parser?: Parser<T>): boolean {
|
|
228
|
+
if (!parser) return false
|
|
229
|
+
|
|
230
|
+
const hasJson = !!(parser.JSON && parser.JSON.length > 0)
|
|
231
|
+
const hasBoolean = !!(parser.BOOLEAN && parser.BOOLEAN.length > 0)
|
|
232
|
+
const hasModule = !!(parser.MODULE && Object.keys(parser.MODULE).length > 0)
|
|
233
|
+
|
|
234
|
+
return hasJson || hasBoolean || hasModule
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Get a summary of parser configuration
|
|
239
|
+
*/
|
|
240
|
+
export function getParserSummary<T>(parser?: Parser<T>): string {
|
|
241
|
+
if (!parser) return "none"
|
|
242
|
+
|
|
243
|
+
const parts: string[] = []
|
|
244
|
+
|
|
245
|
+
if (parser.JSON && parser.JSON.length > 0) {
|
|
246
|
+
parts.push(`JSON(${parser.JSON.length})`)
|
|
247
|
+
}
|
|
248
|
+
if (parser.BOOLEAN && parser.BOOLEAN.length > 0) {
|
|
249
|
+
parts.push(`BOOL(${parser.BOOLEAN.length})`)
|
|
250
|
+
}
|
|
251
|
+
if (parser.MODULE && Object.keys(parser.MODULE).length > 0) {
|
|
252
|
+
parts.push(`MODULE(${Object.keys(parser.MODULE).length})`)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return parts.length > 0 ? parts.join(", ") : "none"
|
|
256
|
+
}
|