@onreza/prisma-adapter-bun 0.5.0 → 0.5.1
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/package.json +1 -1
- package/src/conversion.ts +170 -6
package/package.json
CHANGED
package/src/conversion.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { type ArgType, type ColumnType, ColumnTypeEnum } from "@prisma/driver-adapter-utils";
|
|
2
2
|
|
|
3
3
|
// Top-level regex constants (biome: useTopLevelRegex)
|
|
4
|
-
const RE_TIMESTAMPTZ_OFFSET = /([+-]\d{2})
|
|
4
|
+
const RE_TIMESTAMPTZ_OFFSET = /([+-]\d{2})(:\d{2})?$/;
|
|
5
5
|
const RE_TIMETZ_STRIP = /[+-]\d{2}(:\d{2})?$/;
|
|
6
|
-
const RE_MONEY_SYMBOL =
|
|
6
|
+
const RE_MONEY_SYMBOL = /\$/g;
|
|
7
7
|
const RE_PG_ESCAPE_BACKSLASH = /\\/g;
|
|
8
8
|
const RE_PG_ESCAPE_QUOTE = /"/g;
|
|
9
|
+
const RE_INT8_STRING = /^-?\d+$/;
|
|
10
|
+
const RE_NUMERIC_STRING = /^-?\d+\.\d+$/;
|
|
11
|
+
const RE_UUID_STRING = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
12
|
+
const RE_TIME_STRING = /^(\d{2}:\d{2}:\d{2})(\.\d+)?([+-]\d{2}(:\d{2})?)?$/;
|
|
13
|
+
const RE_MONEY_STRING = /^-?\$[\d,]+\.\d{2}$/;
|
|
14
|
+
const RE_BIT_STRING = /^[01]+$/;
|
|
9
15
|
|
|
10
16
|
// PostgreSQL OIDs (from pg_type system catalog)
|
|
11
17
|
export const PgOid = {
|
|
@@ -200,7 +206,8 @@ function normalizeTimestamptz(value: unknown): unknown {
|
|
|
200
206
|
}
|
|
201
207
|
if (typeof value === "string") {
|
|
202
208
|
// Normalize various timezone formats to +00:00
|
|
203
|
-
|
|
209
|
+
// Only add :00 if not already present (e.g., "+03" → "+03:00", but "+03:00" stays as is)
|
|
210
|
+
return value.replace(" ", "T").replace(RE_TIMESTAMPTZ_OFFSET, "$1$2");
|
|
204
211
|
}
|
|
205
212
|
return value;
|
|
206
213
|
}
|
|
@@ -224,6 +231,10 @@ function normalizeTime(value: unknown): unknown {
|
|
|
224
231
|
if (value instanceof Date) {
|
|
225
232
|
return formatTime(value);
|
|
226
233
|
}
|
|
234
|
+
// Bun.sql returns TIME as string (e.g., "14:30:00")
|
|
235
|
+
if (typeof value === "string") {
|
|
236
|
+
return value;
|
|
237
|
+
}
|
|
227
238
|
return value;
|
|
228
239
|
}
|
|
229
240
|
|
|
@@ -233,6 +244,8 @@ function normalizeNumeric(value: unknown): unknown {
|
|
|
233
244
|
|
|
234
245
|
function normalizeMoney(value: unknown): unknown {
|
|
235
246
|
const s = String(value);
|
|
247
|
+
// Remove $ symbol, preserving minus sign if present
|
|
248
|
+
// Handles: "$100.50" -> "100.50", "-$100.50" -> "-100.50"
|
|
236
249
|
return s.replace(RE_MONEY_SYMBOL, "");
|
|
237
250
|
}
|
|
238
251
|
|
|
@@ -259,7 +272,19 @@ function normalizeArray(value: unknown, elementNormalizer: (v: unknown) => unkno
|
|
|
259
272
|
|
|
260
273
|
type Normalizer = (value: unknown) => unknown;
|
|
261
274
|
|
|
275
|
+
/**
|
|
276
|
+
* Normalize INT8 string to BigInt.
|
|
277
|
+
* Bun.sql returns BIGINT as string, but Prisma expects BigInt for Int64 columns.
|
|
278
|
+
*/
|
|
279
|
+
function normalizeInt8(value: unknown): unknown {
|
|
280
|
+
if (typeof value === "string") {
|
|
281
|
+
return BigInt(value);
|
|
282
|
+
}
|
|
283
|
+
return value;
|
|
284
|
+
}
|
|
285
|
+
|
|
262
286
|
export const resultNormalizers: Record<number, Normalizer> = {
|
|
287
|
+
[PgOid.INT8]: normalizeInt8,
|
|
263
288
|
[PgOid.NUMERIC]: normalizeNumeric,
|
|
264
289
|
[PgOid.MONEY]: normalizeMoney,
|
|
265
290
|
[PgOid.TIME]: normalizeTime,
|
|
@@ -270,8 +295,14 @@ export const resultNormalizers: Record<number, Normalizer> = {
|
|
|
270
295
|
[PgOid.JSON]: normalizeJson,
|
|
271
296
|
[PgOid.JSONB]: normalizeJson,
|
|
272
297
|
[PgOid.BYTEA]: normalizeBytes,
|
|
298
|
+
// BIT/VARBIT are returned as strings by Bun.sql
|
|
299
|
+
[PgOid.BIT]: String,
|
|
300
|
+
[PgOid.VARBIT]: String,
|
|
301
|
+
// UUID is returned as string by Bun.sql
|
|
302
|
+
[PgOid.UUID]: String,
|
|
273
303
|
|
|
274
304
|
// Array normalizers
|
|
305
|
+
[PgOid.INT8_ARRAY]: (v) => normalizeArray(v, normalizeInt8),
|
|
275
306
|
[PgOid.NUMERIC_ARRAY]: (v) => normalizeArray(v, normalizeNumeric),
|
|
276
307
|
[PgOid.MONEY_ARRAY]: (v) => normalizeArray(v, normalizeMoney),
|
|
277
308
|
[PgOid.TIME_ARRAY]: (v) => normalizeArray(v, normalizeTime),
|
|
@@ -284,18 +315,124 @@ export const resultNormalizers: Record<number, Normalizer> = {
|
|
|
284
315
|
[PgOid.BYTEA_ARRAY]: (v) => normalizeArray(v, normalizeBytes),
|
|
285
316
|
[PgOid.BIT_ARRAY]: (v) => normalizeArray(v, String),
|
|
286
317
|
[PgOid.VARBIT_ARRAY]: (v) => normalizeArray(v, String),
|
|
318
|
+
[PgOid.UUID_ARRAY]: (v) => normalizeArray(v, String),
|
|
287
319
|
[PgOid.XML_ARRAY]: (v) => normalizeArray(v, String),
|
|
288
320
|
};
|
|
289
321
|
|
|
290
322
|
// --- Type inference (fallback when .columns metadata is unavailable) ---
|
|
291
323
|
|
|
324
|
+
// INT32 range: -2,147,483,648 to 2,147,483,647
|
|
325
|
+
const INT32_MIN = -2147483648;
|
|
326
|
+
const INT32_MAX = 2147483647;
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Check if a number fits in INT32 range.
|
|
330
|
+
*/
|
|
331
|
+
function isInt32(num: number): boolean {
|
|
332
|
+
return Number.isInteger(num) && num >= INT32_MIN && num <= INT32_MAX;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Check if a string represents an INT8 (bigint) value.
|
|
337
|
+
* Used to distinguish BIGINT columns (returned as strings by Bun.sql)
|
|
338
|
+
* from plain TEXT columns.
|
|
339
|
+
*/
|
|
340
|
+
function isInt8String(value: string): boolean {
|
|
341
|
+
if (!RE_INT8_STRING.test(value)) return false;
|
|
342
|
+
// Check if value is outside INT32 range or has more than 10 digits
|
|
343
|
+
// (indicating it's likely a BIGINT, not INT4)
|
|
344
|
+
// Note: account for minus sign when checking length
|
|
345
|
+
const signOffset = value.startsWith("-") ? 1 : 0;
|
|
346
|
+
if (value.length - signOffset > 10) return true;
|
|
347
|
+
// For values within 10 digits, check the actual numeric range
|
|
348
|
+
const num = Number(value);
|
|
349
|
+
if (!Number.isFinite(num)) return true; // Very large number, treat as INT8
|
|
350
|
+
return num < INT32_MIN || num > INT32_MAX;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Check if a string represents a NUMERIC/DECIMAL value.
|
|
355
|
+
* Bun.sql returns NUMERIC as string (e.g., "99.99").
|
|
356
|
+
*/
|
|
357
|
+
function isNumericString(value: string): boolean {
|
|
358
|
+
return RE_NUMERIC_STRING.test(value);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Check if a string represents a UUID value.
|
|
363
|
+
* Bun.sql returns UUID as string (e.g., "550e8400-e29b-41d4-a716-446655440000").
|
|
364
|
+
*/
|
|
365
|
+
function isUuidString(value: string): boolean {
|
|
366
|
+
return RE_UUID_STRING.test(value);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Check if a string represents a TIME value (without timezone).
|
|
371
|
+
* Bun.sql returns TIME as string (e.g., "14:30:00").
|
|
372
|
+
*/
|
|
373
|
+
function isTimeString(value: string): boolean {
|
|
374
|
+
return RE_TIME_STRING.test(value) && !value.includes("+") && !value.includes("-");
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Check if a string represents a TIMETZ value (with timezone).
|
|
379
|
+
* Bun.sql returns TIMETZ as string (e.g., "14:30:00+03").
|
|
380
|
+
*/
|
|
381
|
+
function isTimetzString(value: string): boolean {
|
|
382
|
+
return RE_TIME_STRING.test(value) && (value.includes("+") || value.includes("-"));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Check if a string represents a MONEY value.
|
|
387
|
+
* Bun.sql returns MONEY as string (e.g., "$100.50").
|
|
388
|
+
*/
|
|
389
|
+
function isMoneyString(value: string): boolean {
|
|
390
|
+
return RE_MONEY_STRING.test(value);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Check if a string represents a BIT/VARBIT value.
|
|
395
|
+
* Bun.sql returns BIT as string of 0s and 1s (e.g., "10101010").
|
|
396
|
+
* Important: must check before isInt8String to avoid misclassifying
|
|
397
|
+
* bit strings like "101010101010" as BIGINT.
|
|
398
|
+
*/
|
|
399
|
+
function isBitString(value: string): boolean {
|
|
400
|
+
// Must be non-empty, only 0s and 1s
|
|
401
|
+
if (value.length === 0) return false;
|
|
402
|
+
if (!RE_BIT_STRING.test(value)) return false;
|
|
403
|
+
// Exclude values that look like valid integers 2-9 (single digits that are not 0 or 1)
|
|
404
|
+
// Bit strings are typically longer or contain only 0/1
|
|
405
|
+
if (value.length === 1) return value === "0" || value === "1";
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Infer OID for a string element in an array.
|
|
411
|
+
* Separated to reduce complexity of inferArrayOid.
|
|
412
|
+
*/
|
|
413
|
+
function inferStringArrayOid(value: string): number {
|
|
414
|
+
// Order matters: check more specific patterns first
|
|
415
|
+
if (isBitString(value)) return PgOid.BIT_ARRAY;
|
|
416
|
+
if (isUuidString(value)) return PgOid.UUID_ARRAY;
|
|
417
|
+
// TIMETZ must be checked before TIME
|
|
418
|
+
if (isTimetzString(value)) return PgOid.TIMETZ_ARRAY;
|
|
419
|
+
if (isTimeString(value)) return PgOid.TIME_ARRAY;
|
|
420
|
+
if (isMoneyString(value)) return PgOid.MONEY_ARRAY;
|
|
421
|
+
if (isInt8String(value)) return PgOid.INT8_ARRAY;
|
|
422
|
+
if (isNumericString(value)) return PgOid.NUMERIC_ARRAY;
|
|
423
|
+
return PgOid.TEXT_ARRAY;
|
|
424
|
+
}
|
|
425
|
+
|
|
292
426
|
function inferArrayOid(arr: unknown[]): number {
|
|
293
427
|
if (arr.length === 0) return PgOid.TEXT_ARRAY;
|
|
294
428
|
const first = arr.find((v) => v !== null && v !== undefined);
|
|
295
429
|
if (first === undefined) return PgOid.TEXT_ARRAY;
|
|
296
430
|
if (typeof first === "number") {
|
|
297
|
-
|
|
431
|
+
if (!Number.isInteger(first)) return PgOid.FLOAT8_ARRAY;
|
|
432
|
+
return isInt32(first) ? PgOid.INT4_ARRAY : PgOid.INT8_ARRAY;
|
|
298
433
|
}
|
|
434
|
+
if (typeof first === "bigint") return PgOid.INT8_ARRAY;
|
|
435
|
+
if (typeof first === "string") return inferStringArrayOid(first);
|
|
299
436
|
if (typeof first === "boolean") return PgOid.BOOL_ARRAY;
|
|
300
437
|
if (first instanceof Date) return PgOid.TIMESTAMPTZ_ARRAY;
|
|
301
438
|
if (first instanceof Uint8Array || first instanceof Buffer) return PgOid.BYTEA_ARRAY;
|
|
@@ -323,6 +460,31 @@ function isJsonString(value: string): boolean {
|
|
|
323
460
|
}
|
|
324
461
|
}
|
|
325
462
|
|
|
463
|
+
/**
|
|
464
|
+
* Infer OID for a string value.
|
|
465
|
+
* Separated to reduce cognitive complexity of inferOidFromValue.
|
|
466
|
+
*/
|
|
467
|
+
function inferStringOid(value: string): number {
|
|
468
|
+
if (isJsonString(value)) return PgOid.JSON;
|
|
469
|
+
// Order matters: check more specific patterns before generic ones
|
|
470
|
+
// BIT strings (e.g., "10101010") - must check before INT8 to avoid misclassification
|
|
471
|
+
if (isBitString(value)) return PgOid.BIT;
|
|
472
|
+
// UUID strings (e.g., "550e8400-e29b-41d4-a716-446655440000")
|
|
473
|
+
if (isUuidString(value)) return PgOid.UUID;
|
|
474
|
+
// TIMETZ strings (e.g., "14:30:00+03") - must check before TIME
|
|
475
|
+
if (isTimetzString(value)) return PgOid.TIMETZ;
|
|
476
|
+
// TIME strings (e.g., "14:30:00")
|
|
477
|
+
if (isTimeString(value)) return PgOid.TIME;
|
|
478
|
+
// MONEY strings (e.g., "$100.50")
|
|
479
|
+
if (isMoneyString(value)) return PgOid.MONEY;
|
|
480
|
+
// BIGINT columns are returned as strings by Bun.sql
|
|
481
|
+
// We need to detect them to return correct ColumnType (Int64, not Text)
|
|
482
|
+
if (isInt8String(value)) return PgOid.INT8;
|
|
483
|
+
// NUMERIC/DECIMAL columns are returned as strings by Bun.sql
|
|
484
|
+
if (isNumericString(value)) return PgOid.NUMERIC;
|
|
485
|
+
return PgOid.TEXT;
|
|
486
|
+
}
|
|
487
|
+
|
|
326
488
|
/**
|
|
327
489
|
* Infer a PostgreSQL OID from a JavaScript value.
|
|
328
490
|
* Used as a fallback when Bun.sql doesn't expose column metadata.
|
|
@@ -333,13 +495,15 @@ export function inferOidFromValue(value: unknown): number {
|
|
|
333
495
|
if (typeof value === "boolean") return PgOid.BOOL;
|
|
334
496
|
if (typeof value === "bigint") return PgOid.INT8;
|
|
335
497
|
if (typeof value === "number") {
|
|
336
|
-
|
|
498
|
+
if (!Number.isInteger(value)) return PgOid.FLOAT8;
|
|
499
|
+
// Distinguish INT4 vs INT8 based on value range
|
|
500
|
+
return isInt32(value) ? PgOid.INT4 : PgOid.INT8;
|
|
337
501
|
}
|
|
338
502
|
if (value instanceof Date) return PgOid.TIMESTAMPTZ;
|
|
339
503
|
if (value instanceof Uint8Array || value instanceof Buffer) return PgOid.BYTEA;
|
|
340
504
|
if (Array.isArray(value)) return inferArrayOid(value);
|
|
341
505
|
if (typeof value === "object") return PgOid.JSONB;
|
|
342
|
-
if (typeof value === "string"
|
|
506
|
+
if (typeof value === "string") return inferStringOid(value);
|
|
343
507
|
return PgOid.TEXT;
|
|
344
508
|
}
|
|
345
509
|
|