@wowsql/sdk 3.6.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/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 ====================
@@ -148,6 +169,37 @@ class Table {
148
169
  const response = await this.client.post(`/${this.tableName}`, data);
149
170
  return response.data;
150
171
  }
172
+ /**
173
+ * Insert a new record (alias for create)
174
+ */
175
+ async insert(data) {
176
+ return this.create(data);
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
+ }
151
203
  /**
152
204
  * Update a record by ID
153
205
  */
@@ -162,6 +214,34 @@ class Table {
162
214
  const response = await this.client.delete(`/${this.tableName}/${id}`);
163
215
  return response.data;
164
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
+ }
165
245
  }
166
246
  exports.Table = Table;
167
247
  // ==================== Query Builder ====================
@@ -272,6 +352,91 @@ class QueryBuilder {
272
352
  this.options.offset = offset;
273
353
  return this;
274
354
  }
355
+ // ==================== Convenience Methods ====================
356
+ /**
357
+ * Filter where column equals value
358
+ */
359
+ eq(column, value) {
360
+ return this.filter(column, 'eq', value);
361
+ }
362
+ /**
363
+ * Filter where column does not equal value
364
+ */
365
+ neq(column, value) {
366
+ return this.filter(column, 'neq', value);
367
+ }
368
+ /**
369
+ * Filter where column is greater than value
370
+ */
371
+ gt(column, value) {
372
+ return this.filter(column, 'gt', value);
373
+ }
374
+ /**
375
+ * Filter where column is greater than or equal to value
376
+ */
377
+ gte(column, value) {
378
+ return this.filter(column, 'gte', value);
379
+ }
380
+ /**
381
+ * Filter where column is less than value
382
+ */
383
+ lt(column, value) {
384
+ return this.filter(column, 'lt', value);
385
+ }
386
+ /**
387
+ * Filter where column is less than or equal to value
388
+ */
389
+ lte(column, value) {
390
+ return this.filter(column, 'lte', value);
391
+ }
392
+ /**
393
+ * Filter where column matches pattern (SQL LIKE)
394
+ */
395
+ like(column, value) {
396
+ return this.filter(column, 'like', value);
397
+ }
398
+ /**
399
+ * Filter where column IS NULL
400
+ */
401
+ isNull(column) {
402
+ return this.filter(column, 'is', null);
403
+ }
404
+ /**
405
+ * Filter where column IS NOT NULL
406
+ */
407
+ isNotNull(column) {
408
+ return this.filter(column, 'is_not', null);
409
+ }
410
+ /**
411
+ * Filter where column is in list of values
412
+ */
413
+ in(column, values) {
414
+ return this.filter(column, 'in', values);
415
+ }
416
+ /**
417
+ * Filter where column is not in list of values
418
+ */
419
+ notIn(column, values) {
420
+ return this.filter(column, 'not_in', values);
421
+ }
422
+ /**
423
+ * Filter where column is between min and max values
424
+ */
425
+ between(column, minValue, maxValue) {
426
+ return this.filter(column, 'between', [minValue, maxValue]);
427
+ }
428
+ /**
429
+ * Filter where column is not between min and max values
430
+ */
431
+ notBetween(column, minValue, maxValue) {
432
+ return this.filter(column, 'not_between', [minValue, maxValue]);
433
+ }
434
+ /**
435
+ * Add an OR filter condition
436
+ */
437
+ or(column, operator, value) {
438
+ return this.filter(column, operator, value, 'OR');
439
+ }
275
440
  /**
276
441
  * Execute query - uses POST /{table}/query for advanced features, GET for simple queries
277
442
  */
@@ -366,6 +531,12 @@ class QueryBuilder {
366
531
  return response.data;
367
532
  }
368
533
  }
534
+ /**
535
+ * Execute the query (alias for get)
536
+ */
537
+ async execute(additionalOptions) {
538
+ return this.get(additionalOptions);
539
+ }
369
540
  /**
370
541
  * Get first record
371
542
  */
@@ -373,6 +544,94 @@ class QueryBuilder {
373
544
  const result = await this.limit(1).get();
374
545
  return result.data[0] || null;
375
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
+ }
376
635
  }
377
636
  exports.QueryBuilder = QueryBuilder;
378
637
  // ==================== Exports ====================
package/dist/schema.d.ts CHANGED
@@ -1,9 +1,15 @@
1
1
  /**
2
- * Schema management client for WowSQL.
3
- * Requires SERVICE ROLE key.
2
+ * WowSQL Schema SDK - DDL operations for PostgreSQL.
3
+ * Requires a SERVICE ROLE key (wowsql_service_...).
4
+ *
5
+ * @version 3.0.0
6
+ * @license MIT
4
7
  */
8
+ import { SchemaPermissionError } from './errors';
9
+ export { SchemaPermissionError };
5
10
  export interface ColumnDefinition {
6
11
  name: string;
12
+ /** PostgreSQL type: SERIAL, BIGSERIAL, VARCHAR(n), TEXT, INT, BIGINT, BOOLEAN, TIMESTAMPTZ, UUID, JSONB, etc. */
7
13
  type: string;
8
14
  auto_increment?: boolean;
9
15
  unique?: boolean;
@@ -25,34 +31,180 @@ export interface AlterTableOptions {
25
31
  nullable?: boolean;
26
32
  default?: string;
27
33
  }
34
+ export interface SchemaConfig {
35
+ /** Project subdomain or full URL (e.g., 'myproject' or 'https://myproject.wowsql.com') */
36
+ projectUrl: string;
37
+ /** SERVICE ROLE key (wowsql_service_...) — NOT an anonymous key */
38
+ serviceKey: string;
39
+ /** Base domain (default: wowsql.com) */
40
+ baseDomain?: string;
41
+ /** Use HTTPS (default: true) */
42
+ secure?: boolean;
43
+ /** Request timeout in milliseconds (default: 30000) */
44
+ timeout?: number;
45
+ /** Verify SSL certificates (default: true). Set to false for self-signed certs in dev. */
46
+ verifySsl?: boolean;
47
+ }
48
+ export interface TableSchema {
49
+ table: string;
50
+ columns: SchemaColumnInfo[];
51
+ primary_key: string | null;
52
+ }
53
+ export interface SchemaColumnInfo {
54
+ name: string;
55
+ type: string;
56
+ nullable: boolean;
57
+ key: string;
58
+ default: any;
59
+ extra: string;
60
+ }
28
61
  export declare class WowSQLSchema {
62
+ private client;
29
63
  private baseUrl;
30
- private serviceKey;
31
64
  /**
32
65
  * Initialize schema client.
33
66
  *
34
- * ⚠️ IMPORTANT: Requires SERVICE ROLE key, not anonymous key!
67
+ * Requires a SERVICE ROLE key. Anonymous keys cannot perform DDL operations.
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * const schema = new WowSQLSchema({
72
+ * projectUrl: 'myproject',
73
+ * serviceKey: 'wowsql_service_...'
74
+ * });
35
75
  *
36
- * @param projectUrl - Project URL (e.g., "https://myproject.wowsql.com")
37
- * @param serviceKey - SERVICE ROLE key (not anonymous key!)
76
+ * await schema.createTable({
77
+ * tableName: 'products',
78
+ * columns: [
79
+ * { name: 'id', type: 'SERIAL', auto_increment: true },
80
+ * { name: 'name', type: 'VARCHAR(255)', nullable: false },
81
+ * { name: 'price', type: 'NUMERIC(10,2)', default: '0' },
82
+ * { name: 'tags', type: 'TEXT[]' },
83
+ * { name: 'metadata', type: 'JSONB', default: "'{}'" },
84
+ * { name: 'created_at', type: 'TIMESTAMPTZ', default: 'CURRENT_TIMESTAMP' }
85
+ * ],
86
+ * primaryKey: 'id',
87
+ * indexes: ['name']
88
+ * });
89
+ * ```
38
90
  */
39
- constructor(projectUrl: string, serviceKey: string);
91
+ constructor(config: SchemaConfig | string, serviceKey?: string);
40
92
  /**
41
- * Create a new table.
93
+ * Create a new table with PostgreSQL column types.
94
+ *
95
+ * Supported PostgreSQL types: SERIAL, BIGSERIAL, VARCHAR(n), TEXT, INT, BIGINT,
96
+ * BOOLEAN, NUMERIC(p,s), REAL, DOUBLE PRECISION, TIMESTAMPTZ, DATE, TIME,
97
+ * UUID, JSONB, TEXT[], INT[], BYTEA, etc.
42
98
  */
43
- createTable(options: CreateTableOptions): Promise<any>;
99
+ createTable(options: CreateTableOptions): Promise<{
100
+ success: boolean;
101
+ message: string;
102
+ table: string;
103
+ already_exists?: boolean;
104
+ }>;
44
105
  /**
45
106
  * Alter an existing table.
107
+ *
108
+ * Operations:
109
+ * - `add_column`: Add a new column
110
+ * - `drop_column`: Remove a column
111
+ * - `modify_column`: Change column type/constraints (PostgreSQL ALTER COLUMN ... TYPE)
112
+ * - `rename_column`: Rename a column
46
113
  */
47
- alterTable(options: AlterTableOptions): Promise<any>;
114
+ alterTable(options: AlterTableOptions): Promise<{
115
+ success: boolean;
116
+ message: string;
117
+ operation: string;
118
+ }>;
48
119
  /**
49
120
  * Drop a table.
50
121
  *
51
- * ⚠️ WARNING: This operation cannot be undone!
122
+ * WARNING: This operation cannot be undone!
123
+ *
124
+ * @param tableName - Table to drop
125
+ * @param cascade - If true, also drops dependent objects (foreign keys, views, etc.)
52
126
  */
53
- dropTable(tableName: string, cascade?: boolean): Promise<any>;
127
+ dropTable(tableName: string, cascade?: boolean): Promise<{
128
+ success: boolean;
129
+ message: string;
130
+ warning: string;
131
+ }>;
54
132
  /**
55
133
  * Execute raw SQL for schema operations.
134
+ *
135
+ * Only DDL statements are allowed: CREATE TABLE, ALTER TABLE, DROP TABLE,
136
+ * CREATE INDEX, DROP INDEX, CREATE EXTENSION, CREATE TYPE, ALTER TYPE,
137
+ * CREATE SEQUENCE, ALTER SEQUENCE, DROP SEQUENCE.
138
+ *
139
+ * @param sql - SQL DDL statement
140
+ */
141
+ executeSQL(sql: string): Promise<{
142
+ success: boolean;
143
+ message: string;
144
+ rows_affected: number;
145
+ }>;
146
+ /**
147
+ * Add a column to an existing table.
148
+ */
149
+ addColumn(tableName: string, columnName: string, columnType: string, options?: {
150
+ nullable?: boolean;
151
+ default?: string;
152
+ }): Promise<{
153
+ success: boolean;
154
+ message: string;
155
+ operation: string;
156
+ }>;
157
+ /**
158
+ * Drop a column from a table.
159
+ */
160
+ dropColumn(tableName: string, columnName: string): Promise<{
161
+ success: boolean;
162
+ message: string;
163
+ operation: string;
164
+ }>;
165
+ /**
166
+ * Rename a column in a table.
167
+ */
168
+ renameColumn(tableName: string, oldName: string, newName: string): Promise<{
169
+ success: boolean;
170
+ message: string;
171
+ operation: string;
172
+ }>;
173
+ /**
174
+ * Modify a column's type or constraints.
175
+ */
176
+ modifyColumn(tableName: string, columnName: string, columnType: string, options?: {
177
+ nullable?: boolean;
178
+ default?: string;
179
+ }): Promise<{
180
+ success: boolean;
181
+ message: string;
182
+ operation: string;
183
+ }>;
184
+ /**
185
+ * Create an index using raw SQL.
186
+ *
187
+ * @param tableName - Table to index
188
+ * @param columnNames - Column(s) to include in the index
189
+ * @param options - Index options
190
+ */
191
+ createIndex(tableName: string, columnNames: string | string[], options?: {
192
+ unique?: boolean;
193
+ name?: string;
194
+ using?: 'btree' | 'hash' | 'gin' | 'gist';
195
+ }): Promise<{
196
+ success: boolean;
197
+ message: string;
198
+ rows_affected: number;
199
+ }>;
200
+ /**
201
+ * List all tables in the database.
202
+ * Uses the v2 tables endpoint (GET /api/v2/tables).
203
+ */
204
+ listTables(): Promise<string[]>;
205
+ /**
206
+ * Get the schema for a specific table.
207
+ * Uses the v2 tables endpoint (GET /api/v2/tables/{name}/schema).
56
208
  */
57
- executeSQL(sql: string): Promise<any>;
209
+ getTableSchema(tableName: string): Promise<TableSchema>;
58
210
  }