supalite 0.3.2 โ†’ 0.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.0] - 2025-06-10
4
+
5
+ ### โœจ Added
6
+ - **Configurable `BIGINT` Transformation**: Introduced `bigintTransform` option in `SupaLitePG` constructor to allow users to specify how `BIGINT` database types are transformed (to `'bigint'`, `'string'`, or `'number'`). Default is `'bigint'`. This provides flexibility and helps mitigate `JSON.stringify` errors with native `BigInt` objects. (See [docs/changelog/2025-06-10-bigint-handling-enhancement.md](docs/changelog/2025-06-10-bigint-handling-enhancement.md) for details)
7
+
8
+ ### ๐Ÿ›  Changed
9
+ - The internal `Json` type in `src/types.ts` now explicitly includes `bigint`, with documentation clarifying user responsibility for `JSON.stringify` handling.
10
+ - Improved client initialization logging for `bigintTransform` mode when `verbose` is enabled.
11
+
3
12
  ## [0.1.8] - 2025-03-04
4
13
 
5
14
  ### Fixed
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # SupaLite
2
2
 
3
- [![npm version](https://img.shields.io/badge/version-0.3.2-blue.svg)](https://www.npmjs.com/package/supalite)
3
+ [![npm version](https://img.shields.io/badge/version-0.3.3-blue.svg)](https://www.npmjs.com/package/supalite)
4
4
 
5
5
  ๊ฐ€๋ณ๊ณ  ํšจ์œจ์ ์ธ PostgreSQL ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. Supabase์™€ ๋™์ผํ•œ API๋ฅผ ์ œ๊ณตํ•˜๋ฉด์„œ๋„ ๋” ๊ฐ€๋ณ๊ณ  ๋น ๋ฅธ ๊ตฌํ˜„์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
6
6
 
@@ -40,7 +40,9 @@ const client = new SupaLitePG<Database>({
40
40
  host: 'localhost',
41
41
  database: 'testdb',
42
42
  port: 5432,
43
- ssl: false
43
+ ssl: false,
44
+ // bigintTransform: 'string', // BIGINT ํƒ€์ž…์„ ๋ฌธ์ž์—ด๋กœ ๋ฐ›๊ธฐ (๊ธฐ๋ณธ๊ฐ’: 'bigint')
45
+ // verbose: true // ์ƒ์„ธ ๋กœ๊ทธ ์ถœ๋ ฅ
44
46
  });
45
47
 
46
48
  // ๋˜๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์‚ฌ์šฉ
@@ -104,7 +106,9 @@ const client = new SupaLitePG<Database>({
104
106
  host: 'localhost',
105
107
  database: 'testdb',
106
108
  port: 5432,
107
- ssl: false
109
+ ssl: false,
110
+ // bigintTransform: 'string', // BIGINT ํƒ€์ž…์„ ๋ฌธ์ž์—ด๋กœ ๋ฐ›๊ธฐ (๊ธฐ๋ณธ๊ฐ’: 'bigint')
111
+ // verbose: true // ์ƒ์„ธ ๋กœ๊ทธ ์ถœ๋ ฅ
108
112
  });
109
113
  ```
110
114
 
@@ -231,14 +235,28 @@ const { data, error } = await client
231
235
  ]);
232
236
 
233
237
  // JSONB ๋ฐฐ์—ด ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ
238
+ // ์ค‘์š”: JSON/JSONB ์ปฌ๋Ÿผ์— ๋ฐฐ์—ด์„ ์‚ฝ์ž…/์—…๋ฐ์ดํŠธํ•  ๊ฒฝ์šฐ, ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ JSON.stringify()๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
239
+ // SupaLite๋Š” ์ผ๋ฐ˜ ๊ฐ์ฒด์— ๋Œ€ํ•ด์„œ๋งŒ ์ž๋™ stringify๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
240
+ const myJsonArray = ['tag1', 2025, { active: true }];
234
241
  const { data: jsonData, error: jsonError } = await client
235
242
  .from('your_jsonb_table') // 'your_jsonb_table'์„ ์‹ค์ œ ํ…Œ์ด๋ธ”๋ช…์œผ๋กœ ๋ณ€๊ฒฝ
236
243
  .insert({
237
- metadata_array: ['tag1', 2025, { active: true }]
244
+ metadata_array: JSON.stringify(myJsonArray) // ๋ฐฐ์—ด์€ ์ง์ ‘ stringify
238
245
  })
239
246
  .select('metadata_array')
240
247
  .single();
241
248
 
249
+ // ๋„ค์ดํ‹ฐ๋ธŒ ๋ฐฐ์—ด(TEXT[], INTEGER[] ๋“ฑ) ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ
250
+ // ์ด ๊ฒฝ์šฐ, JavaScript ๋ฐฐ์—ด์„ ์ง์ ‘ ์ „๋‹ฌํ•˜๋ฉด pg ๋“œ๋ผ์ด๋ฒ„๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
251
+ const { data: nativeArrayData, error: nativeArrayError } = await client
252
+ .from('your_native_array_table') // ์‹ค์ œ ํ…Œ์ด๋ธ”๋ช…์œผ๋กœ ๋ณ€๊ฒฝ
253
+ .insert({
254
+ tags_column: ['tech', 'event'], // TEXT[] ์ปฌ๋Ÿผ ์˜ˆ์‹œ
255
+ scores_column: [100, 95, 88] // INTEGER[] ์ปฌ๋Ÿผ ์˜ˆ์‹œ
256
+ })
257
+ .select('tags_column, scores_column')
258
+ .single();
259
+
242
260
  // ๋‹ค๋ฅธ ์Šคํ‚ค๋งˆ ์‚ฌ์šฉ
243
261
  const { data, error } = await client
244
262
  .from('users', 'other_schema')
@@ -298,8 +316,31 @@ DB_NAME=your_db_name
298
316
  DB_PASS=your_db_password
299
317
  DB_PORT=5432
300
318
  DB_SSL=true
319
+ # SUPALITE_VERBOSE=true # ์ƒ์„ธ ๋กœ๊ทธ ์ถœ๋ ฅ ํ™œ์„ฑํ™”
301
320
  ```
302
321
 
322
+ ### SupaLitePG ์ƒ์„ฑ์ž ์˜ต์…˜
323
+
324
+ `SupaLitePG` ์ƒ์„ฑ์ž๋Š” ๋‹ค์Œ ์˜ต์…˜์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
325
+
326
+ - `connectionString?: string`: PostgreSQL ์—ฐ๊ฒฐ ๋ฌธ์ž์—ด (์˜ˆ: `postgresql://user:password@host:port/database`). ์ œ๊ณต๋˜๋ฉด ๋‹ค๋ฅธ ์—ฐ๊ฒฐ ๋งค๊ฐœ๋ณ€์ˆ˜๋ณด๋‹ค ์šฐ์„ ํ•ฉ๋‹ˆ๋‹ค.
327
+ - `user?: string`: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‚ฌ์šฉ์ž ์ด๋ฆ„ (ํ™˜๊ฒฝ ๋ณ€์ˆ˜: `DB_USER`).
328
+ - `host?: string`: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ˜ธ์ŠคํŠธ (ํ™˜๊ฒฝ ๋ณ€์ˆ˜: `DB_HOST`).
329
+ - `database?: string`: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ด๋ฆ„ (ํ™˜๊ฒฝ ๋ณ€์ˆ˜: `DB_NAME`).
330
+ - `password?: string`: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋น„๋ฐ€๋ฒˆํ˜ธ (ํ™˜๊ฒฝ ๋ณ€์ˆ˜: `DB_PASS`).
331
+ - `port?: number`: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํฌํŠธ (๊ธฐ๋ณธ๊ฐ’: 5432, ํ™˜๊ฒฝ ๋ณ€์ˆ˜: `DB_PORT`).
332
+ - `ssl?: boolean`: SSL ์—ฐ๊ฒฐ ์‚ฌ์šฉ ์—ฌ๋ถ€ (๊ธฐ๋ณธ๊ฐ’: `false`, ํ™˜๊ฒฝ ๋ณ€์ˆ˜: `DB_SSL`).
333
+ - `schema?: string`: ๊ธฐ๋ณธ ์Šคํ‚ค๋งˆ (๊ธฐ๋ณธ๊ฐ’: `'public'`).
334
+ - `verbose?: boolean`: ์ƒ์„ธ ๋กœ๊ทธ ์ถœ๋ ฅ ์—ฌ๋ถ€ (๊ธฐ๋ณธ๊ฐ’: `false`, ํ™˜๊ฒฝ ๋ณ€์ˆ˜: `SUPALITE_VERBOSE`).
335
+ - `bigintTransform?: 'bigint' | 'string' | 'number'`:
336
+ - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ `BIGINT` ํƒ€์ž…์„ ์–ด๋–ป๊ฒŒ ๋ณ€ํ™˜ํ• ์ง€ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
337
+ - `'bigint'` (๊ธฐ๋ณธ๊ฐ’): JavaScript์˜ ๋„ค์ดํ‹ฐ๋ธŒ `BigInt` ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ `Json` ํƒ€์ž…์€ `bigint`๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ๋„๋ก ์ •์˜๋˜์–ด ์žˆ์œผ๋‚˜, ํ‘œ์ค€ `JSON.stringify()` ํ•จ์ˆ˜๋Š” `BigInt`๋ฅผ ์ง์ ‘ ์ฒ˜๋ฆฌํ•˜์ง€ ๋ชปํ•˜๋ฏ€๋กœ `TypeError`๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. `BigInt` ๊ฐ’์„ JSON์œผ๋กœ ์ง๋ ฌํ™”ํ•˜๋ ค๋ฉด ์‚ฌ์šฉ์ž ์ •์˜ replacer ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์‚ฌ์ „์— ๋ฌธ์ž์—ด ๋“ฑ์œผ๋กœ ๋ณ€ํ™˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
338
+ - `'string'`: ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. JSON ์ง๋ ฌํ™”์— ์•ˆ์ „ํ•˜๋ฉฐ, `BigInt`์˜ ์ „์ฒด ์ •๋ฐ€๋„๋ฅผ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.
339
+ - `'number'`: JavaScript์˜ `Number` ํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ’์ด `Number.MAX_SAFE_INTEGER` (๋˜๋Š” `Number.MIN_SAFE_INTEGER`)๋ฅผ ์ดˆ๊ณผํ•˜๋ฉด ์ •๋ฐ€๋„ ์†์‹ค์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ `verbose: true` ์„ค์ • ์‹œ ๊ฒฝ๊ณ  ๋กœ๊ทธ๊ฐ€ ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค. JSON ์ง๋ ฌํ™”์—๋Š” ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.
340
+
341
+ ### Json ํƒ€์ž…๊ณผ BigInt
342
+ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๋‚ด๋ถ€ `Json` ํƒ€์ž… ์ •์˜๋Š” `bigint`๋ฅผ ํฌํ•จํ•˜์—ฌ TypeScript ์ฝ”๋“œ ๋‚ด์—์„œ `BigInt` ๊ฐ’์„ ๋ช…์‹œ์ ์œผ๋กœ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ `Json` ํƒ€์ž…์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œ์ค€ `JSON.stringify()`๋กœ ์ง๋ ฌํ™”ํ•  ๋•Œ, ํฌํ•จ๋œ `BigInt` ๊ฐ์ฒด๋Š” ํŠน๋ณ„ํ•œ ์ฒ˜๋ฆฌ(์˜ˆ: ์‚ฌ์šฉ์ž ์ •์˜ replacer ํ•จ์ˆ˜ ๋˜๋Š” ์‚ฌ์ „ ๋ฌธ์ž์—ด ๋ณ€ํ™˜)๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. `bigintTransform` ์˜ต์…˜์„ `'string'` ๋˜๋Š” `'number'`๋กœ ์„ค์ •ํ•˜๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์กฐํšŒ ์‹œ์ ๋ถ€ํ„ฐ JSON ์ง๋ ฌํ™”์— ์•ˆ์ „ํ•œ ํ˜•ํƒœ๋กœ `BIGINT` ๊ฐ’์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
343
+
303
344
  ## ์‘๋‹ต ํ˜•์‹
304
345
 
305
346
  ๋ชจ๋“  ์ฟผ๋ฆฌ ๋ฉ”์†Œ๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•์‹์˜ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค:
@@ -26,6 +26,9 @@ export declare class SupaLitePG<T extends {
26
26
  private client;
27
27
  private isTransaction;
28
28
  private schema;
29
+ private schemaCache;
30
+ verbose: boolean;
31
+ private bigintTransform;
29
32
  constructor(config?: SupaliteConfig);
30
33
  begin(): Promise<void>;
31
34
  commit(): Promise<void>;
@@ -37,6 +40,7 @@ export declare class SupaLitePG<T extends {
37
40
  from<S extends keyof T, K extends TableOrViewName<T, S>>(table: K, schema: S): QueryBuilder<T, S, K> & Promise<QueryResult<Row<T, S, K>>> & {
38
41
  single(): Promise<SingleQueryResult<Row<T, S, K>>>;
39
42
  };
43
+ getColumnPgType(dbSchema: string, tableName: string, columnName: string): Promise<string | undefined>;
40
44
  rpc(procedureName: string, params?: Record<string, any>): Promise<{
41
45
  data: any;
42
46
  error: PostgresError | null;
@@ -1,68 +1,88 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.supalitePg = exports.SupaLitePG = void 0;
4
- const pg_1 = require("pg");
4
+ const pg_1 = require("pg"); // PoolConfig ์ถ”๊ฐ€
5
5
  const query_builder_1 = require("./query-builder");
6
6
  const errors_1 = require("./errors");
7
7
  const dotenv_1 = require("dotenv");
8
8
  // .env ํŒŒ์ผ ๋กœ๋“œ
9
9
  (0, dotenv_1.config)();
10
- // bigint ํƒ€์ž…(OID: 20)์„ JavaScript์˜ BigInt๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ํŒŒ์„œ ๋“ฑ๋ก
11
- pg_1.types.setTypeParser(20, function (val) {
12
- return val === null ? null : BigInt(val);
13
- });
14
10
  class SupaLitePG {
15
11
  constructor(config) {
16
12
  this.client = null;
17
13
  this.isTransaction = false;
14
+ this.schemaCache = new Map(); // schemaName.tableName -> Map<columnName, pgDataType>
15
+ this.verbose = false;
16
+ this.verbose = config?.verbose || process.env.SUPALITE_VERBOSE === 'true' || false;
17
+ this.bigintTransform = config?.bigintTransform || 'bigint'; // ๊ธฐ๋ณธ๊ฐ’ 'bigint'
18
+ if (this.verbose) {
19
+ console.log(`[SupaLite VERBOSE] BIGINT transform mode set to: '${this.bigintTransform}'`);
20
+ }
21
+ // ํƒ€์ž… ํŒŒ์„œ ์„ค์ •
22
+ switch (this.bigintTransform) {
23
+ case 'string':
24
+ pg_1.types.setTypeParser(20, (val) => val === null ? null : val); // pg๋Š” ์ด๋ฏธ ๋ฌธ์ž์—ด๋กœ ์คŒ
25
+ break;
26
+ case 'number':
27
+ pg_1.types.setTypeParser(20, (val) => {
28
+ if (val === null)
29
+ return null;
30
+ const num = Number(val);
31
+ if (this.verbose && (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER)) {
32
+ console.warn(`[SupaLite VERBOSE WARNING] BIGINT value ${val} converted to Number might lose precision. ` +
33
+ `Max safe integer is ${Number.MAX_SAFE_INTEGER}.`);
34
+ }
35
+ return num;
36
+ });
37
+ break;
38
+ case 'bigint':
39
+ default: // ๊ธฐ๋ณธ๊ฐ’ ๋ฐ 'bigint' ๋ช…์‹œ ์‹œ
40
+ pg_1.types.setTypeParser(20, (val) => val === null ? null : BigInt(val));
41
+ break;
42
+ }
43
+ let poolConfigOptions = {};
18
44
  // connectionString์ด ์ œ๊ณต๋˜๋ฉด ์ด๋ฅผ ์šฐ์„  ์‚ฌ์šฉ
19
45
  if (config?.connectionString || process.env.DB_CONNECTION) {
20
46
  try {
21
47
  const connectionString = config?.connectionString || process.env.DB_CONNECTION || '';
22
- // ๊ฐ„๋‹จํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ (postgresql:// ๋กœ ์‹œ์ž‘ํ•˜๋Š”์ง€)
23
48
  if (!connectionString.startsWith('postgresql://')) {
24
49
  throw new Error('Invalid PostgreSQL connection string format. Must start with postgresql://');
25
50
  }
26
- this.pool = new pg_1.Pool({
27
- connectionString,
28
- ssl: config?.ssl !== undefined ? config.ssl : process.env.DB_SSL === 'true'
29
- });
30
- // ์Šคํ‚ค๋งˆ ์„ค์ •
31
- this.schema = config?.schema || 'public';
32
- // ๋””๋ฒ„๊ทธ์šฉ ๋กœ๊ทธ
33
- console.log('Database connection using connection string');
34
- // Error handling
35
- this.pool.on('error', (err) => {
36
- console.error('Unexpected error on idle client', err);
37
- process.exit(-1);
38
- });
39
- return;
51
+ poolConfigOptions.connectionString = connectionString;
52
+ poolConfigOptions.ssl = config?.ssl !== undefined ? config.ssl : process.env.DB_SSL === 'true';
53
+ if (this.verbose) {
54
+ console.log('[SupaLite VERBOSE] Database connection using connection string');
55
+ }
40
56
  }
41
57
  catch (err) {
42
- console.error('Database connection error:', err.message);
58
+ console.error('[SupaLite ERROR] Database connection error:', err.message);
43
59
  throw new Error(`Failed to establish database connection: ${err.message}`);
44
60
  }
45
61
  }
46
- // ๊ธฐ์กด ์ฝ”๋“œ: ๊ฐœ๋ณ„ ๋งค๊ฐœ๋ณ€์ˆ˜ ์‚ฌ์šฉ
47
- const poolConfig = {
48
- user: config?.user || process.env.DB_USER,
49
- host: config?.host || process.env.DB_HOST,
50
- database: config?.database || process.env.DB_NAME,
51
- password: config?.password || process.env.DB_PASS,
52
- port: config?.port || Number(process.env.DB_PORT) || 5432,
53
- ssl: config?.ssl || process.env.DB_SSL === 'true',
54
- };
62
+ else {
63
+ // ๊ธฐ์กด ์ฝ”๋“œ: ๊ฐœ๋ณ„ ๋งค๊ฐœ๋ณ€์ˆ˜ ์‚ฌ์šฉ
64
+ poolConfigOptions = {
65
+ user: config?.user || process.env.DB_USER,
66
+ host: config?.host || process.env.DB_HOST,
67
+ database: config?.database || process.env.DB_NAME,
68
+ password: config?.password || process.env.DB_PASS,
69
+ port: config?.port || Number(process.env.DB_PORT) || 5432,
70
+ ssl: config?.ssl !== undefined ? config.ssl : process.env.DB_SSL === 'true', // ssl ์„ค์ • ๋ช…์‹œ์  ์ฒ˜๋ฆฌ
71
+ };
72
+ if (this.verbose) {
73
+ console.log('[SupaLite VERBOSE] Database connection using individual parameters:', {
74
+ ...poolConfigOptions,
75
+ password: '********'
76
+ });
77
+ }
78
+ }
79
+ this.pool = new pg_1.Pool(poolConfigOptions);
55
80
  this.schema = config?.schema || 'public';
56
- // ๋””๋ฒ„๊ทธ์šฉ ๋กœ๊ทธ (๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ์ œ์™ธ)
57
- console.log('Database connection config:', {
58
- ...poolConfig,
59
- password: '********'
60
- });
61
- this.pool = new pg_1.Pool(poolConfig);
62
81
  // Error handling
63
82
  this.pool.on('error', (err) => {
64
- console.error('Unexpected error on idle client', err);
65
- process.exit(-1);
83
+ console.error('[SupaLite ERROR] Unexpected error on idle client', err);
84
+ // Consider if process.exit is too drastic for a library. Maybe re-throw or emit an event.
85
+ // process.exit(-1);
66
86
  });
67
87
  }
68
88
  // ํŠธ๋žœ์žญ์…˜ ์‹œ์ž‘
@@ -105,7 +125,58 @@ class SupaLitePG {
105
125
  }
106
126
  }
107
127
  from(table, schema) {
108
- return new query_builder_1.QueryBuilder(this.pool, table, schema || 'public');
128
+ // QueryBuilder constructor will be updated to accept these arguments
129
+ return new query_builder_1.QueryBuilder(// Use 'as any' temporarily if QueryBuilder constructor not yet updated
130
+ this.pool, this, // Pass the SupaLitePG instance itself
131
+ table, schema || 'public', this.verbose // Pass verbose setting
132
+ );
133
+ }
134
+ async getColumnPgType(dbSchema, tableName, columnName) {
135
+ const tableKey = `${dbSchema}.${tableName}`;
136
+ if (this.verbose)
137
+ console.log(`[SupaLite VERBOSE] getColumnPgType called for ${tableKey}.${columnName}`);
138
+ let tableInfo = this.schemaCache.get(tableKey);
139
+ if (!tableInfo) {
140
+ if (this.verbose)
141
+ console.log(`[SupaLite VERBOSE] Cache miss for table ${tableKey}. Querying information_schema.`);
142
+ try {
143
+ const query = `
144
+ SELECT column_name, data_type
145
+ FROM information_schema.columns
146
+ WHERE table_schema = $1 AND table_name = $2;
147
+ `;
148
+ // Use a temporary client from the pool for this schema query
149
+ // if not in a transaction, or use the transaction client if in one.
150
+ const activeClient = this.isTransaction && this.client ? this.client : await this.pool.connect();
151
+ try {
152
+ const result = await activeClient.query(query, [dbSchema, tableName]);
153
+ tableInfo = new Map();
154
+ result.rows.forEach((row) => {
155
+ tableInfo.set(row.column_name, row.data_type.toLowerCase());
156
+ });
157
+ this.schemaCache.set(tableKey, tableInfo);
158
+ if (this.verbose)
159
+ console.log(`[SupaLite VERBOSE] Cached schema for ${tableKey}:`, tableInfo);
160
+ }
161
+ finally {
162
+ if (!(this.isTransaction && this.client)) { // Only release if it's a temp client not managed by transaction
163
+ activeClient.release(); // Cast to any if 'release' is not on type PoolClient from transaction
164
+ }
165
+ }
166
+ }
167
+ catch (err) {
168
+ console.error(`[SupaLite ERROR] Failed to query information_schema for ${tableKey}:`, err.message);
169
+ return undefined;
170
+ }
171
+ }
172
+ else {
173
+ if (this.verbose)
174
+ console.log(`[SupaLite VERBOSE] Cache hit for table ${tableKey}.`);
175
+ }
176
+ const pgType = tableInfo?.get(columnName);
177
+ if (this.verbose)
178
+ console.log(`[SupaLite VERBOSE] pgType for ${tableKey}.${columnName}: ${pgType}`);
179
+ return pgType;
109
180
  }
110
181
  async rpc(procedureName, params = {}) {
111
182
  try {
@@ -1,4 +1,5 @@
1
1
  import { Pool } from 'pg';
2
+ import type { SupaLitePG } from './postgres-client';
2
3
  import { TableName, TableOrViewName, QueryResult, SingleQueryResult, DatabaseSchema, SchemaName, Row, InsertRow, UpdateRow } from './types';
3
4
  export declare class QueryBuilder<T extends DatabaseSchema, S extends SchemaName<T> = 'public', K extends TableOrViewName<T, S> = TableOrViewName<T, S>> implements Promise<QueryResult<Row<T, S, K>> | SingleQueryResult<Row<T, S, K>>> {
4
5
  private pool;
@@ -19,7 +20,10 @@ export declare class QueryBuilder<T extends DatabaseSchema, S extends SchemaName
19
20
  private insertData?;
20
21
  private updateData?;
21
22
  private conflictTarget?;
22
- constructor(pool: Pool, table: K, schema?: S);
23
+ private client;
24
+ private verbose;
25
+ constructor(pool: Pool, client: SupaLitePG<T>, // Accept SupaLitePG instance
26
+ table: K, schema?: S, verbose?: boolean);
23
27
  then<TResult1 = QueryResult<Row<T, S, K>> | SingleQueryResult<Row<T, S, K>>, TResult2 = never>(onfulfilled?: ((value: QueryResult<Row<T, S, K>> | SingleQueryResult<Row<T, S, K>>) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
24
28
  catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<QueryResult<Row<T, S, K>> | SingleQueryResult<Row<T, S, K>> | TResult>;
25
29
  finally(onfinally?: (() => void) | null): Promise<QueryResult<Row<T, S, K>> | SingleQueryResult<Row<T, S, K>>>;
@@ -4,7 +4,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.QueryBuilder = void 0;
5
5
  const errors_1 = require("./errors");
6
6
  class QueryBuilder {
7
- constructor(pool, table, schema = 'public') {
7
+ constructor(pool, client, // Accept SupaLitePG instance
8
+ table, schema = 'public', verbose = false // Accept verbose setting
9
+ ) {
8
10
  this.pool = pool;
9
11
  this[_a] = 'QueryBuilder';
10
12
  this.selectColumns = null;
@@ -14,8 +16,11 @@ class QueryBuilder {
14
16
  this.whereValues = [];
15
17
  this.singleMode = null;
16
18
  this.queryType = 'SELECT';
19
+ this.verbose = false;
20
+ this.client = client;
17
21
  this.table = table;
18
22
  this.schema = schema;
23
+ this.verbose = verbose;
19
24
  }
20
25
  then(onfulfilled, onrejected) {
21
26
  return this.execute().then(onfulfilled, onrejected);
@@ -195,7 +200,7 @@ class QueryBuilder {
195
200
  }
196
201
  return ' WHERE ' + conditions.join(' AND ');
197
202
  }
198
- buildQuery() {
203
+ async buildQuery() {
199
204
  let query = '';
200
205
  let values = [];
201
206
  let insertColumns = [];
@@ -223,32 +228,45 @@ class QueryBuilder {
223
228
  if (rows.length === 0)
224
229
  throw new Error('Empty array provided for insert');
225
230
  insertColumns = Object.keys(rows[0]);
226
- // Process each row for potential JSON stringification
227
- const processedRowsValues = rows.map(row => Object.values(row).map(val => {
228
- if (typeof val === 'bigint') {
229
- return val.toString();
230
- }
231
- if (Array.isArray(val) || (val !== null && typeof val === 'object' && !(val instanceof Date))) {
232
- return JSON.stringify(val);
231
+ const processedRowsValuesPromises = rows.map(async (row) => {
232
+ const rowValues = [];
233
+ for (const colName of insertColumns) { // Ensure order of values matches order of columns
234
+ const val = row[colName];
235
+ const pgType = await this.client.getColumnPgType(String(this.schema), String(this.table), colName);
236
+ if (typeof val === 'bigint') {
237
+ rowValues.push(val.toString());
238
+ }
239
+ else if ((pgType === 'json' || pgType === 'jsonb') &&
240
+ (Array.isArray(val) || (val !== null && typeof val === 'object' && !(val instanceof Date)))) {
241
+ rowValues.push(JSON.stringify(val));
242
+ }
243
+ else {
244
+ rowValues.push(val);
245
+ }
233
246
  }
234
- return val;
235
- }));
236
- values = processedRowsValues.flat();
247
+ return rowValues;
248
+ });
249
+ const processedRowsValuesArrays = await Promise.all(processedRowsValuesPromises);
250
+ values = processedRowsValuesArrays.flat();
237
251
  const placeholders = rows.map((_, i) => `(${insertColumns.map((_, j) => `$${i * insertColumns.length + j + 1}`).join(',')})`).join(',');
238
252
  query = `INSERT INTO ${schemaTable} ("${insertColumns.join('","')}") VALUES ${placeholders}`;
239
253
  }
240
254
  else {
241
255
  const insertData = this.insertData;
242
256
  insertColumns = Object.keys(insertData);
243
- values = Object.values(insertData).map(val => {
257
+ const valuePromises = insertColumns.map(async (colName) => {
258
+ const val = insertData[colName];
259
+ const pgType = await this.client.getColumnPgType(String(this.schema), String(this.table), colName);
244
260
  if (typeof val === 'bigint') {
245
261
  return val.toString();
246
262
  }
247
- if (Array.isArray(val) || (val !== null && typeof val === 'object' && !(val instanceof Date))) {
263
+ if ((pgType === 'json' || pgType === 'jsonb') &&
264
+ (Array.isArray(val) || (val !== null && typeof val === 'object' && !(val instanceof Date)))) {
248
265
  return JSON.stringify(val);
249
266
  }
250
267
  return val;
251
268
  });
269
+ values = await Promise.all(valuePromises);
252
270
  const insertPlaceholders = values.map((_, i) => `$${i + 1}`).join(',');
253
271
  query = `INSERT INTO ${schemaTable} ("${insertColumns.join('","')}") VALUES (${insertPlaceholders})`;
254
272
  }
@@ -276,16 +294,21 @@ class QueryBuilder {
276
294
  if ('updated_at' in updateData && !updateData.updated_at) {
277
295
  updateData.updated_at = now;
278
296
  }
279
- const processedUpdateValues = Object.values(updateData).map(val => {
297
+ const updateColumns = Object.keys(updateData);
298
+ const processedUpdateValuesPromises = updateColumns.map(async (colName) => {
299
+ const val = updateData[colName];
300
+ const pgType = await this.client.getColumnPgType(String(this.schema), String(this.table), colName);
280
301
  if (typeof val === 'bigint') {
281
302
  return val.toString();
282
303
  }
283
- if (Array.isArray(val) || (val !== null && typeof val === 'object' && !(val instanceof Date))) {
304
+ if ((pgType === 'json' || pgType === 'jsonb') &&
305
+ (Array.isArray(val) || (val !== null && typeof val === 'object' && !(val instanceof Date)))) {
284
306
  return JSON.stringify(val);
285
307
  }
286
308
  return val;
287
309
  });
288
- const setColumns = Object.keys(updateData).map((key, index) => `"${String(key)}" = $${index + 1}`);
310
+ const processedUpdateValues = await Promise.all(processedUpdateValuesPromises);
311
+ const setColumns = updateColumns.map((key, index) => `"${String(key)}" = $${index + 1}`);
289
312
  query = `UPDATE ${schemaTable} SET ${setColumns.join(', ')}`;
290
313
  values = [...processedUpdateValues, ...this.whereValues];
291
314
  query += this.buildWhereClause(processedUpdateValues);
@@ -316,7 +339,11 @@ class QueryBuilder {
316
339
  }
317
340
  async execute() {
318
341
  try {
319
- const { query, values } = this.buildQuery();
342
+ const { query, values } = await this.buildQuery(); // await buildQuery
343
+ if (this.verbose) {
344
+ console.log('[SupaLite VERBOSE] SQL:', query);
345
+ console.log('[SupaLite VERBOSE] Values:', values);
346
+ }
320
347
  const result = await this.pool.query(query, values);
321
348
  if (this.queryType === 'DELETE' && !this.shouldReturnData()) {
322
349
  return {
@@ -392,6 +419,9 @@ class QueryBuilder {
392
419
  };
393
420
  }
394
421
  catch (err) {
422
+ if (this.verbose) {
423
+ console.error('[SupaLite VERBOSE] Error:', err);
424
+ }
395
425
  return {
396
426
  data: [],
397
427
  error: new errors_1.PostgresError(err.message),
package/dist/types.d.ts CHANGED
@@ -44,8 +44,10 @@ export type UpdateRow<T extends DatabaseSchema, S extends SchemaName<T>, K exten
44
44
  updated_at?: string | null;
45
45
  } : never;
46
46
  export type EnumType<T extends DatabaseSchema, S extends SchemaName<T>, E extends keyof NonNullable<T[S]['Enums']>> = NonNullable<T[S]['Enums']>[E];
47
+ export type BigintTransformType = 'bigint' | 'string' | 'number';
47
48
  export interface SupaliteConfig {
48
49
  connectionString?: string;
50
+ bigintTransform?: BigintTransformType;
49
51
  user?: string;
50
52
  host?: string;
51
53
  database?: string;
@@ -53,6 +55,7 @@ export interface SupaliteConfig {
53
55
  port?: number;
54
56
  ssl?: boolean;
55
57
  schema?: string;
58
+ verbose?: boolean;
56
59
  }
57
60
  export type QueryType = 'SELECT' | 'INSERT' | 'UPDATE' | 'DELETE' | 'UPSERT';
58
61
  export interface QueryOptions {
@@ -0,0 +1,35 @@
1
+ # Changelog - 2025-06-10: Enhanced BIGINT Handling
2
+
3
+ ## Summary
4
+ This update introduces a flexible mechanism for handling `BIGINT` data types retrieved from PostgreSQL, addressing potential `TypeError` issues during JSON serialization and providing users with more control over type conversion.
5
+
6
+ ## Changes
7
+
8
+ ### โœจ New Features
9
+ * **Configurable `BIGINT` Transformation**:
10
+ * Added a new `bigintTransform` option to the `SupaLitePG` constructor configuration (`SupaliteConfig`).
11
+ * This option allows users to specify how `BIGINT` (OID 20) database columns should be transformed when read.
12
+ * Possible values for `bigintTransform`:
13
+ * `'bigint'` (Default): Converts `BIGINT` values to native JavaScript `BigInt` objects. This maintains precision but can cause issues with direct `JSON.stringify()` if not handled.
14
+ * `'string'`: Converts `BIGINT` values to JavaScript strings. This is safe for JSON serialization and preserves the original value as text.
15
+ * `'number'`: Converts `BIGINT` values to JavaScript `Number` objects. This is convenient for smaller numbers but may lead to precision loss for values exceeding `Number.MAX_SAFE_INTEGER` or `Number.MIN_SAFE_INTEGER`. A warning is logged via `console.warn` if `verbose: true` and potential precision loss is detected.
16
+ * The chosen transformation mode is logged to the console if `verbose: true` during client initialization.
17
+
18
+ ### ๐Ÿ›  Improvements
19
+ * **Type Definitions**:
20
+ * The `Json` type in `src/types.ts` now explicitly includes `bigint`. This allows TypeScript to correctly type-check structures that may contain `BigInt` values. Users are reminded (via documentation) that standard `JSON.stringify` will require special handling for `BigInt` objects (e.g., a custom replacer or pre-conversion to string/number).
21
+ * **Client Initialization**:
22
+ * Refined `Pool` initialization in `src/postgres-client.ts` to more consistently use `PoolConfig` for both connection string and individual parameter setups.
23
+ * Standardized some internal logging prefixes for verbosity and errors.
24
+
25
+ ### ๐Ÿ“„ Documentation
26
+ * Updated `README.md` to include:
27
+ * Detailed explanation of the new `bigintTransform` constructor option, its possible values, default behavior, and implications (especially the precision loss warning for the `'number'` option).
28
+ * Clarification in `README.md` regarding the `Json` type including `bigint` and the user's responsibility for `JSON.stringify` handling of `BigInt` objects.
29
+ * Examples of using `bigintTransform` and `verbose` options in the `SupaLitePG` constructor.
30
+ * Mention of `SUPALITE_VERBOSE=true` as an environment variable option.
31
+
32
+ ## Impact
33
+ - **Error Resolution**: Users experiencing `TypeError: Do not know how to serialize a BigInt` can now resolve this by setting `bigintTransform: 'string'` or `bigintTransform: 'number'` in the `SupaLitePG` configuration.
34
+ - **Flexibility**: Provides developers with greater control over how large integer types are handled, catering to different use cases (e.g., precise arithmetic vs. simple display/serialization).
35
+ - **Backward Compatibility**: The default behavior (`'bigint'`) remains unchanged, minimizing impact on existing users who might rely on receiving `BigInt` objects, though they should be aware of JSON serialization implications.
@@ -4,9 +4,22 @@
4
4
  - Updated `JsonbTestTable` related types to use the global `Json` type.
5
5
  - Added `another_json_field` (JSONB) to `jsonb_test_table` schema and tests.
6
6
  - Modified `beforeAll` to `DROP TABLE IF EXISTS jsonb_test_table` before `CREATE TABLE` to ensure schema updates are applied, fixing a "column does not exist" error during tests.
7
- - Removed explicit `JSON.stringify()` from test cases, relying on internal handling.
7
+ - Removed explicit `JSON.stringify()` from test cases, as this is now handled automatically by `QueryBuilder` based on schema information.
8
8
  - Added a new test case for inserting/selecting an object into `another_json_field`.
9
- - Modified `src/query-builder.ts` (`buildQuery` method):
10
- - Implemented automatic `JSON.stringify()` for array or object values (excluding `Date` instances) when preparing data for `INSERT`, `UPSERT`, and `UPDATE` operations. This allows users to pass JavaScript objects/arrays directly for `json`/`jsonb` columns.
11
- - Corrected a TypeScript error (`Cannot find name 'updateValues'`) in the `UPDATE` case of `buildQuery`.
12
- - Ensured `src/types.ts` contains the `Json` type definition.
9
+ - Added a test case for inserting an empty JavaScript array `[]` into a `jsonb` field, now handled automatically.
10
+ - Modified `src/postgres-client.ts`:
11
+ - Added `schemaCache` to store column type information fetched from `information_schema.columns`.
12
+ - Implemented `getColumnPgType(schema, table, column)` method to retrieve (and cache) PostgreSQL data types for columns.
13
+ - Added `verbose` option to `SupaliteConfig` and `SupaLitePG` for logging.
14
+ - Modified `from()` method to pass `SupaLitePG` instance and `verbose` setting to `QueryBuilder`.
15
+ - Modified `src/query-builder.ts`:
16
+ - Updated constructor to accept `SupaLitePG` client instance and `verbose` setting.
17
+ - Made `buildQuery()` method `async`.
18
+ - Implemented schema-aware value processing in `buildQuery()` for `INSERT`, `UPSERT`, and `UPDATE`:
19
+ - Uses `client.getColumnPgType()` to get the PostgreSQL type of each column.
20
+ - If `pgType` is 'json' or 'jsonb', JavaScript objects and arrays are `JSON.stringify()`'d.
21
+ - If `pgType` is 'bigint', JavaScript `BigInt`s are `toString()`'d.
22
+ - Otherwise (e.g., for `text[]`, `integer[]`), values (including JavaScript arrays) are passed as-is to the `pg` driver.
23
+ - Updated `execute()` method to `await buildQuery()` and include verbose logging for SQL and values if enabled.
24
+ - Ensured `src/types.ts` contains the `Json` type and `verbose` option in `SupaliteConfig`.
25
+ - **Outcome**: `QueryBuilder` now intelligently handles serialization for `json`/`jsonb`, `bigint`, and native array types based on runtime schema information, providing a more seamless experience similar to `supabase-js`. All related tests pass.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supalite",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "A lightweight TypeScript PostgreSQL client with Supabase-style API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",