@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/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 ====================
|