supalite 0.5.0 β 0.5.2
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 +11 -0
- package/README.md +3 -0
- package/dist/query-builder.d.ts +1 -0
- package/dist/query-builder.js +63 -27
- package/docs/changelog/2025-08-28-add-not-method.md +33 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.2] - 2025-10-16
|
|
4
|
+
|
|
5
|
+
### π Fixed
|
|
6
|
+
- `select()` λ©μλμμ `count: 'exact'` μ΅μ
μ¬μ© μ `limit()` λλ `range()`μ ν¨κ» νΈμΆλ λ μ 체 κ°μ λμ νμ΄μ§λ€μ΄μ
λ κ°μλ₯Ό λ°ννλ λ²κ·Έλ₯Ό μμ νμ΅λλ€. μ΄μ νμ μ νν μ 체 κ°μλ₯Ό λ°νν©λλ€.
|
|
7
|
+
- `select()` λ©μλμμ `count: 'exact'`μ `head: true` μ΅μ
μ ν¨κ» μ¬μ©ν λ `count`κ° `null`λ‘ λ°νλλ λ²κ·Έλ₯Ό μμ νμ΅λλ€.
|
|
8
|
+
|
|
9
|
+
## [0.5.1] - 2025-10-16
|
|
10
|
+
|
|
11
|
+
### π Fixed
|
|
12
|
+
- `select()` λ©μλμμ `count: 'exact'` μ΅μ
μ¬μ© μ `limit()` λλ `range()`μ ν¨κ» νΈμΆλ λ μ 체 κ°μ λμ νμ΄μ§λ€μ΄μ
λ κ°μλ₯Ό λ°ννλ λ²κ·Έλ₯Ό μμ νμ΅λλ€. μ΄μ νμ μ νν μ 체 κ°μλ₯Ό λ°νν©λλ€.
|
|
13
|
+
|
|
3
14
|
## [0.5.0] - 2025-07-01
|
|
4
15
|
|
|
5
16
|
### β¨ Added
|
package/README.md
CHANGED
|
@@ -268,6 +268,8 @@ const { data, error } = await client
|
|
|
268
268
|
### 쿼리 λ©μλ
|
|
269
269
|
|
|
270
270
|
- `select(columns?: string, options?: { count?: 'exact' | 'planned' | 'estimated', head?: boolean })`: μ‘°νν μ»¬λΌ μ§μ
|
|
271
|
+
- `options.count`: `'exact'`λ‘ μ€μ νλ©΄ `limit`μ μν₯μ λ°μ§ μλ μ 체 κ²°κ³Όμ κ°μλ₯Ό `count` μμ±μΌλ‘ λ°νν©λλ€.
|
|
272
|
+
- `options.head`: `true`λ‘ μ€μ νλ©΄ λ°μ΄ν° μμ΄ `count`λ§ κ°μ Έμ΅λλ€. `count` μ΅μ
κ³Ό ν¨κ» μ¬μ©νλ©΄ ν¨μ¨μ μΌλ‘ μ 체 κ°μλ§ μ‘°νν μ μμ΅λλ€.
|
|
271
273
|
- `insert(data: T['Tables'][K]['Insert'] | T['Tables'][K]['Insert'][])`: λ¨μΌ λλ λ€μ€ λ μ½λ μ½μ
|
|
272
274
|
- `update(data: T['Tables'][K]['Update'])`: λ μ½λ μ
λ°μ΄νΈ
|
|
273
275
|
- `delete()`: λ μ½λ μμ
|
|
@@ -285,6 +287,7 @@ const { data, error } = await client
|
|
|
285
287
|
- `ilike(column, pattern)`: λμλ¬Έμ κ΅¬λΆ μλ LIKE
|
|
286
288
|
- `in(column, values)`: IN μ°μ°μ
|
|
287
289
|
- `is(column, value)`: IS μ°μ°μ
|
|
290
|
+
- `not(column, operator, value)`: Negates an operator (e.g., `not('column', 'is', null)`).
|
|
288
291
|
- `contains(column, value)`: λ°°μ΄/JSON ν¬ν¨ μ¬λΆ
|
|
289
292
|
- `or(conditions)`: OR 쑰건 (μ: 'status.eq.active,role.eq.admin')
|
|
290
293
|
|
package/dist/query-builder.d.ts
CHANGED
|
@@ -38,6 +38,7 @@ export declare class QueryBuilder<T extends DatabaseSchema, S extends SchemaName
|
|
|
38
38
|
eq(column: string, value: any): this;
|
|
39
39
|
neq(column: string, value: any): this;
|
|
40
40
|
is(column: string, value: any): this;
|
|
41
|
+
not(column: string, operator: string, value: any): this;
|
|
41
42
|
contains(column: string, value: any): this;
|
|
42
43
|
in(column: string, values: any[]): this;
|
|
43
44
|
gte(column: string, value: any): this;
|
package/dist/query-builder.js
CHANGED
|
@@ -97,6 +97,16 @@ class QueryBuilder {
|
|
|
97
97
|
}
|
|
98
98
|
return this;
|
|
99
99
|
}
|
|
100
|
+
not(column, operator, value) {
|
|
101
|
+
if (operator === 'is' && value === null) {
|
|
102
|
+
this.whereConditions.push(`"${column}" IS NOT NULL`);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// μΆν λ€λ₯Έ not μ°μ°μλ€μ μν΄ λ¨κ²¨λ
|
|
106
|
+
throw new Error(`Operator "${operator}" is not supported for "not" operation.`);
|
|
107
|
+
}
|
|
108
|
+
return this;
|
|
109
|
+
}
|
|
100
110
|
contains(column, value) {
|
|
101
111
|
this.whereConditions.push(`"${column}" @> $${this.whereValues.length + 1}`);
|
|
102
112
|
this.whereValues.push(value);
|
|
@@ -235,6 +245,7 @@ class QueryBuilder {
|
|
|
235
245
|
case 'SELECT': {
|
|
236
246
|
if (this.headOption) {
|
|
237
247
|
query = `SELECT COUNT(*) FROM ${schemaTable}`;
|
|
248
|
+
query += this.buildWhereClause();
|
|
238
249
|
values = [...this.whereValues];
|
|
239
250
|
break;
|
|
240
251
|
}
|
|
@@ -248,7 +259,6 @@ class QueryBuilder {
|
|
|
248
259
|
const joinSubqueries = await Promise.all(this.joinClauses.map(async (join) => {
|
|
249
260
|
const fk = await this.client.getForeignKey(String(this.schema), String(this.table), join.foreignTable);
|
|
250
261
|
if (!fk) {
|
|
251
|
-
// In a real scenario, you might want to throw an error or handle this case
|
|
252
262
|
console.warn(`[SupaLite WARNING] No foreign key found from ${join.foreignTable} to ${String(this.table)}`);
|
|
253
263
|
return null;
|
|
254
264
|
}
|
|
@@ -266,12 +276,15 @@ class QueryBuilder {
|
|
|
266
276
|
if (validSubqueries) {
|
|
267
277
|
selectClause += `, ${validSubqueries}`;
|
|
268
278
|
}
|
|
269
|
-
|
|
279
|
+
let baseQuery = `SELECT ${selectClause} FROM ${schemaTable}`;
|
|
280
|
+
baseQuery += this.buildWhereClause();
|
|
281
|
+
values = [...this.whereValues];
|
|
270
282
|
if (this.countOption === 'exact') {
|
|
271
|
-
|
|
272
|
-
|
|
283
|
+
query = `SELECT *, COUNT(*) OVER() as exact_count FROM (${baseQuery}) subquery`;
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
query = baseQuery;
|
|
273
287
|
}
|
|
274
|
-
values = [...this.whereValues];
|
|
275
288
|
break;
|
|
276
289
|
}
|
|
277
290
|
case 'INSERT':
|
|
@@ -378,17 +391,19 @@ class QueryBuilder {
|
|
|
378
391
|
break;
|
|
379
392
|
}
|
|
380
393
|
}
|
|
394
|
+
// Append clauses that apply to the outermost query
|
|
381
395
|
if (this.queryType === 'SELECT') {
|
|
382
|
-
query
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
396
|
+
// WHERE is already in the query or subquery
|
|
397
|
+
// ORDER BY, LIMIT, and OFFSET always apply to the outer query
|
|
398
|
+
if (this.orderByColumns.length > 0) {
|
|
399
|
+
query += ` ORDER BY ${this.orderByColumns.join(', ')}`;
|
|
400
|
+
}
|
|
401
|
+
if (this.limitValue !== undefined) {
|
|
402
|
+
query += ` LIMIT ${this.limitValue}`;
|
|
403
|
+
}
|
|
404
|
+
if (this.offsetValue !== undefined) {
|
|
405
|
+
query += ` OFFSET ${this.offsetValue}`;
|
|
406
|
+
}
|
|
392
407
|
}
|
|
393
408
|
return { query, values };
|
|
394
409
|
}
|
|
@@ -427,27 +442,49 @@ class QueryBuilder {
|
|
|
427
442
|
statusText: 'Created',
|
|
428
443
|
};
|
|
429
444
|
}
|
|
445
|
+
let countResult = null;
|
|
446
|
+
let dataResult = result.rows;
|
|
447
|
+
if (this.headOption) {
|
|
448
|
+
countResult = Number(result.rows[0].count);
|
|
449
|
+
dataResult = [];
|
|
450
|
+
}
|
|
451
|
+
else if (this.countOption === 'exact') {
|
|
452
|
+
if (result.rows.length > 0) {
|
|
453
|
+
countResult = Number(result.rows[0].exact_count);
|
|
454
|
+
// exact_count μ΄μ λͺ¨λ λ°μ΄ν° κ°μ²΄μμ μ κ±°
|
|
455
|
+
dataResult = result.rows.map(row => {
|
|
456
|
+
const newRow = { ...row };
|
|
457
|
+
delete newRow.exact_count;
|
|
458
|
+
return newRow;
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
countResult = 0;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
countResult = result.rowCount;
|
|
467
|
+
}
|
|
430
468
|
if (this.singleMode) {
|
|
431
|
-
if (
|
|
469
|
+
if (dataResult.length > 1) {
|
|
432
470
|
return {
|
|
433
471
|
data: null,
|
|
434
|
-
error: new errors_1.PostgresError('PGRST114: Multiple rows returned'),
|
|
435
|
-
count:
|
|
436
|
-
status: 406,
|
|
472
|
+
error: new errors_1.PostgresError('PGRST114: Multiple rows returned'),
|
|
473
|
+
count: countResult,
|
|
474
|
+
status: 406,
|
|
437
475
|
statusText: 'Not Acceptable. Expected a single row but found multiple.',
|
|
438
476
|
};
|
|
439
477
|
}
|
|
440
|
-
if (
|
|
478
|
+
if (dataResult.length === 0) {
|
|
441
479
|
if (this.singleMode === 'strict') {
|
|
442
480
|
return {
|
|
443
481
|
data: null,
|
|
444
|
-
error: new errors_1.PostgresError('PGRST116: No rows found'),
|
|
482
|
+
error: new errors_1.PostgresError('PGRST116: No rows found'),
|
|
445
483
|
count: 0,
|
|
446
|
-
status: 404,
|
|
484
|
+
status: 404,
|
|
447
485
|
statusText: 'Not Found. Expected a single row but found no rows.',
|
|
448
486
|
};
|
|
449
487
|
}
|
|
450
|
-
// this.singleMode === 'maybe'
|
|
451
488
|
return {
|
|
452
489
|
data: null,
|
|
453
490
|
error: null,
|
|
@@ -456,9 +493,8 @@ class QueryBuilder {
|
|
|
456
493
|
statusText: 'OK',
|
|
457
494
|
};
|
|
458
495
|
}
|
|
459
|
-
// result.rows.length === 1
|
|
460
496
|
return {
|
|
461
|
-
data:
|
|
497
|
+
data: dataResult[0],
|
|
462
498
|
error: null,
|
|
463
499
|
count: 1,
|
|
464
500
|
status: 200,
|
|
@@ -466,9 +502,9 @@ class QueryBuilder {
|
|
|
466
502
|
};
|
|
467
503
|
}
|
|
468
504
|
return {
|
|
469
|
-
data:
|
|
505
|
+
data: dataResult,
|
|
470
506
|
error: null,
|
|
471
|
-
count:
|
|
507
|
+
count: countResult,
|
|
472
508
|
status: 200,
|
|
473
509
|
statusText: 'OK',
|
|
474
510
|
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# λ³κ²½ λ³΄κ³ μ: `not` λ©μλ μΆκ°
|
|
2
|
+
|
|
3
|
+
**λ μ§:** 2025λ
8μ 28μΌ
|
|
4
|
+
|
|
5
|
+
## λ³κ²½ μ ν
|
|
6
|
+
|
|
7
|
+
- [x] κΈ°λ₯ μΆκ°
|
|
8
|
+
- [ ] λ²κ·Έ μμ
|
|
9
|
+
- [ ] μ±λ₯ κ°μ
|
|
10
|
+
- [ ] λ¬Έμ μ
λ°μ΄νΈ
|
|
11
|
+
- [ ] κΈ°ν
|
|
12
|
+
|
|
13
|
+
## λ³κ²½ λ΄μ©
|
|
14
|
+
|
|
15
|
+
### `QueryBuilder`
|
|
16
|
+
|
|
17
|
+
- **`not` λ©μλ μΆκ°**: `is` μ°μ°μμ ν¨κ» μ¬μ©νμ¬ `IS NOT NULL` 쑰건μ μμ±ν μ μλ `not` λ©μλλ₯Ό μΆκ°νμ΅λλ€.
|
|
18
|
+
|
|
19
|
+
**μ¬μ© μμ:**
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
const { data } = await client
|
|
23
|
+
.from('users')
|
|
24
|
+
.select('id, name')
|
|
25
|
+
.not('email', 'is', null);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
μ μ½λλ `SELECT "id", "name" FROM "public"."users" WHERE "email" IS NOT NULL` SQL 쿼리λ₯Ό μμ±ν©λλ€.
|
|
29
|
+
|
|
30
|
+
## κ΄λ ¨ νμΌ
|
|
31
|
+
|
|
32
|
+
- `src/query-builder.ts`
|
|
33
|
+
- `README.md`
|