supalite 0.5.6 → 0.5.7

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,6 +1,9 @@
1
1
  # Changelog
2
2
 
3
- ## [0.5.6] - 2025-12-17
3
+ ## [0.5.7] - 2026-01-14
4
+
5
+ ### ✨ Added
6
+ - `upsert()`에 `onConflict` 다중 컬럼 지정 지원을 추가했습니다. 이제 콤마 구분 문자열 또는 문자열 배열을 사용할 수 있습니다. (예: `'set_id, name'`, `['set_id', 'name']`)
4
7
 
5
8
  ### 🐞 Fixed
6
9
  - `select()`의 PostgREST-style embed(`related_table(*)`)가 **양방향 FK**를 지원하도록 개선했습니다. 이제 1:N 관계는 배열(`[]` 기본값), N:1 관계는 객체(또는 `null`)로 반환합니다. (See [docs/changelog/2025-12-17-embed-many-to-one.md](docs/changelog/2025-12-17-embed-many-to-one.md))
package/README.md CHANGED
@@ -285,7 +285,7 @@ const { data, error } = await client
285
285
  - `insert(data: T['Tables'][K]['Insert'] | T['Tables'][K]['Insert'][])`: 단일 또는 다중 레코드 삽입
286
286
  - `update(data: T['Tables'][K]['Update'])`: 레코드 업데이트
287
287
  - `delete()`: 레코드 삭제
288
- - `upsert(data: T['Tables'][K]['Insert'], options?: { onConflict: string })`: 삽입 또는 업데이트
288
+ - `upsert(data: T['Tables'][K]['Insert'], options?: { onConflict: string | string[] })`: 삽입 또는 업데이트
289
289
 
290
290
  ### 필터 메소드
291
291
 
@@ -57,8 +57,10 @@ export declare class QueryBuilder<T extends DatabaseSchema, S extends SchemaName
57
57
  returns<NewS extends SchemaName<T>, NewK extends TableName<T, NewS>>(): QueryBuilder<T, NewS, NewK>;
58
58
  range(from: number, to: number): this;
59
59
  upsert(values: InsertRow<T, S, K>, options?: {
60
- onConflict: string;
60
+ onConflict: string | string[];
61
61
  }): this;
62
+ private formatConflictTarget;
63
+ private quoteConflictTargetColumn;
62
64
  private shouldReturnData;
63
65
  private buildWhereClause;
64
66
  private buildQuery;
@@ -234,6 +234,39 @@ class QueryBuilder {
234
234
  this.conflictTarget = options?.onConflict;
235
235
  return this;
236
236
  }
237
+ formatConflictTarget(target) {
238
+ if (Array.isArray(target)) {
239
+ return target
240
+ .map((column) => this.quoteConflictTargetColumn(column))
241
+ .filter(Boolean)
242
+ .join(', ');
243
+ }
244
+ const trimmedTarget = target.trim();
245
+ if (!trimmedTarget) {
246
+ return trimmedTarget;
247
+ }
248
+ if (trimmedTarget.includes('"') || trimmedTarget.includes('(') || trimmedTarget.includes(')')) {
249
+ return trimmedTarget;
250
+ }
251
+ if (trimmedTarget.includes(',')) {
252
+ return trimmedTarget
253
+ .split(',')
254
+ .map((column) => this.quoteConflictTargetColumn(column))
255
+ .filter(Boolean)
256
+ .join(', ');
257
+ }
258
+ return this.quoteConflictTargetColumn(trimmedTarget);
259
+ }
260
+ quoteConflictTargetColumn(column) {
261
+ const trimmedColumn = column.trim();
262
+ if (!trimmedColumn) {
263
+ return trimmedColumn;
264
+ }
265
+ if (trimmedColumn.startsWith('"') && trimmedColumn.endsWith('"')) {
266
+ return trimmedColumn;
267
+ }
268
+ return `"${trimmedColumn}"`;
269
+ }
237
270
  shouldReturnData() {
238
271
  return this.selectColumns !== null;
239
272
  }
@@ -367,14 +400,13 @@ class QueryBuilder {
367
400
  query = `INSERT INTO ${schemaTable} ("${insertColumns.join('","')}") VALUES (${insertPlaceholders})`;
368
401
  }
369
402
  if (this.queryType === 'UPSERT' && this.conflictTarget) {
370
- // Quote conflict target if it's a simple column name and not already quoted
371
- const conflictTargetSQL = (this.conflictTarget.includes('"') || this.conflictTarget.includes('(') || this.conflictTarget.includes(','))
372
- ? this.conflictTarget
373
- : `"${this.conflictTarget}"`;
374
- query += ` ON CONFLICT (${conflictTargetSQL}) DO UPDATE SET `;
375
- query += insertColumns
376
- .map((col) => `"${col}" = EXCLUDED."${col}"`)
377
- .join(', ');
403
+ const conflictTargetSQL = this.formatConflictTarget(this.conflictTarget);
404
+ if (conflictTargetSQL) {
405
+ query += ` ON CONFLICT (${conflictTargetSQL}) DO UPDATE SET `;
406
+ query += insertColumns
407
+ .map((col) => `"${col}" = EXCLUDED."${col}"`)
408
+ .join(', ');
409
+ }
378
410
  }
379
411
  query += returning;
380
412
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supalite",
3
- "version": "0.5.6",
3
+ "version": "0.5.7",
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",