@wowsql/sdk 3.7.0 → 3.8.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 +1293 -699
- package/dist/auth.d.ts +46 -0
- package/dist/auth.js +107 -1
- package/dist/errors.d.ts +9 -0
- package/dist/errors.js +22 -1
- package/dist/index.d.ts +71 -0
- package/dist/index.js +162 -0
- package/dist/schema.d.ts +165 -13
- package/dist/schema.js +208 -79
- package/dist/storage.d.ts +138 -322
- package/dist/storage.js +181 -270
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,333 +1,332 @@
|
|
|
1
|
-
#
|
|
1
|
+
# WowSQL TypeScript/JavaScript SDK
|
|
2
2
|
|
|
3
|
-
Official TypeScript/JavaScript
|
|
3
|
+
Official TypeScript/JavaScript client for [WowSQL](https://wowsql.com) - PostgreSQL Backend-as-a-Service with built-in Authentication and Storage.
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/wowsql)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
-
## Features
|
|
9
|
-
|
|
10
|
-
- 🚀 **Zero Configuration** - Get started in seconds
|
|
11
|
-
- 🔒 **Type-Safe** - Full TypeScript support with generics
|
|
12
|
-
- 🎯 **Fluent API** - Intuitive query builder pattern
|
|
13
|
-
- ⚡ **Lightweight** - Minimal dependencies (only axios)
|
|
14
|
-
- 🛡️ **Error Handling** - Comprehensive error messages
|
|
15
|
-
- 📦 **Tree-Shakeable** - Import only what you need
|
|
16
|
-
|
|
17
8
|
## Installation
|
|
18
9
|
|
|
19
10
|
```bash
|
|
20
11
|
# npm
|
|
21
|
-
npm install
|
|
12
|
+
npm install wowsql
|
|
22
13
|
|
|
23
14
|
# yarn
|
|
24
|
-
yarn add
|
|
15
|
+
yarn add wowsql
|
|
25
16
|
|
|
26
17
|
# pnpm
|
|
27
|
-
pnpm add
|
|
18
|
+
pnpm add wowsql
|
|
28
19
|
```
|
|
29
20
|
|
|
30
21
|
## Quick Start
|
|
31
22
|
|
|
23
|
+
### Database Operations
|
|
24
|
+
|
|
32
25
|
```typescript
|
|
33
|
-
import
|
|
26
|
+
import { WowSQLClient } from 'wowsql';
|
|
34
27
|
|
|
35
28
|
// Initialize client
|
|
36
|
-
const client = new
|
|
37
|
-
projectUrl: '
|
|
38
|
-
apiKey: 'your-api-key
|
|
29
|
+
const client = new WowSQLClient({
|
|
30
|
+
projectUrl: 'https://your-project.wowsql.com',
|
|
31
|
+
apiKey: 'your-api-key'
|
|
39
32
|
});
|
|
40
33
|
|
|
41
|
-
//
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
.filter({ column: 'age', operator: 'gt', value: 18 })
|
|
45
|
-
.order('created_at', 'desc')
|
|
46
|
-
.limit(10)
|
|
47
|
-
.get();
|
|
48
|
-
|
|
49
|
-
console.log(users.data); // Array of user records
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## Project Authentication
|
|
34
|
+
// Select data
|
|
35
|
+
const response = await client.table('users').select('*').limit(10).get();
|
|
36
|
+
console.log(response.data); // [{ id: 1, name: 'John', ... }, ...]
|
|
53
37
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const auth = new ProjectAuthClient({
|
|
60
|
-
projectUrl: 'myproject', // or https://myproject.wowsql.com
|
|
61
|
-
apiKey: 'your-anon-key' // Use anon key for client-side, service key for server-side
|
|
38
|
+
// Insert data
|
|
39
|
+
const result = await client.table('users').create({
|
|
40
|
+
name: 'Jane Doe',
|
|
41
|
+
email: 'jane@example.com',
|
|
42
|
+
age: 25
|
|
62
43
|
});
|
|
63
|
-
```
|
|
64
44
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
```typescript
|
|
68
|
-
const { user, session } = await auth.signUp({
|
|
69
|
-
email: 'user@example.com',
|
|
70
|
-
password: 'SuperSecret123',
|
|
71
|
-
full_name: 'Demo User',
|
|
72
|
-
user_metadata: { referrer: 'landing-page' }
|
|
73
|
-
});
|
|
45
|
+
// Update data
|
|
46
|
+
await client.table('users').update(1, { name: 'Jane Smith' });
|
|
74
47
|
|
|
75
|
-
|
|
76
|
-
|
|
48
|
+
// Delete data
|
|
49
|
+
await client.table('users').delete(1);
|
|
77
50
|
```
|
|
78
51
|
|
|
79
|
-
###
|
|
52
|
+
### Storage Operations
|
|
80
53
|
|
|
81
54
|
```typescript
|
|
82
|
-
|
|
83
|
-
email: 'user@example.com',
|
|
84
|
-
password: 'SuperSecret123'
|
|
85
|
-
});
|
|
55
|
+
import { WowSQLStorage } from 'wowsql';
|
|
86
56
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
refreshToken: session.refreshToken
|
|
57
|
+
const storage = new WowSQLStorage({
|
|
58
|
+
projectUrl: 'https://your-project.wowsql.com',
|
|
59
|
+
apiKey: 'your-api-key'
|
|
91
60
|
});
|
|
92
61
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
### OAuth Authentication
|
|
98
|
-
|
|
99
|
-
Complete OAuth flow with callback handling:
|
|
100
|
-
|
|
101
|
-
```typescript
|
|
102
|
-
// Step 1: Get authorization URL
|
|
103
|
-
const { authorizationUrl } = await auth.getOAuthAuthorizationUrl(
|
|
104
|
-
'github',
|
|
105
|
-
'https://app.your-domain.com/auth/callback'
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
window.location.href = authorizationUrl;
|
|
62
|
+
// Create a bucket
|
|
63
|
+
await storage.createBucket('avatars', { public: true });
|
|
109
64
|
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
const result = await auth.exchangeOAuthCallback(
|
|
113
|
-
'github',
|
|
114
|
-
code, // from URL query params
|
|
115
|
-
'https://app.your-domain.com/auth/callback'
|
|
116
|
-
);
|
|
65
|
+
// Upload a file
|
|
66
|
+
await storage.upload('avatars', fileData, 'users/avatar.png');
|
|
117
67
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
```
|
|
68
|
+
// Get public URL
|
|
69
|
+
const url = storage.getPublicUrl('avatars', 'users/avatar.png');
|
|
121
70
|
|
|
122
|
-
|
|
71
|
+
// List files
|
|
72
|
+
const files = await storage.listFiles('avatars', { prefix: 'users/' });
|
|
123
73
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const forgotResult = await auth.forgotPassword('user@example.com');
|
|
127
|
-
console.log(forgotResult.message); // "If that email exists, a password reset link has been sent"
|
|
74
|
+
// Download a file
|
|
75
|
+
const data = await storage.download('avatars', 'users/avatar.png');
|
|
128
76
|
|
|
129
|
-
//
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
'newSecurePassword123'
|
|
133
|
-
);
|
|
134
|
-
console.log(resetResult.message); // "Password reset successfully! You can now login with your new password"
|
|
77
|
+
// Check quota
|
|
78
|
+
const quota = await storage.getQuota();
|
|
79
|
+
console.log(`Used: ${quota.used_gb} GB / ${quota.total_gb} GB`);
|
|
135
80
|
```
|
|
136
81
|
|
|
137
|
-
###
|
|
138
|
-
|
|
139
|
-
The client exposes `getSession`, `setSession`, and `clearSession` utilities so you can wire tokens into your own persistence layer (localStorage, cookies, etc.):
|
|
82
|
+
### Authentication
|
|
140
83
|
|
|
141
84
|
```typescript
|
|
142
|
-
|
|
143
|
-
const session = auth.getSession();
|
|
144
|
-
console.log('Access token:', session.accessToken);
|
|
145
|
-
|
|
146
|
-
// Set session (e.g., from localStorage on page load)
|
|
147
|
-
auth.setSession({
|
|
148
|
-
accessToken: localStorage.getItem('access_token')!,
|
|
149
|
-
refreshToken: localStorage.getItem('refresh_token')!
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
// Clear session (logout)
|
|
153
|
-
auth.clearSession();
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
## Configuration
|
|
157
|
-
|
|
158
|
-
### Basic Configuration
|
|
85
|
+
import { ProjectAuthClient } from 'wowsql';
|
|
159
86
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
apiKey: 'your-api-key' // Your API key from dashboard
|
|
87
|
+
const auth = new ProjectAuthClient({
|
|
88
|
+
projectUrl: 'https://your-project.wowsql.com',
|
|
89
|
+
apiKey: 'your-anon-key'
|
|
164
90
|
});
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
### Advanced Configuration
|
|
168
91
|
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
secure: true, // Use HTTPS (default: true)
|
|
175
|
-
timeout: 30000 // Request timeout in ms (default: 30000)
|
|
92
|
+
// Sign up
|
|
93
|
+
const { user, session } = await auth.signUp({
|
|
94
|
+
email: 'user@example.com',
|
|
95
|
+
password: 'SuperSecret123',
|
|
96
|
+
fullName: 'Demo User'
|
|
176
97
|
});
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
### Using Full URL
|
|
180
98
|
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
99
|
+
// Sign in
|
|
100
|
+
const result = await auth.signIn({
|
|
101
|
+
email: 'user@example.com',
|
|
102
|
+
password: 'SuperSecret123'
|
|
185
103
|
});
|
|
104
|
+
console.log('Access token:', result.session.accessToken);
|
|
186
105
|
```
|
|
187
106
|
|
|
188
|
-
##
|
|
107
|
+
## Features
|
|
189
108
|
|
|
190
|
-
###
|
|
109
|
+
### Database Features
|
|
110
|
+
- Full CRUD operations (Create, Read, Update, Delete)
|
|
111
|
+
- Advanced filtering (eq, neq, gt, gte, lt, lte, like, isNull, isNotNull, in, notIn, between, notBetween)
|
|
112
|
+
- Logical operators (AND, OR)
|
|
113
|
+
- GROUP BY and aggregate functions (COUNT, SUM, AVG, MAX, MIN)
|
|
114
|
+
- HAVING clause for filtering aggregated results
|
|
115
|
+
- Multiple ORDER BY columns
|
|
116
|
+
- Date/time functions in SELECT and filters
|
|
117
|
+
- Expressions in SELECT (e.g., `COUNT(*)`, `DATE(created_at) as date`)
|
|
118
|
+
- Pagination (limit, offset, paginate)
|
|
119
|
+
- Sorting (orderBy)
|
|
120
|
+
- Get record by ID
|
|
121
|
+
- Bulk insert
|
|
122
|
+
- Upsert (insert or update on conflict)
|
|
123
|
+
- Table schema introspection
|
|
124
|
+
- Raw SQL queries
|
|
125
|
+
- Full TypeScript generics support
|
|
126
|
+
- Health check endpoint
|
|
127
|
+
|
|
128
|
+
### Authentication Features
|
|
129
|
+
- Email/password sign up and sign in
|
|
130
|
+
- OAuth provider support (GitHub, Google, etc.)
|
|
131
|
+
- Password reset flow (forgot + reset)
|
|
132
|
+
- OTP (one-time password) via email
|
|
133
|
+
- Magic link authentication
|
|
134
|
+
- Email verification and resend
|
|
135
|
+
- Session management (get, set, clear, refresh)
|
|
136
|
+
- Change password
|
|
137
|
+
- Update user profile
|
|
138
|
+
- Custom token storage
|
|
139
|
+
|
|
140
|
+
### Storage Features
|
|
141
|
+
- Bucket management (create, list, get, update, delete)
|
|
142
|
+
- File upload from data or local path
|
|
143
|
+
- File download to memory or local path
|
|
144
|
+
- File listing with prefix and pagination
|
|
145
|
+
- File deletion
|
|
146
|
+
- Public URL generation
|
|
147
|
+
- Storage statistics and quota management
|
|
148
|
+
|
|
149
|
+
## Database Operations
|
|
150
|
+
|
|
151
|
+
### Select Queries
|
|
191
152
|
|
|
192
153
|
```typescript
|
|
193
|
-
|
|
194
|
-
id: number;
|
|
195
|
-
name: string;
|
|
196
|
-
email: string;
|
|
197
|
-
age: number;
|
|
198
|
-
created_at: string;
|
|
199
|
-
}
|
|
154
|
+
import { WowSQLClient } from 'wowsql';
|
|
200
155
|
|
|
201
|
-
const client = new
|
|
202
|
-
projectUrl: '
|
|
156
|
+
const client = new WowSQLClient({
|
|
157
|
+
projectUrl: 'https://your-project.wowsql.com',
|
|
203
158
|
apiKey: 'your-api-key'
|
|
204
159
|
});
|
|
205
160
|
|
|
206
|
-
//
|
|
207
|
-
const users = await client.table
|
|
208
|
-
users.data.forEach(user => {
|
|
209
|
-
console.log(user.name); // Type-safe!
|
|
210
|
-
});
|
|
211
|
-
```
|
|
161
|
+
// Select all columns
|
|
162
|
+
const users = await client.table('users').select('*').get();
|
|
212
163
|
|
|
213
|
-
|
|
164
|
+
// Select specific columns
|
|
165
|
+
const users = await client.table('users')
|
|
166
|
+
.select('id', 'name', 'email')
|
|
167
|
+
.get();
|
|
214
168
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
});
|
|
169
|
+
// With filters
|
|
170
|
+
const activeUsers = await client.table('users')
|
|
171
|
+
.select('*')
|
|
172
|
+
.eq('status', 'active')
|
|
173
|
+
.gt('age', 18)
|
|
174
|
+
.get();
|
|
222
175
|
|
|
223
|
-
|
|
224
|
-
|
|
176
|
+
// With ordering
|
|
177
|
+
const recentUsers = await client.table('users')
|
|
178
|
+
.select('*')
|
|
179
|
+
.orderBy('created_at', 'desc')
|
|
180
|
+
.limit(10)
|
|
181
|
+
.get();
|
|
225
182
|
|
|
226
|
-
|
|
183
|
+
// With pagination
|
|
184
|
+
const page1 = await client.table('users').select('*').limit(20).offset(0).get();
|
|
185
|
+
const page2 = await client.table('users').select('*').limit(20).offset(20).get();
|
|
186
|
+
|
|
187
|
+
// Using paginate helper
|
|
188
|
+
const paginated = await client.table('users').paginate(1, 20);
|
|
189
|
+
console.log(paginated.data); // records for page 1
|
|
190
|
+
console.log(paginated.total); // total record count
|
|
191
|
+
console.log(paginated.page); // current page
|
|
192
|
+
console.log(paginated.perPage); // items per page
|
|
193
|
+
|
|
194
|
+
// Pattern matching
|
|
195
|
+
const gmailUsers = await client.table('users')
|
|
196
|
+
.select('*')
|
|
197
|
+
.like('email', '%@gmail.com')
|
|
198
|
+
.get();
|
|
227
199
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
200
|
+
// IN operator
|
|
201
|
+
const categories = await client.table('products')
|
|
202
|
+
.select('*')
|
|
203
|
+
.in('category', ['electronics', 'books', 'clothing'])
|
|
204
|
+
.get();
|
|
231
205
|
|
|
232
|
-
//
|
|
233
|
-
const
|
|
206
|
+
// BETWEEN operator
|
|
207
|
+
const priceRange = await client.table('products')
|
|
208
|
+
.select('*')
|
|
209
|
+
.between('price', [10, 100])
|
|
210
|
+
.get();
|
|
234
211
|
|
|
235
|
-
//
|
|
236
|
-
const
|
|
237
|
-
.select(
|
|
212
|
+
// NOT IN operator
|
|
213
|
+
const active = await client.table('products')
|
|
214
|
+
.select('*')
|
|
215
|
+
.notIn('status', ['deleted', 'archived'])
|
|
238
216
|
.get();
|
|
239
217
|
|
|
240
|
-
//
|
|
241
|
-
const
|
|
242
|
-
.
|
|
218
|
+
// NOT BETWEEN operator
|
|
219
|
+
const outliers = await client.table('products')
|
|
220
|
+
.select('*')
|
|
221
|
+
.notBetween('price', [10, 100])
|
|
243
222
|
.get();
|
|
244
223
|
|
|
245
|
-
//
|
|
246
|
-
const
|
|
247
|
-
.
|
|
248
|
-
.
|
|
224
|
+
// IS NULL / IS NOT NULL
|
|
225
|
+
const noEmail = await client.table('users')
|
|
226
|
+
.select('*')
|
|
227
|
+
.isNull('email')
|
|
249
228
|
.get();
|
|
250
229
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
.
|
|
230
|
+
const hasEmail = await client.table('users')
|
|
231
|
+
.select('*')
|
|
232
|
+
.isNotNull('email')
|
|
254
233
|
.get();
|
|
255
234
|
|
|
256
|
-
//
|
|
257
|
-
const
|
|
258
|
-
.
|
|
259
|
-
.
|
|
235
|
+
// OR conditions
|
|
236
|
+
const results = await client.table('products')
|
|
237
|
+
.select('*')
|
|
238
|
+
.filter('category', 'eq', 'electronics', 'AND')
|
|
239
|
+
.or('price', 'gt', 1000)
|
|
260
240
|
.get();
|
|
261
241
|
|
|
262
|
-
// Get first record
|
|
242
|
+
// Get first record only
|
|
263
243
|
const firstUser = await client.table('users')
|
|
264
|
-
.
|
|
244
|
+
.select('*')
|
|
245
|
+
.eq('email', 'john@example.com')
|
|
265
246
|
.first();
|
|
247
|
+
|
|
248
|
+
// Get single record (throws if not exactly one)
|
|
249
|
+
const singleUser = await client.table('users')
|
|
250
|
+
.select('*')
|
|
251
|
+
.eq('id', 1)
|
|
252
|
+
.single();
|
|
253
|
+
|
|
254
|
+
// Count records
|
|
255
|
+
const totalUsers = await client.table('users').count();
|
|
256
|
+
console.log(`Total users: ${totalUsers}`);
|
|
257
|
+
|
|
258
|
+
// Sum a column
|
|
259
|
+
const totalRevenue = await client.table('orders')
|
|
260
|
+
.select('*')
|
|
261
|
+
.eq('status', 'completed')
|
|
262
|
+
.sum('total');
|
|
263
|
+
|
|
264
|
+
// Average a column
|
|
265
|
+
const avgPrice = await client.table('products')
|
|
266
|
+
.select('*')
|
|
267
|
+
.avg('price');
|
|
266
268
|
```
|
|
267
269
|
|
|
268
|
-
###
|
|
270
|
+
### Insert Data
|
|
269
271
|
|
|
270
272
|
```typescript
|
|
271
|
-
//
|
|
272
|
-
const result = await client.table('users').
|
|
273
|
-
name: '
|
|
274
|
-
|
|
273
|
+
// Insert a single record
|
|
274
|
+
const result = await client.table('users').create({
|
|
275
|
+
name: 'John Doe',
|
|
276
|
+
email: 'john@example.com',
|
|
277
|
+
age: 30
|
|
278
|
+
});
|
|
279
|
+
console.log(result.id); // new record ID
|
|
280
|
+
|
|
281
|
+
// Using the insert alias
|
|
282
|
+
const result = await client.table('users').insert({
|
|
283
|
+
name: 'Alice',
|
|
284
|
+
email: 'alice@example.com'
|
|
275
285
|
});
|
|
276
286
|
|
|
277
|
-
|
|
287
|
+
// Bulk insert multiple records
|
|
288
|
+
const results = await client.table('users').bulkInsert([
|
|
289
|
+
{ name: 'Alice', email: 'alice@example.com', age: 28 },
|
|
290
|
+
{ name: 'Bob', email: 'bob@example.com', age: 32 },
|
|
291
|
+
{ name: 'Carol', email: 'carol@example.com', age: 24 }
|
|
292
|
+
]);
|
|
293
|
+
console.log(`Inserted ${results.length} records`);
|
|
278
294
|
```
|
|
279
295
|
|
|
280
|
-
###
|
|
296
|
+
### Upsert Data
|
|
281
297
|
|
|
282
298
|
```typescript
|
|
283
|
-
//
|
|
284
|
-
const result = await client.table('users').
|
|
299
|
+
// Insert or update on conflict (default conflict column: id)
|
|
300
|
+
const result = await client.table('users').upsert({
|
|
301
|
+
id: 1,
|
|
302
|
+
name: 'John Updated',
|
|
303
|
+
email: 'john@example.com'
|
|
304
|
+
});
|
|
285
305
|
|
|
286
|
-
|
|
306
|
+
// Specify conflict column
|
|
307
|
+
const result = await client.table('users').upsert(
|
|
308
|
+
{ email: 'john@example.com', name: 'John Updated' },
|
|
309
|
+
'email'
|
|
310
|
+
);
|
|
287
311
|
```
|
|
288
312
|
|
|
289
|
-
###
|
|
313
|
+
### Update Data
|
|
290
314
|
|
|
291
315
|
```typescript
|
|
292
|
-
//
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
.filter({ column: 'age', operator: 'gt', value: 18 })
|
|
300
|
-
|
|
301
|
-
// Greater than or equal
|
|
302
|
-
.filter({ column: 'age', operator: 'gte', value: 18 })
|
|
303
|
-
|
|
304
|
-
// Less than
|
|
305
|
-
.filter({ column: 'price', operator: 'lt', value: 100 })
|
|
306
|
-
|
|
307
|
-
// Less than or equal
|
|
308
|
-
.filter({ column: 'price', operator: 'lte', value: 100 })
|
|
309
|
-
|
|
310
|
-
// Like (pattern matching)
|
|
311
|
-
.filter({ column: 'name', operator: 'like', value: '%John%' })
|
|
312
|
-
|
|
313
|
-
// Is null
|
|
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])
|
|
316
|
+
// Update by ID
|
|
317
|
+
const result = await client.table('users').update(1, {
|
|
318
|
+
name: 'Jane Smith',
|
|
319
|
+
age: 26
|
|
320
|
+
});
|
|
321
|
+
console.log(result.affected_rows);
|
|
322
|
+
```
|
|
324
323
|
|
|
325
|
-
|
|
326
|
-
.filter('age', 'not_between', [18, 65])
|
|
324
|
+
### Delete Data
|
|
327
325
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
326
|
+
```typescript
|
|
327
|
+
// Delete by ID
|
|
328
|
+
const result = await client.table('users').delete(1);
|
|
329
|
+
console.log(result.affected_rows);
|
|
331
330
|
```
|
|
332
331
|
|
|
333
332
|
## Advanced Query Features
|
|
@@ -340,93 +339,198 @@ GROUP BY supports both simple column names and SQL expressions with functions. A
|
|
|
340
339
|
|
|
341
340
|
```typescript
|
|
342
341
|
// Group by single column
|
|
343
|
-
const result = await client.table(
|
|
344
|
-
.select(
|
|
345
|
-
.groupBy(
|
|
342
|
+
const result = await client.table('products')
|
|
343
|
+
.select('category', 'COUNT(*) as count', 'AVG(price) as avg_price')
|
|
344
|
+
.groupBy('category')
|
|
346
345
|
.get();
|
|
347
346
|
|
|
348
347
|
// Group by multiple columns
|
|
349
|
-
const result = await client.table(
|
|
350
|
-
.select(
|
|
351
|
-
.groupBy([
|
|
348
|
+
const result = await client.table('sales')
|
|
349
|
+
.select('region', 'category', 'SUM(amount) as total')
|
|
350
|
+
.groupBy(['region', 'category'])
|
|
352
351
|
.get();
|
|
353
352
|
```
|
|
354
353
|
|
|
355
354
|
#### GROUP BY with Date/Time Functions
|
|
356
355
|
|
|
357
356
|
```typescript
|
|
358
|
-
// Group by date
|
|
359
|
-
const result = await client.table(
|
|
360
|
-
.select(
|
|
361
|
-
.groupBy(
|
|
362
|
-
.orderBy(
|
|
357
|
+
// Group by date (extract date part)
|
|
358
|
+
const result = await client.table('orders')
|
|
359
|
+
.select('DATE(created_at) as date', 'COUNT(*) as orders', 'SUM(total) as revenue')
|
|
360
|
+
.groupBy('DATE(created_at)')
|
|
361
|
+
.orderBy('date', 'desc')
|
|
362
|
+
.get();
|
|
363
|
+
|
|
364
|
+
// Group by year
|
|
365
|
+
const result = await client.table('orders')
|
|
366
|
+
.select('YEAR(created_at) as year', 'COUNT(*) as orders')
|
|
367
|
+
.groupBy('YEAR(created_at)')
|
|
363
368
|
.get();
|
|
364
369
|
|
|
365
370
|
// Group by year and month
|
|
366
|
-
const result = await client.table(
|
|
367
|
-
.select(
|
|
368
|
-
|
|
371
|
+
const result = await client.table('orders')
|
|
372
|
+
.select(
|
|
373
|
+
'YEAR(created_at) as year',
|
|
374
|
+
'MONTH(created_at) as month',
|
|
375
|
+
'SUM(total) as revenue'
|
|
376
|
+
)
|
|
377
|
+
.groupBy(['YEAR(created_at)', 'MONTH(created_at)'])
|
|
378
|
+
.orderBy('year', 'desc')
|
|
379
|
+
.orderBy('month', 'desc')
|
|
369
380
|
.get();
|
|
370
381
|
|
|
371
382
|
// Group by week
|
|
372
|
-
const result = await client.table(
|
|
373
|
-
.select(
|
|
374
|
-
.groupBy(
|
|
383
|
+
const result = await client.table('orders')
|
|
384
|
+
.select('WEEK(created_at) as week', 'COUNT(*) as orders')
|
|
385
|
+
.groupBy('WEEK(created_at)')
|
|
386
|
+
.get();
|
|
387
|
+
|
|
388
|
+
// Group by quarter
|
|
389
|
+
const result = await client.table('orders')
|
|
390
|
+
.select('QUARTER(created_at) as quarter', 'SUM(total) as revenue')
|
|
391
|
+
.groupBy('QUARTER(created_at)')
|
|
392
|
+
.get();
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
#### GROUP BY with String Functions
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
// Group by first letter of name
|
|
399
|
+
const result = await client.table('users')
|
|
400
|
+
.select('LEFT(name, 1) as first_letter', 'COUNT(*) as count')
|
|
401
|
+
.groupBy('LEFT(name, 1)')
|
|
402
|
+
.get();
|
|
403
|
+
|
|
404
|
+
// Group by uppercase category
|
|
405
|
+
const result = await client.table('products')
|
|
406
|
+
.select('UPPER(category) as category_upper', 'COUNT(*) as count')
|
|
407
|
+
.groupBy('UPPER(category)')
|
|
408
|
+
.get();
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
#### GROUP BY with Mathematical Functions
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
// Group by rounded price ranges
|
|
415
|
+
const result = await client.table('products')
|
|
416
|
+
.select('ROUND(price, -1) as price_range', 'COUNT(*) as count')
|
|
417
|
+
.groupBy('ROUND(price, -1)')
|
|
418
|
+
.get();
|
|
419
|
+
|
|
420
|
+
// Group by price tier (using FLOOR)
|
|
421
|
+
const result = await client.table('products')
|
|
422
|
+
.select('FLOOR(price / 10) * 10 as price_tier', 'COUNT(*) as count')
|
|
423
|
+
.groupBy('FLOOR(price / 10) * 10')
|
|
375
424
|
.get();
|
|
376
425
|
```
|
|
377
426
|
|
|
378
427
|
#### Supported Functions in GROUP BY
|
|
379
428
|
|
|
380
|
-
|
|
429
|
+
The following functions are allowed in GROUP BY expressions:
|
|
430
|
+
|
|
431
|
+
**Date/Time Functions:**
|
|
432
|
+
- `DATE()`, `YEAR()`, `MONTH()`, `DAY()`, `DAYOFMONTH()`, `DAYOFWEEK()`, `DAYOFYEAR()`
|
|
433
|
+
- `WEEK()`, `QUARTER()`, `HOUR()`, `MINUTE()`, `SECOND()`
|
|
434
|
+
- `DATE_FORMAT()`, `TIME()`, `DATE_ADD()`, `DATE_SUB()`
|
|
435
|
+
- `DATEDIFF()`, `TIMEDIFF()`, `TIMESTAMPDIFF()`
|
|
436
|
+
- `NOW()`, `CURRENT_TIMESTAMP()`, `CURDATE()`, `CURRENT_DATE()`
|
|
437
|
+
- `CURTIME()`, `CURRENT_TIME()`, `UNIX_TIMESTAMP()`
|
|
438
|
+
|
|
439
|
+
**String Functions:**
|
|
440
|
+
- `CONCAT()`, `CONCAT_WS()`, `SUBSTRING()`, `SUBSTR()`, `LEFT()`, `RIGHT()`
|
|
441
|
+
- `LENGTH()`, `CHAR_LENGTH()`, `UPPER()`, `LOWER()`, `TRIM()`, `LTRIM()`, `RTRIM()`
|
|
442
|
+
- `REPLACE()`, `LOCATE()`, `POSITION()`
|
|
381
443
|
|
|
382
|
-
**
|
|
444
|
+
**Mathematical Functions:**
|
|
445
|
+
- `ABS()`, `ROUND()`, `CEIL()`, `CEILING()`, `FLOOR()`, `POW()`, `POWER()`, `SQRT()`, `MOD()`, `RAND()`
|
|
383
446
|
|
|
384
|
-
|
|
447
|
+
> All expressions are validated for security. Only whitelisted functions are allowed.
|
|
385
448
|
|
|
386
449
|
### HAVING Clause
|
|
387
450
|
|
|
388
|
-
HAVING
|
|
451
|
+
HAVING is used to filter aggregated results after GROUP BY. It supports aggregate functions and comparison operators.
|
|
452
|
+
|
|
453
|
+
#### Basic HAVING
|
|
389
454
|
|
|
390
455
|
```typescript
|
|
391
456
|
// Filter aggregated results
|
|
392
|
-
const result = await client.table(
|
|
393
|
-
.select(
|
|
394
|
-
.groupBy(
|
|
395
|
-
.having(
|
|
457
|
+
const result = await client.table('products')
|
|
458
|
+
.select('category', 'COUNT(*) as count')
|
|
459
|
+
.groupBy('category')
|
|
460
|
+
.having('COUNT(*)', 'gt', 10)
|
|
461
|
+
.get();
|
|
462
|
+
|
|
463
|
+
// Multiple HAVING conditions (AND logic)
|
|
464
|
+
const result = await client.table('orders')
|
|
465
|
+
.select('DATE(created_at) as date', 'SUM(total) as revenue')
|
|
466
|
+
.groupBy('DATE(created_at)')
|
|
467
|
+
.having('SUM(total)', 'gt', 1000)
|
|
468
|
+
.having('COUNT(*)', 'gte', 5)
|
|
469
|
+
.get();
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
#### HAVING with Aggregate Functions
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
// Filter by average
|
|
476
|
+
const result = await client.table('products')
|
|
477
|
+
.select('category', 'AVG(price) as avg_price', 'COUNT(*) as count')
|
|
478
|
+
.groupBy('category')
|
|
479
|
+
.having('AVG(price)', 'gt', 100)
|
|
480
|
+
.having('COUNT(*)', 'gte', 5)
|
|
396
481
|
.get();
|
|
397
482
|
|
|
398
|
-
//
|
|
399
|
-
const result = await client.table(
|
|
400
|
-
.select(
|
|
401
|
-
.groupBy(
|
|
402
|
-
.having(
|
|
403
|
-
.having("COUNT(*)", "gte", 5)
|
|
483
|
+
// Filter by sum
|
|
484
|
+
const result = await client.table('orders')
|
|
485
|
+
.select('customer_id', 'SUM(total) as total_spent')
|
|
486
|
+
.groupBy('customer_id')
|
|
487
|
+
.having('SUM(total)', 'gt', 1000)
|
|
404
488
|
.get();
|
|
405
489
|
|
|
406
|
-
//
|
|
407
|
-
const result = await client.table(
|
|
408
|
-
.select(
|
|
409
|
-
.groupBy(
|
|
410
|
-
.having(
|
|
411
|
-
.having("COUNT(*)", "gte", 5)
|
|
490
|
+
// Filter by max/min
|
|
491
|
+
const result = await client.table('products')
|
|
492
|
+
.select('category', 'MAX(price) as max_price', 'MIN(price) as min_price')
|
|
493
|
+
.groupBy('category')
|
|
494
|
+
.having('MAX(price)', 'gt', 500)
|
|
412
495
|
.get();
|
|
413
496
|
```
|
|
414
497
|
|
|
415
|
-
|
|
498
|
+
#### Supported HAVING Operators
|
|
416
499
|
|
|
417
|
-
|
|
500
|
+
- `eq` - Equal to
|
|
501
|
+
- `neq` - Not equal to
|
|
502
|
+
- `gt` - Greater than
|
|
503
|
+
- `gte` - Greater than or equal to
|
|
504
|
+
- `lt` - Less than
|
|
505
|
+
- `lte` - Less than or equal to
|
|
506
|
+
|
|
507
|
+
#### Supported Aggregate Functions in HAVING
|
|
508
|
+
|
|
509
|
+
- `COUNT(*)` or `COUNT(column)` - Count of rows
|
|
510
|
+
- `SUM(column)` - Sum of values
|
|
511
|
+
- `AVG(column)` - Average of values
|
|
512
|
+
- `MAX(column)` - Maximum value
|
|
513
|
+
- `MIN(column)` - Minimum value
|
|
514
|
+
- `GROUP_CONCAT(column)` - Concatenated values
|
|
515
|
+
- `STDDEV(column)`, `STDDEV_POP(column)`, `STDDEV_SAMP(column)` - Standard deviation
|
|
516
|
+
- `VARIANCE(column)`, `VAR_POP(column)`, `VAR_SAMP(column)` - Variance
|
|
418
517
|
|
|
419
518
|
### Multiple ORDER BY
|
|
420
519
|
|
|
421
520
|
```typescript
|
|
422
|
-
//
|
|
423
|
-
const result = await client.table(
|
|
424
|
-
.select(
|
|
425
|
-
.
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
521
|
+
// Chain multiple orderBy calls
|
|
522
|
+
const result = await client.table('products')
|
|
523
|
+
.select('*')
|
|
524
|
+
.orderBy('category', 'asc')
|
|
525
|
+
.orderBy('price', 'desc')
|
|
526
|
+
.orderBy('created_at', 'desc')
|
|
527
|
+
.get();
|
|
528
|
+
|
|
529
|
+
// Using the order alias
|
|
530
|
+
const result = await client.table('products')
|
|
531
|
+
.select('*')
|
|
532
|
+
.order('category', 'asc')
|
|
533
|
+
.order('price', 'desc')
|
|
430
534
|
.get();
|
|
431
535
|
```
|
|
432
536
|
|
|
@@ -434,37 +538,23 @@ const result = await client.table("products")
|
|
|
434
538
|
|
|
435
539
|
```typescript
|
|
436
540
|
// Filter by date range using functions
|
|
437
|
-
const result = await client.table(
|
|
438
|
-
.select(
|
|
439
|
-
.filter(
|
|
541
|
+
const result = await client.table('orders')
|
|
542
|
+
.select('*')
|
|
543
|
+
.filter('created_at', 'gte', 'DATE_SUB(NOW(), INTERVAL 7 DAY)')
|
|
440
544
|
.get();
|
|
441
545
|
|
|
442
546
|
// Group by date
|
|
443
|
-
const result = await client.table(
|
|
444
|
-
.select(
|
|
445
|
-
.groupBy(
|
|
547
|
+
const result = await client.table('orders')
|
|
548
|
+
.select('DATE(created_at) as date', 'COUNT(*) as count')
|
|
549
|
+
.groupBy('DATE(created_at)')
|
|
446
550
|
.get();
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
### Complex Queries
|
|
450
551
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
.
|
|
455
|
-
.filter(
|
|
456
|
-
.filter({ column: 'price', operator: 'lt', value: 1000 })
|
|
457
|
-
.filter({ column: 'in_stock', operator: 'eq', value: true })
|
|
458
|
-
.order('price', 'asc')
|
|
459
|
-
.limit(20)
|
|
460
|
-
.offset(0)
|
|
552
|
+
// Filter by year/month
|
|
553
|
+
const result = await client.table('orders')
|
|
554
|
+
.select('*')
|
|
555
|
+
.filter('YEAR(created_at)', 'eq', 2024)
|
|
556
|
+
.filter('MONTH(created_at)', 'eq', 1)
|
|
461
557
|
.get();
|
|
462
|
-
|
|
463
|
-
console.log(`Found ${result.total} products`);
|
|
464
|
-
console.log(`Showing ${result.count} products`);
|
|
465
|
-
result.data.forEach(product => {
|
|
466
|
-
console.log(`${product.name}: $${product.price}`);
|
|
467
|
-
});
|
|
468
558
|
```
|
|
469
559
|
|
|
470
560
|
### Raw SQL Queries
|
|
@@ -472,15 +562,15 @@ result.data.forEach(product => {
|
|
|
472
562
|
```typescript
|
|
473
563
|
// Execute custom SQL (read-only)
|
|
474
564
|
const results = await client.query<User>(`
|
|
475
|
-
SELECT id, name, email
|
|
476
|
-
FROM users
|
|
477
|
-
WHERE age > 18
|
|
478
|
-
ORDER BY created_at DESC
|
|
565
|
+
SELECT id, name, email
|
|
566
|
+
FROM users
|
|
567
|
+
WHERE age > 18
|
|
568
|
+
ORDER BY created_at DESC
|
|
479
569
|
LIMIT 10
|
|
480
570
|
`);
|
|
481
571
|
```
|
|
482
572
|
|
|
483
|
-
###
|
|
573
|
+
### Utility Methods
|
|
484
574
|
|
|
485
575
|
```typescript
|
|
486
576
|
// List all tables
|
|
@@ -489,448 +579,471 @@ console.log(tables); // ['users', 'posts', 'comments']
|
|
|
489
579
|
|
|
490
580
|
// Get table schema
|
|
491
581
|
const schema = await client.getTableSchema('users');
|
|
492
|
-
console.log(schema.columns);
|
|
493
|
-
console.log(schema.primary_key);
|
|
494
|
-
```
|
|
582
|
+
console.log(schema.columns); // [{ name: 'id', type: 'INT', ... }, ...]
|
|
583
|
+
console.log(schema.primary_key); // 'id'
|
|
495
584
|
|
|
496
|
-
|
|
585
|
+
// Get record by ID
|
|
586
|
+
const user = await client.table('users').getById(1);
|
|
587
|
+
console.log(user); // { id: 1, name: 'John', ... }
|
|
497
588
|
|
|
498
|
-
|
|
499
|
-
// Check API health
|
|
589
|
+
// Health check
|
|
500
590
|
const health = await client.health();
|
|
501
|
-
console.log(health.status);
|
|
591
|
+
console.log(health.status); // 'ok'
|
|
592
|
+
console.log(health.timestamp); // '2024-01-15T10:30:00Z'
|
|
593
|
+
|
|
594
|
+
// Close the client
|
|
595
|
+
client.close();
|
|
502
596
|
```
|
|
503
597
|
|
|
504
|
-
##
|
|
598
|
+
## Filter Operators
|
|
599
|
+
|
|
600
|
+
Complete list of all available filter operators with examples:
|
|
505
601
|
|
|
506
602
|
```typescript
|
|
507
|
-
|
|
603
|
+
// Equal
|
|
604
|
+
.eq('status', 'active')
|
|
508
605
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
console.error(error.response); // Full error response
|
|
515
|
-
} else {
|
|
516
|
-
console.error('Unexpected error:', error);
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
```
|
|
606
|
+
// Not equal
|
|
607
|
+
.neq('status', 'deleted')
|
|
608
|
+
|
|
609
|
+
// Greater than
|
|
610
|
+
.gt('age', 18)
|
|
520
611
|
|
|
521
|
-
|
|
612
|
+
// Greater than or equal
|
|
613
|
+
.gte('age', 18)
|
|
522
614
|
|
|
523
|
-
|
|
615
|
+
// Less than
|
|
616
|
+
.lt('price', 100)
|
|
524
617
|
|
|
525
|
-
|
|
618
|
+
// Less than or equal
|
|
619
|
+
.lte('price', 100)
|
|
526
620
|
|
|
527
|
-
|
|
621
|
+
// Pattern matching (SQL LIKE)
|
|
622
|
+
.like('email', '%@gmail.com')
|
|
528
623
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
- `getTableSchema(tableName: string): Promise<TableSchema>` - Get table schema
|
|
532
|
-
- `query<T>(sql: string): Promise<T[]>` - Execute raw SQL
|
|
533
|
-
- `health(): Promise<{status: string, timestamp: string}>` - Health check
|
|
624
|
+
// IS NULL
|
|
625
|
+
.isNull('deleted_at')
|
|
534
626
|
|
|
535
|
-
|
|
627
|
+
// IS NOT NULL
|
|
628
|
+
.isNotNull('email')
|
|
536
629
|
|
|
537
|
-
|
|
630
|
+
// IN operator (value must be an array)
|
|
631
|
+
.in('category', ['electronics', 'books', 'clothing'])
|
|
538
632
|
|
|
539
|
-
|
|
633
|
+
// NOT IN operator
|
|
634
|
+
.notIn('status', ['deleted', 'archived'])
|
|
540
635
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
- `get(options?: QueryOptions): Promise<QueryResponse<T>>` - Get records
|
|
544
|
-
- `getById(id: string | number): Promise<T>` - Get by ID
|
|
545
|
-
- `create(data: Partial<T>): Promise<CreateResponse>` - Create record
|
|
546
|
-
- `update(id: string | number, data: Partial<T>): Promise<UpdateResponse>` - Update record
|
|
547
|
-
- `delete(id: string | number): Promise<DeleteResponse>` - Delete record
|
|
636
|
+
// BETWEEN operator (value must be an array of 2 values)
|
|
637
|
+
.between('price', [10, 100])
|
|
548
638
|
|
|
549
|
-
|
|
639
|
+
// NOT BETWEEN operator
|
|
640
|
+
.notBetween('age', [18, 65])
|
|
550
641
|
|
|
551
|
-
|
|
642
|
+
// OR logical operator
|
|
643
|
+
.filter('category', 'eq', 'electronics', 'AND')
|
|
644
|
+
.filter('price', 'gt', 1000, 'OR')
|
|
552
645
|
|
|
553
|
-
|
|
646
|
+
// Using the generic filter method
|
|
647
|
+
.filter('column', 'operator', value)
|
|
648
|
+
.filter('column', 'operator', value, 'OR')
|
|
649
|
+
```
|
|
554
650
|
|
|
555
|
-
|
|
556
|
-
- `filter(filter: FilterExpression): this` - Add filter
|
|
557
|
-
- `order(column: string, direction?: 'asc' | 'desc'): this` - Order by
|
|
558
|
-
- `limit(limit: number): this` - Limit results
|
|
559
|
-
- `offset(offset: number): this` - Skip records
|
|
560
|
-
- `get(options?: QueryOptions): Promise<QueryResponse<T>>` - Execute query
|
|
561
|
-
- `first(): Promise<T | null>` - Get first record
|
|
651
|
+
## Authentication
|
|
562
652
|
|
|
563
|
-
|
|
653
|
+
The `ProjectAuthClient` provides a complete authentication system for your application.
|
|
564
654
|
|
|
565
|
-
###
|
|
655
|
+
### Initialization
|
|
566
656
|
|
|
567
657
|
```typescript
|
|
568
|
-
|
|
569
|
-
import { NextResponse } from 'next/server';
|
|
570
|
-
import WOWSQLClient from '@wowsql/sdk';
|
|
658
|
+
import { ProjectAuthClient } from 'wowsql';
|
|
571
659
|
|
|
572
|
-
const
|
|
573
|
-
projectUrl:
|
|
574
|
-
apiKey:
|
|
660
|
+
const auth = new ProjectAuthClient({
|
|
661
|
+
projectUrl: 'https://your-project.wowsql.com',
|
|
662
|
+
apiKey: 'your-anon-key'
|
|
575
663
|
});
|
|
576
664
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
.order('created_at', 'desc')
|
|
586
|
-
.limit(limit)
|
|
587
|
-
.offset((page - 1) * limit)
|
|
588
|
-
.get();
|
|
589
|
-
|
|
590
|
-
return NextResponse.json(users);
|
|
591
|
-
} catch (error) {
|
|
592
|
-
return NextResponse.json(
|
|
593
|
-
{ error: 'Failed to fetch users' },
|
|
594
|
-
{ status: 500 }
|
|
595
|
-
);
|
|
665
|
+
// With custom token storage (e.g., for React Native)
|
|
666
|
+
const auth = new ProjectAuthClient({
|
|
667
|
+
projectUrl: 'https://your-project.wowsql.com',
|
|
668
|
+
apiKey: 'your-anon-key',
|
|
669
|
+
storage: {
|
|
670
|
+
getItem: (key: string) => AsyncStorage.getItem(key),
|
|
671
|
+
setItem: (key: string, value: string) => AsyncStorage.setItem(key, value),
|
|
672
|
+
removeItem: (key: string) => AsyncStorage.removeItem(key)
|
|
596
673
|
}
|
|
597
|
-
}
|
|
674
|
+
});
|
|
675
|
+
```
|
|
598
676
|
|
|
599
|
-
|
|
600
|
-
try {
|
|
601
|
-
const body = await request.json();
|
|
602
|
-
const result = await client.table('users').create(body);
|
|
603
|
-
return NextResponse.json(result, { status: 201 });
|
|
604
|
-
} catch (error) {
|
|
605
|
-
return NextResponse.json(
|
|
606
|
-
{ error: 'Failed to create user' },
|
|
607
|
-
{ status: 500 }
|
|
608
|
-
);
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
```
|
|
612
|
-
|
|
613
|
-
### React Hook
|
|
677
|
+
### Sign Up
|
|
614
678
|
|
|
615
679
|
```typescript
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
projectUrl: process.env.NEXT_PUBLIC_WOWSQL_PROJECT!,
|
|
622
|
-
apiKey: process.env.NEXT_PUBLIC_WOWSQL_API_KEY!
|
|
680
|
+
const { user, session } = await auth.signUp({
|
|
681
|
+
email: 'user@example.com',
|
|
682
|
+
password: 'SuperSecret123',
|
|
683
|
+
fullName: 'Demo User',
|
|
684
|
+
userMetadata: { referrer: 'landing-page', plan: 'free' }
|
|
623
685
|
});
|
|
624
686
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
const [error, setError] = useState(null);
|
|
629
|
-
|
|
630
|
-
useEffect(() => {
|
|
631
|
-
async function fetchUsers() {
|
|
632
|
-
try {
|
|
633
|
-
const result = await client.table('users').get();
|
|
634
|
-
setUsers(result.data);
|
|
635
|
-
} catch (err) {
|
|
636
|
-
setError(err);
|
|
637
|
-
} finally {
|
|
638
|
-
setLoading(false);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
fetchUsers();
|
|
642
|
-
}, []);
|
|
643
|
-
|
|
644
|
-
return { users, loading, error };
|
|
645
|
-
}
|
|
687
|
+
console.log('User ID:', user?.id);
|
|
688
|
+
console.log('Email:', user?.email);
|
|
689
|
+
console.log('Access token:', session.accessToken);
|
|
646
690
|
```
|
|
647
691
|
|
|
648
|
-
###
|
|
692
|
+
### Sign In
|
|
649
693
|
|
|
650
694
|
```typescript
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
const app = express();
|
|
656
|
-
const client = new WOWSQLClient({
|
|
657
|
-
projectUrl: process.env.WOWSQL_PROJECT!,
|
|
658
|
-
apiKey: process.env.WOWSQL_API_KEY!
|
|
659
|
-
});
|
|
660
|
-
|
|
661
|
-
app.use(express.json());
|
|
662
|
-
|
|
663
|
-
// Get all posts
|
|
664
|
-
app.get('/api/posts', async (req, res) => {
|
|
665
|
-
try {
|
|
666
|
-
const { page = 1, limit = 10 } = req.query;
|
|
667
|
-
const posts = await client.table('posts')
|
|
668
|
-
.order('created_at', 'desc')
|
|
669
|
-
.limit(Number(limit))
|
|
670
|
-
.offset((Number(page) - 1) * Number(limit))
|
|
671
|
-
.get();
|
|
672
|
-
res.json(posts);
|
|
673
|
-
} catch (error) {
|
|
674
|
-
res.status(500).json({ error: error.message });
|
|
675
|
-
}
|
|
695
|
+
const { user, session } = await auth.signIn({
|
|
696
|
+
email: 'user@example.com',
|
|
697
|
+
password: 'SuperSecret123'
|
|
676
698
|
});
|
|
677
699
|
|
|
678
|
-
//
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
res.status(201).json(result);
|
|
683
|
-
} catch (error) {
|
|
684
|
-
res.status(500).json({ error: error.message });
|
|
685
|
-
}
|
|
700
|
+
// Persist the session for subsequent requests
|
|
701
|
+
auth.setSession({
|
|
702
|
+
accessToken: session.accessToken,
|
|
703
|
+
refreshToken: session.refreshToken
|
|
686
704
|
});
|
|
687
705
|
|
|
688
|
-
|
|
706
|
+
console.log('Logged in as:', user?.email);
|
|
689
707
|
```
|
|
690
708
|
|
|
691
|
-
|
|
709
|
+
### Get Current User
|
|
692
710
|
|
|
693
|
-
|
|
711
|
+
```typescript
|
|
712
|
+
// Uses the stored session token automatically
|
|
713
|
+
const currentUser = await auth.getUser();
|
|
714
|
+
console.log(currentUser.id);
|
|
715
|
+
console.log(currentUser.email);
|
|
716
|
+
console.log(currentUser.email_verified);
|
|
717
|
+
console.log(currentUser.full_name);
|
|
718
|
+
|
|
719
|
+
// Or pass a specific access token
|
|
720
|
+
const user = await auth.getUser('specific-access-token');
|
|
721
|
+
```
|
|
694
722
|
|
|
695
|
-
|
|
723
|
+
### OAuth Authentication
|
|
724
|
+
|
|
725
|
+
Complete OAuth flow with provider callback handling:
|
|
696
726
|
|
|
697
727
|
```typescript
|
|
698
|
-
//
|
|
699
|
-
|
|
700
|
-
|
|
728
|
+
// Step 1: Get the authorization URL
|
|
729
|
+
const { authorizationUrl } = await auth.getOAuthAuthorizationUrl(
|
|
730
|
+
'github',
|
|
731
|
+
'https://app.your-domain.com/auth/callback'
|
|
732
|
+
);
|
|
733
|
+
|
|
734
|
+
// Redirect user to the authorization URL
|
|
735
|
+
window.location.href = authorizationUrl;
|
|
736
|
+
|
|
737
|
+
// Step 2: Handle the callback (in your callback route)
|
|
738
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
739
|
+
const code = urlParams.get('code');
|
|
740
|
+
|
|
741
|
+
const { user, session } = await auth.exchangeOAuthCallback(
|
|
742
|
+
'github',
|
|
743
|
+
code!,
|
|
744
|
+
'https://app.your-domain.com/auth/callback'
|
|
745
|
+
);
|
|
701
746
|
|
|
702
|
-
|
|
703
|
-
|
|
747
|
+
console.log('Logged in via GitHub:', user?.email);
|
|
748
|
+
console.log('Access token:', session.accessToken);
|
|
704
749
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
750
|
+
// Save the session
|
|
751
|
+
auth.setSession({
|
|
752
|
+
accessToken: session.accessToken,
|
|
753
|
+
refreshToken: session.refreshToken
|
|
708
754
|
});
|
|
709
755
|
```
|
|
710
756
|
|
|
711
|
-
###
|
|
712
|
-
|
|
713
|
-
Create a single client instance:
|
|
757
|
+
### Password Reset
|
|
714
758
|
|
|
715
759
|
```typescript
|
|
716
|
-
//
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
projectUrl: process.env.WOWSQL_PROJECT!,
|
|
721
|
-
apiKey: process.env.WOWSQL_API_KEY!
|
|
722
|
-
});
|
|
760
|
+
// Step 1: Request a password reset email
|
|
761
|
+
const result = await auth.forgotPassword('user@example.com');
|
|
762
|
+
console.log(result.message);
|
|
763
|
+
// "If that email exists, a password reset link has been sent"
|
|
723
764
|
|
|
724
|
-
//
|
|
725
|
-
|
|
726
|
-
|
|
765
|
+
// Step 2: Reset the password (user clicks link in email, you extract the token)
|
|
766
|
+
const resetResult = await auth.resetPassword(
|
|
767
|
+
'reset-token-from-email',
|
|
768
|
+
'NewSecurePassword123'
|
|
769
|
+
);
|
|
770
|
+
console.log(resetResult.message);
|
|
771
|
+
// "Password reset successfully! You can now login with your new password"
|
|
727
772
|
```
|
|
728
773
|
|
|
729
|
-
###
|
|
730
|
-
|
|
731
|
-
Define interfaces for your data:
|
|
774
|
+
### OTP (One-Time Password)
|
|
732
775
|
|
|
733
776
|
```typescript
|
|
734
|
-
//
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
777
|
+
// Send OTP to user's email
|
|
778
|
+
await auth.sendOtp('user@example.com', 'login');
|
|
779
|
+
|
|
780
|
+
// Verify OTP entered by user
|
|
781
|
+
const result = await auth.verifyOtp(
|
|
782
|
+
'user@example.com',
|
|
783
|
+
'123456', // OTP code
|
|
784
|
+
'login'
|
|
785
|
+
);
|
|
786
|
+
console.log('Verified:', result);
|
|
787
|
+
|
|
788
|
+
// OTP for password reset
|
|
789
|
+
await auth.sendOtp('user@example.com', 'password_reset');
|
|
790
|
+
const resetResult = await auth.verifyOtp(
|
|
791
|
+
'user@example.com',
|
|
792
|
+
'654321',
|
|
793
|
+
'password_reset',
|
|
794
|
+
'NewPassword123' // new password
|
|
795
|
+
);
|
|
796
|
+
```
|
|
741
797
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
published_at: string | null;
|
|
748
|
-
}
|
|
798
|
+
### Magic Link
|
|
799
|
+
|
|
800
|
+
```typescript
|
|
801
|
+
// Send a magic link to user's email
|
|
802
|
+
await auth.sendMagicLink('user@example.com', 'login');
|
|
749
803
|
|
|
750
|
-
//
|
|
751
|
-
|
|
804
|
+
// The user clicks the link in their email, which redirects to your app
|
|
805
|
+
// with a token. No additional verification step needed on client side.
|
|
752
806
|
```
|
|
753
807
|
|
|
754
|
-
###
|
|
808
|
+
### Email Verification
|
|
755
809
|
|
|
756
|
-
|
|
810
|
+
```typescript
|
|
811
|
+
// Verify email with token (from verification email link)
|
|
812
|
+
await auth.verifyEmail('verification-token-from-email');
|
|
813
|
+
|
|
814
|
+
// Resend verification email
|
|
815
|
+
await auth.resendVerification('user@example.com');
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
### Change Password
|
|
757
819
|
|
|
758
820
|
```typescript
|
|
759
|
-
|
|
821
|
+
// Change password for authenticated user
|
|
822
|
+
await auth.changePassword(
|
|
823
|
+
'currentPassword123',
|
|
824
|
+
'newSecurePassword456'
|
|
825
|
+
);
|
|
760
826
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
console.error(`Database error: ${error.message}`);
|
|
768
|
-
return { success: false, error: error.message };
|
|
769
|
-
}
|
|
770
|
-
throw error;
|
|
771
|
-
}
|
|
772
|
-
}
|
|
827
|
+
// With explicit access token
|
|
828
|
+
await auth.changePassword(
|
|
829
|
+
'currentPassword123',
|
|
830
|
+
'newSecurePassword456',
|
|
831
|
+
'specific-access-token'
|
|
832
|
+
);
|
|
773
833
|
```
|
|
774
834
|
|
|
775
|
-
|
|
835
|
+
### Update User Profile
|
|
776
836
|
|
|
777
|
-
|
|
837
|
+
```typescript
|
|
838
|
+
// Update user metadata
|
|
839
|
+
const updatedUser = await auth.updateUser({
|
|
840
|
+
full_name: 'New Display Name',
|
|
841
|
+
user_metadata: { theme: 'dark', language: 'en' }
|
|
842
|
+
});
|
|
778
843
|
|
|
779
|
-
|
|
844
|
+
console.log('Updated name:', updatedUser.full_name);
|
|
780
845
|
|
|
781
|
-
|
|
846
|
+
// With explicit access token
|
|
847
|
+
const user = await auth.updateUser(
|
|
848
|
+
{ full_name: 'Admin User' },
|
|
849
|
+
'specific-access-token'
|
|
850
|
+
);
|
|
851
|
+
```
|
|
782
852
|
|
|
783
|
-
|
|
853
|
+
### Session Management
|
|
784
854
|
|
|
785
|
-
|
|
855
|
+
```typescript
|
|
856
|
+
// Get current session
|
|
857
|
+
const session = auth.getSession();
|
|
858
|
+
if (session) {
|
|
859
|
+
console.log('Access token:', session.accessToken);
|
|
860
|
+
console.log('Refresh token:', session.refreshToken);
|
|
861
|
+
}
|
|
786
862
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
863
|
+
// Set session (e.g., restore from localStorage on page load)
|
|
864
|
+
auth.setSession({
|
|
865
|
+
accessToken: localStorage.getItem('access_token')!,
|
|
866
|
+
refreshToken: localStorage.getItem('refresh_token')!
|
|
867
|
+
});
|
|
791
868
|
|
|
792
|
-
|
|
869
|
+
// Refresh an expired session
|
|
870
|
+
const newSession = await auth.refreshSession();
|
|
871
|
+
// Or with explicit refresh token
|
|
872
|
+
const newSession = await auth.refreshSession('stored-refresh-token');
|
|
793
873
|
|
|
794
|
-
|
|
874
|
+
// Logout
|
|
875
|
+
await auth.logout();
|
|
876
|
+
// Or with explicit access token
|
|
877
|
+
await auth.logout('specific-access-token');
|
|
795
878
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
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)
|
|
879
|
+
// Clear local session data
|
|
880
|
+
auth.clearSession();
|
|
881
|
+
```
|
|
802
882
|
|
|
803
|
-
|
|
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!
|
|
883
|
+
## Storage Operations
|
|
809
884
|
|
|
810
|
-
|
|
885
|
+
The `WowSQLStorage` client provides file storage capabilities backed by PostgreSQL.
|
|
811
886
|
|
|
812
|
-
|
|
887
|
+
### Initialization
|
|
813
888
|
|
|
814
889
|
```typescript
|
|
815
|
-
import
|
|
816
|
-
|
|
817
|
-
// Using Service Role Key (recommended for server-side, full access)
|
|
818
|
-
const client = new WOWSQLClient({
|
|
819
|
-
projectUrl: 'myproject',
|
|
820
|
-
apiKey: 'wowsql_service_your-service-key-here' // Service Role Key
|
|
821
|
-
});
|
|
890
|
+
import { WowSQLStorage } from 'wowsql';
|
|
822
891
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
apiKey: 'wowsql_anon_your-anon-key-here' // Anonymous Key
|
|
892
|
+
const storage = new WowSQLStorage({
|
|
893
|
+
projectUrl: 'https://your-project.wowsql.com',
|
|
894
|
+
apiKey: 'your-api-key'
|
|
827
895
|
});
|
|
828
|
-
|
|
829
|
-
// Query data
|
|
830
|
-
const users = await client.table('users').get();
|
|
831
896
|
```
|
|
832
897
|
|
|
833
|
-
###
|
|
834
|
-
|
|
835
|
-
**✨ UNIFIED AUTHENTICATION:** Use the **same keys** as database operations!
|
|
898
|
+
### Bucket Operations
|
|
836
899
|
|
|
837
900
|
```typescript
|
|
838
|
-
|
|
901
|
+
// Create a new bucket
|
|
902
|
+
await storage.createBucket('avatars', {
|
|
903
|
+
public: true,
|
|
904
|
+
allowedMimeTypes: ['image/png', 'image/jpeg', 'image/webp'],
|
|
905
|
+
fileSizeLimit: 5 * 1024 * 1024 // 5 MB
|
|
906
|
+
});
|
|
839
907
|
|
|
840
|
-
//
|
|
841
|
-
const
|
|
842
|
-
|
|
843
|
-
|
|
908
|
+
// List all buckets
|
|
909
|
+
const buckets = await storage.listBuckets();
|
|
910
|
+
buckets.forEach(bucket => {
|
|
911
|
+
console.log(`${bucket.name}: ${bucket.public ? 'public' : 'private'}`);
|
|
844
912
|
});
|
|
845
913
|
|
|
846
|
-
//
|
|
847
|
-
const
|
|
848
|
-
|
|
849
|
-
|
|
914
|
+
// Get bucket details
|
|
915
|
+
const bucket = await storage.getBucket('avatars');
|
|
916
|
+
console.log(bucket.name, bucket.created_at);
|
|
917
|
+
|
|
918
|
+
// Update bucket settings
|
|
919
|
+
await storage.updateBucket('avatars', {
|
|
920
|
+
public: false,
|
|
921
|
+
fileSizeLimit: 10 * 1024 * 1024 // increase to 10 MB
|
|
850
922
|
});
|
|
851
923
|
|
|
852
|
-
//
|
|
853
|
-
|
|
854
|
-
'github',
|
|
855
|
-
'https://app.example.com/auth/callback'
|
|
856
|
-
);
|
|
924
|
+
// Delete a bucket
|
|
925
|
+
await storage.deleteBucket('old-bucket');
|
|
857
926
|
```
|
|
858
927
|
|
|
859
|
-
|
|
928
|
+
### File Upload
|
|
860
929
|
|
|
861
|
-
|
|
930
|
+
```typescript
|
|
931
|
+
// Upload file data (Buffer, Blob, or File)
|
|
932
|
+
const result = await storage.upload('documents', fileData, 'reports/q1.pdf');
|
|
933
|
+
console.log('Uploaded:', result);
|
|
934
|
+
|
|
935
|
+
// Using the uploadFile alias
|
|
936
|
+
await storage.uploadFile('documents', fileBlob, 'reports/', 'q1-report.pdf');
|
|
937
|
+
|
|
938
|
+
// Upload from a local file path (Node.js)
|
|
939
|
+
await storage.uploadFromPath(
|
|
940
|
+
'/home/user/report.pdf',
|
|
941
|
+
'documents',
|
|
942
|
+
'reports/'
|
|
943
|
+
);
|
|
944
|
+
```
|
|
862
945
|
|
|
863
|
-
|
|
946
|
+
### File Download
|
|
864
947
|
|
|
865
948
|
```typescript
|
|
866
|
-
//
|
|
949
|
+
// Download file to memory
|
|
950
|
+
const data = await storage.download('documents', 'reports/q1.pdf');
|
|
951
|
+
|
|
952
|
+
// Download to a local file path (Node.js)
|
|
953
|
+
await storage.downloadToFile(
|
|
954
|
+
'documents',
|
|
955
|
+
'reports/q1.pdf',
|
|
956
|
+
'/home/user/downloads/q1.pdf'
|
|
957
|
+
);
|
|
958
|
+
```
|
|
867
959
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
960
|
+
### File Listing
|
|
961
|
+
|
|
962
|
+
```typescript
|
|
963
|
+
// List all files in a bucket
|
|
964
|
+
const files = await storage.listFiles('documents');
|
|
965
|
+
files.forEach(file => {
|
|
966
|
+
console.log(`${file.name}: ${file.size} bytes`);
|
|
872
967
|
});
|
|
873
968
|
|
|
874
|
-
//
|
|
875
|
-
const
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
969
|
+
// List with options (prefix, limit, offset)
|
|
970
|
+
const files = await storage.listFiles('documents', {
|
|
971
|
+
prefix: 'reports/',
|
|
972
|
+
limit: 50,
|
|
973
|
+
offset: 0
|
|
879
974
|
});
|
|
880
975
|
```
|
|
881
976
|
|
|
882
|
-
###
|
|
977
|
+
### File Deletion and URLs
|
|
883
978
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
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)
|
|
979
|
+
```typescript
|
|
980
|
+
// Delete a file
|
|
981
|
+
await storage.deleteFile('documents', 'reports/old-report.pdf');
|
|
890
982
|
|
|
891
|
-
|
|
983
|
+
// Get a public URL for a file
|
|
984
|
+
const url = storage.getPublicUrl('avatars', 'users/123/avatar.png');
|
|
985
|
+
console.log(url); // https://your-project.wowsql.com/storage/v1/...
|
|
986
|
+
```
|
|
892
987
|
|
|
893
|
-
|
|
894
|
-
2. **Use Anonymous Key** for client-side authentication flows (same key as database operations)
|
|
895
|
-
3. **Use Anonymous Key** for public database access with limited permissions
|
|
896
|
-
4. **Store keys in environment variables**, never hardcode them
|
|
897
|
-
5. **Rotate keys regularly** if compromised
|
|
988
|
+
### Storage Stats and Quota
|
|
898
989
|
|
|
899
|
-
|
|
990
|
+
```typescript
|
|
991
|
+
// Get storage statistics
|
|
992
|
+
const stats = await storage.getStats();
|
|
993
|
+
console.log('Total files:', stats.total_files);
|
|
994
|
+
console.log('Total size:', stats.total_size);
|
|
995
|
+
|
|
996
|
+
// Check storage quota
|
|
997
|
+
const quota = await storage.getQuota();
|
|
998
|
+
console.log(`Used: ${quota.used_gb} GB`);
|
|
999
|
+
console.log(`Total: ${quota.total_gb} GB`);
|
|
1000
|
+
console.log(`Available: ${quota.available_gb} GB`);
|
|
1001
|
+
|
|
1002
|
+
// Check quota before uploading
|
|
1003
|
+
if (quota.available_gb > fileSizeInGb) {
|
|
1004
|
+
await storage.upload('documents', fileData, 'path/to/file.pdf');
|
|
1005
|
+
} else {
|
|
1006
|
+
console.log('Storage limit reached. Upgrade your plan.');
|
|
1007
|
+
}
|
|
1008
|
+
```
|
|
900
1009
|
|
|
901
|
-
|
|
902
|
-
- Ensure you're using the correct key type for the operation
|
|
903
|
-
- Database operations require Service Role Key or Anonymous Key
|
|
904
|
-
- Authentication operations require Anonymous Key (client-side) or Service Role Key (server-side)
|
|
905
|
-
- Verify the key is copied correctly (no extra spaces)
|
|
1010
|
+
### Storage Error Handling
|
|
906
1011
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
1012
|
+
```typescript
|
|
1013
|
+
import { WowSQLStorage, StorageError, StorageLimitExceededError } from 'wowsql';
|
|
1014
|
+
|
|
1015
|
+
try {
|
|
1016
|
+
await storage.upload('documents', largeFile, 'path/to/huge-file.zip');
|
|
1017
|
+
} catch (error) {
|
|
1018
|
+
if (error instanceof StorageLimitExceededError) {
|
|
1019
|
+
console.error('Storage limit exceeded:', error.message);
|
|
1020
|
+
console.error('Please upgrade your plan or delete old files');
|
|
1021
|
+
} else if (error instanceof StorageError) {
|
|
1022
|
+
console.error('Storage error:', error.message);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
```
|
|
911
1026
|
|
|
912
|
-
##
|
|
1027
|
+
## Schema Management
|
|
913
1028
|
|
|
914
|
-
Programmatically manage your database schema with the `
|
|
1029
|
+
Programmatically manage your database schema with the `WowSQLSchema` client.
|
|
915
1030
|
|
|
916
|
-
>
|
|
1031
|
+
> **IMPORTANT**: Schema operations require a **Service Role Key** (`wowsql_service_...`). Anonymous keys will return a 403 Forbidden error.
|
|
917
1032
|
|
|
918
|
-
###
|
|
1033
|
+
### Initialization
|
|
919
1034
|
|
|
920
1035
|
```typescript
|
|
921
|
-
import {
|
|
1036
|
+
import { WowSQLSchema } from 'wowsql';
|
|
922
1037
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
'
|
|
926
|
-
|
|
927
|
-
);
|
|
1038
|
+
const schema = new WowSQLSchema({
|
|
1039
|
+
projectUrl: 'https://your-project.wowsql.com',
|
|
1040
|
+
serviceKey: 'wowsql_service_xyz789...'
|
|
1041
|
+
});
|
|
928
1042
|
```
|
|
929
1043
|
|
|
930
1044
|
### Create Table
|
|
931
1045
|
|
|
932
1046
|
```typescript
|
|
933
|
-
// Create a new table
|
|
934
1047
|
await schema.createTable({
|
|
935
1048
|
tableName: 'products',
|
|
936
1049
|
columns: [
|
|
@@ -946,8 +1059,6 @@ await schema.createTable({
|
|
|
946
1059
|
{ name: 'idx_price', columns: ['price'] }
|
|
947
1060
|
]
|
|
948
1061
|
});
|
|
949
|
-
|
|
950
|
-
console.log('Table created successfully!');
|
|
951
1062
|
```
|
|
952
1063
|
|
|
953
1064
|
### Alter Table
|
|
@@ -965,7 +1076,7 @@ await schema.alterTable({
|
|
|
965
1076
|
await schema.alterTable({
|
|
966
1077
|
tableName: 'products',
|
|
967
1078
|
modifyColumns: [
|
|
968
|
-
{ name: 'price', type: 'DECIMAL(12,2)' }
|
|
1079
|
+
{ name: 'price', type: 'DECIMAL(12,2)' }
|
|
969
1080
|
]
|
|
970
1081
|
});
|
|
971
1082
|
|
|
@@ -984,6 +1095,37 @@ await schema.alterTable({
|
|
|
984
1095
|
});
|
|
985
1096
|
```
|
|
986
1097
|
|
|
1098
|
+
### Column Operations
|
|
1099
|
+
|
|
1100
|
+
```typescript
|
|
1101
|
+
// Add a column
|
|
1102
|
+
await schema.addColumn('products', 'description', 'TEXT', {
|
|
1103
|
+
not_null: false,
|
|
1104
|
+
default: "''"
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
// Drop a column
|
|
1108
|
+
await schema.dropColumn('products', 'old_field');
|
|
1109
|
+
|
|
1110
|
+
// Rename a column
|
|
1111
|
+
await schema.renameColumn('products', 'name', 'product_name');
|
|
1112
|
+
|
|
1113
|
+
// Modify a column type or constraints
|
|
1114
|
+
await schema.modifyColumn('products', 'price', 'DECIMAL(14,2)', {
|
|
1115
|
+
not_null: true
|
|
1116
|
+
});
|
|
1117
|
+
```
|
|
1118
|
+
|
|
1119
|
+
### Index Operations
|
|
1120
|
+
|
|
1121
|
+
```typescript
|
|
1122
|
+
// Create an index
|
|
1123
|
+
await schema.createIndex('products', ['category', 'price'], {
|
|
1124
|
+
name: 'idx_category_price',
|
|
1125
|
+
unique: false
|
|
1126
|
+
});
|
|
1127
|
+
```
|
|
1128
|
+
|
|
987
1129
|
### Drop Table
|
|
988
1130
|
|
|
989
1131
|
```typescript
|
|
@@ -991,127 +1133,581 @@ await schema.alterTable({
|
|
|
991
1133
|
await schema.dropTable('old_table');
|
|
992
1134
|
|
|
993
1135
|
// Drop with CASCADE (removes dependent objects)
|
|
994
|
-
await schema.dropTable('products',
|
|
1136
|
+
await schema.dropTable('products', true);
|
|
995
1137
|
```
|
|
996
1138
|
|
|
997
1139
|
### Execute Raw SQL
|
|
998
1140
|
|
|
999
1141
|
```typescript
|
|
1000
|
-
// Execute custom schema SQL
|
|
1001
1142
|
await schema.executeSQL(`
|
|
1002
|
-
CREATE INDEX idx_product_name
|
|
1143
|
+
CREATE INDEX idx_product_name
|
|
1003
1144
|
ON products(product_name);
|
|
1004
1145
|
`);
|
|
1005
1146
|
|
|
1006
|
-
// Add a foreign key constraint
|
|
1007
1147
|
await schema.executeSQL(`
|
|
1008
|
-
ALTER TABLE orders
|
|
1009
|
-
ADD CONSTRAINT fk_product
|
|
1010
|
-
FOREIGN KEY (product_id)
|
|
1148
|
+
ALTER TABLE orders
|
|
1149
|
+
ADD CONSTRAINT fk_product
|
|
1150
|
+
FOREIGN KEY (product_id)
|
|
1011
1151
|
REFERENCES products(id);
|
|
1012
1152
|
`);
|
|
1013
1153
|
```
|
|
1014
1154
|
|
|
1015
|
-
###
|
|
1155
|
+
### Schema Introspection
|
|
1156
|
+
|
|
1157
|
+
```typescript
|
|
1158
|
+
// List all tables
|
|
1159
|
+
const tables = await schema.listTables();
|
|
1160
|
+
console.log(tables);
|
|
1161
|
+
|
|
1162
|
+
// Get detailed table schema
|
|
1163
|
+
const tableSchema = await schema.getTableSchema('users');
|
|
1164
|
+
console.log(tableSchema.columns);
|
|
1165
|
+
console.log(tableSchema.primary_key);
|
|
1166
|
+
console.log(tableSchema.indexes);
|
|
1167
|
+
```
|
|
1168
|
+
|
|
1169
|
+
### Schema Error Handling
|
|
1170
|
+
|
|
1171
|
+
```typescript
|
|
1172
|
+
import { WowSQLSchema, SchemaPermissionError } from 'wowsql';
|
|
1016
1173
|
|
|
1017
|
-
|
|
1174
|
+
try {
|
|
1175
|
+
await schema.createTable({
|
|
1176
|
+
tableName: 'test',
|
|
1177
|
+
columns: [{ name: 'id', type: 'INT', auto_increment: true }],
|
|
1178
|
+
primaryKey: 'id'
|
|
1179
|
+
});
|
|
1180
|
+
} catch (error) {
|
|
1181
|
+
if (error instanceof SchemaPermissionError) {
|
|
1182
|
+
console.error('Permission denied:', error.message);
|
|
1183
|
+
console.error('Make sure you are using a SERVICE ROLE KEY!');
|
|
1184
|
+
} else {
|
|
1185
|
+
console.error('Error:', error);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
```
|
|
1189
|
+
|
|
1190
|
+
### Schema Security Best Practices
|
|
1191
|
+
|
|
1192
|
+
**DO:**
|
|
1018
1193
|
- Use service role keys **only in backend/server code** (Node.js, Next.js API routes)
|
|
1019
1194
|
- Store service keys in environment variables
|
|
1020
1195
|
- Use anonymous keys for client-side data operations
|
|
1021
1196
|
- Test schema changes in development first
|
|
1022
1197
|
|
|
1023
|
-
|
|
1198
|
+
**DON'T:**
|
|
1024
1199
|
- Never expose service role keys in frontend/browser code
|
|
1025
1200
|
- Never commit service keys to version control
|
|
1026
|
-
- Don't use anonymous keys for schema operations (will fail)
|
|
1201
|
+
- Don't use anonymous keys for schema operations (will fail with 403)
|
|
1202
|
+
|
|
1203
|
+
## API Keys
|
|
1204
|
+
|
|
1205
|
+
WowSQL uses **unified authentication** - the same API keys work for database operations, authentication, storage, and schema management.
|
|
1206
|
+
|
|
1207
|
+
### Key Types
|
|
1208
|
+
|
|
1209
|
+
| Operation Type | Recommended Key | Alternative Key | Used By |
|
|
1210
|
+
|---|---|---|---|
|
|
1211
|
+
| **Database Operations** (CRUD) | Service Role Key (`wowsql_service_...`) | Anonymous Key (`wowsql_anon_...`) | `WowSQLClient` |
|
|
1212
|
+
| **Authentication** (sign-up, login, OAuth) | Anonymous Key (`wowsql_anon_...`) | Service Role Key (`wowsql_service_...`) | `ProjectAuthClient` |
|
|
1213
|
+
| **Storage** (upload, download) | Service Role Key (`wowsql_service_...`) | Anonymous Key (`wowsql_anon_...`) | `WowSQLStorage` |
|
|
1214
|
+
| **Schema Management** (DDL) | Service Role Key (`wowsql_service_...`) | N/A | `WowSQLSchema` |
|
|
1215
|
+
|
|
1216
|
+
### Where to Find Your Keys
|
|
1217
|
+
|
|
1218
|
+
All keys are in: **WowSQL Dashboard > Settings > API Keys** or **Authentication > PROJECT KEYS**
|
|
1219
|
+
|
|
1220
|
+
1. **Anonymous Key** (`wowsql_anon_...`)
|
|
1221
|
+
- Client-side auth operations (signup, login, OAuth)
|
|
1222
|
+
- Public/client-side database operations with limited permissions
|
|
1223
|
+
- **Safe to expose** in frontend code (browser, mobile apps)
|
|
1224
|
+
|
|
1225
|
+
2. **Service Role Key** (`wowsql_service_...`)
|
|
1226
|
+
- Server-side operations with full access (bypass RLS)
|
|
1227
|
+
- Schema management operations
|
|
1228
|
+
- **NEVER expose** in frontend code - server-side only!
|
|
1229
|
+
|
|
1230
|
+
### Environment Variables
|
|
1231
|
+
|
|
1232
|
+
```bash
|
|
1233
|
+
# .env
|
|
1234
|
+
WOWSQL_PROJECT_URL=https://your-project.wowsql.com
|
|
1235
|
+
WOWSQL_ANON_KEY=wowsql_anon_your-anon-key
|
|
1236
|
+
WOWSQL_SERVICE_ROLE_KEY=wowsql_service_your-service-key
|
|
1237
|
+
```
|
|
1238
|
+
|
|
1239
|
+
```typescript
|
|
1240
|
+
const dbClient = new WowSQLClient({
|
|
1241
|
+
projectUrl: process.env.WOWSQL_PROJECT_URL!,
|
|
1242
|
+
apiKey: process.env.WOWSQL_SERVICE_ROLE_KEY!
|
|
1243
|
+
});
|
|
1027
1244
|
|
|
1028
|
-
|
|
1245
|
+
const authClient = new ProjectAuthClient({
|
|
1246
|
+
projectUrl: process.env.WOWSQL_PROJECT_URL!,
|
|
1247
|
+
apiKey: process.env.WOWSQL_ANON_KEY!
|
|
1248
|
+
});
|
|
1249
|
+
|
|
1250
|
+
const storage = new WowSQLStorage({
|
|
1251
|
+
projectUrl: process.env.WOWSQL_PROJECT_URL!,
|
|
1252
|
+
apiKey: process.env.WOWSQL_SERVICE_ROLE_KEY!
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
const schema = new WowSQLSchema({
|
|
1256
|
+
projectUrl: process.env.WOWSQL_PROJECT_URL!,
|
|
1257
|
+
serviceKey: process.env.WOWSQL_SERVICE_ROLE_KEY!
|
|
1258
|
+
});
|
|
1259
|
+
```
|
|
1260
|
+
|
|
1261
|
+
### Security Best Practices
|
|
1262
|
+
|
|
1263
|
+
1. **Never expose Service Role Key** in client-side code or public repositories
|
|
1264
|
+
2. **Use Anonymous Key** for client-side authentication and limited database access
|
|
1265
|
+
3. **Store keys in environment variables**, never hardcode them
|
|
1266
|
+
4. **Rotate keys regularly** if compromised
|
|
1267
|
+
|
|
1268
|
+
### Troubleshooting
|
|
1269
|
+
|
|
1270
|
+
**Error: "Invalid API key for project"**
|
|
1271
|
+
- Ensure you're using the correct key type for the operation
|
|
1272
|
+
- Verify the key is copied correctly (no extra spaces)
|
|
1273
|
+
- Check that the project URL matches your dashboard
|
|
1274
|
+
|
|
1275
|
+
**Error: "Authentication failed"**
|
|
1276
|
+
- Check that you're using the correct key: Anonymous Key for client-side, Service Role Key for server-side
|
|
1277
|
+
- Ensure the key hasn't been revoked or expired
|
|
1278
|
+
|
|
1279
|
+
## Error Handling
|
|
1280
|
+
|
|
1281
|
+
```typescript
|
|
1282
|
+
import {
|
|
1283
|
+
WowSQLClient,
|
|
1284
|
+
WowSQLStorage,
|
|
1285
|
+
WOWSQLError,
|
|
1286
|
+
StorageError,
|
|
1287
|
+
StorageLimitExceededError,
|
|
1288
|
+
SchemaPermissionError
|
|
1289
|
+
} from 'wowsql';
|
|
1290
|
+
|
|
1291
|
+
// Database errors
|
|
1292
|
+
try {
|
|
1293
|
+
const users = await client.table('users').select('*').get();
|
|
1294
|
+
} catch (error) {
|
|
1295
|
+
if (error instanceof WOWSQLError) {
|
|
1296
|
+
console.error(`Database error [${error.statusCode}]: ${error.message}`);
|
|
1297
|
+
console.error('Response:', error.response);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
// Storage errors
|
|
1302
|
+
try {
|
|
1303
|
+
await storage.upload('bucket', fileData, 'path/to/file');
|
|
1304
|
+
} catch (error) {
|
|
1305
|
+
if (error instanceof StorageLimitExceededError) {
|
|
1306
|
+
console.error('Storage full:', error.message);
|
|
1307
|
+
} else if (error instanceof StorageError) {
|
|
1308
|
+
console.error('Storage error:', error.message);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
// Schema errors
|
|
1313
|
+
try {
|
|
1314
|
+
await schema.createTable({ tableName: 'test', columns: [] });
|
|
1315
|
+
} catch (error) {
|
|
1316
|
+
if (error instanceof SchemaPermissionError) {
|
|
1317
|
+
console.error('Use a Service Role Key for schema operations');
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
// Generic catch-all
|
|
1322
|
+
try {
|
|
1323
|
+
await client.table('users').getById(999);
|
|
1324
|
+
} catch (error) {
|
|
1325
|
+
if (error instanceof WOWSQLError) {
|
|
1326
|
+
switch (error.statusCode) {
|
|
1327
|
+
case 401: console.error('Unauthorized - check your API key'); break;
|
|
1328
|
+
case 404: console.error('Record not found'); break;
|
|
1329
|
+
case 429: console.error('Rate limit exceeded - slow down'); break;
|
|
1330
|
+
default: console.error(`Error ${error.statusCode}: ${error.message}`);
|
|
1331
|
+
}
|
|
1332
|
+
} else {
|
|
1333
|
+
console.error('Unexpected error:', error);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
```
|
|
1337
|
+
|
|
1338
|
+
## Configuration
|
|
1339
|
+
|
|
1340
|
+
### Basic Configuration
|
|
1341
|
+
|
|
1342
|
+
```typescript
|
|
1343
|
+
const client = new WowSQLClient({
|
|
1344
|
+
projectUrl: 'https://your-project.wowsql.com',
|
|
1345
|
+
apiKey: 'your-api-key'
|
|
1346
|
+
});
|
|
1347
|
+
```
|
|
1348
|
+
|
|
1349
|
+
### Advanced Configuration
|
|
1350
|
+
|
|
1351
|
+
```typescript
|
|
1352
|
+
const client = new WowSQLClient({
|
|
1353
|
+
projectUrl: 'your-project', // subdomain or full URL
|
|
1354
|
+
apiKey: 'your-api-key',
|
|
1355
|
+
baseDomain: 'wowsql.com', // custom domain (default: wowsql.com)
|
|
1356
|
+
secure: true, // use HTTPS (default: true)
|
|
1357
|
+
timeout: 30000, // request timeout in ms (default: 30000)
|
|
1358
|
+
verifySsl: true // verify SSL certificates (default: true)
|
|
1359
|
+
});
|
|
1360
|
+
```
|
|
1361
|
+
|
|
1362
|
+
### Storage Timeout
|
|
1363
|
+
|
|
1364
|
+
```typescript
|
|
1365
|
+
const storage = new WowSQLStorage({
|
|
1366
|
+
projectUrl: 'https://your-project.wowsql.com',
|
|
1367
|
+
apiKey: 'your-api-key',
|
|
1368
|
+
timeout: 120000 // 2 minutes for large file uploads
|
|
1369
|
+
});
|
|
1370
|
+
```
|
|
1371
|
+
|
|
1372
|
+
### Auth with Custom Storage
|
|
1373
|
+
|
|
1374
|
+
```typescript
|
|
1375
|
+
const auth = new ProjectAuthClient({
|
|
1376
|
+
projectUrl: 'https://your-project.wowsql.com',
|
|
1377
|
+
apiKey: 'your-anon-key',
|
|
1378
|
+
storage: {
|
|
1379
|
+
getItem: (key) => localStorage.getItem(key),
|
|
1380
|
+
setItem: (key, value) => localStorage.setItem(key, value),
|
|
1381
|
+
removeItem: (key) => localStorage.removeItem(key)
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1384
|
+
```
|
|
1385
|
+
|
|
1386
|
+
### Disabling SSL Verification (development only)
|
|
1387
|
+
|
|
1388
|
+
```typescript
|
|
1389
|
+
const client = new WowSQLClient({
|
|
1390
|
+
projectUrl: 'https://localhost:8443',
|
|
1391
|
+
apiKey: 'dev-key',
|
|
1392
|
+
verifySsl: false
|
|
1393
|
+
});
|
|
1394
|
+
```
|
|
1395
|
+
|
|
1396
|
+
## TypeScript Support
|
|
1397
|
+
|
|
1398
|
+
The SDK is written in TypeScript and provides full type safety with generics.
|
|
1399
|
+
|
|
1400
|
+
### Defining Your Types
|
|
1401
|
+
|
|
1402
|
+
```typescript
|
|
1403
|
+
interface User {
|
|
1404
|
+
id: number;
|
|
1405
|
+
name: string;
|
|
1406
|
+
email: string;
|
|
1407
|
+
age: number;
|
|
1408
|
+
created_at: string;
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
interface Post {
|
|
1412
|
+
id: number;
|
|
1413
|
+
user_id: number;
|
|
1414
|
+
title: string;
|
|
1415
|
+
content: string;
|
|
1416
|
+
published: boolean;
|
|
1417
|
+
published_at: string | null;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
// Type-safe queries
|
|
1421
|
+
const users = await client.table<User>('users').get();
|
|
1422
|
+
users.data.forEach(user => {
|
|
1423
|
+
console.log(user.name); // TypeScript knows this is string
|
|
1424
|
+
console.log(user.age); // TypeScript knows this is number
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1427
|
+
// Type-safe getById
|
|
1428
|
+
const user: User = await client.table<User>('users').getById(1);
|
|
1429
|
+
|
|
1430
|
+
// Type-safe create
|
|
1431
|
+
await client.table<User>('users').create({
|
|
1432
|
+
name: 'John',
|
|
1433
|
+
email: 'john@example.com',
|
|
1434
|
+
age: 30
|
|
1435
|
+
});
|
|
1436
|
+
|
|
1437
|
+
// Type-safe query builder
|
|
1438
|
+
const results = await client.table<User>('users')
|
|
1439
|
+
.select('id', 'name', 'email')
|
|
1440
|
+
.eq('age', 30)
|
|
1441
|
+
.orderBy('name', 'asc')
|
|
1442
|
+
.limit(10)
|
|
1443
|
+
.get();
|
|
1444
|
+
```
|
|
1445
|
+
|
|
1446
|
+
### Key Interfaces
|
|
1447
|
+
|
|
1448
|
+
```typescript
|
|
1449
|
+
import type {
|
|
1450
|
+
WowSQLConfig,
|
|
1451
|
+
QueryOptions,
|
|
1452
|
+
FilterExpression,
|
|
1453
|
+
HavingFilter,
|
|
1454
|
+
OrderByItem,
|
|
1455
|
+
QueryResponse,
|
|
1456
|
+
CreateResponse,
|
|
1457
|
+
UpdateResponse,
|
|
1458
|
+
DeleteResponse,
|
|
1459
|
+
TableSchema,
|
|
1460
|
+
ColumnInfo,
|
|
1461
|
+
AuthTokenStorage,
|
|
1462
|
+
AuthUser,
|
|
1463
|
+
AuthSession,
|
|
1464
|
+
AuthResponse,
|
|
1465
|
+
StorageBucket,
|
|
1466
|
+
StorageFile,
|
|
1467
|
+
StorageQuota
|
|
1468
|
+
} from 'wowsql';
|
|
1469
|
+
```
|
|
1470
|
+
|
|
1471
|
+
## Examples
|
|
1472
|
+
|
|
1473
|
+
### Blog Application
|
|
1474
|
+
|
|
1475
|
+
```typescript
|
|
1476
|
+
import { WowSQLClient } from 'wowsql';
|
|
1477
|
+
|
|
1478
|
+
interface Post {
|
|
1479
|
+
id: number;
|
|
1480
|
+
title: string;
|
|
1481
|
+
content: string;
|
|
1482
|
+
author_id: number;
|
|
1483
|
+
published: boolean;
|
|
1484
|
+
created_at: string;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
interface Comment {
|
|
1488
|
+
id: number;
|
|
1489
|
+
post_id: number;
|
|
1490
|
+
author_name: string;
|
|
1491
|
+
body: string;
|
|
1492
|
+
created_at: string;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
const client = new WowSQLClient({
|
|
1496
|
+
projectUrl: process.env.WOWSQL_PROJECT_URL!,
|
|
1497
|
+
apiKey: process.env.WOWSQL_SERVICE_ROLE_KEY!
|
|
1498
|
+
});
|
|
1499
|
+
|
|
1500
|
+
// Create a new post
|
|
1501
|
+
const post = await client.table<Post>('posts').create({
|
|
1502
|
+
title: 'Hello World',
|
|
1503
|
+
content: 'My first blog post',
|
|
1504
|
+
author_id: 1,
|
|
1505
|
+
published: true
|
|
1506
|
+
});
|
|
1507
|
+
|
|
1508
|
+
// Get published posts with pagination
|
|
1509
|
+
const posts = await client.table<Post>('posts')
|
|
1510
|
+
.select('id', 'title', 'content', 'created_at')
|
|
1511
|
+
.eq('published', true)
|
|
1512
|
+
.orderBy('created_at', 'desc')
|
|
1513
|
+
.limit(10)
|
|
1514
|
+
.get();
|
|
1515
|
+
|
|
1516
|
+
// Get a single post by ID
|
|
1517
|
+
const singlePost = await client.table<Post>('posts').getById(1);
|
|
1518
|
+
|
|
1519
|
+
// Get comments for a post
|
|
1520
|
+
const comments = await client.table<Comment>('comments')
|
|
1521
|
+
.select('*')
|
|
1522
|
+
.eq('post_id', 1)
|
|
1523
|
+
.orderBy('created_at', 'asc')
|
|
1524
|
+
.get();
|
|
1525
|
+
|
|
1526
|
+
// Get post statistics
|
|
1527
|
+
const stats = await client.table<Post>('posts')
|
|
1528
|
+
.select('DATE(created_at) as date', 'COUNT(*) as count')
|
|
1529
|
+
.eq('published', true)
|
|
1530
|
+
.groupBy('DATE(created_at)')
|
|
1531
|
+
.orderBy('date', 'desc')
|
|
1532
|
+
.limit(30)
|
|
1533
|
+
.get();
|
|
1534
|
+
```
|
|
1535
|
+
|
|
1536
|
+
### File Upload Application
|
|
1029
1537
|
|
|
1030
1538
|
```typescript
|
|
1031
|
-
|
|
1032
|
-
|
|
1539
|
+
import { WowSQLClient, WowSQLStorage } from 'wowsql';
|
|
1540
|
+
|
|
1541
|
+
const client = new WowSQLClient({
|
|
1542
|
+
projectUrl: process.env.WOWSQL_PROJECT_URL!,
|
|
1543
|
+
apiKey: process.env.WOWSQL_SERVICE_ROLE_KEY!
|
|
1544
|
+
});
|
|
1545
|
+
|
|
1546
|
+
const storage = new WowSQLStorage({
|
|
1547
|
+
projectUrl: process.env.WOWSQL_PROJECT_URL!,
|
|
1548
|
+
apiKey: process.env.WOWSQL_SERVICE_ROLE_KEY!
|
|
1549
|
+
});
|
|
1550
|
+
|
|
1551
|
+
// Create an avatars bucket
|
|
1552
|
+
await storage.createBucket('avatars', { public: true });
|
|
1553
|
+
|
|
1554
|
+
// Upload user avatar
|
|
1555
|
+
const userId = 123;
|
|
1556
|
+
const avatarPath = `users/${userId}/avatar.jpg`;
|
|
1557
|
+
await storage.upload('avatars', avatarBuffer, avatarPath);
|
|
1558
|
+
|
|
1559
|
+
// Get the public URL and save to database
|
|
1560
|
+
const avatarUrl = storage.getPublicUrl('avatars', avatarPath);
|
|
1561
|
+
await client.table('users').update(userId, {
|
|
1562
|
+
avatar_url: avatarUrl
|
|
1563
|
+
});
|
|
1564
|
+
|
|
1565
|
+
// List user's uploaded files
|
|
1566
|
+
const files = await storage.listFiles('avatars', {
|
|
1567
|
+
prefix: `users/${userId}/`
|
|
1568
|
+
});
|
|
1569
|
+
console.log(`User has ${files.length} files`);
|
|
1570
|
+
|
|
1571
|
+
// Check storage quota before upload
|
|
1572
|
+
const quota = await storage.getQuota();
|
|
1573
|
+
if (quota.available_gb > 0.1) {
|
|
1574
|
+
await storage.upload('avatars', largeFile, 'users/123/banner.jpg');
|
|
1575
|
+
} else {
|
|
1576
|
+
console.log('Low storage! Consider upgrading.');
|
|
1577
|
+
}
|
|
1578
|
+
```
|
|
1579
|
+
|
|
1580
|
+
### Next.js API Route
|
|
1581
|
+
|
|
1582
|
+
```typescript
|
|
1583
|
+
// app/api/users/route.ts
|
|
1033
1584
|
import { NextResponse } from 'next/server';
|
|
1585
|
+
import { WowSQLClient } from 'wowsql';
|
|
1586
|
+
|
|
1587
|
+
const client = new WowSQLClient({
|
|
1588
|
+
projectUrl: process.env.WOWSQL_PROJECT_URL!,
|
|
1589
|
+
apiKey: process.env.WOWSQL_SERVICE_ROLE_KEY!
|
|
1590
|
+
});
|
|
1591
|
+
|
|
1592
|
+
export async function GET(request: Request) {
|
|
1593
|
+
const { searchParams } = new URL(request.url);
|
|
1594
|
+
const page = parseInt(searchParams.get('page') || '1');
|
|
1595
|
+
const perPage = 20;
|
|
1034
1596
|
|
|
1035
|
-
export async function POST() {
|
|
1036
|
-
const schema = new WOWSQLSchema(
|
|
1037
|
-
process.env.WOWSQL_PROJECT_URL!,
|
|
1038
|
-
process.env.WOWSQL_SERVICE_KEY! // From env var
|
|
1039
|
-
);
|
|
1040
|
-
|
|
1041
1597
|
try {
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
{ name: 'name', type: 'VARCHAR(255)', not_null: true },
|
|
1049
|
-
{ name: 'created_at', type: 'TIMESTAMP', default: 'CURRENT_TIMESTAMP' }
|
|
1050
|
-
],
|
|
1051
|
-
primaryKey: 'id',
|
|
1052
|
-
indexes: [{ name: 'idx_email', columns: ['email'] }]
|
|
1053
|
-
});
|
|
1054
|
-
|
|
1055
|
-
return NextResponse.json({ success: true });
|
|
1598
|
+
const result = await client.table('users')
|
|
1599
|
+
.select('id', 'name', 'email', 'created_at')
|
|
1600
|
+
.orderBy('created_at', 'desc')
|
|
1601
|
+
.paginate(page, perPage);
|
|
1602
|
+
|
|
1603
|
+
return NextResponse.json(result);
|
|
1056
1604
|
} catch (error) {
|
|
1057
|
-
return NextResponse.json({ error:
|
|
1605
|
+
return NextResponse.json({ error: 'Failed to fetch users' }, { status: 500 });
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
export async function POST(request: Request) {
|
|
1610
|
+
try {
|
|
1611
|
+
const body = await request.json();
|
|
1612
|
+
const result = await client.table('users').create(body);
|
|
1613
|
+
return NextResponse.json(result, { status: 201 });
|
|
1614
|
+
} catch (error) {
|
|
1615
|
+
return NextResponse.json({ error: 'Failed to create user' }, { status: 500 });
|
|
1058
1616
|
}
|
|
1059
1617
|
}
|
|
1060
1618
|
```
|
|
1061
1619
|
|
|
1062
|
-
###
|
|
1620
|
+
### Migration Script
|
|
1063
1621
|
|
|
1064
1622
|
```typescript
|
|
1065
|
-
|
|
1623
|
+
// scripts/migrate.ts
|
|
1624
|
+
import { WowSQLSchema } from 'wowsql';
|
|
1066
1625
|
|
|
1067
|
-
|
|
1068
|
-
const schema = new
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
);
|
|
1072
|
-
|
|
1626
|
+
async function runMigration() {
|
|
1627
|
+
const schema = new WowSQLSchema({
|
|
1628
|
+
projectUrl: process.env.WOWSQL_PROJECT_URL!,
|
|
1629
|
+
serviceKey: process.env.WOWSQL_SERVICE_ROLE_KEY!
|
|
1630
|
+
});
|
|
1631
|
+
|
|
1632
|
+
// Create users table
|
|
1073
1633
|
await schema.createTable({
|
|
1074
|
-
tableName: '
|
|
1075
|
-
columns: [
|
|
1634
|
+
tableName: 'users',
|
|
1635
|
+
columns: [
|
|
1636
|
+
{ name: 'id', type: 'INT', auto_increment: true },
|
|
1637
|
+
{ name: 'email', type: 'VARCHAR(255)', unique: true, not_null: true },
|
|
1638
|
+
{ name: 'name', type: 'VARCHAR(255)', not_null: true },
|
|
1639
|
+
{ name: 'created_at', type: 'TIMESTAMP', default: 'CURRENT_TIMESTAMP' }
|
|
1640
|
+
],
|
|
1641
|
+
primaryKey: 'id',
|
|
1642
|
+
indexes: [{ name: 'idx_email', columns: ['email'] }]
|
|
1076
1643
|
});
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1644
|
+
|
|
1645
|
+
// Create posts table
|
|
1646
|
+
await schema.createTable({
|
|
1647
|
+
tableName: 'posts',
|
|
1648
|
+
columns: [
|
|
1649
|
+
{ name: 'id', type: 'INT', auto_increment: true },
|
|
1650
|
+
{ name: 'user_id', type: 'INT', not_null: true },
|
|
1651
|
+
{ name: 'title', type: 'VARCHAR(255)', not_null: true },
|
|
1652
|
+
{ name: 'content', type: 'TEXT' },
|
|
1653
|
+
{ name: 'published', type: 'BOOLEAN', default: 'false' },
|
|
1654
|
+
{ name: 'created_at', type: 'TIMESTAMP', default: 'CURRENT_TIMESTAMP' }
|
|
1655
|
+
],
|
|
1656
|
+
primaryKey: 'id'
|
|
1657
|
+
});
|
|
1658
|
+
|
|
1659
|
+
// Add foreign key
|
|
1660
|
+
await schema.executeSQL(`
|
|
1661
|
+
ALTER TABLE posts
|
|
1662
|
+
ADD CONSTRAINT fk_user
|
|
1663
|
+
FOREIGN KEY (user_id)
|
|
1664
|
+
REFERENCES users(id);
|
|
1665
|
+
`);
|
|
1666
|
+
|
|
1667
|
+
console.log('Migration completed!');
|
|
1084
1668
|
}
|
|
1669
|
+
|
|
1670
|
+
runMigration().catch(console.error);
|
|
1085
1671
|
```
|
|
1086
1672
|
|
|
1087
|
-
|
|
1673
|
+
## Requirements
|
|
1674
|
+
|
|
1675
|
+
- Node.js 16+
|
|
1676
|
+
- TypeScript 4.7+ (for TypeScript users)
|
|
1677
|
+
- Works in Node.js, browser, Deno, Bun, and edge runtimes
|
|
1088
1678
|
|
|
1089
1679
|
## FAQ
|
|
1090
1680
|
|
|
1091
1681
|
### Can I use this in the browser?
|
|
1092
1682
|
|
|
1093
|
-
Yes! The SDK works in both Node.js and browser environments.
|
|
1683
|
+
Yes! The SDK works in both Node.js and browser environments. Use the **Anonymous Key** for client-side code. Never expose the Service Role Key in frontend code.
|
|
1094
1684
|
|
|
1095
1685
|
### What about rate limits?
|
|
1096
1686
|
|
|
1097
|
-
Rate limits depend on your
|
|
1687
|
+
Rate limits depend on your WowSQL plan. The SDK throws a `WOWSQLError` with status code 429 when rate limits are exceeded.
|
|
1098
1688
|
|
|
1099
1689
|
### Does it support transactions?
|
|
1100
1690
|
|
|
1101
|
-
Currently, the SDK doesn't support transactions directly. Use raw SQL queries for complex transactional operations.
|
|
1691
|
+
Currently, the SDK doesn't support transactions directly. Use raw SQL queries via `client.query()` for complex transactional operations.
|
|
1102
1692
|
|
|
1103
1693
|
### How do I upgrade?
|
|
1104
1694
|
|
|
1105
1695
|
```bash
|
|
1106
|
-
npm update
|
|
1696
|
+
npm update wowsql
|
|
1107
1697
|
```
|
|
1108
1698
|
|
|
1699
|
+
## Links
|
|
1700
|
+
|
|
1701
|
+
- [Documentation](https://wowsql.com/docs)
|
|
1702
|
+
- [Website](https://wowsql.com)
|
|
1703
|
+
- [Discord](https://discord.gg/wowsql)
|
|
1704
|
+
- [GitHub Issues](https://github.com/wowsql/wowsql/issues)
|
|
1705
|
+
|
|
1109
1706
|
## Support
|
|
1110
1707
|
|
|
1111
|
-
-
|
|
1112
|
-
-
|
|
1113
|
-
-
|
|
1114
|
-
- 🐛 Issues: [GitHub Issues](https://github.com/wowsql/wowsql/issues)
|
|
1708
|
+
- Email: support@wowsql.com
|
|
1709
|
+
- Discord: https://discord.gg/wowsql
|
|
1710
|
+
- Documentation: https://wowsql.com/docs
|
|
1115
1711
|
|
|
1116
1712
|
## Contributing
|
|
1117
1713
|
|
|
@@ -1127,6 +1723,4 @@ See [CHANGELOG.md](CHANGELOG.md) for version history.
|
|
|
1127
1723
|
|
|
1128
1724
|
---
|
|
1129
1725
|
|
|
1130
|
-
Made with
|
|
1131
|
-
|
|
1132
|
-
|
|
1726
|
+
Made with care by the WowSQL Team
|