wowsql-sdk 1.3.0 โ†’ 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -1,554 +1,496 @@
1
- # ๐Ÿš€ WOWSQL Ruby SDK
1
+ # WowSQL Ruby SDK
2
2
 
3
- Official Ruby client for [WOWSQL](https://wowsql.com) - MySQL Backend-as-a-Service with S3 Storage.
3
+ Official Ruby client for [WowSQL](https://wowsql.com) โ€” PostgreSQL backend-as-a-service with project auth and object storage.
4
+
5
+ **Gem:** `wowsql-sdk` ยท **Module:** `WOWSQL` ยท **Ruby:** 2.6+
4
6
 
5
- [![Gem Version](https://img.shields.io/gem/v/wowsql-sdk.svg)](https://rubygems.org/gems/wowsql-sdk)
6
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
8
 
8
- ## โœจ Features
9
-
10
- ### Database Features
11
- - ๐Ÿ—„๏ธ Full CRUD operations (Create, Read, Update, Delete)
12
- - ๐Ÿ” Advanced filtering (eq, neq, gt, gte, lt, lte, like, is_null)
13
- - ๐Ÿ“„ Pagination (limit, offset)
14
- - ๐Ÿ“Š Sorting (order_by)
15
- - ๐ŸŽฏ Fluent query builder API
16
- - ๐Ÿ“ Raw SQL queries (read-only)
17
- - ๐Ÿ“‹ Table schema introspection
18
- - ๐Ÿ”’ Built-in error handling
19
-
20
- ### Storage Features
21
- - ๐Ÿ“ฆ S3-compatible storage client
22
- - โฌ†๏ธ File upload with automatic quota validation
23
- - โฌ‡๏ธ File download (presigned URLs)
24
- - ๐Ÿ“‚ File listing with metadata
25
- - ๐Ÿ—‘๏ธ File deletion
26
- - ๐Ÿ“Š Storage quota management
27
- - ๐ŸŒ Multi-region support
28
-
29
- ## ๐Ÿ“ฆ Installation
9
+ ---
10
+
11
+ ## Table of contents
12
+
13
+ 1. [Installation](#installation)
14
+ 2. [Quick start](#quick-start)
15
+ 3. [Concepts & API keys](#concepts--api-keys)
16
+ 4. [Database: `WOWSQLClient`](#database-wowsqlclient)
17
+ 5. [Table & `QueryBuilder`](#table--querybuilder)
18
+ 6. [Authentication: `ProjectAuthClient`](#authentication-projectauthclient)
19
+ 7. [Storage: `WOWSQLStorage`](#storage-wowsqlstorage)
20
+ 8. [Schema: `WOWSQLSchema`](#schema-wowsqlschema)
21
+ 9. [Models & types](#models--types)
22
+ 10. [Exceptions](#exceptions)
23
+ 11. [Configuration](#configuration)
24
+ 12. [Rails integration](#rails-integration)
25
+ 13. [Examples](#examples)
26
+ 14. [Troubleshooting](#troubleshooting)
27
+ 15. [Links](#links)
28
+
29
+ ---
30
+
31
+ ## Installation
30
32
 
31
33
  ### Gemfile
32
34
 
33
35
  ```ruby
34
- gem 'wowsql-sdk', '~> 1.0'
36
+ gem 'wowsql-sdk', '~> 1.3'
35
37
  ```
36
38
 
37
- Then run:
38
-
39
39
  ```bash
40
40
  bundle install
41
41
  ```
42
42
 
43
- ### Manual Installation
43
+ ### Manual
44
44
 
45
45
  ```bash
46
46
  gem install wowsql-sdk
47
47
  ```
48
48
 
49
- ## ๐Ÿš€ Quick Start
49
+ ### Require
50
50
 
51
- ### Database Operations
51
+ ```ruby
52
+ require 'wowsql'
53
+ # or
54
+ require 'wowmysql' # legacy alias entry (same library)
55
+ ```
56
+
57
+ ---
58
+
59
+ ## Quick start
60
+
61
+ ### Database (CRUD + query builder)
52
62
 
53
63
  ```ruby
54
64
  require 'wowsql'
55
65
 
56
- # Initialize client
57
66
  client = WOWSQL::WOWSQLClient.new(
58
67
  'https://your-project.wowsql.com',
59
- 'your-api-key' # Get from dashboard
68
+ ENV.fetch('WOWSQL_SERVICE_KEY'),
69
+ base_domain: 'wowsql.com',
70
+ secure: true,
71
+ timeout: 30,
72
+ verify_ssl: true
60
73
  )
61
74
 
62
- # Select data
63
- response = client.table('users')
64
- .select('id', 'name', 'email')
65
- .eq('status', 'active')
75
+ rows = client.table('posts')
76
+ .select('id', 'title', 'created_at')
77
+ .eq('published', true)
78
+ .order_by('created_at', 'desc')
66
79
  .limit(10)
67
80
  .get
68
81
 
69
- response['data'].each do |user|
70
- puts "#{user['name']} (#{user['email']})"
71
- end
72
-
73
- # Insert data
74
- new_user = {
75
- 'name' => 'Jane Doe',
76
- 'email' => 'jane@example.com',
77
- 'age' => 25
78
- }
79
-
80
- result = client.table('users').create(new_user)
81
- puts "Created user with ID: #{result['id']}"
82
+ puts rows['data'].inspect
82
83
 
83
- # Update data
84
- updates = { 'name' => 'Jane Smith' }
85
- client.table('users').update(result['id'], updates)
84
+ created = client.table('posts').create(
85
+ 'title' => 'Hello',
86
+ 'body' => 'World',
87
+ 'published' => true
88
+ )
89
+ puts created['id']
86
90
 
87
- # Delete data
88
- client.table('users').delete(result['id'])
91
+ client.close
89
92
  ```
90
93
 
91
- ### Storage Operations
94
+ ### Project authentication
92
95
 
93
96
  ```ruby
94
- require 'wowsql'
95
-
96
- # Initialize storage client
97
- storage = WOWSQL::WOWSQLStorage.new(
98
- 'your-project-slug',
99
- 'your-api-key'
97
+ auth = WOWSQL::ProjectAuthClient.new(
98
+ ENV.fetch('WOWSQL_PROJECT_URL'),
99
+ ENV.fetch('WOWSQL_ANON_KEY'),
100
+ base_domain: 'wowsql.com'
100
101
  )
101
102
 
102
- # Upload file
103
- storage.upload_from_path('local-file.pdf', 'uploads/document.pdf', 'documents')
104
-
105
- # Get presigned URL
106
- url_data = storage.get_file_url('uploads/document.pdf', 3600)
107
- puts "File URL: #{url_data['file_url']}"
103
+ res = auth.sign_up(
104
+ email: 'user@example.com',
105
+ password: 'SecurePass123!',
106
+ full_name: 'Ada Lovelace'
107
+ )
108
108
 
109
- # List files
110
- files = storage.list_files('uploads/')
111
- files.each do |file|
112
- puts "#{file['key']}: #{(file['size'] / 1024.0 / 1024.0).round(2)} MB"
113
- end
109
+ auth.set_session(
110
+ access_token: res.session.access_token,
111
+ refresh_token: res.session.refresh_token
112
+ )
114
113
 
115
- # Delete file
116
- storage.delete_file('uploads/document.pdf')
114
+ user = auth.get_user
115
+ puts user.email
117
116
 
118
- # Check storage quota
119
- quota = storage.get_quota
120
- puts "Used: #{quota['used_gb']} GB"
121
- puts "Available: #{quota['available_gb']} GB"
117
+ auth.close
122
118
  ```
123
119
 
124
- ## ๐Ÿ“š Usage Examples
125
-
126
- ### Select Queries
120
+ ### Storage (buckets & files)
127
121
 
128
122
  ```ruby
129
- # Select all columns
130
- users = client.table('users').select('*').get
131
-
132
- # Select specific columns
133
- users = client.table('users')
134
- .select('id', 'name', 'email')
135
- .get
123
+ storage = WOWSQL::WOWSQLStorage.new(
124
+ ENV.fetch('WOWSQL_PROJECT_URL'),
125
+ ENV.fetch('WOWSQL_SERVICE_KEY'),
126
+ base_domain: 'wowsql.com',
127
+ timeout: 60
128
+ )
136
129
 
137
- # With filters
138
- active_users = client.table('users')
139
- .select('id', 'name', 'email')
140
- .eq('status', 'active')
141
- .gt('age', 18)
142
- .get
130
+ bucket = storage.create_bucket('avatars', public: true)
131
+ storage.upload(bucket.name, File.binread('face.png'), path: 'u/1.png', file_name: '1.png')
132
+ puts storage.get_public_url('avatars', 'u/1.png')
143
133
 
144
- # With ordering
145
- recent_users = client.table('users')
146
- .select('*')
147
- .order_by('created_at', desc: true)
148
- .limit(10)
149
- .get
134
+ storage.close
135
+ ```
150
136
 
151
- # With pagination
152
- page1 = client.table('users')
153
- .select('*')
154
- .limit(20)
155
- .offset(0)
156
- .get
137
+ ### Schema (service role only)
157
138
 
158
- page2 = client.table('users')
159
- .select('*')
160
- .limit(20)
161
- .offset(20)
162
- .get
139
+ ```ruby
140
+ schema = WOWSQL::WOWSQLSchema.new(
141
+ ENV.fetch('WOWSQL_PROJECT_URL'),
142
+ ENV.fetch('WOWSQL_SERVICE_KEY')
143
+ )
163
144
 
164
- # Pattern matching
165
- gmail_users = client.table('users')
166
- .select('*')
167
- .like('email', '%@gmail.com')
168
- .get
145
+ schema.create_table(
146
+ 'notes',
147
+ [
148
+ { 'name' => 'id', 'type' => 'SERIAL', 'auto_increment' => true },
149
+ { 'name' => 'body', 'type' => 'TEXT', 'nullable' => false }
150
+ ],
151
+ primary_key: 'id'
152
+ )
169
153
 
170
- # Get single record by ID
171
- user = client.table('users').get_by_id(123)
154
+ schema.close
172
155
  ```
173
156
 
174
- ### Insert Data
157
+ ---
175
158
 
176
- ```ruby
177
- # Insert single record
178
- new_user = {
179
- 'name' => 'John Doe',
180
- 'email' => 'john@example.com',
181
- 'age' => 30,
182
- 'status' => 'active'
183
- }
184
-
185
- result = client.table('users').create(new_user)
186
- puts "New user ID: #{result['id']}"
187
- ```
159
+ ## Concepts & API keys
188
160
 
189
- ### Update Data
161
+ | Key | Prefix | Typical use |
162
+ |-----|--------|-------------|
163
+ | **Anonymous** | `wowsql_anon_โ€ฆ` | Browser/mobile auth flows, limited DB access |
164
+ | **Service role** | `wowsql_service_โ€ฆ` | **Server only** โ€” full DB, storage, schema DDL |
190
165
 
191
- ```ruby
192
- # Update by ID
193
- updates = {
194
- 'name' => 'Jane Smith',
195
- 'age' => 26
196
- }
197
-
198
- result = client.table('users').update(1, updates)
199
- puts "Updated #{result['affected_rows']} row(s)"
200
- ```
166
+ - **`WOWSQLClient`** / **`WOWSQLStorage`**: use anon or service key depending on RLS and server vs client.
167
+ - **`WOWSQLSchema`**: **requires service role** (403 otherwise).
168
+ - **`ProjectAuthClient`**: usually **anon** on clients; service key only on trusted servers.
201
169
 
202
- ### Delete Data
170
+ Store keys in `ENV`, never commit them.
203
171
 
204
- ```ruby
205
- # Delete by ID
206
- result = client.table('users').delete(1)
207
- puts "Deleted #{result['affected_rows']} row(s)"
208
-
209
- # Delete with conditions
210
- deleted = client.table('users')
211
- .eq('status', 'deleted')
212
- .delete
213
- ```
172
+ **Project URL:** full `https://{slug}.wowsql.com` or just the slug; the client normalizes against `base_domain` and `secure`.
214
173
 
215
- ### Filter Operators
174
+ ---
175
+
176
+ ## Database: `WOWSQLClient`
216
177
 
217
178
  ```ruby
218
- # Equal
219
- .eq('status', 'active')
179
+ WOWSQL::WOWSQLClient.new(
180
+ project_url,
181
+ api_key,
182
+ base_domain: 'wowsql.com',
183
+ secure: true,
184
+ timeout: 30,
185
+ verify_ssl: true
186
+ )
187
+ ```
220
188
 
221
- # Not equal
222
- .neq('status', 'deleted')
189
+ | Method | Returns | Description |
190
+ |--------|---------|-------------|
191
+ | `table(table_name)` | `Table` | Fluent access to one table (`public` schema via API). |
192
+ | `list_tables` | `Array<String>` | Table names in the project DB. |
193
+ | `get_table_schema(table_name)` | `Hash` | Column metadata from the API. |
194
+ | `request(method, path, params, json)` | `Hash` | Low-level JSON request (advanced). |
195
+ | `close` | โ€” | Release resources. |
223
196
 
224
- # Greater than
225
- .gt('age', 18)
197
+ **Reader:** `api_url`, `api_key`, `timeout`, `verify_ssl`.
226
198
 
227
- # Greater than or equal
228
- .gte('age', 18)
199
+ ---
229
200
 
230
- # Less than
231
- .lt('age', 65)
201
+ ## Table & `QueryBuilder`
232
202
 
233
- # Less than or equal
234
- .lte('age', 65)
203
+ ### `Table`
235
204
 
236
- # Pattern matching (SQL LIKE)
237
- .like('email', '%@gmail.com')
205
+ Obtained via `client.table('name')`.
238
206
 
239
- # Is null
240
- .is_null('deleted_at')
207
+ | Method | Returns | Notes |
208
+ |--------|---------|------|
209
+ | `select(*columns)` | `QueryBuilder` | Column list or `'*'`-style strings per your API. |
210
+ | `filter(column, operator, value, logical_op: 'AND')` | `QueryBuilder` | See operators below. |
211
+ | `get(options = nil)` | `Hash` | Executes SELECT pipeline. |
212
+ | `get_by_id(record_id)` | `Hash` | Single row by PK. |
213
+ | `create(data)` / `insert(data)` | `Hash` | Insert one row. |
214
+ | `bulk_insert(records)` | `Array` | Multiple inserts. |
215
+ | `upsert(data, on_conflict: 'id')` | `Hash` | Upsert semantics per backend. |
216
+ | `update(record_id, data)` | `Hash` | Update by id. |
217
+ | `delete(record_id)` | `Hash` | Delete by id. |
218
+ | `eq` / `neq` / `gt` / `gte` / `lt` / `lte` | `QueryBuilder` | Shorthand filters. |
219
+ | `order_by(column, direction = 'asc')` | `QueryBuilder` | |
220
+ | `count` | `Integer` | |
221
+ | `paginate(page: 1, per_page: 20)` | `Hash` | Paginated result envelope. |
241
222
 
242
- # Is not null
243
- .is_not_null('email')
244
- ```
223
+ ### `QueryBuilder`
245
224
 
246
- ### Advanced Queries
225
+ Chainable; ends with `get`, `execute`, `first`, `single`, `count`, or `paginate`.
247
226
 
248
- ```ruby
249
- # Multiple filters with sorting
250
- active_users = client.table('users')
251
- .select('id', 'name', 'email')
252
- .eq('status', 'active')
253
- .gt('age', 18)
254
- .order_by('created_at', desc: true)
255
- .limit(20)
256
- .get
227
+ **Filters:** `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `like`, `is_null`, `is_not_null`, `in_list`, `not_in`, `between`, `not_between`, `or_filter`.
257
228
 
258
- # Complex filtering
259
- results = client.table('products')
260
- .select('*')
261
- .gt('price', 100)
262
- .lt('price', 1000)
263
- .like('name', '%widget%')
264
- .is_not_null('category')
265
- .order_by('price', desc: false) # asc
266
- .limit(50)
267
- .offset(0)
268
- .get
269
- ```
229
+ **Grouping / aggregates:** `group_by(*columns)`, `having(column, operator, value)`.
270
230
 
271
- ### Storage Operations
231
+ **Sort / page:** `order_by`, `order`, `limit`, `offset`.
272
232
 
273
- ```ruby
274
- storage = WOWSQL::WOWSQLStorage.new(
275
- 'your-project-slug',
276
- 'your-api-key'
277
- )
233
+ **Terminal:**
278
234
 
279
- # Upload file from local path
280
- storage.upload_from_path(
281
- 'local-file.pdf',
282
- 'uploads/document.pdf',
283
- 'documents' # bucket/folder
284
- )
235
+ | Method | Behavior |
236
+ |--------|----------|
237
+ | `get` / `execute` | Full result hash (`data`, counts, etc.). |
238
+ | `first` | First row or `nil`. |
239
+ | `single` | Exactly one row; raises `WOWSQLError` if not one row. |
240
+ | `count` | Integer count. |
241
+ | `paginate(page:, per_page:)` | Paginated structure. |
285
242
 
286
- # Check if file exists
287
- if storage.file_exists?('uploads/document.pdf')
288
- puts "File exists!"
289
- end
243
+ ### Filter operators (string)
290
244
 
291
- # Get file information
292
- info = storage.get_file_info('uploads/document.pdf')
293
- puts "Size: #{info['size']} bytes"
294
- puts "Modified: #{info['last_modified']}"
245
+ Use with `filter` or the shorthand methods:
295
246
 
296
- # List files with prefix
297
- files = storage.list_files('uploads/2024/')
298
- files.each do |file|
299
- puts "#{file['key']}: #{file['size']} bytes"
300
- end
247
+ `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `like`, `is`, `in`, `not_in`, `between`, `not_between`, `is_not` โ€” as supported by the WowSQL REST API for your project.
301
248
 
302
- # Download file (get presigned URL)
303
- url_data = storage.get_file_url('uploads/document.pdf', 3600)
304
- puts "Download URL: #{url_data['file_url']}"
305
- # URL is valid for 1 hour (3600 seconds)
306
-
307
- # Delete single file
308
- storage.delete_file('uploads/old-file.pdf')
309
-
310
- # Delete multiple files
311
- storage.delete_files([
312
- 'uploads/file1.pdf',
313
- 'uploads/file2.pdf',
314
- 'uploads/file3.pdf'
315
- ])
316
-
317
- # Check quota
318
- quota = storage.get_quota
319
- puts "Used: #{quota['used_gb'].round(2)} GB"
320
- puts "Available: #{quota['available_gb'].round(2)} GB"
321
- puts "Usage: #{quota['usage_percentage'].round(1)}%"
322
-
323
- # Check if enough storage before upload
324
- file_size = File.size('large-file.zip')
325
- if quota['available_bytes'] < file_size
326
- puts "Not enough storage!"
327
- else
328
- storage.upload_from_path('large-file.zip', 'uploads/large-file.zip')
329
- end
330
- ```
249
+ ---
331
250
 
332
- ### Error Handling
251
+ ## Authentication: `ProjectAuthClient`
333
252
 
334
253
  ```ruby
335
- begin
336
- users = client.table('users').select('*').get
337
- puts "Success: #{users['data'].count} users"
338
- rescue WOWSQL::AuthenticationException => e
339
- puts "Authentication error: #{e.message}"
340
- rescue WOWSQL::NotFoundException => e
341
- puts "Not found: #{e.message}"
342
- rescue WOWSQL::RateLimitException => e
343
- puts "Rate limit exceeded: #{e.message}"
344
- rescue WOWSQL::WOWSQLException => e
345
- puts "Database error (#{e.status_code}): #{e.message}"
346
- end
347
-
348
- begin
349
- storage.upload_from_path('file.pdf', 'uploads/file.pdf')
350
- rescue WOWSQL::StorageLimitExceededException => e
351
- puts "Storage full: #{e.message}"
352
- puts "Please upgrade your plan or delete old files"
353
- rescue WOWSQL::StorageException => e
354
- puts "Storage error: #{e.message}"
355
- end
254
+ WOWSQL::ProjectAuthClient.new(
255
+ project_url,
256
+ api_key,
257
+ base_domain: 'wowsql.com',
258
+ secure: true,
259
+ timeout: 30,
260
+ verify_ssl: true,
261
+ token_storage: nil # optional WOWSQL::MemoryTokenStorage or custom
262
+ )
356
263
  ```
357
264
 
358
- ### Utility Methods
359
-
360
- ```ruby
361
- # List all tables
362
- tables = client.list_tables
363
- puts "Tables: #{tables.inspect}"
364
-
365
- # Get table schema
366
- schema = client.get_table_schema('users')
367
- puts "Columns: #{schema['columns'].count}"
368
- schema['columns'].each do |column|
369
- puts " - #{column['name']} (#{column['type']})"
370
- end
371
-
372
- # Check API health
373
- health = client.health
374
- puts "Status: #{health['status']}"
375
- ```
265
+ ### `TokenStorage` (module)
266
+
267
+ Implement in your app for Redis/DB/session persistence:
268
+
269
+ - `get_access_token` / `set_access_token(token)`
270
+ - `get_refresh_token` / `set_refresh_token(token)`
271
+
272
+ `WOWSQL::MemoryTokenStorage` is in-memory only.
273
+
274
+ ### Methods
275
+
276
+ | Method | Returns |
277
+ |--------|---------|
278
+ | `sign_up(email:, password:, full_name:, user_metadata:)` | `AuthResponse` |
279
+ | `sign_in(email:, password:)` | `AuthResponse` |
280
+ | `get_user(access_token: nil)` | `AuthUser` |
281
+ | `get_oauth_authorization_url(provider:, redirect_uri:)` | `Hash` |
282
+ | `exchange_oauth_callback(provider:, code:, redirect_uri:)` | `AuthResponse` |
283
+ | `forgot_password(email:)` | `Hash` |
284
+ | `reset_password(token:, new_password:)` | `Hash` |
285
+ | `send_otp(email:, purpose: 'login')` | `Hash` |
286
+ | `verify_otp(email:, otp:, purpose: 'login', new_password: nil)` | `AuthResponse` or `Hash` |
287
+ | `send_magic_link(email:, purpose: 'login')` | `Hash` |
288
+ | `verify_email(token:)` | `Hash` |
289
+ | `resend_verification(email:)` | `Hash` |
290
+ | `logout(access_token: nil)` | `Hash` |
291
+ | `refresh_token(refresh_token: nil)` | `AuthResponse` |
292
+ | `change_password(current_password:, new_password:, access_token: nil)` | `Hash` |
293
+ | `update_user(full_name:, avatar_url:, username:, user_metadata:, access_token:)` | `AuthUser` |
294
+ | `get_session` | `Hash` with token keys |
295
+ | `set_session(access_token:, refresh_token: nil)` | |
296
+ | `clear_session` | |
297
+ | `close` | |
298
+
299
+ **Structs:** `AuthUser`, `AuthSession`, `AuthResponse` (see [Models](#models--types)).
376
300
 
377
- ## ๐Ÿ”ง Configuration
301
+ ---
378
302
 
379
- ### Custom Timeout
303
+ ## Storage: `WOWSQLStorage`
380
304
 
381
305
  ```ruby
382
- # Database client
383
- client = WOWSQL::WOWSQLClient.new(
384
- 'https://your-project.wowsql.com',
385
- 'your-api-key',
386
- 60 # 60 seconds timeout
387
- )
388
-
389
- # Storage client
390
- storage = WOWSQL::WOWSQLStorage.new(
391
- 'your-project-slug',
392
- 'your-api-key',
393
- 120 # 2 minutes for large files
306
+ WOWSQL::WOWSQLStorage.new(
307
+ project_url = '',
308
+ api_key = '',
309
+ project_slug: '',
310
+ base_url: '',
311
+ base_domain: 'wowsql.com',
312
+ secure: true,
313
+ timeout: 60,
314
+ verify_ssl: true
394
315
  )
395
316
  ```
396
317
 
397
- ### Using Environment Variables
318
+ | Method | Returns |
319
+ |--------|---------|
320
+ | `create_bucket(name, public: false, file_size_limit: nil, allowed_mime_types: nil)` | `StorageBucket` |
321
+ | `list_buckets` | `Array<StorageBucket>` |
322
+ | `get_bucket(name)` | `StorageBucket` |
323
+ | `update_bucket(name, **options)` | `StorageBucket` |
324
+ | `delete_bucket(name)` | `Hash` |
325
+ | `upload(bucket_name, file_data, path: nil, file_name: nil)` | `StorageFile` |
326
+ | `upload_from_path(file_path, bucket_name: 'default', path: nil)` | `StorageFile` |
327
+ | `list_files(bucket_name, prefix: nil, limit: 100, offset: 0)` | `Array<StorageFile>` |
328
+ | `download(bucket_name, file_path)` | `String` (binary string) |
329
+ | `download_to_file(bucket_name, file_path, local_path)` | `String` (path) |
330
+ | `delete_file(bucket_name, file_path)` | `Hash` |
331
+ | `get_public_url(bucket_name, file_path)` | `String` |
332
+ | `get_stats` | `StorageQuota` |
333
+ | `get_quota(force_refresh: false)` | `StorageQuota` |
334
+ | `close` | |
335
+
336
+ **Value objects:** `StorageBucket`, `StorageFile` (`size_mb`, `size_gb`), `StorageQuota`.
398
337
 
399
- ```ruby
400
- # Best practice: Use environment variables for API keys
401
- client = WOWSQL::WOWSQLClient.new(
402
- ENV['WOWSQL_PROJECT_URL'],
403
- ENV['WOWSQL_API_KEY']
404
- )
338
+ ---
405
339
 
406
- storage = WOWSQL::WOWSQLStorage.new(
407
- ENV['WOWSQL_PROJECT_URL'],
408
- ENV['WOWSQL_API_KEY']
340
+ ## Schema: `WOWSQLSchema`
341
+
342
+ **Requires service role key.**
343
+
344
+ ```ruby
345
+ WOWSQL::WOWSQLSchema.new(
346
+ project_url,
347
+ service_key,
348
+ base_domain: 'wowsql.com',
349
+ secure: true,
350
+ timeout: 30,
351
+ verify_ssl: true
409
352
  )
410
353
  ```
411
354
 
412
- ## ๐Ÿ”‘ API Keys
355
+ | Method | Returns |
356
+ |--------|---------|
357
+ | `create_table(table_name, columns, primary_key: nil, indexes: nil)` | `Hash` |
358
+ | `alter_table(table_name, operation, column_name: nil, column_type: nil, new_column_name: nil, nullable: true, default: nil)` | `Hash` |
359
+ | `drop_table(table_name, cascade: false)` | `Hash` |
360
+ | `execute_sql(sql)` | `Hash` |
361
+ | `add_column(table_name, column_name, column_type, nullable: true, default: nil)` | `Hash` |
362
+ | `drop_column(table_name, column_name)` | `Hash` |
363
+ | `rename_column(table_name, old_name, new_name)` | `Hash` |
364
+ | `modify_column(table_name, column_name, column_type: nil, nullable: nil, default: nil)` | `Hash` |
365
+ | `create_index(table_name, columns, unique: false, name: nil, using: nil)` | `Hash` |
366
+ | `list_tables` | `Array<String>` |
367
+ | `get_table_schema(table_name)` | `Hash` |
368
+ | `close` | |
369
+
370
+ `operation` examples: `add_column`, `drop_column`, `modify_column`, `rename_column` (per API).
413
371
 
414
- WOWSQL uses **different API keys for different operations**. Understanding which key to use is crucial for proper authentication.
372
+ ---
373
+
374
+ ## Models & types
415
375
 
416
- ### Key Types Overview
376
+ ### Auth (structs)
417
377
 
418
- | Operation Type | Recommended Key | Alternative Key | Used By |
419
- |---------------|----------------|-----------------|---------|
420
- | **Database Operations** (CRUD) | Service Role Key (`wowbase_service_...`) | Anonymous Key (`wowbase_anon_...`) | `WOWSQLClient` |
421
- | **Storage Operations** | Service Role Key (`wowbase_service_...`) | Anonymous Key (`wowbase_anon_...`) | `WOWSQLStorage` |
378
+ - **`AuthUser`**: `id`, `email`, `full_name`, `avatar_url`, `email_verified`, `user_metadata`, `app_metadata`, `created_at`
379
+ - **`AuthSession`**: `access_token`, `refresh_token`, `token_type`, `expires_in`
380
+ - **`AuthResponse`**: `session`, `user`
422
381
 
423
- ### Where to Find Your Keys
382
+ ### Storage
424
383
 
425
- All keys are found in: **WOWSQL Dashboard โ†’ Authentication โ†’ PROJECT KEYS**
384
+ - **`StorageBucket`**: `id`, `name`, `public`, `file_size_limit`, `allowed_mime_types`, `created_at`, `object_count`, `total_size`
385
+ - **`StorageFile`**: `id`, `bucket_id`, `name`, `path`, `mime_type`, `size`, `metadata`, `created_at`, `public_url` โ€” `size_mb`, `size_gb`
386
+ - **`StorageQuota`**: `total_files`, `total_size_bytes`, `total_size_gb`, `file_types`
426
387
 
427
- 1. **Service Role Key** (`wowbase_service_...`)
428
- - Location: "Service Role Key (keep secret)"
429
- - Used for: Database CRUD operations and storage (recommended for server-side)
430
- - **Important**: Click the eye icon to reveal this key
388
+ ---
431
389
 
432
- 2. **Anonymous Key** (`wowbase_anon_...`)
433
- - Location: "Anonymous Key"
434
- - Used for: Public/client-side database operations with limited permissions
435
- - Optional: Use when exposing database access to frontend/client
390
+ ## Exceptions
436
391
 
437
- ### Database Operations
392
+ | Class | Typical HTTP | When |
393
+ |-------|----------------|------|
394
+ | `WOWSQLError` | any | Base error; `message`, `status_code`, `response`. |
395
+ | `StorageError` | 4xx/5xx | Storage API failures. |
396
+ | `StorageLimitExceededError` | 413 | Quota / size limit. |
397
+ | `SchemaPermissionError` | 403 | Schema call without service key. |
438
398
 
439
- Use **Service Role Key** or **Anonymous Key** for database operations:
399
+ Aliases: `WOWSQLException`, `StorageException`, `StorageLimitExceededException`, `PermissionException` โ†’ map to the classes above.
440
400
 
441
401
  ```ruby
442
- # Using Service Role Key (recommended for server-side, full access)
443
- client = WOWSQL::WOWSQLClient.new(
444
- 'https://your-project.wowsql.com',
445
- 'wowbase_service_your-service-key-here' # Service Role Key
446
- )
402
+ begin
403
+ client.table('x').get
404
+ rescue WOWSQL::WOWSQLError => e
405
+ warn "#{e.status_code}: #{e.message}"
406
+ end
407
+ ```
447
408
 
448
- # Using Anonymous Key (for public/client-side access with limited permissions)
449
- client = WOWSQL::WOWSQLClient.new(
450
- 'https://your-project.wowsql.com',
451
- 'wowbase_anon_your-anon-key-here' # Anonymous Key
452
- )
409
+ ---
453
410
 
454
- # Query data
455
- users = client.table('users').get
456
- ```
411
+ ## Configuration
457
412
 
458
- ### Security Best Practices
413
+ | Option | Default | Notes |
414
+ |--------|---------|------|
415
+ | `base_domain` | `wowsql.com` | Custom cloud domain if applicable. |
416
+ | `secure` | `true` | Use HTTPS. |
417
+ | `timeout` | 30 (DB/auth/schema), 60 (storage) | Seconds. |
418
+ | `verify_ssl` | `true` | Set `false` only for local dev with self-signed certs. |
459
419
 
460
- 1. **Never expose Service Role Key** in client-side code or public repositories
461
- 2. **Use Anonymous Key** for public database access with limited permissions
462
- 3. **Store keys in environment variables**, never hardcode them
463
- 4. **Rotate keys regularly** if compromised
420
+ ---
464
421
 
465
- ## ๐ŸŽจ Framework Integration
422
+ ## Rails integration
466
423
 
467
- ### Rails
424
+ **config/initializers/wowsql.rb**
468
425
 
469
426
  ```ruby
470
- # config/initializers/wowsql.rb
427
+ # frozen_string_literal: true
428
+
471
429
  WOWSQL_CLIENT = WOWSQL::WOWSQLClient.new(
472
- Rails.application.credentials.wowsql[:project_url],
473
- Rails.application.credentials.wowsql[:api_key]
430
+ ENV.fetch('WOWSQL_PROJECT_URL'),
431
+ ENV.fetch('WOWSQL_SERVICE_KEY')
474
432
  )
475
433
 
476
- # app/models/user.rb
477
- class User < ApplicationRecord
478
- def self.sync_from_wowsql
479
- response = WOWSQL_CLIENT.table('users')
480
- .select('*')
481
- .get
482
-
483
- response['data'].each do |user_data|
484
- User.find_or_create_by(external_id: user_data['id']) do |user|
485
- user.name = user_data['name']
486
- user.email = user_data['email']
487
- end
488
- end
489
- end
490
- end
491
-
492
- # Usage in Controller
493
- class UsersController < ApplicationController
494
- def index
495
- @users = WOWSQL_CLIENT.table('users')
496
- .select('id', 'name', 'email')
497
- .get['data']
498
- end
499
- end
434
+ WOWSQL_AUTH = WOWSQL::ProjectAuthClient.new(
435
+ ENV.fetch('WOWSQL_PROJECT_URL'),
436
+ ENV.fetch('WOWSQL_ANON_KEY')
437
+ )
500
438
  ```
501
439
 
502
- ### Sinatra
440
+ Use **service key** only in server-side code (jobs, controllers that must bypass restrictions). Prefer **anon** for end-user auth flows.
503
441
 
504
- ```ruby
505
- require 'sinatra'
506
- require 'wowsql'
442
+ ---
507
443
 
508
- configure do
509
- set :wowsql_client, WOWSQL::WOWSQLClient.new(
510
- ENV['WOWSQL_PROJECT_URL'],
511
- ENV['WOWSQL_API_KEY']
512
- )
513
- end
444
+ ## Examples
514
445
 
515
- get '/users' do
516
- users = settings.wowsql_client.table('users')
517
- .select('id', 'name', 'email')
518
- .get['data']
519
-
520
- content_type :json
521
- users.to_json
522
- end
523
- ```
446
+ ### Blog: posts and comments
524
447
 
525
- ## ๐Ÿ“‹ Requirements
448
+ ```ruby
449
+ posts = WOWSQL_CLIENT.table('posts')
450
+ .select('id', 'title')
451
+ .eq('published', true)
452
+ .order_by('created_at', 'desc')
453
+ .limit(20)
454
+ .get
526
455
 
527
- - Ruby 2.6.0 or higher
528
- - Faraday 2.0+
529
- - JSON 2.0+
456
+ posts['data'].each do |p|
457
+ puts p['title']
458
+ end
459
+ ```
530
460
 
531
- ## ๐Ÿ”— Links
461
+ ### Upload avatar then save URL in `public` table
532
462
 
533
- - ๐Ÿ“š [Documentation](https://wowsql.com/docs)
534
- - ๐ŸŒ [Website](https://wowsql.com)
535
- - ๐Ÿ’ฌ [Discord](https://discord.gg/WOWSQL)
536
- - ๐Ÿ› [Issues](https://github.com/wowsql/wowsql/issues)
463
+ ```ruby
464
+ storage = WOWSQL::WOWSQLStorage.new(ENV['WOWSQL_PROJECT_URL'], ENV['WOWSQL_SERVICE_KEY'])
465
+ path = "avatars/#{user_id}.jpg"
466
+ storage.upload('default', File.binread(local_path), path: path, file_name: 'avatar.jpg')
467
+ url = storage.get_public_url('default', path)
468
+ WOWSQL_CLIENT.table('profiles').update(user_id, 'avatar_url' => url)
469
+ storage.close
470
+ ```
537
471
 
538
- ## ๐Ÿ“„ License
472
+ ---
539
473
 
540
- MIT License - see [LICENSE](LICENSE) file for details.
474
+ ## Troubleshooting
541
475
 
542
- ## ๐Ÿค Contributing
476
+ | Issue | Check |
477
+ |-------|------|
478
+ | `cannot load such file -- faraday/multipart` | Install **`faraday-multipart`** (`gem install faraday-multipart`) or upgrade to **wowsql-sdk โ‰ฅ 3.0.1**, which declares this dependency. Faraday 2 moved multipart into that gem. |
479
+ | 401 Invalid API key | Key matches project; no extra spaces; key active in dashboard. |
480
+ | 403 Schema | Using **service role** for `WOWSQLSchema`. |
481
+ | 413 Storage | `StorageLimitExceededError` โ€” plan / quota / object size. |
482
+ | SSL errors | `verify_ssl: false` temporarily on dev only. |
543
483
 
544
- Contributions are welcome! Please open an issue or submit a pull request.
484
+ ---
545
485
 
546
- ## ๐Ÿ“ž Support
486
+ ## Links
547
487
 
548
- - Email: support@wowsql.com
549
- - Discord: https://discord.gg/WOWSQL
550
- - Documentation: https://wowsql.com/docs
488
+ - [WowSQL Docs](https://wowsql.com/docs)
489
+ - [Dashboard](https://wowsql.com)
490
+ - [Support](mailto:support@wowsql.com)
551
491
 
552
492
  ---
553
493
 
554
- Made with โค๏ธ by the WOWSQL Team
494
+ **License:** MIT โ€” see included `LICENSE`.
495
+
496
+ *WowSQL Team*