@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.
@@ -1,358 +1,358 @@
1
- import type { Database, SQLQueryBindings } from 'bun:sqlite'
2
- import type { ColumnNames, JsonColumnConfig, OrderDirection } from '../types'
3
- import { WhereQueryBuilder } from './where'
4
-
5
- /**
6
- * Mixin class that adds SELECT-specific functionality to the QueryBuilder.
7
- * Handles column selection, ordering, limiting, and result execution methods.
8
- */
9
- export class SelectQueryBuilder<
10
- T extends Record<string, unknown>,
11
- > extends WhereQueryBuilder<T> {
12
- private selectedColumns: ColumnNames<T>
13
- private orderColumn?: keyof T
14
- private orderDirection: OrderDirection
15
- private limitValue?: number
16
- private offsetValue?: number
17
-
18
- constructor(
19
- db: Database,
20
- tableName: string,
21
- jsonConfig?: JsonColumnConfig<T>
22
- ) {
23
- super(db, tableName, jsonConfig)
24
- this.selectedColumns = ['*']
25
- this.orderDirection = 'ASC'
26
- }
27
-
28
- /**
29
- * Specify which columns to select.
30
- *
31
- * @param columns - Array of column names or ["*"] for all columns
32
- * @returns this for method chaining
33
- */
34
- select(columns: ColumnNames<T>): this {
35
- this.selectedColumns = columns
36
- return this
37
- }
38
-
39
- /**
40
- * Add ORDER BY clause.
41
- *
42
- * @param column - Column name to order by
43
- * @returns this for method chaining
44
- */
45
- orderBy(column: keyof T): this {
46
- this.orderColumn = column
47
- return this
48
- }
49
-
50
- /**
51
- * Set order direction to descending.
52
- *
53
- * @returns this for method chaining
54
- */
55
- desc(): this {
56
- this.orderDirection = 'DESC'
57
- return this
58
- }
59
-
60
- /**
61
- * Set order direction to ascending (default).
62
- *
63
- * @returns this for method chaining
64
- */
65
- asc(): this {
66
- this.orderDirection = 'ASC'
67
- return this
68
- }
69
-
70
- /**
71
- * Add LIMIT clause.
72
- *
73
- * @param amount - Maximum number of rows to return
74
- * @returns this for method chaining
75
- */
76
- limit(amount: number): this {
77
- if (amount < 0) {
78
- throw new Error('limit: amount must be non-negative')
79
- }
80
- this.limitValue = amount
81
- return this
82
- }
83
-
84
- /**
85
- * Add OFFSET clause.
86
- *
87
- * @param start - Number of rows to skip
88
- * @returns this for method chaining
89
- */
90
- offset(start: number): this {
91
- if (start < 0) {
92
- throw new Error('offset: start must be non-negative')
93
- }
94
- this.offsetValue = start
95
- return this
96
- }
97
-
98
- /**
99
- * Build the complete SELECT query.
100
- * If regex conditions exist, ORDER/LIMIT/OFFSET are not included in SQL
101
- * as they will be applied client-side after regex filtering.
102
- *
103
- * @param includeOrderAndLimit - Whether to include ORDER/LIMIT/OFFSET in SQL
104
- * @returns Tuple of [query, parameters]
105
- */
106
- private buildSelectQuery(
107
- includeOrderAndLimit = true
108
- ): [string, SQLQueryBindings[]] {
109
- const cols =
110
- this.selectedColumns[0] === '*'
111
- ? '*'
112
- : (this.selectedColumns as string[]).join(', ')
113
-
114
- let query = `SELECT ${cols} FROM ${this.quoteIdentifier(this.getTableName())}`
115
-
116
- const [whereClause, whereParams] = this.buildWhereClause()
117
- query += whereClause
118
-
119
- if (includeOrderAndLimit && !this.hasRegexConditions()) {
120
- if (this.orderColumn) {
121
- query += ` ORDER BY ${String(this.orderColumn)} ${this.orderDirection}`
122
- }
123
-
124
- if (this.limitValue !== undefined) {
125
- query += ` LIMIT ${this.limitValue}`
126
- }
127
-
128
- if (this.offsetValue !== undefined) {
129
- query += ` OFFSET ${this.offsetValue}`
130
- }
131
- }
132
-
133
- return [query, whereParams]
134
- }
135
-
136
- /**
137
- * Apply JavaScript-based filtering, ordering, and pagination.
138
- * Used when regex conditions require client-side processing.
139
- *
140
- * @param rows - Rows to process
141
- * @returns Processed rows
142
- */
143
- private applyClientSideOperations(rows: T[]): T[] {
144
- if (!this.hasRegexConditions()) return rows
145
-
146
- // Apply regex filters
147
- let filtered = this.applyRegexFiltering(rows)
148
-
149
- // Apply ordering in JavaScript
150
- if (this.orderColumn) {
151
- const col = String(this.orderColumn)
152
- filtered.sort((a: T, b: T) => {
153
- const va = a[col]
154
- const vb = b[col]
155
- if (va === vb) return 0
156
- if (va === null || va === undefined) return -1
157
- if (vb === null || vb === undefined) return 1
158
- if (va < vb) return this.orderDirection === 'ASC' ? -1 : 1
159
- return this.orderDirection === 'ASC' ? 1 : -1
160
- })
161
- }
162
-
163
- // Apply offset & limit in JavaScript
164
- const start = this.offsetValue ?? 0
165
- if (this.limitValue !== undefined) {
166
- filtered = filtered.slice(start, start + this.limitValue)
167
- } else if (start > 0) {
168
- filtered = filtered.slice(start)
169
- }
170
-
171
- return filtered
172
- }
173
-
174
- /**
175
- * Execute the query and return all matching rows.
176
- *
177
- * @returns Array of rows matching the query
178
- */
179
- all(): T[] {
180
- if (!this.hasRegexConditions()) {
181
- const [query, params] = this.buildSelectQuery(true)
182
- this.getLogger().debug(
183
- `Executing SELECT query - query: ${query}, params: ${JSON.stringify(params)}, hasJsonColumns: ${!!this.state.jsonColumns}`
184
- )
185
- const rows = this.getDb()
186
- .prepare(query, params)
187
- .all() as T[]
188
- this.getLogger().debug(`Retrieved ${rows.length} rows from database`)
189
- const transformed = this.transformRowsFromDb(rows)
190
- this.getLogger().debug(`Transformed ${transformed.length} rows`)
191
- this.reset()
192
- return transformed
193
- }
194
-
195
- const [query, params] = this.buildSelectQuery(false)
196
- this.getLogger().debug(
197
- `Executing SELECT query with regex conditions - query: ${query}, params: ${JSON.stringify(params)}, hasJsonColumns: ${!!this.state.jsonColumns}`
198
- )
199
- const rows = this.getDb()
200
- .prepare(query)
201
- .all(...params) as T[]
202
- this.getLogger().debug(`Retrieved ${rows.length} rows for regex filtering`)
203
- const transformedRows = this.transformRowsFromDb(rows)
204
- this.getLogger().debug(
205
- `Transformed ${transformedRows.length} rows for regex filtering`
206
- )
207
- this.reset()
208
- return this.applyClientSideOperations(transformedRows)
209
- }
210
-
211
- /**
212
- * Execute the query and return the first matching row, or null if none found.
213
- * If no explicit LIMIT is set, adds LIMIT 1 for efficiency.
214
- *
215
- * @returns First matching row or null
216
- */
217
- get(): T | null {
218
- if (!this.hasRegexConditions() && this.limitValue === undefined) {
219
- // No regex and no explicit limit, we can safely add LIMIT 1
220
- const [query, params] = this.buildSelectQuery(true)
221
- const q = query.includes('LIMIT') ? query : `${query} LIMIT 1`
222
- this.getLogger().debug(
223
- `Executing single-row SELECT query - query: ${q}, params: ${JSON.stringify(params)}, hasJsonColumns: ${!!this.state.jsonColumns}`
224
- )
225
- const row = this.getDb()
226
- .prepare(q)
227
- .get(...params) as T | null
228
- this.getLogger().debug(`Row found: ${row ? 'yes' : 'no'}`)
229
- const transformed = row ? this.transformRowFromDb(row) : null
230
- this.getLogger().debug(
231
- `Transformed row available: ${transformed ? 'yes' : 'no'}`
232
- )
233
- this.reset()
234
- return transformed
235
- }
236
-
237
- if (!this.hasRegexConditions() && this.limitValue !== undefined) {
238
- // Limit is present; just use the query as-is
239
- const [query, params] = this.buildSelectQuery(true)
240
- this.getLogger().debug(
241
- `get() - path 2: ${JSON.stringify({
242
- query,
243
- params,
244
- hasJsonColumns: !!this.state.jsonColumns,
245
- })}`
246
- )
247
- const row = this.getDb()
248
- .prepare(query)
249
- .get(...params) as T | null
250
- this.getLogger().debug(`raw row (path 2): ${row ? 'found' : 'null'}`)
251
- const transformed = row ? this.transformRowFromDb(row) : null
252
- this.getLogger().debug(
253
- `transformed row (path 2): ${transformed ? 'found' : 'null'}`
254
- )
255
- this.reset()
256
- return transformed
257
- }
258
-
259
- // Has regex conditions, need to process client-side
260
- this.getLogger().debug('path 3 (regex fallback)')
261
- const results = this.all()
262
- this.reset()
263
- return results[0] ?? null
264
- }
265
-
266
- /**
267
- * Execute the query and return the first matching row, or null if none found.
268
- * Always respects the semantics of returning the first row regardless of LIMIT.
269
- *
270
- * @returns First matching row or null
271
- */
272
- first(): T | null {
273
- // Temporarily set limit to 1 but preserve previous value
274
- const prevLimit = this.limitValue
275
- this.limitValue = 1
276
- const result = this.get()
277
- this.limitValue = prevLimit
278
- this.reset()
279
- return result
280
- }
281
-
282
- /**
283
- * Execute a COUNT query and return the number of matching rows.
284
- * For regex conditions, this fetches all rows and counts client-side.
285
- *
286
- * @returns Number of matching rows
287
- */
288
- count(): number {
289
- if (!this.hasRegexConditions()) {
290
- // Safe to do COUNT(*) in SQL
291
- const [baseQuery, params] = this.buildSelectQuery(true)
292
- const countQuery = baseQuery.replace(
293
- /SELECT (.+?) FROM/i,
294
- 'SELECT COUNT(*) AS __count FROM'
295
- )
296
- const result = this.getDb()
297
- .prepare(countQuery)
298
- .get(...params) as {
299
- __count: number
300
- }
301
- this.reset()
302
- return result?.__count ?? 0
303
- }
304
- this.reset()
305
-
306
- // Has regex conditions, count client-side
307
- return this.all().length
308
- }
309
-
310
- /**
311
- * Check if any rows match the current conditions.
312
- *
313
- * @returns true if at least one row matches, false otherwise
314
- */
315
- exists(): boolean {
316
- if (!this.hasRegexConditions()) {
317
- // Use EXISTS for efficiency
318
- const [baseQuery, params] = this.buildSelectQuery(true)
319
- const existsQuery = `SELECT EXISTS(${baseQuery}) AS __exists`
320
- const result = this.getDb()
321
- .prepare(existsQuery)
322
- .get(...params) as {
323
- __exists: number
324
- }
325
- this.reset()
326
- return Boolean(result?.__exists)
327
- }
328
- this.reset()
329
-
330
- // Has regex conditions, check client-side
331
- return this.count() > 0
332
- }
333
-
334
- /**
335
- * Execute the query and return a single column value from the first row.
336
- * Useful for getting a specific field value.
337
- *
338
- * @param column - Column name to extract the value from
339
- * @returns The value of the specified column from the first row, or null
340
- */
341
- value<K extends keyof T>(column: K): T[K] | null {
342
- const row = this.first()
343
- this.reset()
344
- return row ? row[column] : null
345
- }
346
-
347
- /**
348
- * Execute the query and return an array of values from a single column.
349
- *
350
- * @param column - Column name to extract values from
351
- * @returns Array of values from the specified column
352
- */
353
- pluck<K extends keyof T>(column: K): T[K][] {
354
- const rows = this.all()
355
- this.reset()
356
- return rows.map((row) => row[column])
357
- }
358
- }
1
+ import type { Database, SQLQueryBindings } from 'bun:sqlite'
2
+ import type { ColumnNames, JsonColumnConfig, OrderDirection } from '../types'
3
+ import { WhereQueryBuilder } from './where'
4
+
5
+ /**
6
+ * Mixin class that adds SELECT-specific functionality to the QueryBuilder.
7
+ * Handles column selection, ordering, limiting, and result execution methods.
8
+ */
9
+ export class SelectQueryBuilder<
10
+ T extends Record<string, unknown>,
11
+ > extends WhereQueryBuilder<T> {
12
+ private selectedColumns: ColumnNames<T>
13
+ private orderColumn?: keyof T
14
+ private orderDirection: OrderDirection
15
+ private limitValue?: number
16
+ private offsetValue?: number
17
+
18
+ constructor(
19
+ db: Database,
20
+ tableName: string,
21
+ jsonConfig?: JsonColumnConfig<T>
22
+ ) {
23
+ super(db, tableName, jsonConfig)
24
+ this.selectedColumns = ['*']
25
+ this.orderDirection = 'ASC'
26
+ }
27
+
28
+ /**
29
+ * Specify which columns to select.
30
+ *
31
+ * @param columns - Array of column names or ["*"] for all columns
32
+ * @returns this for method chaining
33
+ */
34
+ select(columns: ColumnNames<T>): this {
35
+ this.selectedColumns = columns
36
+ return this
37
+ }
38
+
39
+ /**
40
+ * Add ORDER BY clause.
41
+ *
42
+ * @param column - Column name to order by
43
+ * @returns this for method chaining
44
+ */
45
+ orderBy(column: keyof T): this {
46
+ this.orderColumn = column
47
+ return this
48
+ }
49
+
50
+ /**
51
+ * Set order direction to descending.
52
+ *
53
+ * @returns this for method chaining
54
+ */
55
+ desc(): this {
56
+ this.orderDirection = 'DESC'
57
+ return this
58
+ }
59
+
60
+ /**
61
+ * Set order direction to ascending (default).
62
+ *
63
+ * @returns this for method chaining
64
+ */
65
+ asc(): this {
66
+ this.orderDirection = 'ASC'
67
+ return this
68
+ }
69
+
70
+ /**
71
+ * Add LIMIT clause.
72
+ *
73
+ * @param amount - Maximum number of rows to return
74
+ * @returns this for method chaining
75
+ */
76
+ limit(amount: number): this {
77
+ if (amount < 0) {
78
+ throw new Error('limit: amount must be non-negative')
79
+ }
80
+ this.limitValue = amount
81
+ return this
82
+ }
83
+
84
+ /**
85
+ * Add OFFSET clause.
86
+ *
87
+ * @param start - Number of rows to skip
88
+ * @returns this for method chaining
89
+ */
90
+ offset(start: number): this {
91
+ if (start < 0) {
92
+ throw new Error('offset: start must be non-negative')
93
+ }
94
+ this.offsetValue = start
95
+ return this
96
+ }
97
+
98
+ /**
99
+ * Build the complete SELECT query.
100
+ * If regex conditions exist, ORDER/LIMIT/OFFSET are not included in SQL
101
+ * as they will be applied client-side after regex filtering.
102
+ *
103
+ * @param includeOrderAndLimit - Whether to include ORDER/LIMIT/OFFSET in SQL
104
+ * @returns Tuple of [query, parameters]
105
+ */
106
+ private buildSelectQuery(
107
+ includeOrderAndLimit = true
108
+ ): [string, SQLQueryBindings[]] {
109
+ const cols =
110
+ this.selectedColumns[0] === '*'
111
+ ? '*'
112
+ : (this.selectedColumns as string[]).join(', ')
113
+
114
+ let query = `SELECT ${cols} FROM ${this.quoteIdentifier(this.getTableName())}`
115
+
116
+ const [whereClause, whereParams] = this.buildWhereClause()
117
+ query += whereClause
118
+
119
+ if (includeOrderAndLimit && !this.hasRegexConditions()) {
120
+ if (this.orderColumn) {
121
+ query += ` ORDER BY ${String(this.orderColumn)} ${this.orderDirection}`
122
+ }
123
+
124
+ if (this.limitValue !== undefined) {
125
+ query += ` LIMIT ${this.limitValue}`
126
+ }
127
+
128
+ if (this.offsetValue !== undefined) {
129
+ query += ` OFFSET ${this.offsetValue}`
130
+ }
131
+ }
132
+
133
+ return [query, whereParams]
134
+ }
135
+
136
+ /**
137
+ * Apply JavaScript-based filtering, ordering, and pagination.
138
+ * Used when regex conditions require client-side processing.
139
+ *
140
+ * @param rows - Rows to process
141
+ * @returns Processed rows
142
+ */
143
+ private applyClientSideOperations(rows: T[]): T[] {
144
+ if (!this.hasRegexConditions()) return rows
145
+
146
+ // Apply regex filters
147
+ let filtered = this.applyRegexFiltering(rows)
148
+
149
+ // Apply ordering in JavaScript
150
+ if (this.orderColumn) {
151
+ const col = String(this.orderColumn)
152
+ filtered.sort((a: T, b: T) => {
153
+ const va = a[col]
154
+ const vb = b[col]
155
+ if (va === vb) return 0
156
+ if (va === null || va === undefined) return -1
157
+ if (vb === null || vb === undefined) return 1
158
+ if (va < vb) return this.orderDirection === 'ASC' ? -1 : 1
159
+ return this.orderDirection === 'ASC' ? 1 : -1
160
+ })
161
+ }
162
+
163
+ // Apply offset & limit in JavaScript
164
+ const start = this.offsetValue ?? 0
165
+ if (this.limitValue !== undefined) {
166
+ filtered = filtered.slice(start, start + this.limitValue)
167
+ } else if (start > 0) {
168
+ filtered = filtered.slice(start)
169
+ }
170
+
171
+ return filtered
172
+ }
173
+
174
+ /**
175
+ * Execute the query and return all matching rows.
176
+ *
177
+ * @returns Array of rows matching the query
178
+ */
179
+ all(): T[] {
180
+ if (!this.hasRegexConditions()) {
181
+ const [query, params] = this.buildSelectQuery(true)
182
+ this.getLogger().debug(
183
+ `Executing SELECT query - query: ${query}, params: ${JSON.stringify(params)}, hasJsonColumns: ${!!this.state.jsonColumns}`
184
+ )
185
+ const rows = this.getDb()
186
+ .prepare(query, params)
187
+ .all() as T[]
188
+ this.getLogger().debug(`Retrieved ${rows.length} rows from database`)
189
+ const transformed = this.transformRowsFromDb(rows)
190
+ this.getLogger().debug(`Transformed ${transformed.length} rows`)
191
+ this.reset()
192
+ return transformed
193
+ }
194
+
195
+ const [query, params] = this.buildSelectQuery(false)
196
+ this.getLogger().debug(
197
+ `Executing SELECT query with regex conditions - query: ${query}, params: ${JSON.stringify(params)}, hasJsonColumns: ${!!this.state.jsonColumns}`
198
+ )
199
+ const rows = this.getDb()
200
+ .prepare(query)
201
+ .all(...params) as T[]
202
+ this.getLogger().debug(`Retrieved ${rows.length} rows for regex filtering`)
203
+ const transformedRows = this.transformRowsFromDb(rows)
204
+ this.getLogger().debug(
205
+ `Transformed ${transformedRows.length} rows for regex filtering`
206
+ )
207
+ this.reset()
208
+ return this.applyClientSideOperations(transformedRows)
209
+ }
210
+
211
+ /**
212
+ * Execute the query and return the first matching row, or null if none found.
213
+ * If no explicit LIMIT is set, adds LIMIT 1 for efficiency.
214
+ *
215
+ * @returns First matching row or null
216
+ */
217
+ get(): T | null {
218
+ if (!this.hasRegexConditions() && this.limitValue === undefined) {
219
+ // No regex and no explicit limit, we can safely add LIMIT 1
220
+ const [query, params] = this.buildSelectQuery(true)
221
+ const q = query.includes('LIMIT') ? query : `${query} LIMIT 1`
222
+ this.getLogger().debug(
223
+ `Executing single-row SELECT query - query: ${q}, params: ${JSON.stringify(params)}, hasJsonColumns: ${!!this.state.jsonColumns}`
224
+ )
225
+ const row = this.getDb()
226
+ .prepare(q)
227
+ .get(...params) as T | null
228
+ this.getLogger().debug(`Row found: ${row ? 'yes' : 'no'}`)
229
+ const transformed = row ? this.transformRowFromDb(row) : null
230
+ this.getLogger().debug(
231
+ `Transformed row available: ${transformed ? 'yes' : 'no'}`
232
+ )
233
+ this.reset()
234
+ return transformed
235
+ }
236
+
237
+ if (!this.hasRegexConditions() && this.limitValue !== undefined) {
238
+ // Limit is present; just use the query as-is
239
+ const [query, params] = this.buildSelectQuery(true)
240
+ this.getLogger().debug(
241
+ `get() - path 2: ${JSON.stringify({
242
+ query,
243
+ params,
244
+ hasJsonColumns: !!this.state.jsonColumns,
245
+ })}`
246
+ )
247
+ const row = this.getDb()
248
+ .prepare(query)
249
+ .get(...params) as T | null
250
+ this.getLogger().debug(`raw row (path 2): ${row ? 'found' : 'null'}`)
251
+ const transformed = row ? this.transformRowFromDb(row) : null
252
+ this.getLogger().debug(
253
+ `transformed row (path 2): ${transformed ? 'found' : 'null'}`
254
+ )
255
+ this.reset()
256
+ return transformed
257
+ }
258
+
259
+ // Has regex conditions, need to process client-side
260
+ this.getLogger().debug('path 3 (regex fallback)')
261
+ const results = this.all()
262
+ this.reset()
263
+ return results[0] ?? null
264
+ }
265
+
266
+ /**
267
+ * Execute the query and return the first matching row, or null if none found.
268
+ * Always respects the semantics of returning the first row regardless of LIMIT.
269
+ *
270
+ * @returns First matching row or null
271
+ */
272
+ first(): T | null {
273
+ // Temporarily set limit to 1 but preserve previous value
274
+ const prevLimit = this.limitValue
275
+ this.limitValue = 1
276
+ const result = this.get()
277
+ this.limitValue = prevLimit
278
+ this.reset()
279
+ return result
280
+ }
281
+
282
+ /**
283
+ * Execute a COUNT query and return the number of matching rows.
284
+ * For regex conditions, this fetches all rows and counts client-side.
285
+ *
286
+ * @returns Number of matching rows
287
+ */
288
+ count(): number {
289
+ if (!this.hasRegexConditions()) {
290
+ // Safe to do COUNT(*) in SQL
291
+ const [baseQuery, params] = this.buildSelectQuery(true)
292
+ const countQuery = baseQuery.replace(
293
+ /SELECT (.+?) FROM/i,
294
+ 'SELECT COUNT(*) AS __count FROM'
295
+ )
296
+ const result = this.getDb()
297
+ .prepare(countQuery)
298
+ .get(...params) as {
299
+ __count: number
300
+ }
301
+ this.reset()
302
+ return result?.__count ?? 0
303
+ }
304
+ this.reset()
305
+
306
+ // Has regex conditions, count client-side
307
+ return this.all().length
308
+ }
309
+
310
+ /**
311
+ * Check if any rows match the current conditions.
312
+ *
313
+ * @returns true if at least one row matches, false otherwise
314
+ */
315
+ exists(): boolean {
316
+ if (!this.hasRegexConditions()) {
317
+ // Use EXISTS for efficiency
318
+ const [baseQuery, params] = this.buildSelectQuery(true)
319
+ const existsQuery = `SELECT EXISTS(${baseQuery}) AS __exists`
320
+ const result = this.getDb()
321
+ .prepare(existsQuery)
322
+ .get(...params) as {
323
+ __exists: number
324
+ }
325
+ this.reset()
326
+ return Boolean(result?.__exists)
327
+ }
328
+ this.reset()
329
+
330
+ // Has regex conditions, check client-side
331
+ return this.count() > 0
332
+ }
333
+
334
+ /**
335
+ * Execute the query and return a single column value from the first row.
336
+ * Useful for getting a specific field value.
337
+ *
338
+ * @param column - Column name to extract the value from
339
+ * @returns The value of the specified column from the first row, or null
340
+ */
341
+ value<K extends keyof T>(column: K): T[K] | null {
342
+ const row = this.first()
343
+ this.reset()
344
+ return row ? row[column] : null
345
+ }
346
+
347
+ /**
348
+ * Execute the query and return an array of values from a single column.
349
+ *
350
+ * @param column - Column name to extract values from
351
+ * @returns Array of values from the specified column
352
+ */
353
+ pluck<K extends keyof T>(column: K): T[K][] {
354
+ const rows = this.all()
355
+ this.reset()
356
+ return rows.map((row) => row[column])
357
+ }
358
+ }