supalite 0.2.1 → 0.3.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/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
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
- 💪 트랜잭션 지원: Supabase에서 지원하지 않는 안전한 데이터베이스 트랜잭션 처리
|
|
16
16
|
- 🎯 UPSERT 지원: 삽입/업데이트 동작 제어
|
|
17
17
|
- 🔍 고급 필터링: OR 조건, ILIKE 검색 등 지원
|
|
18
|
-
- 📚 배열 작업: 다중 레코드 삽입 및 배열 데이터 처리
|
|
18
|
+
- 📚 배열 작업: 다중 레코드 삽입 및 배열 데이터 처리 (JSON/JSONB 필드 포함)
|
|
19
19
|
- 🔄 Views, Functions, Enums 지원: Supabase 스타일의 완벽한 타입 지원
|
|
20
20
|
|
|
21
21
|
## 설치 방법
|
|
@@ -230,6 +230,15 @@ const { data, error } = await client
|
|
|
230
230
|
}
|
|
231
231
|
]);
|
|
232
232
|
|
|
233
|
+
// JSONB 배열 데이터 처리
|
|
234
|
+
const { data: jsonData, error: jsonError } = await client
|
|
235
|
+
.from('your_jsonb_table') // 'your_jsonb_table'을 실제 테이블명으로 변경
|
|
236
|
+
.insert({
|
|
237
|
+
metadata_array: ['tag1', 2025, { active: true }]
|
|
238
|
+
})
|
|
239
|
+
.select('metadata_array')
|
|
240
|
+
.single();
|
|
241
|
+
|
|
233
242
|
// 다른 스키마 사용
|
|
234
243
|
const { data, error } = await client
|
|
235
244
|
.from('users', 'other_schema')
|
package/dist/query-builder.js
CHANGED
|
@@ -27,7 +27,15 @@ class QueryBuilder {
|
|
|
27
27
|
return this.execute().finally(onfinally);
|
|
28
28
|
}
|
|
29
29
|
select(columns = '*', options) {
|
|
30
|
-
|
|
30
|
+
if (columns && columns !== '*') {
|
|
31
|
+
this.selectColumns = columns.split(',')
|
|
32
|
+
.map(col => col.trim())
|
|
33
|
+
.map(col => col.startsWith('"') && col.endsWith('"') ? col : `"${col}"`)
|
|
34
|
+
.join(', ');
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
this.selectColumns = columns;
|
|
38
|
+
}
|
|
31
39
|
this.countOption = options?.count;
|
|
32
40
|
this.headOption = options?.head;
|
|
33
41
|
return this;
|
|
@@ -215,19 +223,35 @@ class QueryBuilder {
|
|
|
215
223
|
if (rows.length === 0)
|
|
216
224
|
throw new Error('Empty array provided for insert');
|
|
217
225
|
insertColumns = Object.keys(rows[0]);
|
|
218
|
-
|
|
226
|
+
// Process each row for potential JSON stringification
|
|
227
|
+
const processedRowsValues = rows.map(row => Object.values(row).map(val => {
|
|
228
|
+
if (Array.isArray(val) || (val !== null && typeof val === 'object' && !(val instanceof Date))) {
|
|
229
|
+
return JSON.stringify(val);
|
|
230
|
+
}
|
|
231
|
+
return val;
|
|
232
|
+
}));
|
|
233
|
+
values = processedRowsValues.flat();
|
|
219
234
|
const placeholders = rows.map((_, i) => `(${insertColumns.map((_, j) => `$${i * insertColumns.length + j + 1}`).join(',')})`).join(',');
|
|
220
235
|
query = `INSERT INTO ${schemaTable} ("${insertColumns.join('","')}") VALUES ${placeholders}`;
|
|
221
236
|
}
|
|
222
237
|
else {
|
|
223
238
|
const insertData = this.insertData;
|
|
224
239
|
insertColumns = Object.keys(insertData);
|
|
225
|
-
values = Object.values(insertData)
|
|
240
|
+
values = Object.values(insertData).map(val => {
|
|
241
|
+
if (Array.isArray(val) || (val !== null && typeof val === 'object' && !(val instanceof Date))) {
|
|
242
|
+
return JSON.stringify(val);
|
|
243
|
+
}
|
|
244
|
+
return val;
|
|
245
|
+
});
|
|
226
246
|
const insertPlaceholders = values.map((_, i) => `$${i + 1}`).join(',');
|
|
227
247
|
query = `INSERT INTO ${schemaTable} ("${insertColumns.join('","')}") VALUES (${insertPlaceholders})`;
|
|
228
248
|
}
|
|
229
249
|
if (this.queryType === 'UPSERT' && this.conflictTarget) {
|
|
230
|
-
|
|
250
|
+
// Quote conflict target if it's a simple column name and not already quoted
|
|
251
|
+
const conflictTargetSQL = (this.conflictTarget.includes('"') || this.conflictTarget.includes('(') || this.conflictTarget.includes(','))
|
|
252
|
+
? this.conflictTarget
|
|
253
|
+
: `"${this.conflictTarget}"`;
|
|
254
|
+
query += ` ON CONFLICT (${conflictTargetSQL}) DO UPDATE SET `;
|
|
231
255
|
query += insertColumns
|
|
232
256
|
.map((col) => `"${col}" = EXCLUDED."${col}"`)
|
|
233
257
|
.join(', ');
|
|
@@ -246,11 +270,16 @@ class QueryBuilder {
|
|
|
246
270
|
if ('updated_at' in updateData && !updateData.updated_at) {
|
|
247
271
|
updateData.updated_at = now;
|
|
248
272
|
}
|
|
249
|
-
const
|
|
273
|
+
const processedUpdateValues = Object.values(updateData).map(val => {
|
|
274
|
+
if (Array.isArray(val) || (val !== null && typeof val === 'object' && !(val instanceof Date))) {
|
|
275
|
+
return JSON.stringify(val);
|
|
276
|
+
}
|
|
277
|
+
return val;
|
|
278
|
+
});
|
|
250
279
|
const setColumns = Object.keys(updateData).map((key, index) => `"${String(key)}" = $${index + 1}`);
|
|
251
280
|
query = `UPDATE ${schemaTable} SET ${setColumns.join(', ')}`;
|
|
252
|
-
values = [...
|
|
253
|
-
query += this.buildWhereClause(
|
|
281
|
+
values = [...processedUpdateValues, ...this.whereValues];
|
|
282
|
+
query += this.buildWhereClause(processedUpdateValues);
|
|
254
283
|
query += returning;
|
|
255
284
|
break;
|
|
256
285
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
## 2025-06-10: Added JSONB Array Tests
|
|
2
|
+
|
|
3
|
+
- Modified `src/__tests__/query-builder-single.test.ts`:
|
|
4
|
+
- Updated `JsonbTestTable` related types to use the global `Json` type.
|
|
5
|
+
- Added `another_json_field` (JSONB) to `jsonb_test_table` schema and tests.
|
|
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.
|
|
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.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
## 2025-06-10: Improved Handling of Reserved Keywords as Column Names
|
|
2
|
+
|
|
3
|
+
- **New Test Suite**: Created `src/__tests__/query-builder-reserved.test.ts` to specifically test scenarios where table column names are SQL reserved keywords (e.g., "order", "desc", "user", "limit", "group").
|
|
4
|
+
- Includes tests for SELECT, INSERT, UPDATE, ORDER BY, and UPSERT operations on these columns.
|
|
5
|
+
- **`QueryBuilder` Modifications (`src/query-builder.ts`)**:
|
|
6
|
+
- **`select()` method**: Enhanced to automatically quote individual column names if a comma-separated string of unquoted names is provided (e.g., `select('order, desc')` will now correctly generate `SELECT "order", "desc"`). This does not affect `select('*')` or already quoted identifiers.
|
|
7
|
+
- **`upsert()` method**: The `onConflict` option, when provided as a simple unquoted column name, will now be automatically quoted (e.g., `onConflict: 'order'` becomes `ON CONFLICT ("order")`). Complex constraint names or multi-column conflict targets provided by the user are not modified.
|
|
8
|
+
- **Previous JSONB Update Integration**: The work on reserved keywords was done on top of previous changes for automatic JSON stringification. The changelog entry `docs/changelog/2025-06-10-jsonb-array-test.md` covers those details. This entry focuses on reserved keyword handling.
|