@wowsql/sdk 3.4.0 → 3.6.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/README.md +177 -35
- package/dist/auth.d.ts +10 -1
- package/dist/auth.js +4 -1
- package/dist/index.d.ts +49 -11
- package/dist/index.js +144 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ import { ProjectAuthClient } from '@wowsql/sdk';
|
|
|
58
58
|
|
|
59
59
|
const auth = new ProjectAuthClient({
|
|
60
60
|
projectUrl: 'myproject', // or https://myproject.wowsql.com
|
|
61
|
-
|
|
61
|
+
apiKey: 'your-anon-key' // Use anon key for client-side, service key for server-side
|
|
62
62
|
});
|
|
63
63
|
```
|
|
64
64
|
|
|
@@ -312,6 +312,138 @@ console.log(result.affected_rows); // Number of rows deleted
|
|
|
312
312
|
|
|
313
313
|
// Is null
|
|
314
314
|
.filter({ column: 'deleted_at', operator: 'is', value: null })
|
|
315
|
+
|
|
316
|
+
// IN operator (value must be an array)
|
|
317
|
+
.filter('category', 'in', ['electronics', 'books', 'clothing'])
|
|
318
|
+
|
|
319
|
+
// NOT IN operator
|
|
320
|
+
.filter('status', 'not_in', ['deleted', 'archived'])
|
|
321
|
+
|
|
322
|
+
// BETWEEN operator (value must be an array of 2 values)
|
|
323
|
+
.filter('price', 'between', [10, 100])
|
|
324
|
+
|
|
325
|
+
// NOT BETWEEN operator
|
|
326
|
+
.filter('age', 'not_between', [18, 65])
|
|
327
|
+
|
|
328
|
+
// OR logical operator
|
|
329
|
+
.filter('category', 'eq', 'electronics', 'AND')
|
|
330
|
+
.filter('price', 'gt', 1000, 'OR') // OR condition
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Advanced Query Features
|
|
334
|
+
|
|
335
|
+
### GROUP BY and Aggregates
|
|
336
|
+
|
|
337
|
+
GROUP BY supports both simple column names and SQL expressions with functions. All expressions are validated for security.
|
|
338
|
+
|
|
339
|
+
#### Basic GROUP BY
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
// Group by single column
|
|
343
|
+
const result = await client.table("products")
|
|
344
|
+
.select("category", "COUNT(*) as count", "AVG(price) as avg_price")
|
|
345
|
+
.groupBy("category")
|
|
346
|
+
.get();
|
|
347
|
+
|
|
348
|
+
// Group by multiple columns
|
|
349
|
+
const result = await client.table("sales")
|
|
350
|
+
.select("region", "category", "SUM(amount) as total")
|
|
351
|
+
.groupBy(["region", "category"])
|
|
352
|
+
.get();
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
#### GROUP BY with Date/Time Functions
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
// Group by date
|
|
359
|
+
const result = await client.table("orders")
|
|
360
|
+
.select("DATE(created_at) as date", "COUNT(*) as orders", "SUM(total) as revenue")
|
|
361
|
+
.groupBy("DATE(created_at)")
|
|
362
|
+
.orderBy("date", "desc")
|
|
363
|
+
.get();
|
|
364
|
+
|
|
365
|
+
// Group by year and month
|
|
366
|
+
const result = await client.table("orders")
|
|
367
|
+
.select("YEAR(created_at) as year", "MONTH(created_at) as month", "SUM(total) as revenue")
|
|
368
|
+
.groupBy(["YEAR(created_at)", "MONTH(created_at)"])
|
|
369
|
+
.get();
|
|
370
|
+
|
|
371
|
+
// Group by week
|
|
372
|
+
const result = await client.table("orders")
|
|
373
|
+
.select("WEEK(created_at) as week", "COUNT(*) as orders")
|
|
374
|
+
.groupBy("WEEK(created_at)")
|
|
375
|
+
.get();
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
#### Supported Functions in GROUP BY
|
|
379
|
+
|
|
380
|
+
**Date/Time:** `DATE()`, `YEAR()`, `MONTH()`, `DAY()`, `WEEK()`, `QUARTER()`, `HOUR()`, `MINUTE()`, `SECOND()`, `DATE_FORMAT()`, `DATE_ADD()`, `DATE_SUB()`, `DATEDIFF()`, `NOW()`, `CURRENT_TIMESTAMP()`, etc.
|
|
381
|
+
|
|
382
|
+
**String:** `CONCAT()`, `SUBSTRING()`, `LEFT()`, `RIGHT()`, `UPPER()`, `LOWER()`, `LENGTH()`, `TRIM()`, etc.
|
|
383
|
+
|
|
384
|
+
**Mathematical:** `ROUND()`, `CEIL()`, `FLOOR()`, `ABS()`, `POW()`, `SQRT()`, `MOD()`, etc.
|
|
385
|
+
|
|
386
|
+
### HAVING Clause
|
|
387
|
+
|
|
388
|
+
HAVING filters aggregated results after GROUP BY. Supports aggregate functions and comparison operators.
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
// Filter aggregated results
|
|
392
|
+
const result = await client.table("products")
|
|
393
|
+
.select("category", "COUNT(*) as count")
|
|
394
|
+
.groupBy("category")
|
|
395
|
+
.having("COUNT(*)", "gt", 10)
|
|
396
|
+
.get();
|
|
397
|
+
|
|
398
|
+
// Multiple HAVING conditions
|
|
399
|
+
const result = await client.table("orders")
|
|
400
|
+
.select("DATE(created_at) as date", "SUM(total) as revenue")
|
|
401
|
+
.groupBy("DATE(created_at)")
|
|
402
|
+
.having("SUM(total)", "gt", 1000)
|
|
403
|
+
.having("COUNT(*)", "gte", 5)
|
|
404
|
+
.get();
|
|
405
|
+
|
|
406
|
+
// HAVING with different aggregate functions
|
|
407
|
+
const result = await client.table("products")
|
|
408
|
+
.select("category", "AVG(price) as avg_price", "COUNT(*) as count")
|
|
409
|
+
.groupBy("category")
|
|
410
|
+
.having("AVG(price)", "gt", 100)
|
|
411
|
+
.having("COUNT(*)", "gte", 5)
|
|
412
|
+
.get();
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
**Supported HAVING Operators:** `eq`, `neq`, `gt`, `gte`, `lt`, `lte`
|
|
416
|
+
|
|
417
|
+
**Supported Aggregate Functions:** `COUNT(*)`, `SUM()`, `AVG()`, `MAX()`, `MIN()`, `GROUP_CONCAT()`, `STDDEV()`, `VARIANCE()`
|
|
418
|
+
|
|
419
|
+
### Multiple ORDER BY
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
// Order by multiple columns
|
|
423
|
+
const result = await client.table("products")
|
|
424
|
+
.select("*")
|
|
425
|
+
.orderByMultiple([
|
|
426
|
+
{ column: "category", direction: "asc" },
|
|
427
|
+
{ column: "price", direction: "desc" },
|
|
428
|
+
{ column: "created_at", direction: "desc" }
|
|
429
|
+
])
|
|
430
|
+
.get();
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Date/Time Functions
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
// Filter by date range using functions
|
|
437
|
+
const result = await client.table("orders")
|
|
438
|
+
.select("*")
|
|
439
|
+
.filter("created_at", "gte", "DATE_SUB(NOW(), INTERVAL 7 DAY)")
|
|
440
|
+
.get();
|
|
441
|
+
|
|
442
|
+
// Group by date
|
|
443
|
+
const result = await client.table("orders")
|
|
444
|
+
.select("DATE(created_at) as date", "COUNT(*) as count")
|
|
445
|
+
.groupBy("DATE(created_at)")
|
|
446
|
+
.get();
|
|
315
447
|
```
|
|
316
448
|
|
|
317
449
|
### Complex Queries
|
|
@@ -646,30 +778,34 @@ WOWSQL uses **different API keys for different operations**. Understanding which
|
|
|
646
778
|
|
|
647
779
|
### Key Types Overview
|
|
648
780
|
|
|
781
|
+
## 🔑 Unified Authentication
|
|
782
|
+
|
|
783
|
+
**✨ One Project = One Set of Keys for ALL Operations**
|
|
784
|
+
|
|
785
|
+
WOWSQL uses **unified authentication** - the same API keys work for both database operations AND authentication operations.
|
|
786
|
+
|
|
649
787
|
| Operation Type | Recommended Key | Alternative Key | Used By |
|
|
650
788
|
|---------------|----------------|-----------------|---------|
|
|
651
|
-
| **Database Operations** (CRUD) | Service Role Key (`
|
|
652
|
-
| **Authentication Operations** (OAuth, sign-in) |
|
|
789
|
+
| **Database Operations** (CRUD) | Service Role Key (`wowsql_service_...`) | Anonymous Key (`wowsql_anon_...`) | `WOWSQLClient` |
|
|
790
|
+
| **Authentication Operations** (OAuth, sign-in) | Anonymous Key (`wowsql_anon_...`) | Service Role Key (`wowsql_service_...`) | `ProjectAuthClient` |
|
|
653
791
|
|
|
654
792
|
### Where to Find Your Keys
|
|
655
793
|
|
|
656
|
-
All keys are found in: **WOWSQL Dashboard → Authentication → PROJECT KEYS**
|
|
657
|
-
|
|
658
|
-
1. **Service Role Key** (`wowbase_service_...`)
|
|
659
|
-
- Location: "Service Role Key (keep secret)"
|
|
660
|
-
- Used for: Database CRUD operations (recommended for server-side)
|
|
661
|
-
- Can also be used for authentication operations (fallback)
|
|
662
|
-
- **Important**: Click the eye icon to reveal this key
|
|
794
|
+
All keys are found in: **WOWSQL Dashboard → Settings → API Keys** or **Authentication → PROJECT KEYS**
|
|
663
795
|
|
|
664
|
-
|
|
665
|
-
- Location: "
|
|
666
|
-
- Used for:
|
|
667
|
-
|
|
796
|
+
1. **Anonymous Key** (`wowsql_anon_...`) ✨ **Unified Key**
|
|
797
|
+
- Location: "Anonymous Key (Public)"
|
|
798
|
+
- Used for:
|
|
799
|
+
- ✅ Client-side auth operations (signup, login, OAuth)
|
|
800
|
+
- ✅ Public/client-side database operations with limited permissions
|
|
801
|
+
- **Safe to expose** in frontend code (browser, mobile apps)
|
|
668
802
|
|
|
669
|
-
|
|
670
|
-
- Location: "
|
|
671
|
-
- Used for:
|
|
672
|
-
|
|
803
|
+
2. **Service Role Key** (`wowsql_service_...`) ✨ **Unified Key**
|
|
804
|
+
- Location: "Service Role Key (keep secret)"
|
|
805
|
+
- Used for:
|
|
806
|
+
- ✅ Server-side auth operations (admin, full access)
|
|
807
|
+
- ✅ Server-side database operations (full access, bypass RLS)
|
|
808
|
+
- **NEVER expose** in frontend code - server-side only!
|
|
673
809
|
|
|
674
810
|
### Database Operations
|
|
675
811
|
|
|
@@ -681,13 +817,13 @@ import WOWSQLClient from '@wowsql/sdk';
|
|
|
681
817
|
// Using Service Role Key (recommended for server-side, full access)
|
|
682
818
|
const client = new WOWSQLClient({
|
|
683
819
|
projectUrl: 'myproject',
|
|
684
|
-
apiKey: '
|
|
820
|
+
apiKey: 'wowsql_service_your-service-key-here' // Service Role Key
|
|
685
821
|
});
|
|
686
822
|
|
|
687
823
|
// Using Anonymous Key (for public/client-side access with limited permissions)
|
|
688
824
|
const client = new WOWSQLClient({
|
|
689
825
|
projectUrl: 'myproject',
|
|
690
|
-
apiKey: '
|
|
826
|
+
apiKey: 'wowsql_anon_your-anon-key-here' // Anonymous Key
|
|
691
827
|
});
|
|
692
828
|
|
|
693
829
|
// Query data
|
|
@@ -696,21 +832,21 @@ const users = await client.table('users').get();
|
|
|
696
832
|
|
|
697
833
|
### Authentication Operations
|
|
698
834
|
|
|
699
|
-
|
|
835
|
+
**✨ UNIFIED AUTHENTICATION:** Use the **same keys** as database operations!
|
|
700
836
|
|
|
701
837
|
```typescript
|
|
702
838
|
import { ProjectAuthClient } from '@wowsql/sdk';
|
|
703
839
|
|
|
704
|
-
// Using
|
|
840
|
+
// Using Anonymous Key (recommended for client-side auth operations)
|
|
705
841
|
const auth = new ProjectAuthClient({
|
|
706
842
|
projectUrl: 'myproject',
|
|
707
|
-
|
|
843
|
+
apiKey: 'wowsql_anon_your-anon-key-here' // Same key as database operations!
|
|
708
844
|
});
|
|
709
845
|
|
|
710
|
-
// Using Service Role Key (
|
|
846
|
+
// Using Service Role Key (for server-side auth operations)
|
|
711
847
|
const auth = new ProjectAuthClient({
|
|
712
848
|
projectUrl: 'myproject',
|
|
713
|
-
|
|
849
|
+
apiKey: 'wowsql_service_your-service-key-here' // Same key as database operations!
|
|
714
850
|
});
|
|
715
851
|
|
|
716
852
|
// OAuth authentication
|
|
@@ -720,36 +856,42 @@ const { authorizationUrl } = await auth.getOAuthAuthorizationUrl(
|
|
|
720
856
|
);
|
|
721
857
|
```
|
|
722
858
|
|
|
859
|
+
**Note:** The `publicApiKey` parameter is deprecated but still works for backward compatibility. Use `apiKey` instead.
|
|
860
|
+
|
|
723
861
|
### Environment Variables
|
|
724
862
|
|
|
725
863
|
Best practice: Use environment variables for API keys:
|
|
726
864
|
|
|
727
865
|
```typescript
|
|
866
|
+
// UNIFIED AUTHENTICATION: Same keys for both operations!
|
|
867
|
+
|
|
728
868
|
// Database operations - Service Role Key
|
|
729
869
|
const dbClient = new WOWSQLClient({
|
|
730
870
|
projectUrl: process.env.WOWSQL_PROJECT_URL!,
|
|
731
871
|
apiKey: process.env.WOWSQL_SERVICE_ROLE_KEY! // or WOWSQL_ANON_KEY
|
|
732
872
|
});
|
|
733
873
|
|
|
734
|
-
// Authentication operations -
|
|
874
|
+
// Authentication operations - Use the SAME key!
|
|
735
875
|
const authClient = new ProjectAuthClient({
|
|
736
876
|
projectUrl: process.env.WOWSQL_PROJECT_URL!,
|
|
737
|
-
|
|
877
|
+
apiKey: process.env.WOWSQL_ANON_KEY! // Same key for client-side auth
|
|
878
|
+
// Or use WOWSQL_SERVICE_ROLE_KEY for server-side auth
|
|
738
879
|
});
|
|
739
880
|
```
|
|
740
881
|
|
|
741
882
|
### Key Usage Summary
|
|
742
883
|
|
|
884
|
+
**✨ UNIFIED AUTHENTICATION:**
|
|
743
885
|
- **`WOWSQLClient`** → Uses **Service Role Key** or **Anonymous Key** for database operations
|
|
744
|
-
- **`ProjectAuthClient`** → Uses **
|
|
745
|
-
- **
|
|
746
|
-
- **
|
|
747
|
-
- **
|
|
886
|
+
- **`ProjectAuthClient`** → Uses **Anonymous Key** (client-side) or **Service Role Key** (server-side) for authentication operations
|
|
887
|
+
- **Same keys work for both** database AND authentication operations! 🎉
|
|
888
|
+
- **Anonymous Key** (`wowsql_anon_...`) → Client-side operations (auth + database)
|
|
889
|
+
- **Service Role Key** (`wowsql_service_...`) → Server-side operations (auth + database)
|
|
748
890
|
|
|
749
891
|
### Security Best Practices
|
|
750
892
|
|
|
751
893
|
1. **Never expose Service Role Key** in client-side code or public repositories
|
|
752
|
-
2. **Use
|
|
894
|
+
2. **Use Anonymous Key** for client-side authentication flows (same key as database operations)
|
|
753
895
|
3. **Use Anonymous Key** for public database access with limited permissions
|
|
754
896
|
4. **Store keys in environment variables**, never hardcode them
|
|
755
897
|
5. **Rotate keys regularly** if compromised
|
|
@@ -759,11 +901,11 @@ const authClient = new ProjectAuthClient({
|
|
|
759
901
|
**Error: "Invalid API key for project"**
|
|
760
902
|
- Ensure you're using the correct key type for the operation
|
|
761
903
|
- Database operations require Service Role Key or Anonymous Key
|
|
762
|
-
- Authentication operations require
|
|
904
|
+
- Authentication operations require Anonymous Key (client-side) or Service Role Key (server-side)
|
|
763
905
|
- Verify the key is copied correctly (no extra spaces)
|
|
764
906
|
|
|
765
907
|
**Error: "Authentication failed"**
|
|
766
|
-
- Check that you're using
|
|
908
|
+
- Check that you're using the correct key: Anonymous Key for client-side, Service Role Key for server-side
|
|
767
909
|
- Verify the project URL matches your dashboard
|
|
768
910
|
- Ensure the key hasn't been revoked or expired
|
|
769
911
|
|
|
@@ -948,7 +1090,7 @@ try {
|
|
|
948
1090
|
|
|
949
1091
|
### Can I use this in the browser?
|
|
950
1092
|
|
|
951
|
-
Yes! The SDK works in both Node.js and browser environments. However, **never expose your Service Role Key in client-side code** for production applications. Use
|
|
1093
|
+
Yes! The SDK works in both Node.js and browser environments. However, **never expose your Service Role Key in client-side code** for production applications. Use **Anonymous Key** for both authentication and limited database access. For full database operations, use a backend proxy with Service Role Key.
|
|
952
1094
|
|
|
953
1095
|
### What about rate limits?
|
|
954
1096
|
|
package/dist/auth.d.ts
CHANGED
|
@@ -7,7 +7,16 @@ export interface ProjectAuthClientConfig {
|
|
|
7
7
|
secure?: boolean;
|
|
8
8
|
/** Request timeout in milliseconds */
|
|
9
9
|
timeout?: number;
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Unified API key - Anonymous Key (wowsql_anon_...) for client-side,
|
|
12
|
+
* or Service Role Key (wowsql_service_...) for server-side.
|
|
13
|
+
* UNIFIED AUTHENTICATION: Same key works for both auth and database operations.
|
|
14
|
+
*/
|
|
15
|
+
apiKey?: string;
|
|
16
|
+
/**
|
|
17
|
+
* @deprecated Use apiKey instead. Kept for backward compatibility.
|
|
18
|
+
* Unified API key - same as apiKey parameter.
|
|
19
|
+
*/
|
|
11
20
|
publicApiKey?: string;
|
|
12
21
|
/** Custom token storage implementation (defaults to in-memory) */
|
|
13
22
|
storage?: AuthTokenStorage;
|
package/dist/auth.js
CHANGED
|
@@ -31,12 +31,15 @@ class ProjectAuthClient {
|
|
|
31
31
|
this.storage = config.storage || new MemoryAuthTokenStorage();
|
|
32
32
|
this.accessToken = this.storage.getAccessToken();
|
|
33
33
|
this.refreshToken = this.storage.getRefreshToken();
|
|
34
|
+
// UNIFIED AUTHENTICATION: Use apiKey (new) or publicApiKey (deprecated) for backward compatibility
|
|
35
|
+
const unifiedApiKey = config.apiKey || config.publicApiKey;
|
|
34
36
|
this.client = axios_1.default.create({
|
|
35
37
|
baseURL: baseUrl,
|
|
36
38
|
timeout: config.timeout ?? 30000,
|
|
37
39
|
headers: {
|
|
38
40
|
'Content-Type': 'application/json',
|
|
39
|
-
|
|
41
|
+
// UNIFIED AUTHENTICATION: Use Authorization header (same as database operations)
|
|
42
|
+
...(unifiedApiKey ? { 'Authorization': `Bearer ${unifiedApiKey}` } : {}),
|
|
40
43
|
},
|
|
41
44
|
});
|
|
42
45
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -20,13 +20,17 @@ export interface WowSQLConfig {
|
|
|
20
20
|
timeout?: number;
|
|
21
21
|
}
|
|
22
22
|
export interface QueryOptions {
|
|
23
|
-
/** Columns to select (comma-separated or array) */
|
|
23
|
+
/** Columns to select (comma-separated or array) - can include expressions like "COUNT(*)", "DATE(created_at) as date" */
|
|
24
24
|
select?: string | string[];
|
|
25
25
|
/** Filter expressions */
|
|
26
26
|
filter?: FilterExpression | FilterExpression[];
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
/**
|
|
27
|
+
/** Columns to group by */
|
|
28
|
+
group_by?: string | string[];
|
|
29
|
+
/** HAVING clause filters for aggregated results */
|
|
30
|
+
having?: HavingFilter | HavingFilter[];
|
|
31
|
+
/** Column(s) to sort by - can be string or array of OrderByItem */
|
|
32
|
+
order?: string | OrderByItem[];
|
|
33
|
+
/** Sort direction (used only if order is a string) */
|
|
30
34
|
orderDirection?: 'asc' | 'desc';
|
|
31
35
|
/** Maximum records to return */
|
|
32
36
|
limit?: number;
|
|
@@ -35,9 +39,19 @@ export interface QueryOptions {
|
|
|
35
39
|
}
|
|
36
40
|
export interface FilterExpression {
|
|
37
41
|
column: string;
|
|
38
|
-
operator: 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'is';
|
|
42
|
+
operator: 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'is' | 'in' | 'not_in' | 'between' | 'not_between' | 'is_not';
|
|
43
|
+
value: string | number | boolean | null | any[] | [any, any];
|
|
44
|
+
logical_op?: 'AND' | 'OR';
|
|
45
|
+
}
|
|
46
|
+
export interface HavingFilter {
|
|
47
|
+
column: string;
|
|
48
|
+
operator: 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte';
|
|
39
49
|
value: string | number | boolean | null;
|
|
40
50
|
}
|
|
51
|
+
export interface OrderByItem {
|
|
52
|
+
column: string;
|
|
53
|
+
direction: 'asc' | 'desc';
|
|
54
|
+
}
|
|
41
55
|
export interface QueryResponse<T = any> {
|
|
42
56
|
data: T[];
|
|
43
57
|
count: number;
|
|
@@ -117,7 +131,7 @@ export declare class Table<T = any> {
|
|
|
117
131
|
/**
|
|
118
132
|
* Query with filter
|
|
119
133
|
*/
|
|
120
|
-
filter(
|
|
134
|
+
filter(column: string, operator: FilterExpression['operator'], value: any, logical_op?: 'AND' | 'OR'): QueryBuilder<T>;
|
|
121
135
|
/**
|
|
122
136
|
* Get all records (with optional limit)
|
|
123
137
|
*/
|
|
@@ -145,15 +159,39 @@ export declare class QueryBuilder<T = any> {
|
|
|
145
159
|
private options;
|
|
146
160
|
constructor(client: AxiosInstance, tableName: string);
|
|
147
161
|
/**
|
|
148
|
-
* Select specific columns
|
|
162
|
+
* Select specific columns or expressions
|
|
163
|
+
* @example query.select('id', 'name')
|
|
164
|
+
* @example query.select('category', 'COUNT(*) as count', 'AVG(price) as avg_price')
|
|
149
165
|
*/
|
|
150
|
-
select(columns: string | string[]): this;
|
|
166
|
+
select(...columns: (string | string[])[]): this;
|
|
151
167
|
/**
|
|
152
168
|
* Add filter condition
|
|
169
|
+
* @example query.filter('age', 'gt', 18)
|
|
170
|
+
* @example query.filter('category', 'in', ['electronics', 'books'])
|
|
171
|
+
* @example query.filter('price', 'between', [10, 100])
|
|
172
|
+
*/
|
|
173
|
+
filter(column: string, operator: FilterExpression['operator'], value: any, logical_op?: 'AND' | 'OR'): this;
|
|
174
|
+
/**
|
|
175
|
+
* Group results by column(s)
|
|
176
|
+
* @example query.groupBy('category')
|
|
177
|
+
* @example query.groupBy(['category', 'status'])
|
|
178
|
+
* @example query.groupBy('DATE(created_at)')
|
|
179
|
+
*/
|
|
180
|
+
groupBy(columns: string | string[]): this;
|
|
181
|
+
/**
|
|
182
|
+
* Add HAVING clause filter (for filtering aggregated results)
|
|
183
|
+
* @example query.having('COUNT(*)', 'gt', 10)
|
|
184
|
+
* @example query.having('AVG(price)', 'gte', 50)
|
|
185
|
+
*/
|
|
186
|
+
having(column: string, operator: HavingFilter['operator'], value: any): this;
|
|
187
|
+
/**
|
|
188
|
+
* Order by column(s)
|
|
189
|
+
* @example query.orderBy('created_at', 'desc')
|
|
190
|
+
* @example query.orderBy([{column: 'category', direction: 'asc'}, {column: 'price', direction: 'desc'}])
|
|
153
191
|
*/
|
|
154
|
-
|
|
192
|
+
orderBy(column: string | OrderByItem[], direction?: 'asc' | 'desc'): this;
|
|
155
193
|
/**
|
|
156
|
-
* Order by column
|
|
194
|
+
* Order by a single column (alias for orderBy for backward compatibility)
|
|
157
195
|
*/
|
|
158
196
|
order(column: string, direction?: 'asc' | 'desc'): this;
|
|
159
197
|
/**
|
|
@@ -165,7 +203,7 @@ export declare class QueryBuilder<T = any> {
|
|
|
165
203
|
*/
|
|
166
204
|
offset(offset: number): this;
|
|
167
205
|
/**
|
|
168
|
-
* Execute query
|
|
206
|
+
* Execute query - uses POST /{table}/query for advanced features, GET for simple queries
|
|
169
207
|
*/
|
|
170
208
|
get(additionalOptions?: QueryOptions): Promise<QueryResponse<T>>;
|
|
171
209
|
/**
|
package/dist/index.js
CHANGED
|
@@ -125,8 +125,8 @@ class Table {
|
|
|
125
125
|
/**
|
|
126
126
|
* Query with filter
|
|
127
127
|
*/
|
|
128
|
-
filter(
|
|
129
|
-
return new QueryBuilder(this.client, this.tableName).filter(
|
|
128
|
+
filter(column, operator, value, logical_op = 'AND') {
|
|
129
|
+
return new QueryBuilder(this.client, this.tableName).filter(column, operator, value, logical_op);
|
|
130
130
|
}
|
|
131
131
|
/**
|
|
132
132
|
* Get all records (with optional limit)
|
|
@@ -172,19 +172,33 @@ class QueryBuilder {
|
|
|
172
172
|
this.options = {};
|
|
173
173
|
}
|
|
174
174
|
/**
|
|
175
|
-
* Select specific columns
|
|
175
|
+
* Select specific columns or expressions
|
|
176
|
+
* @example query.select('id', 'name')
|
|
177
|
+
* @example query.select('category', 'COUNT(*) as count', 'AVG(price) as avg_price')
|
|
176
178
|
*/
|
|
177
|
-
select(columns) {
|
|
178
|
-
|
|
179
|
+
select(...columns) {
|
|
180
|
+
if (columns.length === 1 && Array.isArray(columns[0])) {
|
|
181
|
+
this.options.select = columns[0];
|
|
182
|
+
}
|
|
183
|
+
else if (columns.length === 1 && typeof columns[0] === 'string') {
|
|
184
|
+
this.options.select = columns[0];
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
this.options.select = columns;
|
|
188
|
+
}
|
|
179
189
|
return this;
|
|
180
190
|
}
|
|
181
191
|
/**
|
|
182
192
|
* Add filter condition
|
|
193
|
+
* @example query.filter('age', 'gt', 18)
|
|
194
|
+
* @example query.filter('category', 'in', ['electronics', 'books'])
|
|
195
|
+
* @example query.filter('price', 'between', [10, 100])
|
|
183
196
|
*/
|
|
184
|
-
filter(
|
|
197
|
+
filter(column, operator, value, logical_op = 'AND') {
|
|
185
198
|
if (!this.options.filter) {
|
|
186
199
|
this.options.filter = [];
|
|
187
200
|
}
|
|
201
|
+
const filter = { column, operator, value, logical_op };
|
|
188
202
|
if (Array.isArray(this.options.filter)) {
|
|
189
203
|
this.options.filter.push(filter);
|
|
190
204
|
}
|
|
@@ -194,13 +208,56 @@ class QueryBuilder {
|
|
|
194
208
|
return this;
|
|
195
209
|
}
|
|
196
210
|
/**
|
|
197
|
-
*
|
|
211
|
+
* Group results by column(s)
|
|
212
|
+
* @example query.groupBy('category')
|
|
213
|
+
* @example query.groupBy(['category', 'status'])
|
|
214
|
+
* @example query.groupBy('DATE(created_at)')
|
|
198
215
|
*/
|
|
199
|
-
|
|
200
|
-
this.options.
|
|
201
|
-
this
|
|
216
|
+
groupBy(columns) {
|
|
217
|
+
this.options.group_by = columns;
|
|
218
|
+
return this;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Add HAVING clause filter (for filtering aggregated results)
|
|
222
|
+
* @example query.having('COUNT(*)', 'gt', 10)
|
|
223
|
+
* @example query.having('AVG(price)', 'gte', 50)
|
|
224
|
+
*/
|
|
225
|
+
having(column, operator, value) {
|
|
226
|
+
if (!this.options.having) {
|
|
227
|
+
this.options.having = [];
|
|
228
|
+
}
|
|
229
|
+
const havingFilter = { column, operator, value };
|
|
230
|
+
if (Array.isArray(this.options.having)) {
|
|
231
|
+
this.options.having.push(havingFilter);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
this.options.having = [this.options.having, havingFilter];
|
|
235
|
+
}
|
|
236
|
+
return this;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Order by column(s)
|
|
240
|
+
* @example query.orderBy('created_at', 'desc')
|
|
241
|
+
* @example query.orderBy([{column: 'category', direction: 'asc'}, {column: 'price', direction: 'desc'}])
|
|
242
|
+
*/
|
|
243
|
+
orderBy(column, direction) {
|
|
244
|
+
if (typeof column === 'string') {
|
|
245
|
+
this.options.order = column;
|
|
246
|
+
if (direction) {
|
|
247
|
+
this.options.orderDirection = direction;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
this.options.order = column;
|
|
252
|
+
}
|
|
202
253
|
return this;
|
|
203
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Order by a single column (alias for orderBy for backward compatibility)
|
|
257
|
+
*/
|
|
258
|
+
order(column, direction = 'asc') {
|
|
259
|
+
return this.orderBy(column, direction);
|
|
260
|
+
}
|
|
204
261
|
/**
|
|
205
262
|
* Limit number of results
|
|
206
263
|
*/
|
|
@@ -216,35 +273,98 @@ class QueryBuilder {
|
|
|
216
273
|
return this;
|
|
217
274
|
}
|
|
218
275
|
/**
|
|
219
|
-
* Execute query
|
|
276
|
+
* Execute query - uses POST /{table}/query for advanced features, GET for simple queries
|
|
220
277
|
*/
|
|
221
278
|
async get(additionalOptions) {
|
|
222
279
|
const finalOptions = { ...this.options, ...additionalOptions };
|
|
223
|
-
// Build query
|
|
224
|
-
const
|
|
280
|
+
// Build query request body for POST endpoint
|
|
281
|
+
const body = {};
|
|
282
|
+
// Select
|
|
225
283
|
if (finalOptions.select) {
|
|
226
|
-
|
|
284
|
+
body.select = Array.isArray(finalOptions.select)
|
|
285
|
+
? finalOptions.select
|
|
286
|
+
: typeof finalOptions.select === 'string'
|
|
287
|
+
? finalOptions.select.split(',').map(s => s.trim())
|
|
288
|
+
: [finalOptions.select];
|
|
227
289
|
}
|
|
290
|
+
// Filters
|
|
228
291
|
if (finalOptions.filter) {
|
|
229
|
-
|
|
292
|
+
body.filters = Array.isArray(finalOptions.filter)
|
|
230
293
|
? finalOptions.filter
|
|
231
294
|
: [finalOptions.filter];
|
|
232
|
-
params.filter = filters
|
|
233
|
-
.map((f) => `${f.column}.${f.operator}.${f.value}`)
|
|
234
|
-
.join(',');
|
|
235
295
|
}
|
|
296
|
+
// Group by
|
|
297
|
+
if (finalOptions.group_by) {
|
|
298
|
+
body.group_by = Array.isArray(finalOptions.group_by)
|
|
299
|
+
? finalOptions.group_by
|
|
300
|
+
: typeof finalOptions.group_by === 'string'
|
|
301
|
+
? finalOptions.group_by.split(',').map(s => s.trim())
|
|
302
|
+
: [finalOptions.group_by];
|
|
303
|
+
}
|
|
304
|
+
// Having
|
|
305
|
+
if (finalOptions.having) {
|
|
306
|
+
body.having = Array.isArray(finalOptions.having)
|
|
307
|
+
? finalOptions.having
|
|
308
|
+
: [finalOptions.having];
|
|
309
|
+
}
|
|
310
|
+
// Order by
|
|
236
311
|
if (finalOptions.order) {
|
|
237
|
-
|
|
238
|
-
|
|
312
|
+
if (typeof finalOptions.order === 'string') {
|
|
313
|
+
body.order_by = finalOptions.order;
|
|
314
|
+
body.order_direction = finalOptions.orderDirection || 'asc';
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
body.order_by = finalOptions.order;
|
|
318
|
+
}
|
|
239
319
|
}
|
|
320
|
+
// Limit and offset
|
|
240
321
|
if (finalOptions.limit !== undefined) {
|
|
241
|
-
|
|
322
|
+
body.limit = finalOptions.limit;
|
|
242
323
|
}
|
|
243
324
|
if (finalOptions.offset !== undefined) {
|
|
244
|
-
|
|
325
|
+
body.offset = finalOptions.offset;
|
|
326
|
+
}
|
|
327
|
+
// Check if we need POST endpoint (advanced features)
|
|
328
|
+
const hasAdvancedFeatures = body.group_by ||
|
|
329
|
+
body.having ||
|
|
330
|
+
Array.isArray(body.order_by) ||
|
|
331
|
+
(body.filters && body.filters.some((f) => ['in', 'not_in', 'between', 'not_between'].includes(f.operator)));
|
|
332
|
+
if (hasAdvancedFeatures) {
|
|
333
|
+
// Use POST endpoint for advanced queries
|
|
334
|
+
const response = await this.client.post(`/${this.tableName}/query`, body);
|
|
335
|
+
return response.data;
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
// Use GET endpoint for simple queries (backward compatibility)
|
|
339
|
+
const params = {};
|
|
340
|
+
if (body.select) {
|
|
341
|
+
params.select = Array.isArray(body.select) ? body.select.join(',') : body.select;
|
|
342
|
+
}
|
|
343
|
+
if (body.filters && body.filters.length > 0) {
|
|
344
|
+
// Check if any filter uses array values (can't use GET)
|
|
345
|
+
const hasArrayValues = body.filters.some((f) => Array.isArray(f.value));
|
|
346
|
+
if (hasArrayValues) {
|
|
347
|
+
// Must use POST
|
|
348
|
+
const response = await this.client.post(`/${this.tableName}/query`, body);
|
|
349
|
+
return response.data;
|
|
350
|
+
}
|
|
351
|
+
params.filter = body.filters
|
|
352
|
+
.map((f) => `${f.column}.${f.operator}.${f.value}`)
|
|
353
|
+
.join(',');
|
|
354
|
+
}
|
|
355
|
+
if (body.order_by && typeof body.order_by === 'string') {
|
|
356
|
+
params.order = body.order_by;
|
|
357
|
+
params.order_direction = body.order_direction || 'asc';
|
|
358
|
+
}
|
|
359
|
+
if (body.limit !== undefined) {
|
|
360
|
+
params.limit = body.limit;
|
|
361
|
+
}
|
|
362
|
+
if (body.offset !== undefined) {
|
|
363
|
+
params.offset = body.offset;
|
|
364
|
+
}
|
|
365
|
+
const response = await this.client.get(`/${this.tableName}`, { params });
|
|
366
|
+
return response.data;
|
|
245
367
|
}
|
|
246
|
-
const response = await this.client.get(`/${this.tableName}`, { params });
|
|
247
|
-
return response.data;
|
|
248
368
|
}
|
|
249
369
|
/**
|
|
250
370
|
* Get first record
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wowsql/sdk",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"description": "Official TypeScript/JavaScript SDK for WowSQL - MySQL Backend-as-a-Service with S3 Storage, type-safe queries and fluent API",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|