@leonardovida-md/drizzle-neo-duckdb 1.1.1 → 1.1.2

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/src/client.ts CHANGED
@@ -2,6 +2,7 @@ import {
2
2
  listValue,
3
3
  timestampValue,
4
4
  type DuckDBConnection,
5
+ type DuckDBPreparedStatement,
5
6
  type DuckDBValue,
6
7
  } from '@duckdb/node-api';
7
8
  import {
@@ -9,6 +10,7 @@ import {
9
10
  wrapperToNodeApiValue,
10
11
  type AnyDuckDBValueWrapper,
11
12
  } from './value-wrappers.ts';
13
+ import type { PreparedStatementCacheConfig } from './options.ts';
12
14
 
13
15
  export type DuckDBClientLike = DuckDBConnection | DuckDBConnectionPool;
14
16
  export type RowData = Record<string, unknown>;
@@ -25,6 +27,25 @@ export function isPool(
25
27
  return typeof (client as DuckDBConnectionPool).acquire === 'function';
26
28
  }
27
29
 
30
+ export interface ExecuteClientOptions {
31
+ prepareCache?: PreparedStatementCacheConfig;
32
+ }
33
+
34
+ export type ExecuteArraysResult = { columns: string[]; rows: unknown[][] };
35
+
36
+ type MaterializedRows = ExecuteArraysResult;
37
+
38
+ type PreparedCacheEntry = {
39
+ statement: DuckDBPreparedStatement;
40
+ };
41
+
42
+ type PreparedStatementCache = {
43
+ size: number;
44
+ entries: Map<string, PreparedCacheEntry>;
45
+ };
46
+
47
+ const PREPARED_CACHE = Symbol.for('drizzle-duckdb:prepared-cache');
48
+
28
49
  export interface PrepareParamsOptions {
29
50
  rejectStringArrayLiterals?: boolean;
30
51
  warnOnStringArrayLiteral?: () => void;
@@ -49,19 +70,30 @@ export function prepareParams(
49
70
  options: PrepareParamsOptions = {}
50
71
  ): unknown[] {
51
72
  return params.map((param) => {
52
- if (typeof param === 'string') {
53
- const trimmed = param.trim();
54
- if (trimmed && isPgArrayLiteral(trimmed)) {
55
- if (options.rejectStringArrayLiterals) {
56
- throw new Error(
57
- 'Stringified array literals are not supported. Use duckDbList()/duckDbArray() or pass native arrays.'
58
- );
59
- }
60
-
61
- if (options.warnOnStringArrayLiteral) {
62
- options.warnOnStringArrayLiteral();
73
+ if (typeof param === 'string' && param.length > 0) {
74
+ const firstChar = param[0];
75
+ const maybeArrayLiteral =
76
+ firstChar === '{' ||
77
+ firstChar === '[' ||
78
+ firstChar === ' ' ||
79
+ firstChar === '\t';
80
+
81
+ if (maybeArrayLiteral) {
82
+ const trimmed =
83
+ firstChar === '{' || firstChar === '[' ? param : param.trim();
84
+
85
+ if (trimmed && isPgArrayLiteral(trimmed)) {
86
+ if (options.rejectStringArrayLiterals) {
87
+ throw new Error(
88
+ 'Stringified array literals are not supported. Use duckDbList()/duckDbArray() or pass native arrays.'
89
+ );
90
+ }
91
+
92
+ if (options.warnOnStringArrayLiteral) {
93
+ options.warnOnStringArrayLiteral();
94
+ }
95
+ return parsePgArrayLiteral(trimmed);
63
96
  }
64
- return parsePgArrayLiteral(trimmed);
65
97
  }
66
98
  }
67
99
  return param;
@@ -106,14 +138,177 @@ function toNodeApiValue(value: unknown): DuckDBValue {
106
138
  }
107
139
 
108
140
  function deduplicateColumns(columns: string[]): string[] {
109
- const seen: Record<string, number> = {};
110
- return columns.map((col) => {
111
- const count = seen[col] ?? 0;
112
- seen[col] = count + 1;
113
- return count === 0 ? col : `${col}_${count}`;
141
+ const counts = new Map<string, number>();
142
+ let hasDuplicates = false;
143
+
144
+ for (const column of columns) {
145
+ const next = (counts.get(column) ?? 0) + 1;
146
+ counts.set(column, next);
147
+ if (next > 1) {
148
+ hasDuplicates = true;
149
+ break;
150
+ }
151
+ }
152
+
153
+ if (!hasDuplicates) {
154
+ return columns;
155
+ }
156
+
157
+ counts.clear();
158
+ return columns.map((column) => {
159
+ const count = counts.get(column) ?? 0;
160
+ counts.set(column, count + 1);
161
+ return count === 0 ? column : `${column}_${count}`;
114
162
  });
115
163
  }
116
164
 
165
+ function destroyPreparedStatement(entry: PreparedCacheEntry | undefined): void {
166
+ if (!entry) return;
167
+ try {
168
+ entry.statement.destroySync();
169
+ } catch {
170
+ // Ignore cleanup errors
171
+ }
172
+ }
173
+
174
+ function getPreparedCache(
175
+ connection: DuckDBConnection,
176
+ size: number
177
+ ): PreparedStatementCache {
178
+ const store = connection as unknown as Record<
179
+ symbol,
180
+ PreparedStatementCache | undefined
181
+ >;
182
+ const existing = store[PREPARED_CACHE];
183
+ if (existing) {
184
+ existing.size = size;
185
+ return existing;
186
+ }
187
+
188
+ const cache: PreparedStatementCache = { size, entries: new Map() };
189
+ store[PREPARED_CACHE] = cache;
190
+ return cache;
191
+ }
192
+
193
+ function evictOldest(cache: PreparedStatementCache): void {
194
+ const oldest = cache.entries.keys().next();
195
+ if (!oldest.done) {
196
+ const key = oldest.value as string;
197
+ const entry = cache.entries.get(key);
198
+ cache.entries.delete(key);
199
+ destroyPreparedStatement(entry);
200
+ }
201
+ }
202
+
203
+ function evictCacheEntry(cache: PreparedStatementCache, key: string): void {
204
+ const entry = cache.entries.get(key);
205
+ cache.entries.delete(key);
206
+ destroyPreparedStatement(entry);
207
+ }
208
+
209
+ async function getOrPrepareStatement(
210
+ connection: DuckDBConnection,
211
+ query: string,
212
+ cacheConfig: PreparedStatementCacheConfig
213
+ ): Promise<DuckDBPreparedStatement> {
214
+ const cache = getPreparedCache(connection, cacheConfig.size);
215
+ const cached = cache.entries.get(query);
216
+ if (cached) {
217
+ cache.entries.delete(query);
218
+ cache.entries.set(query, cached);
219
+ return cached.statement;
220
+ }
221
+
222
+ const statement = await connection.prepare(query);
223
+ cache.entries.set(query, { statement });
224
+
225
+ while (cache.entries.size > cache.size) {
226
+ evictOldest(cache);
227
+ }
228
+
229
+ return statement;
230
+ }
231
+
232
+ async function materializeResultRows(result: {
233
+ getRowsJS: () => Promise<unknown[][] | undefined>;
234
+ columnNames: () => string[];
235
+ deduplicatedColumnNames?: () => string[];
236
+ }): Promise<MaterializedRows> {
237
+ const rows = (await result.getRowsJS()) ?? [];
238
+ const baseColumns =
239
+ typeof result.deduplicatedColumnNames === 'function'
240
+ ? result.deduplicatedColumnNames()
241
+ : result.columnNames();
242
+ const columns =
243
+ typeof result.deduplicatedColumnNames === 'function'
244
+ ? baseColumns
245
+ : deduplicateColumns(baseColumns);
246
+
247
+ return { columns, rows };
248
+ }
249
+
250
+ async function materializeRows(
251
+ client: DuckDBClientLike,
252
+ query: string,
253
+ params: unknown[],
254
+ options: ExecuteClientOptions = {}
255
+ ): Promise<MaterializedRows> {
256
+ if (isPool(client)) {
257
+ const connection = await client.acquire();
258
+ try {
259
+ return await materializeRows(connection, query, params, options);
260
+ } finally {
261
+ await client.release(connection);
262
+ }
263
+ }
264
+
265
+ const values =
266
+ params.length > 0
267
+ ? (params.map((param) => toNodeApiValue(param)) as DuckDBValue[])
268
+ : undefined;
269
+
270
+ const connection = client as DuckDBConnection;
271
+
272
+ if (options.prepareCache && typeof connection.prepare === 'function') {
273
+ const cache = getPreparedCache(connection, options.prepareCache.size);
274
+ try {
275
+ const statement = await getOrPrepareStatement(
276
+ connection,
277
+ query,
278
+ options.prepareCache
279
+ );
280
+ if (values) {
281
+ statement.bind(values as DuckDBValue[]);
282
+ } else {
283
+ statement.clearBindings?.();
284
+ }
285
+ const result = await statement.run();
286
+ cache.entries.delete(query);
287
+ cache.entries.set(query, { statement });
288
+ return await materializeResultRows(result);
289
+ } catch (error) {
290
+ evictCacheEntry(cache, query);
291
+ throw error;
292
+ }
293
+ }
294
+
295
+ const result = await connection.run(query, values);
296
+ return await materializeResultRows(result);
297
+ }
298
+
299
+ function clearPreparedCache(connection: DuckDBConnection): void {
300
+ const store = connection as unknown as Record<
301
+ symbol,
302
+ PreparedStatementCache | undefined
303
+ >;
304
+ const cache = store[PREPARED_CACHE];
305
+ if (!cache) return;
306
+ for (const entry of cache.entries.values()) {
307
+ destroyPreparedStatement(entry);
308
+ }
309
+ cache.entries.clear();
310
+ }
311
+
117
312
  function mapRowsToObjects(columns: string[], rows: unknown[][]): RowData[] {
118
313
  return rows.map((vals) => {
119
314
  const obj: Record<string, unknown> = {};
@@ -127,6 +322,8 @@ function mapRowsToObjects(columns: string[], rows: unknown[][]): RowData[] {
127
322
  export async function closeClientConnection(
128
323
  connection: DuckDBConnection
129
324
  ): Promise<void> {
325
+ clearPreparedCache(connection);
326
+
130
327
  if ('close' in connection && typeof connection.close === 'function') {
131
328
  await connection.close();
132
329
  return;
@@ -148,35 +345,41 @@ export async function closeClientConnection(
148
345
  export async function executeOnClient(
149
346
  client: DuckDBClientLike,
150
347
  query: string,
151
- params: unknown[]
348
+ params: unknown[],
349
+ options: ExecuteClientOptions = {}
152
350
  ): Promise<RowData[]> {
153
- if (isPool(client)) {
154
- const connection = await client.acquire();
155
- try {
156
- return await executeOnClient(connection, query, params);
157
- } finally {
158
- await client.release(connection);
159
- }
351
+ const { columns, rows } = await materializeRows(
352
+ client,
353
+ query,
354
+ params,
355
+ options
356
+ );
357
+
358
+ if (!rows || rows.length === 0) {
359
+ return [];
160
360
  }
161
361
 
162
- const values =
163
- params.length > 0
164
- ? (params.map((param) => toNodeApiValue(param)) as DuckDBValue[])
165
- : undefined;
166
- const result = await client.run(query, values);
167
- const rows = await result.getRowsJS();
168
- const columns =
169
- // prefer deduplicated names when available (Node API >=1.4.2)
170
- result.deduplicatedColumnNames?.() ?? result.columnNames();
171
- const uniqueColumns = deduplicateColumns(columns);
362
+ return mapRowsToObjects(columns, rows);
363
+ }
172
364
 
173
- return rows ? mapRowsToObjects(uniqueColumns, rows) : [];
365
+ export async function executeArraysOnClient(
366
+ client: DuckDBClientLike,
367
+ query: string,
368
+ params: unknown[],
369
+ options: ExecuteClientOptions = {}
370
+ ): Promise<ExecuteArraysResult> {
371
+ return await materializeRows(client, query, params, options);
174
372
  }
175
373
 
176
374
  export interface ExecuteInBatchesOptions {
177
375
  rowsPerChunk?: number;
178
376
  }
179
377
 
378
+ export interface ExecuteBatchesRawChunk {
379
+ columns: string[];
380
+ rows: unknown[][];
381
+ }
382
+
180
383
  /**
181
384
  * Stream results from DuckDB in batches to avoid fully materializing rows in JS.
182
385
  */
@@ -206,15 +409,19 @@ export async function* executeInBatches(
206
409
  : undefined;
207
410
 
208
411
  const result = await client.stream(query, values);
412
+ const rawColumns =
413
+ typeof result.deduplicatedColumnNames === 'function'
414
+ ? result.deduplicatedColumnNames()
415
+ : result.columnNames();
209
416
  const columns =
210
- // prefer deduplicated names when available (Node API >=1.4.2)
211
- result.deduplicatedColumnNames?.() ?? result.columnNames();
212
- const uniqueColumns = deduplicateColumns(columns);
417
+ typeof result.deduplicatedColumnNames === 'function'
418
+ ? rawColumns
419
+ : deduplicateColumns(rawColumns);
213
420
 
214
421
  let buffer: RowData[] = [];
215
422
 
216
423
  for await (const chunk of result.yieldRowsJs()) {
217
- const objects = mapRowsToObjects(uniqueColumns, chunk);
424
+ const objects = mapRowsToObjects(columns, chunk);
218
425
  for (const row of objects) {
219
426
  buffer.push(row);
220
427
  if (buffer.length >= rowsPerChunk) {
@@ -229,6 +436,59 @@ export async function* executeInBatches(
229
436
  }
230
437
  }
231
438
 
439
+ export async function* executeInBatchesRaw(
440
+ client: DuckDBClientLike,
441
+ query: string,
442
+ params: unknown[],
443
+ options: ExecuteInBatchesOptions = {}
444
+ ): AsyncGenerator<ExecuteBatchesRawChunk, void, void> {
445
+ if (isPool(client)) {
446
+ const connection = await client.acquire();
447
+ try {
448
+ yield* executeInBatchesRaw(connection, query, params, options);
449
+ return;
450
+ } finally {
451
+ await client.release(connection);
452
+ }
453
+ }
454
+
455
+ const rowsPerChunk =
456
+ options.rowsPerChunk && options.rowsPerChunk > 0
457
+ ? options.rowsPerChunk
458
+ : 100_000;
459
+
460
+ const values =
461
+ params.length > 0
462
+ ? (params.map((param) => toNodeApiValue(param)) as DuckDBValue[])
463
+ : undefined;
464
+
465
+ const result = await client.stream(query, values);
466
+ const rawColumns =
467
+ typeof result.deduplicatedColumnNames === 'function'
468
+ ? result.deduplicatedColumnNames()
469
+ : result.columnNames();
470
+ const columns =
471
+ typeof result.deduplicatedColumnNames === 'function'
472
+ ? rawColumns
473
+ : deduplicateColumns(rawColumns);
474
+
475
+ let buffer: unknown[][] = [];
476
+
477
+ for await (const chunk of result.yieldRowsJs()) {
478
+ for (const row of chunk) {
479
+ buffer.push(row as unknown[]);
480
+ if (buffer.length >= rowsPerChunk) {
481
+ yield { columns, rows: buffer };
482
+ buffer = [];
483
+ }
484
+ }
485
+ }
486
+
487
+ if (buffer.length > 0) {
488
+ yield { columns, rows: buffer };
489
+ }
490
+ }
491
+
232
492
  /**
233
493
  * Return columnar results when the underlying node-api exposes an Arrow/columnar API.
234
494
  * Falls back to column-major JS arrays when Arrow is unavailable.
package/src/columns.ts CHANGED
@@ -7,11 +7,13 @@ import {
7
7
  wrapMap,
8
8
  wrapBlob,
9
9
  wrapJson,
10
+ wrapTimestamp,
10
11
  type ListValueWrapper,
11
12
  type ArrayValueWrapper,
12
13
  type MapValueWrapper,
13
14
  type BlobValueWrapper,
14
15
  type JsonValueWrapper,
16
+ type TimestampValueWrapper,
15
17
  } from './value-wrappers-core.ts';
16
18
 
17
19
  type IntColType =
@@ -355,12 +357,35 @@ interface TimestampOptions {
355
357
  withTimezone?: boolean;
356
358
  mode?: TimestampMode;
357
359
  precision?: number;
360
+ bindMode?: 'auto' | 'bind' | 'literal';
361
+ }
362
+
363
+ function shouldBindTimestamp(options: TimestampOptions): boolean {
364
+ const bindMode = options.bindMode ?? 'auto';
365
+ if (bindMode === 'bind') return true;
366
+ if (bindMode === 'literal') return false;
367
+
368
+ const isBun =
369
+ typeof process !== 'undefined' &&
370
+ typeof process.versions?.bun !== 'undefined';
371
+ if (isBun) return false;
372
+
373
+ const forceLiteral =
374
+ typeof process !== 'undefined'
375
+ ? process.env.DRIZZLE_DUCKDB_FORCE_LITERAL_TIMESTAMPS
376
+ : undefined;
377
+
378
+ if (forceLiteral && forceLiteral !== '0') {
379
+ return false;
380
+ }
381
+
382
+ return true;
358
383
  }
359
384
 
360
385
  export const duckDbTimestamp = (name: string, options: TimestampOptions = {}) =>
361
386
  customType<{
362
387
  data: Date | string;
363
- driverData: SQL | string | Date;
388
+ driverData: SQL | string | Date | TimestampValueWrapper;
364
389
  }>({
365
390
  dataType() {
366
391
  if (options.withTimezone) {
@@ -369,14 +394,36 @@ export const duckDbTimestamp = (name: string, options: TimestampOptions = {}) =>
369
394
  const precision = options.precision ? `(${options.precision})` : '';
370
395
  return `TIMESTAMP${precision}`;
371
396
  },
372
- toDriver(value: Date | string) {
373
- // Use SQL literals for timestamps due to Bun/DuckDB bigint binding issues
397
+ toDriver(
398
+ value: Date | string
399
+ ): SQL | string | Date | TimestampValueWrapper {
400
+ if (shouldBindTimestamp(options)) {
401
+ return wrapTimestamp(
402
+ value,
403
+ options.withTimezone ?? false,
404
+ options.precision
405
+ );
406
+ }
407
+
374
408
  const iso = value instanceof Date ? value.toISOString() : value;
375
409
  const normalized = iso.replace('T', ' ').replace('Z', '+00');
376
410
  const typeKeyword = options.withTimezone ? 'TIMESTAMPTZ' : 'TIMESTAMP';
377
411
  return sql.raw(`${typeKeyword} '${normalized}'`);
378
412
  },
379
- fromDriver(value: Date | string | SQL) {
413
+ fromDriver(value: Date | string | SQL | TimestampValueWrapper) {
414
+ if (
415
+ value &&
416
+ typeof value === 'object' &&
417
+ 'kind' in value &&
418
+ (value as TimestampValueWrapper).kind === 'timestamp'
419
+ ) {
420
+ const wrapped = value as TimestampValueWrapper;
421
+ return wrapped.data instanceof Date
422
+ ? wrapped.data
423
+ : typeof wrapped.data === 'number' || typeof wrapped.data === 'bigint'
424
+ ? new Date(Number(wrapped.data) / 1000)
425
+ : wrapped.data;
426
+ }
380
427
  if (options.mode === 'string') {
381
428
  if (value instanceof Date) {
382
429
  return value.toISOString().replace('T', ' ').replace('Z', '+00');
package/src/driver.ts CHANGED
@@ -23,7 +23,11 @@ import { DuckDBSession } from './session.ts';
23
23
  import { DuckDBDialect } from './dialect.ts';
24
24
  import { DuckDBSelectBuilder } from './select-builder.ts';
25
25
  import { aliasFields } from './sql/selection.ts';
26
- import type { ExecuteInBatchesOptions, RowData } from './client.ts';
26
+ import type {
27
+ ExecuteBatchesRawChunk,
28
+ ExecuteInBatchesOptions,
29
+ RowData,
30
+ } from './client.ts';
27
31
  import { closeClientConnection, isPool } from './client.ts';
28
32
  import {
29
33
  createDuckDBConnectionPool,
@@ -31,11 +35,20 @@ import {
31
35
  type DuckDBPoolConfig,
32
36
  type PoolPreset,
33
37
  } from './pool.ts';
38
+ import {
39
+ resolvePrepareCacheOption,
40
+ resolveRewriteArraysOption,
41
+ type PreparedStatementCacheConfig,
42
+ type PrepareCacheOption,
43
+ type RewriteArraysMode,
44
+ type RewriteArraysOption,
45
+ } from './options.ts';
34
46
 
35
47
  export interface PgDriverOptions {
36
48
  logger?: Logger;
37
- rewriteArrays?: boolean;
49
+ rewriteArrays?: RewriteArraysMode;
38
50
  rejectStringArrayLiterals?: boolean;
51
+ prepareCache?: PreparedStatementCacheConfig;
39
52
  }
40
53
 
41
54
  export class DuckDBDriver {
@@ -52,8 +65,9 @@ export class DuckDBDriver {
52
65
  ): DuckDBSession<Record<string, unknown>, TablesRelationalConfig> {
53
66
  return new DuckDBSession(this.client, this.dialect, schema, {
54
67
  logger: this.options.logger,
55
- rewriteArrays: this.options.rewriteArrays,
68
+ rewriteArrays: this.options.rewriteArrays ?? 'auto',
56
69
  rejectStringArrayLiterals: this.options.rejectStringArrayLiterals,
70
+ prepareCache: this.options.prepareCache,
57
71
  });
58
72
  }
59
73
  }
@@ -69,8 +83,9 @@ export interface DuckDBConnectionConfig {
69
83
  export interface DuckDBDrizzleConfig<
70
84
  TSchema extends Record<string, unknown> = Record<string, never>,
71
85
  > extends DrizzleConfig<TSchema> {
72
- rewriteArrays?: boolean;
86
+ rewriteArrays?: RewriteArraysOption;
73
87
  rejectStringArrayLiterals?: boolean;
88
+ prepareCache?: PrepareCacheOption;
74
89
  /** Pool configuration. Use preset name, size config, or false to disable. */
75
90
  pool?: DuckDBPoolConfig | PoolPreset | false;
76
91
  }
@@ -111,6 +126,8 @@ function createFromClient<
111
126
  instance?: DuckDBInstance
112
127
  ): DuckDBDatabase<TSchema, ExtractTablesWithRelations<TSchema>> {
113
128
  const dialect = new DuckDBDialect();
129
+ const rewriteArraysMode = resolveRewriteArraysOption(config.rewriteArrays);
130
+ const prepareCache = resolvePrepareCacheOption(config.prepareCache);
114
131
 
115
132
  const logger =
116
133
  config.logger === true ? new DefaultLogger() : config.logger || undefined;
@@ -131,8 +148,9 @@ function createFromClient<
131
148
 
132
149
  const driver = new DuckDBDriver(client, dialect, {
133
150
  logger,
134
- rewriteArrays: config.rewriteArrays,
151
+ rewriteArrays: rewriteArraysMode,
135
152
  rejectStringArrayLiterals: config.rejectStringArrayLiterals,
153
+ prepareCache,
136
154
  });
137
155
  const session = driver.createSession(schema);
138
156
 
@@ -330,6 +348,13 @@ export class DuckDBDatabase<
330
348
  return this.session.executeBatches<T>(query, options);
331
349
  }
332
350
 
351
+ executeBatchesRaw(
352
+ query: SQL,
353
+ options: ExecuteInBatchesOptions = {}
354
+ ): AsyncGenerator<ExecuteBatchesRawChunk, void, void> {
355
+ return this.session.executeBatchesRaw(query, options);
356
+ }
357
+
333
358
  executeArrow(query: SQL): Promise<unknown> {
334
359
  return this.session.executeArrow(query);
335
360
  }
package/src/index.ts CHANGED
@@ -7,3 +7,4 @@ export * from './client.ts';
7
7
  export * from './pool.ts';
8
8
  export * from './olap.ts';
9
9
  export * from './value-wrappers.ts';
10
+ export * from './options.ts';
package/src/options.ts ADDED
@@ -0,0 +1,40 @@
1
+ export type RewriteArraysMode = 'auto' | 'always' | 'never';
2
+
3
+ export type RewriteArraysOption = boolean | RewriteArraysMode;
4
+
5
+ const DEFAULT_REWRITE_ARRAYS_MODE: RewriteArraysMode = 'auto';
6
+
7
+ export function resolveRewriteArraysOption(
8
+ value?: RewriteArraysOption
9
+ ): RewriteArraysMode {
10
+ if (value === undefined) return DEFAULT_REWRITE_ARRAYS_MODE;
11
+ if (value === true) return 'auto';
12
+ if (value === false) return 'never';
13
+ return value;
14
+ }
15
+
16
+ export type PrepareCacheOption = boolean | number | { size?: number };
17
+
18
+ export interface PreparedStatementCacheConfig {
19
+ size: number;
20
+ }
21
+
22
+ const DEFAULT_PREPARED_CACHE_SIZE = 32;
23
+
24
+ export function resolvePrepareCacheOption(
25
+ option?: PrepareCacheOption
26
+ ): PreparedStatementCacheConfig | undefined {
27
+ if (!option) return undefined;
28
+
29
+ if (option === true) {
30
+ return { size: DEFAULT_PREPARED_CACHE_SIZE };
31
+ }
32
+
33
+ if (typeof option === 'number') {
34
+ const size = Math.max(1, Math.floor(option));
35
+ return { size };
36
+ }
37
+
38
+ const size = option.size ?? DEFAULT_PREPARED_CACHE_SIZE;
39
+ return { size: Math.max(1, Math.floor(size)) };
40
+ }