@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
package/package.json
CHANGED
|
@@ -1,54 +1,60 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@dockstat/sqlite-wrapper",
|
|
3
|
-
"version": "1.
|
|
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
|
-
"
|
|
9
|
-
"index.ts",
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"bun"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
},
|
|
50
|
-
"
|
|
51
|
-
"@
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@dockstat/sqlite-wrapper",
|
|
3
|
+
"version": "1.3.0",
|
|
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
|
+
"exports": {
|
|
9
|
+
".": "./index.ts",
|
|
10
|
+
"./types": "./types.ts",
|
|
11
|
+
"./utils": "./utils/index.ts"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"index.ts",
|
|
15
|
+
"query-builder/**/*",
|
|
16
|
+
"utils/**/*",
|
|
17
|
+
"README.md",
|
|
18
|
+
"types.ts"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"dev": "bun build index.ts",
|
|
22
|
+
"lint": "biome lint .",
|
|
23
|
+
"lint:fix": "biome lint --write .",
|
|
24
|
+
"lint:gh": "turbo run lint -- --reporter=github",
|
|
25
|
+
"check-types": "bunx tsc --noEmit",
|
|
26
|
+
"test": "DOCKSTAT_LOGGER_IGNORE_MESSAGES='Logger Status: active - ignoring messages:' bun test"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"sqlite",
|
|
30
|
+
"database",
|
|
31
|
+
"typescript",
|
|
32
|
+
"bun",
|
|
33
|
+
"query-builder",
|
|
34
|
+
"orm"
|
|
35
|
+
],
|
|
36
|
+
"author": "Its4Nik",
|
|
37
|
+
"license": "MPL-2.0",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "https://github.com/Its4Nik/DockStat/tree/main/packages/sqlite-wrapper",
|
|
41
|
+
"directory": "packages/sqlite-wrapper"
|
|
42
|
+
},
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/Its4Nik/DockStat/issues"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://outline.itsnik.de/s/9d88c471-373e-4ef2-a955-b1058eb7dc99/doc/dockstatsqlite-wrapper-Lxt4IphXI5",
|
|
47
|
+
"engines": {
|
|
48
|
+
"bun": ">=1.0.0"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@dockstat/logger": "1.0.1"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"typescript": "^5.9.3"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/bun": "latest",
|
|
58
|
+
"@types/dockerode": "^3.3.44"
|
|
59
|
+
}
|
|
60
|
+
}
|
package/query-builder/base.ts
CHANGED
|
@@ -1,221 +1,183 @@
|
|
|
1
|
-
import type { Database, SQLQueryBindings } from
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
protected
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
*
|
|
65
|
-
*/
|
|
66
|
-
protected
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (this.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
*
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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 type { Parser, QueryBuilderState } from "../types"
|
|
3
|
+
import {
|
|
4
|
+
createLogger,
|
|
5
|
+
quoteIdentifier,
|
|
6
|
+
type RowData,
|
|
7
|
+
transformFromDb,
|
|
8
|
+
transformRowsFromDb,
|
|
9
|
+
transformToDb,
|
|
10
|
+
} from "../utils"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Base QueryBuilder class that manages core state and shared functionality.
|
|
14
|
+
*
|
|
15
|
+
* This class provides the foundation for all query operations including:
|
|
16
|
+
* - Database connection and table name management
|
|
17
|
+
* - WHERE clause building and management
|
|
18
|
+
* - Regex condition handling (client-side filtering)
|
|
19
|
+
* - Row transformation (JSON/Boolean serialization)
|
|
20
|
+
*/
|
|
21
|
+
export abstract class BaseQueryBuilder<T extends Record<string, unknown>> {
|
|
22
|
+
protected state: QueryBuilderState<T>
|
|
23
|
+
protected log = createLogger("query")
|
|
24
|
+
|
|
25
|
+
constructor(db: Database, tableName: string, parser?: Parser<T>) {
|
|
26
|
+
this.state = {
|
|
27
|
+
db,
|
|
28
|
+
tableName,
|
|
29
|
+
whereConditions: [],
|
|
30
|
+
whereParams: [],
|
|
31
|
+
regexConditions: [],
|
|
32
|
+
parser,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.log.debug(`QueryBuilder initialized for table: ${tableName}`)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ===== State Accessors =====
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get the database instance
|
|
42
|
+
*/
|
|
43
|
+
protected getDb(): Database {
|
|
44
|
+
return this.state.db
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get the table name
|
|
49
|
+
*/
|
|
50
|
+
protected getTableName(): string {
|
|
51
|
+
return this.state.tableName
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get the parser configuration
|
|
56
|
+
*/
|
|
57
|
+
protected getParser(): Parser<T> | undefined {
|
|
58
|
+
return this.state.parser
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ===== State Management =====
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Reset query builder state to initial values
|
|
65
|
+
*/
|
|
66
|
+
protected reset(): void {
|
|
67
|
+
this.state.whereConditions = []
|
|
68
|
+
this.state.whereParams = []
|
|
69
|
+
this.state.regexConditions = []
|
|
70
|
+
|
|
71
|
+
// Reset any additional state in subclasses
|
|
72
|
+
if ("orderColumn" in this) this.orderColumn = undefined
|
|
73
|
+
if ("orderDirection" in this) this.orderDirection = "ASC"
|
|
74
|
+
if ("limitValue" in this) this.limitValue = undefined
|
|
75
|
+
if ("offsetValue" in this) this.offsetValue = undefined
|
|
76
|
+
if ("selectedColumns" in this) this.selectedColumns = ["*"]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Reset only WHERE conditions (useful for reusing builder)
|
|
81
|
+
*/
|
|
82
|
+
protected resetWhereConditions(): void {
|
|
83
|
+
this.state.whereConditions = []
|
|
84
|
+
this.state.whereParams = []
|
|
85
|
+
this.state.regexConditions = []
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ===== SQL Building Helpers =====
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Quote a SQL identifier to prevent injection
|
|
92
|
+
*/
|
|
93
|
+
protected quoteIdentifier(identifier: string): string {
|
|
94
|
+
return quoteIdentifier(identifier)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Build the WHERE clause from accumulated conditions
|
|
99
|
+
*
|
|
100
|
+
* @returns Tuple of [whereClause, parameters]
|
|
101
|
+
*/
|
|
102
|
+
protected buildWhereClause(): [string, SQLQueryBindings[]] {
|
|
103
|
+
if (this.state.whereConditions.length === 0) {
|
|
104
|
+
return ["", []]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const clause = ` WHERE ${this.state.whereConditions.join(" AND ")}`
|
|
108
|
+
const params = [...this.state.whereParams]
|
|
109
|
+
|
|
110
|
+
return [clause, params]
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ===== Regex Condition Handling =====
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Check if there are any regex conditions requiring client-side filtering
|
|
117
|
+
*/
|
|
118
|
+
protected hasRegexConditions(): boolean {
|
|
119
|
+
return this.state.regexConditions.length > 0
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Apply regex filtering to rows (client-side)
|
|
124
|
+
*/
|
|
125
|
+
protected applyRegexFiltering(rows: T[]): T[] {
|
|
126
|
+
if (!this.hasRegexConditions()) {
|
|
127
|
+
return rows
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return rows.filter((row) =>
|
|
131
|
+
this.state.regexConditions.every(({ column, regex }) => {
|
|
132
|
+
const value = row[String(column)]
|
|
133
|
+
if (value === null || value === undefined) return false
|
|
134
|
+
return regex.test(String(value))
|
|
135
|
+
})
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ===== Validation =====
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Validate that WHERE conditions exist for destructive operations
|
|
143
|
+
*
|
|
144
|
+
* @throws Error if no WHERE conditions are present
|
|
145
|
+
*/
|
|
146
|
+
protected requireWhereClause(operation: string): void {
|
|
147
|
+
const hasWhere = this.state.whereConditions.length > 0
|
|
148
|
+
const hasRegex = this.state.regexConditions.length > 0
|
|
149
|
+
|
|
150
|
+
if (!hasWhere && !hasRegex) {
|
|
151
|
+
const message =
|
|
152
|
+
`${operation} requires at least one WHERE condition. ` +
|
|
153
|
+
`Use where(), whereRaw(), whereIn(), whereOp(), or whereRgx().`
|
|
154
|
+
this.log.error(message)
|
|
155
|
+
throw new Error(message)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ===== Row Transformation =====
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Transform a single row FROM the database
|
|
163
|
+
* (deserialize JSON, convert booleans, etc.)
|
|
164
|
+
*/
|
|
165
|
+
protected transformRowFromDb(row: unknown): T {
|
|
166
|
+
return transformFromDb<T>(row, { parser: this.state.parser })
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Transform multiple rows FROM the database
|
|
171
|
+
*/
|
|
172
|
+
protected transformRowsFromDb(rows: unknown[]): T[] {
|
|
173
|
+
return transformRowsFromDb<T>(rows, { parser: this.state.parser })
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Transform a row TO the database
|
|
178
|
+
* (serialize JSON, stringify functions, etc.)
|
|
179
|
+
*/
|
|
180
|
+
protected transformRowToDb(row: Partial<T>): RowData {
|
|
181
|
+
return transformToDb<T>(row, { parser: this.state.parser })
|
|
182
|
+
}
|
|
183
|
+
}
|