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
- [![npm version](https://img.shields.io/badge/version-0.2.1-blue.svg)](https://www.npmjs.com/package/supalite)
3
+ [![npm version](https://img.shields.io/badge/version-0.3.1-blue.svg)](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')
@@ -27,7 +27,15 @@ class QueryBuilder {
27
27
  return this.execute().finally(onfinally);
28
28
  }
29
29
  select(columns = '*', options) {
30
- this.selectColumns = columns;
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
- values = rows.map(row => Object.values(row)).flat();
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
- query += ` ON CONFLICT (${this.conflictTarget}) DO UPDATE SET `;
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 updateValues = Object.values(updateData);
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 = [...updateValues, ...this.whereValues];
253
- query += this.buildWhereClause(updateValues);
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supalite",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
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",