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 +9 -0
- package/README.md +45 -4
- package/dist/postgres-client.d.ts +4 -0
- package/dist/postgres-client.js +110 -39
- package/dist/query-builder.d.ts +5 -1
- package/dist/query-builder.js +48 -18
- package/dist/types.d.ts +3 -0
- package/docs/changelog/2025-06-10-bigint-handling-enhancement.md +35 -0
- package/docs/changelog/2025-06-10-jsonb-array-test.md +18 -5
- package/package.json +1 -1
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
|
-
[](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:
|
|
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;
|
package/dist/postgres-client.js
CHANGED
|
@@ -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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
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
|
-
|
|
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 {
|
package/dist/query-builder.d.ts
CHANGED
|
@@ -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
|
-
|
|
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>>>;
|
package/dist/query-builder.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
|
235
|
-
})
|
|
236
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
|
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,
|
|
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
|
-
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
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.
|