@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/dist/auth.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { WOWSQLError } from './errors';
2
+ export { WOWSQLError };
1
3
  export interface ProjectAuthClientConfig {
2
4
  /** Project slug or full URL (e.g., `myproject` or `https://myproject.wowsql.com`) */
3
5
  projectUrl: string;
@@ -7,6 +9,8 @@ export interface ProjectAuthClientConfig {
7
9
  secure?: boolean;
8
10
  /** Request timeout in milliseconds */
9
11
  timeout?: number;
12
+ /** Verify SSL certificates (default: true). Set to false for self-signed certs in dev. */
13
+ verifySsl?: boolean;
10
14
  /**
11
15
  * Unified API key - Anonymous Key (wowsql_anon_...) for client-side,
12
16
  * or Service Role Key (wowsql_service_...) for server-side.
@@ -232,6 +236,48 @@ export declare class ProjectAuthClient {
232
236
  * Clear all stored tokens.
233
237
  */
234
238
  clearSession(): void;
239
+ /**
240
+ * Log out the current user. Invalidates the session on the server.
241
+ *
242
+ * @param accessToken - Optional access token override (uses stored token if not provided)
243
+ * @returns Object with success status and message
244
+ */
245
+ logout(accessToken?: string): Promise<{
246
+ success: boolean;
247
+ message: string;
248
+ }>;
249
+ /**
250
+ * Refresh the access token using a refresh token.
251
+ *
252
+ * @param refreshToken - Optional refresh token override (uses stored token if not provided)
253
+ * @returns AuthResponse with new session tokens
254
+ */
255
+ refreshSession(refreshToken?: string): Promise<AuthResponse>;
256
+ /**
257
+ * Change the current user's password.
258
+ *
259
+ * @param currentPassword - Current password for verification
260
+ * @param newPassword - New password (minimum 8 characters)
261
+ * @param accessToken - Optional access token override
262
+ * @returns Object with success status and message
263
+ */
264
+ changePassword(currentPassword: string, newPassword: string, accessToken?: string): Promise<{
265
+ success: boolean;
266
+ message: string;
267
+ }>;
268
+ /**
269
+ * Update the current user's profile.
270
+ *
271
+ * @param updates - Fields to update
272
+ * @param accessToken - Optional access token override
273
+ * @returns Updated AuthUser
274
+ */
275
+ updateUser(updates: {
276
+ fullName?: string;
277
+ avatarUrl?: string;
278
+ username?: string;
279
+ userMetadata?: Record<string, any>;
280
+ }, accessToken?: string): Promise<AuthUser>;
235
281
  private persistSession;
236
282
  private toWowError;
237
283
  }
package/dist/auth.js CHANGED
@@ -3,9 +3,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.ProjectAuthClient = void 0;
6
+ exports.ProjectAuthClient = exports.WOWSQLError = void 0;
7
7
  const axios_1 = __importDefault(require("axios"));
8
8
  const errors_1 = require("./errors");
9
+ Object.defineProperty(exports, "WOWSQLError", { enumerable: true, get: function () { return errors_1.WOWSQLError; } });
9
10
  class MemoryAuthTokenStorage {
10
11
  constructor() {
11
12
  this.accessToken = null;
@@ -382,6 +383,111 @@ class ProjectAuthClient {
382
383
  this.storage.setAccessToken(null);
383
384
  this.storage.setRefreshToken(null);
384
385
  }
386
+ /**
387
+ * Log out the current user. Invalidates the session on the server.
388
+ *
389
+ * @param accessToken - Optional access token override (uses stored token if not provided)
390
+ * @returns Object with success status and message
391
+ */
392
+ async logout(accessToken) {
393
+ const token = accessToken || this.accessToken || this.storage.getAccessToken();
394
+ if (!token) {
395
+ throw new errors_1.WOWSQLError('Access token is required. Please sign in first.');
396
+ }
397
+ try {
398
+ const response = await this.client.post('/logout', {}, { headers: { Authorization: `Bearer ${token}` } });
399
+ this.clearSession();
400
+ return {
401
+ success: response.data.success ?? true,
402
+ message: response.data.message ?? 'Logged out successfully',
403
+ };
404
+ }
405
+ catch (error) {
406
+ this.clearSession();
407
+ throw this.toWowError(error);
408
+ }
409
+ }
410
+ /**
411
+ * Refresh the access token using a refresh token.
412
+ *
413
+ * @param refreshToken - Optional refresh token override (uses stored token if not provided)
414
+ * @returns AuthResponse with new session tokens
415
+ */
416
+ async refreshSession(refreshToken) {
417
+ const token = refreshToken || this.refreshToken || this.storage.getRefreshToken();
418
+ if (!token) {
419
+ throw new errors_1.WOWSQLError('Refresh token is required');
420
+ }
421
+ try {
422
+ const response = await this.client.post('/token/refresh', {
423
+ refresh_token: token,
424
+ });
425
+ const session = this.persistSession(response.data);
426
+ const user = response.data.user ? mapUser(response.data.user) : undefined;
427
+ return { user, session };
428
+ }
429
+ catch (error) {
430
+ throw this.toWowError(error);
431
+ }
432
+ }
433
+ /**
434
+ * Change the current user's password.
435
+ *
436
+ * @param currentPassword - Current password for verification
437
+ * @param newPassword - New password (minimum 8 characters)
438
+ * @param accessToken - Optional access token override
439
+ * @returns Object with success status and message
440
+ */
441
+ async changePassword(currentPassword, newPassword, accessToken) {
442
+ const token = accessToken || this.accessToken || this.storage.getAccessToken();
443
+ if (!token) {
444
+ throw new errors_1.WOWSQLError('Access token is required. Please sign in first.');
445
+ }
446
+ try {
447
+ const response = await this.client.post('/change-password', {
448
+ current_password: currentPassword,
449
+ new_password: newPassword,
450
+ }, { headers: { Authorization: `Bearer ${token}` } });
451
+ return {
452
+ success: response.data.success ?? true,
453
+ message: response.data.message ?? 'Password changed successfully',
454
+ };
455
+ }
456
+ catch (error) {
457
+ throw this.toWowError(error);
458
+ }
459
+ }
460
+ /**
461
+ * Update the current user's profile.
462
+ *
463
+ * @param updates - Fields to update
464
+ * @param accessToken - Optional access token override
465
+ * @returns Updated AuthUser
466
+ */
467
+ async updateUser(updates, accessToken) {
468
+ const token = accessToken || this.accessToken || this.storage.getAccessToken();
469
+ if (!token) {
470
+ throw new errors_1.WOWSQLError('Access token is required. Please sign in first.');
471
+ }
472
+ try {
473
+ const body = {};
474
+ if (updates.fullName !== undefined)
475
+ body.full_name = updates.fullName;
476
+ if (updates.avatarUrl !== undefined)
477
+ body.avatar_url = updates.avatarUrl;
478
+ if (updates.username !== undefined)
479
+ body.username = updates.username;
480
+ if (updates.userMetadata !== undefined)
481
+ body.user_metadata = updates.userMetadata;
482
+ const response = await this.client.patch('/me', body, {
483
+ headers: { Authorization: `Bearer ${token}` },
484
+ });
485
+ return mapUser(response.data);
486
+ }
487
+ catch (error) {
488
+ throw this.toWowError(error);
489
+ }
490
+ }
385
491
  persistSession(raw) {
386
492
  const session = mapSession(raw);
387
493
  this.accessToken = session.accessToken;
package/dist/errors.d.ts CHANGED
@@ -3,3 +3,12 @@ export declare class WOWSQLError extends Error {
3
3
  response?: any | undefined;
4
4
  constructor(message: string, statusCode?: number | undefined, response?: any | undefined);
5
5
  }
6
+ export declare class SchemaPermissionError extends WOWSQLError {
7
+ constructor(message?: string, statusCode?: number, response?: any);
8
+ }
9
+ export declare class StorageError extends WOWSQLError {
10
+ constructor(message: string, statusCode?: number, response?: any);
11
+ }
12
+ export declare class StorageLimitExceededError extends StorageError {
13
+ constructor(message?: string, statusCode?: number, response?: any);
14
+ }
package/dist/errors.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.WOWSQLError = void 0;
3
+ exports.StorageLimitExceededError = exports.StorageError = exports.SchemaPermissionError = exports.WOWSQLError = void 0;
4
4
  class WOWSQLError extends Error {
5
5
  constructor(message, statusCode, response) {
6
6
  super(message);
@@ -10,3 +10,24 @@ class WOWSQLError extends Error {
10
10
  }
11
11
  }
12
12
  exports.WOWSQLError = WOWSQLError;
13
+ class SchemaPermissionError extends WOWSQLError {
14
+ constructor(message = 'Schema operations require a SERVICE ROLE key. You are using an anonymous key which cannot modify database schema.', statusCode = 403, response) {
15
+ super(message, statusCode, response);
16
+ this.name = 'SchemaPermissionError';
17
+ }
18
+ }
19
+ exports.SchemaPermissionError = SchemaPermissionError;
20
+ class StorageError extends WOWSQLError {
21
+ constructor(message, statusCode, response) {
22
+ super(message, statusCode, response);
23
+ this.name = 'StorageError';
24
+ }
25
+ }
26
+ exports.StorageError = StorageError;
27
+ class StorageLimitExceededError extends StorageError {
28
+ constructor(message = 'Storage limit exceeded', statusCode = 413, response) {
29
+ super(message, statusCode, response);
30
+ this.name = 'StorageLimitExceededError';
31
+ }
32
+ }
33
+ exports.StorageLimitExceededError = StorageLimitExceededError;
package/dist/index.d.ts CHANGED
@@ -18,6 +18,8 @@ export interface WowSQLConfig {
18
18
  secure?: boolean;
19
19
  /** Request timeout in milliseconds (default: 30000) */
20
20
  timeout?: number;
21
+ /** Verify SSL certificates (default: true). Set to false for self-signed certs in dev. */
22
+ verifySsl?: boolean;
21
23
  }
22
24
  export interface QueryOptions {
23
25
  /** Columns to select (comma-separated or array) - can include expressions like "COUNT(*)", "DATE(created_at) as date" */
@@ -111,6 +113,19 @@ export declare class WowSQLClient {
111
113
  status: string;
112
114
  timestamp: string;
113
115
  }>;
116
+ /**
117
+ * Get the underlying axios instance for advanced use cases
118
+ */
119
+ getHttpClient(): AxiosInstance;
120
+ /**
121
+ * Get the base URL for this client
122
+ */
123
+ getBaseUrl(): string;
124
+ /**
125
+ * Close the client and clean up resources.
126
+ * Cancels any pending requests.
127
+ */
128
+ close(): void;
114
129
  }
115
130
  export declare class Table<T = any> {
116
131
  private client;
@@ -148,6 +163,17 @@ export declare class Table<T = any> {
148
163
  * Insert a new record (alias for create)
149
164
  */
150
165
  insert(data: Partial<T>): Promise<CreateResponse>;
166
+ /**
167
+ * Insert multiple records at once
168
+ */
169
+ bulkInsert(records: Partial<T>[]): Promise<CreateResponse[]>;
170
+ /**
171
+ * Upsert a record — insert or update on conflict.
172
+ *
173
+ * @param data - Record data to upsert
174
+ * @param onConflict - Column to detect conflict on (default: 'id')
175
+ */
176
+ upsert(data: Partial<T>, onConflict?: string): Promise<CreateResponse | UpdateResponse>;
151
177
  /**
152
178
  * Update a record by ID
153
179
  */
@@ -156,6 +182,23 @@ export declare class Table<T = any> {
156
182
  * Delete a record by ID
157
183
  */
158
184
  delete(id: string | number): Promise<DeleteResponse>;
185
+ eq(column: string, value: any): QueryBuilder<T>;
186
+ neq(column: string, value: any): QueryBuilder<T>;
187
+ gt(column: string, value: any): QueryBuilder<T>;
188
+ gte(column: string, value: any): QueryBuilder<T>;
189
+ lt(column: string, value: any): QueryBuilder<T>;
190
+ lte(column: string, value: any): QueryBuilder<T>;
191
+ orderBy(column: string | OrderByItem[], direction?: 'asc' | 'desc'): QueryBuilder<T>;
192
+ count(): Promise<number>;
193
+ paginate(page?: number, perPage?: number): Promise<{
194
+ data: T[];
195
+ page: number;
196
+ perPage: number;
197
+ total: number;
198
+ totalPages: number;
199
+ hasNext: boolean;
200
+ hasPrev: boolean;
201
+ }>;
159
202
  }
160
203
  export declare class QueryBuilder<T = any> {
161
204
  private client;
@@ -274,6 +317,34 @@ export declare class QueryBuilder<T = any> {
274
317
  * Get first record
275
318
  */
276
319
  first(): Promise<T | null>;
320
+ /**
321
+ * Get exactly one record. Throws if zero or multiple results.
322
+ */
323
+ single(): Promise<T>;
324
+ /**
325
+ * Get record count matching current filters (uses COUNT(*) aggregate).
326
+ */
327
+ count(): Promise<number>;
328
+ /**
329
+ * Get sum of a column matching current filters.
330
+ */
331
+ sum(column: string): Promise<number>;
332
+ /**
333
+ * Get average of a column matching current filters.
334
+ */
335
+ avg(column: string): Promise<number>;
336
+ /**
337
+ * Paginate results. Returns data along with pagination metadata.
338
+ */
339
+ paginate(page?: number, perPage?: number): Promise<{
340
+ data: T[];
341
+ page: number;
342
+ perPage: number;
343
+ total: number;
344
+ totalPages: number;
345
+ hasNext: boolean;
346
+ hasPrev: boolean;
347
+ }>;
277
348
  }
278
349
  export * from './storage';
279
350
  export * from './auth';
package/dist/index.js CHANGED
@@ -100,6 +100,27 @@ class WowSQLClient {
100
100
  const response = await this.client.get('/health');
101
101
  return response.data;
102
102
  }
103
+ /**
104
+ * Get the underlying axios instance for advanced use cases
105
+ */
106
+ getHttpClient() {
107
+ return this.client;
108
+ }
109
+ /**
110
+ * Get the base URL for this client
111
+ */
112
+ getBaseUrl() {
113
+ return this.baseUrl;
114
+ }
115
+ /**
116
+ * Close the client and clean up resources.
117
+ * Cancels any pending requests.
118
+ */
119
+ close() {
120
+ // Nothing to explicitly close with axios, but clear defaults to prevent reuse
121
+ this.client.defaults.baseURL = '';
122
+ this.client.defaults.headers.common = {};
123
+ }
103
124
  }
104
125
  exports.WowSQLClient = WowSQLClient;
105
126
  // ==================== Table Class ====================
@@ -154,6 +175,31 @@ class Table {
154
175
  async insert(data) {
155
176
  return this.create(data);
156
177
  }
178
+ /**
179
+ * Insert multiple records at once
180
+ */
181
+ async bulkInsert(records) {
182
+ const promises = records.map(record => this.client.post(`/${this.tableName}`, record).then(r => r.data));
183
+ return Promise.all(promises);
184
+ }
185
+ /**
186
+ * Upsert a record — insert or update on conflict.
187
+ *
188
+ * @param data - Record data to upsert
189
+ * @param onConflict - Column to detect conflict on (default: 'id')
190
+ */
191
+ async upsert(data, onConflict = 'id') {
192
+ try {
193
+ return await this.create(data);
194
+ }
195
+ catch {
196
+ const id = data[onConflict];
197
+ if (id !== undefined) {
198
+ return await this.update(id, data);
199
+ }
200
+ throw new errors_1.WOWSQLError(`Upsert failed: no '${onConflict}' field in data for conflict resolution`);
201
+ }
202
+ }
157
203
  /**
158
204
  * Update a record by ID
159
205
  */
@@ -168,6 +214,34 @@ class Table {
168
214
  const response = await this.client.delete(`/${this.tableName}/${id}`);
169
215
  return response.data;
170
216
  }
217
+ // ==================== Convenience Shortcuts ====================
218
+ eq(column, value) {
219
+ return new QueryBuilder(this.client, this.tableName).eq(column, value);
220
+ }
221
+ neq(column, value) {
222
+ return new QueryBuilder(this.client, this.tableName).neq(column, value);
223
+ }
224
+ gt(column, value) {
225
+ return new QueryBuilder(this.client, this.tableName).gt(column, value);
226
+ }
227
+ gte(column, value) {
228
+ return new QueryBuilder(this.client, this.tableName).gte(column, value);
229
+ }
230
+ lt(column, value) {
231
+ return new QueryBuilder(this.client, this.tableName).lt(column, value);
232
+ }
233
+ lte(column, value) {
234
+ return new QueryBuilder(this.client, this.tableName).lte(column, value);
235
+ }
236
+ orderBy(column, direction) {
237
+ return new QueryBuilder(this.client, this.tableName).orderBy(column, direction);
238
+ }
239
+ async count() {
240
+ return new QueryBuilder(this.client, this.tableName).count();
241
+ }
242
+ async paginate(page = 1, perPage = 20) {
243
+ return new QueryBuilder(this.client, this.tableName).paginate(page, perPage);
244
+ }
171
245
  }
172
246
  exports.Table = Table;
173
247
  // ==================== Query Builder ====================
@@ -470,6 +544,94 @@ class QueryBuilder {
470
544
  const result = await this.limit(1).get();
471
545
  return result.data[0] || null;
472
546
  }
547
+ /**
548
+ * Get exactly one record. Throws if zero or multiple results.
549
+ */
550
+ async single() {
551
+ const result = await this.limit(2).get();
552
+ if (result.data.length === 0) {
553
+ throw new errors_1.WOWSQLError('No records found', 404);
554
+ }
555
+ if (result.data.length > 1) {
556
+ throw new errors_1.WOWSQLError('Multiple records found when expecting single result', 400);
557
+ }
558
+ return result.data[0];
559
+ }
560
+ /**
561
+ * Get record count matching current filters (uses COUNT(*) aggregate).
562
+ */
563
+ async count() {
564
+ const saved = { ...this.options };
565
+ this.options.select = ['COUNT(*) as count'];
566
+ this.options.group_by = undefined;
567
+ this.options.having = undefined;
568
+ this.options.order = undefined;
569
+ this.options.limit = undefined;
570
+ this.options.offset = undefined;
571
+ try {
572
+ const result = await this.get();
573
+ return result.data[0]?.count ?? 0;
574
+ }
575
+ finally {
576
+ this.options = saved;
577
+ }
578
+ }
579
+ /**
580
+ * Get sum of a column matching current filters.
581
+ */
582
+ async sum(column) {
583
+ const saved = { ...this.options };
584
+ this.options.select = [`SUM("${column}") as total`];
585
+ this.options.group_by = undefined;
586
+ this.options.having = undefined;
587
+ this.options.order = undefined;
588
+ this.options.limit = undefined;
589
+ this.options.offset = undefined;
590
+ try {
591
+ const result = await this.get();
592
+ return result.data[0]?.total ?? 0;
593
+ }
594
+ finally {
595
+ this.options = saved;
596
+ }
597
+ }
598
+ /**
599
+ * Get average of a column matching current filters.
600
+ */
601
+ async avg(column) {
602
+ const saved = { ...this.options };
603
+ this.options.select = [`AVG("${column}") as average`];
604
+ this.options.group_by = undefined;
605
+ this.options.having = undefined;
606
+ this.options.order = undefined;
607
+ this.options.limit = undefined;
608
+ this.options.offset = undefined;
609
+ try {
610
+ const result = await this.get();
611
+ return result.data[0]?.average ?? 0;
612
+ }
613
+ finally {
614
+ this.options = saved;
615
+ }
616
+ }
617
+ /**
618
+ * Paginate results. Returns data along with pagination metadata.
619
+ */
620
+ async paginate(page = 1, perPage = 20) {
621
+ this.options.limit = perPage;
622
+ this.options.offset = (page - 1) * perPage;
623
+ const result = await this.get();
624
+ const totalPages = Math.ceil(result.total / perPage);
625
+ return {
626
+ data: result.data,
627
+ page,
628
+ perPage,
629
+ total: result.total,
630
+ totalPages,
631
+ hasNext: page < totalPages,
632
+ hasPrev: page > 1,
633
+ };
634
+ }
473
635
  }
474
636
  exports.QueryBuilder = QueryBuilder;
475
637
  // ==================== Exports ====================