@dockstat/sqlite-wrapper 1.2.6 → 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.
@@ -1,307 +1,307 @@
1
- import type { SQLQueryBindings } from "bun:sqlite";
2
- import type { WhereCondition, RegexCondition } from "../types";
3
- import { BaseQueryBuilder } from "./base";
4
-
5
- /**
6
- * Mixin class that adds WHERE-related functionality to the QueryBuilder.
7
- * This includes all conditional filtering methods.
8
- */
9
- export class WhereQueryBuilder<
10
- T extends Record<string, unknown>,
11
- > extends BaseQueryBuilder<T> {
12
- /**
13
- * Remove existing condition for a column
14
- * @param column - Column name to check
15
- * @param operation - Optional operation type (e.g., '=', 'IN', 'BETWEEN')
16
- */
17
- private removeExistingCondition(column: string, operation?: string): void {
18
- let existingIndex = -1;
19
-
20
- if (operation) {
21
- // Look for specific operation
22
- existingIndex = this.state.whereConditions.findIndex(condition =>
23
- condition.startsWith(`${String(column)} ${operation}`)
24
- );
25
- } else {
26
- // Look for any condition on this column
27
- existingIndex = this.state.whereConditions.findIndex(condition =>
28
- condition.startsWith(`${String(column)} `)
29
- );
30
- }
31
-
32
- if (existingIndex !== -1) {
33
- this.state.whereConditions.splice(existingIndex, 1);
34
- // Only remove params if they exist (some conditions might not have params)
35
- if (existingIndex < this.state.whereParams.length) {
36
- this.state.whereParams.splice(existingIndex, 1);
37
- }
38
- }
39
-
40
- // Also remove any regex conditions for this column
41
- this.state.regexConditions = this.state.regexConditions.filter(
42
- cond => String(cond.column) !== column
43
- );
44
- }
45
-
46
- /**
47
- * Add simple equality conditions to the WHERE clause.
48
- * Handles null values appropriately with IS NULL / IS NOT NULL.
49
- * Prevents duplicate conditions for the same column.
50
- *
51
- * @param conditions - Object with column-value pairs for equality checks
52
- * @returns this for method chaining
53
- */
54
- where(conditions: WhereCondition<T>): this {
55
- for (const [column, value] of Object.entries(conditions)) {
56
- // Remove any existing conditions for this column
57
- this.removeExistingCondition(column);
58
-
59
- if (value === null || value === undefined) {
60
- this.state.whereConditions.push(`${String(column)} IS NULL`);
61
- } else {
62
- this.state.whereConditions.push(`${String(column)} = ?`);
63
-
64
- // Convert JavaScript boolean to SQLite integer (0/1)
65
- let sqliteValue: SQLQueryBindings = value;
66
- if (typeof value === 'boolean') {
67
- sqliteValue = value ? 1 : 0;
68
- this.getLogger().debug(`Converting boolean value ${value} to ${sqliteValue} for column ${column}`);
69
- }
70
-
71
- this.state.whereParams.push(sqliteValue);
72
- }
73
- }
74
- return this;
75
- }
76
-
77
- /**
78
- * Add regex conditions. Note: regex conditions are applied client-side
79
- * after SQL execution due to Bun's SQLite limitations.
80
- * Prevents duplicate regex conditions for the same column.
81
- *
82
- * @param conditions - Object with column-regex pairs
83
- * @returns this for method chaining
84
- */
85
- whereRgx(conditions: RegexCondition<T>): this {
86
- for (const [column, value] of Object.entries(conditions)) {
87
- // Remove any existing conditions for this column
88
- this.removeExistingCondition(column);
89
-
90
- if (value instanceof RegExp) {
91
- this.state.regexConditions.push({
92
- column: column as keyof T,
93
- regex: value,
94
- });
95
- } else if (typeof value === "string") {
96
- this.state.regexConditions.push({
97
- column: column as keyof T,
98
- regex: new RegExp(value),
99
- });
100
- } else if (value !== null && value !== undefined) {
101
- this.state.whereConditions.push(`${String(column)} = ?`);
102
- this.state.whereParams.push(value);
103
- }
104
- }
105
- return this;
106
- }
107
-
108
- /**
109
- * Add a raw SQL WHERE fragment with parameter binding.
110
- * Note: Raw expressions bypass duplicate checking as they may be complex conditions.
111
- *
112
- * @param expr - SQL fragment (without leading WHERE/AND), can use ? placeholders
113
- * @param params - Values for the placeholders in order
114
- * @returns this for method chaining
115
- */
116
- whereExpr(expr: string, params: SQLQueryBindings[] = []): this {
117
- if (!expr || typeof expr !== "string") {
118
- throw new Error("whereExpr: expr must be a non-empty string");
119
- }
120
- // Wrap in parentheses to preserve grouping when combined with other clauses
121
- this.state.whereConditions.push(`(${expr})`);
122
- if (params.length) {
123
- this.state.whereParams.push(...params);
124
- }
125
- return this;
126
- }
127
-
128
- /**
129
- * Alias for whereExpr for compatibility
130
- */
131
- whereRaw(expr: string, params: SQLQueryBindings[] = []): this {
132
- return this.whereExpr(expr, params);
133
- }
134
-
135
- /**
136
- * Add an IN clause for the given column with proper parameter binding.
137
- * Replaces any existing conditions for the same column.
138
- *
139
- * @param column - Column name to check
140
- * @param values - Non-empty array of values for the IN clause
141
- * @returns this for method chaining
142
- */
143
- whereIn(column: keyof T, values: SQLQueryBindings[]): this {
144
- if (!Array.isArray(values) || values.length === 0) {
145
- throw new Error("whereIn: values must be a non-empty array");
146
- }
147
-
148
- // Remove any existing conditions for this column
149
- this.removeExistingCondition(String(column), "IN");
150
-
151
- const placeholders = values.map(() => "?").join(", ");
152
- this.state.whereConditions.push(`${String(column)} IN (${placeholders})`);
153
- this.state.whereParams.push(...values);
154
- return this;
155
- }
156
-
157
- /**
158
- * Add a NOT IN clause for the given column with proper parameter binding.
159
- * Replaces any existing conditions for the same column.
160
- *
161
- * @param column - Column name to check
162
- * @param values - Non-empty array of values for the NOT IN clause
163
- * @returns this for method chaining
164
- */
165
- whereNotIn(column: keyof T, values: SQLQueryBindings[]): this {
166
- if (!Array.isArray(values) || values.length === 0) {
167
- throw new Error("whereNotIn: values must be a non-empty array");
168
- }
169
-
170
- // Remove any existing conditions for this column
171
- this.removeExistingCondition(String(column), "NOT IN");
172
-
173
- const placeholders = values.map(() => "?").join(", ");
174
- this.state.whereConditions.push(`${String(column)} NOT IN (${placeholders})`);
175
- this.state.whereParams.push(...values);
176
- return this;
177
- }
178
-
179
- /**
180
- * Add a comparison operator condition with proper null handling.
181
- * Replaces any existing conditions for the same column.
182
- * Supports: =, !=, <>, <, <=, >, >=, LIKE, GLOB, IS
183
- *
184
- * @param column - Column name
185
- * @param op - Comparison operator
186
- * @param value - Value to compare (handles null appropriately)
187
- * @returns this for method chaining
188
- */
189
- whereOp(column: keyof T, op: string, value: SQLQueryBindings): this {
190
- const normalizedOp = (op ?? "").toUpperCase().trim();
191
- const allowed = [
192
- "=",
193
- "!=",
194
- "<>",
195
- "<",
196
- "<=",
197
- ">",
198
- ">=",
199
- "LIKE",
200
- "GLOB",
201
- "IS",
202
- "IS NOT",
203
- ];
204
-
205
- if (!allowed.includes(normalizedOp)) {
206
- throw new Error(`whereOp: operator "${op}" not supported`);
207
- }
208
-
209
- // Handle null special-casing for IS / IS NOT and equality operators
210
- if (
211
- (value === null || value === undefined) &&
212
- (normalizedOp === "=" || normalizedOp === "IS")
213
- ) {
214
- this.state.whereConditions.push(`${String(column)} IS NULL`);
215
- return this;
216
- }
217
-
218
- if (
219
- (value === null || value === undefined) &&
220
- (normalizedOp === "!=" ||
221
- normalizedOp === "<>" ||
222
- normalizedOp === "IS NOT")
223
- ) {
224
- this.state.whereConditions.push(`${String(column)} IS NOT NULL`);
225
- return this;
226
- }
227
-
228
- // Normal param-bound condition
229
- this.state.whereConditions.push(`${String(column)} ${normalizedOp} ?`);
230
- this.state.whereParams.push(value);
231
- return this;
232
- }
233
-
234
- /**
235
- * Add a BETWEEN condition for the given column.
236
- * Replaces any existing conditions for the same column.
237
- *
238
- * @param column - Column name
239
- * @param min - Minimum value (inclusive)
240
- * @param max - Maximum value (inclusive)
241
- * @returns this for method chaining
242
- */
243
- whereBetween(
244
- column: keyof T,
245
- min: SQLQueryBindings,
246
- max: SQLQueryBindings,
247
- ): this {
248
- // Remove any existing conditions for this column
249
- this.removeExistingCondition(String(column), "BETWEEN");
250
-
251
- this.state.whereConditions.push(`${String(column)} BETWEEN ? AND ?`);
252
- this.state.whereParams.push(min, max);
253
- return this;
254
- }
255
-
256
- /**
257
- * Add a NOT BETWEEN condition for the given column.
258
- * Replaces any existing conditions for the same column.
259
- *
260
- * @param column - Column name
261
- * @param min - Minimum value (exclusive)
262
- * @param max - Maximum value (exclusive)
263
- * @returns this for method chaining
264
- */
265
- whereNotBetween(
266
- column: keyof T,
267
- min: SQLQueryBindings,
268
- max: SQLQueryBindings,
269
- ): this {
270
- // Remove any existing conditions for this column
271
- this.removeExistingCondition(String(column), "NOT BETWEEN");
272
-
273
- this.state.whereConditions.push(`${String(column)} NOT BETWEEN ? AND ?`);
274
- this.state.whereParams.push(min, max);
275
- return this;
276
- }
277
-
278
- /**
279
- * Add an IS NULL condition for the given column.
280
- * Replaces any existing conditions for the same column.
281
- *
282
- * @param column - Column name
283
- * @returns this for method chaining
284
- */
285
- whereNull(column: keyof T): this {
286
- // Remove any existing conditions for this column
287
- this.removeExistingCondition(String(column));
288
-
289
- this.state.whereConditions.push(`${String(column)} IS NULL`);
290
- return this;
291
- }
292
-
293
- /**
294
- * Add an IS NOT NULL condition for the given column.
295
- * Replaces any existing conditions for the same column.
296
- *
297
- * @param column - Column name
298
- * @returns this for method chaining
299
- */
300
- whereNotNull(column: keyof T): this {
301
- // Remove any existing conditions for this column
302
- this.removeExistingCondition(String(column));
303
-
304
- this.state.whereConditions.push(`${String(column)} IS NOT NULL`);
305
- return this;
306
- }
307
- }
1
+ import type { SQLQueryBindings } from "bun:sqlite";
2
+ import type { WhereCondition, RegexCondition } from "../types";
3
+ import { BaseQueryBuilder } from "./base";
4
+
5
+ /**
6
+ * Mixin class that adds WHERE-related functionality to the QueryBuilder.
7
+ * This includes all conditional filtering methods.
8
+ */
9
+ export class WhereQueryBuilder<
10
+ T extends Record<string, unknown>,
11
+ > extends BaseQueryBuilder<T> {
12
+ /**
13
+ * Remove existing condition for a column
14
+ * @param column - Column name to check
15
+ * @param operation - Optional operation type (e.g., '=', 'IN', 'BETWEEN')
16
+ */
17
+ private removeExistingCondition(column: string, operation?: string): void {
18
+ let existingIndex = -1;
19
+
20
+ if (operation) {
21
+ // Look for specific operation
22
+ existingIndex = this.state.whereConditions.findIndex(condition =>
23
+ condition.startsWith(`${String(column)} ${operation}`)
24
+ );
25
+ } else {
26
+ // Look for any condition on this column
27
+ existingIndex = this.state.whereConditions.findIndex(condition =>
28
+ condition.startsWith(`${String(column)} `)
29
+ );
30
+ }
31
+
32
+ if (existingIndex !== -1) {
33
+ this.state.whereConditions.splice(existingIndex, 1);
34
+ // Only remove params if they exist (some conditions might not have params)
35
+ if (existingIndex < this.state.whereParams.length) {
36
+ this.state.whereParams.splice(existingIndex, 1);
37
+ }
38
+ }
39
+
40
+ // Also remove any regex conditions for this column
41
+ this.state.regexConditions = this.state.regexConditions.filter(
42
+ cond => String(cond.column) !== column
43
+ );
44
+ }
45
+
46
+ /**
47
+ * Add simple equality conditions to the WHERE clause.
48
+ * Handles null values appropriately with IS NULL / IS NOT NULL.
49
+ * Prevents duplicate conditions for the same column.
50
+ *
51
+ * @param conditions - Object with column-value pairs for equality checks
52
+ * @returns this for method chaining
53
+ */
54
+ where(conditions: WhereCondition<T>): this {
55
+ for (const [column, value] of Object.entries(conditions)) {
56
+ // Remove any existing conditions for this column
57
+ this.removeExistingCondition(column);
58
+
59
+ if (value === null || value === undefined) {
60
+ this.state.whereConditions.push(`${String(column)} IS NULL`);
61
+ } else {
62
+ this.state.whereConditions.push(`${String(column)} = ?`);
63
+
64
+ // Convert JavaScript boolean to SQLite integer (0/1)
65
+ let sqliteValue: SQLQueryBindings = value;
66
+ if (typeof value === 'boolean') {
67
+ sqliteValue = value ? 1 : 0;
68
+ this.getLogger().debug(`Converting boolean value ${value} to ${sqliteValue} for column ${column}`);
69
+ }
70
+
71
+ this.state.whereParams.push(sqliteValue);
72
+ }
73
+ }
74
+ return this;
75
+ }
76
+
77
+ /**
78
+ * Add regex conditions. Note: regex conditions are applied client-side
79
+ * after SQL execution due to Bun's SQLite limitations.
80
+ * Prevents duplicate regex conditions for the same column.
81
+ *
82
+ * @param conditions - Object with column-regex pairs
83
+ * @returns this for method chaining
84
+ */
85
+ whereRgx(conditions: RegexCondition<T>): this {
86
+ for (const [column, value] of Object.entries(conditions)) {
87
+ // Remove any existing conditions for this column
88
+ this.removeExistingCondition(column);
89
+
90
+ if (value instanceof RegExp) {
91
+ this.state.regexConditions.push({
92
+ column: column as keyof T,
93
+ regex: value,
94
+ });
95
+ } else if (typeof value === "string") {
96
+ this.state.regexConditions.push({
97
+ column: column as keyof T,
98
+ regex: new RegExp(value),
99
+ });
100
+ } else if (value !== null && value !== undefined) {
101
+ this.state.whereConditions.push(`${String(column)} = ?`);
102
+ this.state.whereParams.push(value);
103
+ }
104
+ }
105
+ return this;
106
+ }
107
+
108
+ /**
109
+ * Add a raw SQL WHERE fragment with parameter binding.
110
+ * Note: Raw expressions bypass duplicate checking as they may be complex conditions.
111
+ *
112
+ * @param expr - SQL fragment (without leading WHERE/AND), can use ? placeholders
113
+ * @param params - Values for the placeholders in order
114
+ * @returns this for method chaining
115
+ */
116
+ whereExpr(expr: string, params: SQLQueryBindings[] = []): this {
117
+ if (!expr || typeof expr !== "string") {
118
+ throw new Error("whereExpr: expr must be a non-empty string");
119
+ }
120
+ // Wrap in parentheses to preserve grouping when combined with other clauses
121
+ this.state.whereConditions.push(`(${expr})`);
122
+ if (params.length) {
123
+ this.state.whereParams.push(...params);
124
+ }
125
+ return this;
126
+ }
127
+
128
+ /**
129
+ * Alias for whereExpr for compatibility
130
+ */
131
+ whereRaw(expr: string, params: SQLQueryBindings[] = []): this {
132
+ return this.whereExpr(expr, params);
133
+ }
134
+
135
+ /**
136
+ * Add an IN clause for the given column with proper parameter binding.
137
+ * Replaces any existing conditions for the same column.
138
+ *
139
+ * @param column - Column name to check
140
+ * @param values - Non-empty array of values for the IN clause
141
+ * @returns this for method chaining
142
+ */
143
+ whereIn(column: keyof T, values: SQLQueryBindings[]): this {
144
+ if (!Array.isArray(values) || values.length === 0) {
145
+ throw new Error("whereIn: values must be a non-empty array");
146
+ }
147
+
148
+ // Remove any existing conditions for this column
149
+ this.removeExistingCondition(String(column), "IN");
150
+
151
+ const placeholders = values.map(() => "?").join(", ");
152
+ this.state.whereConditions.push(`${String(column)} IN (${placeholders})`);
153
+ this.state.whereParams.push(...values);
154
+ return this;
155
+ }
156
+
157
+ /**
158
+ * Add a NOT IN clause for the given column with proper parameter binding.
159
+ * Replaces any existing conditions for the same column.
160
+ *
161
+ * @param column - Column name to check
162
+ * @param values - Non-empty array of values for the NOT IN clause
163
+ * @returns this for method chaining
164
+ */
165
+ whereNotIn(column: keyof T, values: SQLQueryBindings[]): this {
166
+ if (!Array.isArray(values) || values.length === 0) {
167
+ throw new Error("whereNotIn: values must be a non-empty array");
168
+ }
169
+
170
+ // Remove any existing conditions for this column
171
+ this.removeExistingCondition(String(column), "NOT IN");
172
+
173
+ const placeholders = values.map(() => "?").join(", ");
174
+ this.state.whereConditions.push(`${String(column)} NOT IN (${placeholders})`);
175
+ this.state.whereParams.push(...values);
176
+ return this;
177
+ }
178
+
179
+ /**
180
+ * Add a comparison operator condition with proper null handling.
181
+ * Replaces any existing conditions for the same column.
182
+ * Supports: =, !=, <>, <, <=, >, >=, LIKE, GLOB, IS
183
+ *
184
+ * @param column - Column name
185
+ * @param op - Comparison operator
186
+ * @param value - Value to compare (handles null appropriately)
187
+ * @returns this for method chaining
188
+ */
189
+ whereOp(column: keyof T, op: string, value: SQLQueryBindings): this {
190
+ const normalizedOp = (op ?? "").toUpperCase().trim();
191
+ const allowed = [
192
+ "=",
193
+ "!=",
194
+ "<>",
195
+ "<",
196
+ "<=",
197
+ ">",
198
+ ">=",
199
+ "LIKE",
200
+ "GLOB",
201
+ "IS",
202
+ "IS NOT",
203
+ ];
204
+
205
+ if (!allowed.includes(normalizedOp)) {
206
+ throw new Error(`whereOp: operator "${op}" not supported`);
207
+ }
208
+
209
+ // Handle null special-casing for IS / IS NOT and equality operators
210
+ if (
211
+ (value === null || value === undefined) &&
212
+ (normalizedOp === "=" || normalizedOp === "IS")
213
+ ) {
214
+ this.state.whereConditions.push(`${String(column)} IS NULL`);
215
+ return this;
216
+ }
217
+
218
+ if (
219
+ (value === null || value === undefined) &&
220
+ (normalizedOp === "!=" ||
221
+ normalizedOp === "<>" ||
222
+ normalizedOp === "IS NOT")
223
+ ) {
224
+ this.state.whereConditions.push(`${String(column)} IS NOT NULL`);
225
+ return this;
226
+ }
227
+
228
+ // Normal param-bound condition
229
+ this.state.whereConditions.push(`${String(column)} ${normalizedOp} ?`);
230
+ this.state.whereParams.push(value);
231
+ return this;
232
+ }
233
+
234
+ /**
235
+ * Add a BETWEEN condition for the given column.
236
+ * Replaces any existing conditions for the same column.
237
+ *
238
+ * @param column - Column name
239
+ * @param min - Minimum value (inclusive)
240
+ * @param max - Maximum value (inclusive)
241
+ * @returns this for method chaining
242
+ */
243
+ whereBetween(
244
+ column: keyof T,
245
+ min: SQLQueryBindings,
246
+ max: SQLQueryBindings,
247
+ ): this {
248
+ // Remove any existing conditions for this column
249
+ this.removeExistingCondition(String(column), "BETWEEN");
250
+
251
+ this.state.whereConditions.push(`${String(column)} BETWEEN ? AND ?`);
252
+ this.state.whereParams.push(min, max);
253
+ return this;
254
+ }
255
+
256
+ /**
257
+ * Add a NOT BETWEEN condition for the given column.
258
+ * Replaces any existing conditions for the same column.
259
+ *
260
+ * @param column - Column name
261
+ * @param min - Minimum value (exclusive)
262
+ * @param max - Maximum value (exclusive)
263
+ * @returns this for method chaining
264
+ */
265
+ whereNotBetween(
266
+ column: keyof T,
267
+ min: SQLQueryBindings,
268
+ max: SQLQueryBindings,
269
+ ): this {
270
+ // Remove any existing conditions for this column
271
+ this.removeExistingCondition(String(column), "NOT BETWEEN");
272
+
273
+ this.state.whereConditions.push(`${String(column)} NOT BETWEEN ? AND ?`);
274
+ this.state.whereParams.push(min, max);
275
+ return this;
276
+ }
277
+
278
+ /**
279
+ * Add an IS NULL condition for the given column.
280
+ * Replaces any existing conditions for the same column.
281
+ *
282
+ * @param column - Column name
283
+ * @returns this for method chaining
284
+ */
285
+ whereNull(column: keyof T): this {
286
+ // Remove any existing conditions for this column
287
+ this.removeExistingCondition(String(column));
288
+
289
+ this.state.whereConditions.push(`${String(column)} IS NULL`);
290
+ return this;
291
+ }
292
+
293
+ /**
294
+ * Add an IS NOT NULL condition for the given column.
295
+ * Replaces any existing conditions for the same column.
296
+ *
297
+ * @param column - Column name
298
+ * @returns this for method chaining
299
+ */
300
+ whereNotNull(column: keyof T): this {
301
+ // Remove any existing conditions for this column
302
+ this.removeExistingCondition(String(column));
303
+
304
+ this.state.whereConditions.push(`${String(column)} IS NOT NULL`);
305
+ return this;
306
+ }
307
+ }