@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/schema.js CHANGED
@@ -1,116 +1,245 @@
1
1
  "use strict";
2
2
  /**
3
- * Schema management client for WowSQL.
4
- * Requires SERVICE ROLE key.
3
+ * WowSQL Schema SDK - DDL operations for PostgreSQL.
4
+ * Requires a SERVICE ROLE key (wowsql_service_...).
5
+ *
6
+ * @version 3.0.0
7
+ * @license MIT
5
8
  */
9
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10
+ return (mod && mod.__esModule) ? mod : { "default": mod };
11
+ };
6
12
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.WowSQLSchema = void 0;
13
+ exports.WowSQLSchema = exports.SchemaPermissionError = void 0;
14
+ const axios_1 = __importDefault(require("axios"));
15
+ const errors_1 = require("./errors");
16
+ Object.defineProperty(exports, "SchemaPermissionError", { enumerable: true, get: function () { return errors_1.SchemaPermissionError; } });
17
+ // ==================== Schema Client ====================
8
18
  class WowSQLSchema {
9
19
  /**
10
20
  * Initialize schema client.
11
21
  *
12
- * ⚠️ IMPORTANT: Requires SERVICE ROLE key, not anonymous key!
22
+ * Requires a SERVICE ROLE key. Anonymous keys cannot perform DDL operations.
13
23
  *
14
- * @param projectUrl - Project URL (e.g., "https://myproject.wowsql.com")
15
- * @param serviceKey - SERVICE ROLE key (not anonymous key!)
24
+ * @example
25
+ * ```typescript
26
+ * const schema = new WowSQLSchema({
27
+ * projectUrl: 'myproject',
28
+ * serviceKey: 'wowsql_service_...'
29
+ * });
30
+ *
31
+ * await schema.createTable({
32
+ * tableName: 'products',
33
+ * columns: [
34
+ * { name: 'id', type: 'SERIAL', auto_increment: true },
35
+ * { name: 'name', type: 'VARCHAR(255)', nullable: false },
36
+ * { name: 'price', type: 'NUMERIC(10,2)', default: '0' },
37
+ * { name: 'tags', type: 'TEXT[]' },
38
+ * { name: 'metadata', type: 'JSONB', default: "'{}'" },
39
+ * { name: 'created_at', type: 'TIMESTAMPTZ', default: 'CURRENT_TIMESTAMP' }
40
+ * ],
41
+ * primaryKey: 'id',
42
+ * indexes: ['name']
43
+ * });
44
+ * ```
16
45
  */
17
- constructor(projectUrl, serviceKey) {
18
- this.baseUrl = projectUrl.replace(/\/$/, '');
19
- this.serviceKey = serviceKey;
46
+ constructor(config, serviceKey) {
47
+ if (typeof config === 'string') {
48
+ this.baseUrl = config.replace(/\/$/, '');
49
+ if (!this.baseUrl.startsWith('http')) {
50
+ this.baseUrl = `https://${this.baseUrl}`;
51
+ }
52
+ this.client = axios_1.default.create({
53
+ baseURL: `${this.baseUrl}/api/v2/schema`,
54
+ headers: {
55
+ 'Authorization': `Bearer ${serviceKey}`,
56
+ 'Content-Type': 'application/json',
57
+ },
58
+ timeout: 30000,
59
+ });
60
+ }
61
+ else {
62
+ const protocol = config.secure !== false ? 'https' : 'http';
63
+ if (config.projectUrl.startsWith('http://') || config.projectUrl.startsWith('https://')) {
64
+ this.baseUrl = config.projectUrl.replace(/\/+$/, '');
65
+ }
66
+ else {
67
+ const domain = config.baseDomain || 'wowsql.com';
68
+ if (config.projectUrl.includes(`.${domain}`) || config.projectUrl.endsWith(domain)) {
69
+ this.baseUrl = `${protocol}://${config.projectUrl}`;
70
+ }
71
+ else {
72
+ this.baseUrl = `${protocol}://${config.projectUrl}.${domain}`;
73
+ }
74
+ }
75
+ this.client = axios_1.default.create({
76
+ baseURL: `${this.baseUrl}/api/v2/schema`,
77
+ headers: {
78
+ 'Authorization': `Bearer ${config.serviceKey}`,
79
+ 'Content-Type': 'application/json',
80
+ },
81
+ timeout: config.timeout || 30000,
82
+ });
83
+ }
84
+ this.client.interceptors.response.use((response) => response, (error) => {
85
+ if (error.response) {
86
+ const data = error.response.data;
87
+ const message = data?.detail || data?.message || error.message;
88
+ if (error.response.status === 403) {
89
+ throw new errors_1.SchemaPermissionError(message, 403, data);
90
+ }
91
+ throw new errors_1.WOWSQLError(message, error.response.status, data);
92
+ }
93
+ throw new errors_1.WOWSQLError(error.message);
94
+ });
20
95
  }
21
96
  /**
22
- * Create a new table.
97
+ * Create a new table with PostgreSQL column types.
98
+ *
99
+ * Supported PostgreSQL types: SERIAL, BIGSERIAL, VARCHAR(n), TEXT, INT, BIGINT,
100
+ * BOOLEAN, NUMERIC(p,s), REAL, DOUBLE PRECISION, TIMESTAMPTZ, DATE, TIME,
101
+ * UUID, JSONB, TEXT[], INT[], BYTEA, etc.
23
102
  */
24
103
  async createTable(options) {
25
- const url = `${this.baseUrl}/api/v2/schema/tables`;
26
- const response = await fetch(url, {
27
- method: 'POST',
28
- headers: {
29
- 'Authorization': `Bearer ${this.serviceKey}`,
30
- 'Content-Type': 'application/json'
31
- },
32
- body: JSON.stringify({
33
- table_name: options.tableName,
34
- columns: options.columns,
35
- primary_key: options.primaryKey,
36
- indexes: options.indexes
37
- })
104
+ const response = await this.client.post('/tables', {
105
+ table_name: options.tableName,
106
+ columns: options.columns,
107
+ primary_key: options.primaryKey,
108
+ indexes: options.indexes,
38
109
  });
39
- if (response.status === 403) {
40
- throw new Error('Schema operations require a SERVICE ROLE key. ' +
41
- 'You are using an anonymous key which cannot modify database schema.');
42
- }
43
- if (!response.ok) {
44
- const error = await response.json();
45
- throw new Error(`Failed to create table: ${error.detail || response.statusText}`);
46
- }
47
- return response.json();
110
+ return response.data;
48
111
  }
49
112
  /**
50
113
  * Alter an existing table.
114
+ *
115
+ * Operations:
116
+ * - `add_column`: Add a new column
117
+ * - `drop_column`: Remove a column
118
+ * - `modify_column`: Change column type/constraints (PostgreSQL ALTER COLUMN ... TYPE)
119
+ * - `rename_column`: Rename a column
51
120
  */
52
121
  async alterTable(options) {
53
- const url = `${this.baseUrl}/api/v2/schema/tables/${options.tableName}`;
54
- const response = await fetch(url, {
55
- method: 'PATCH',
56
- headers: {
57
- 'Authorization': `Bearer ${this.serviceKey}`,
58
- 'Content-Type': 'application/json'
59
- },
60
- body: JSON.stringify(options)
122
+ const response = await this.client.patch(`/tables/${options.tableName}`, {
123
+ table_name: options.tableName,
124
+ operation: options.operation,
125
+ column_name: options.columnName,
126
+ column_type: options.columnType,
127
+ new_column_name: options.newColumnName,
128
+ nullable: options.nullable,
129
+ default: options.default,
61
130
  });
62
- if (response.status === 403) {
63
- throw new Error('Schema operations require a SERVICE ROLE key.');
64
- }
65
- if (!response.ok) {
66
- const error = await response.json();
67
- throw new Error(`Failed to alter table: ${error.detail || response.statusText}`);
68
- }
69
- return response.json();
131
+ return response.data;
70
132
  }
71
133
  /**
72
134
  * Drop a table.
73
135
  *
74
- * ⚠️ WARNING: This operation cannot be undone!
136
+ * WARNING: This operation cannot be undone!
137
+ *
138
+ * @param tableName - Table to drop
139
+ * @param cascade - If true, also drops dependent objects (foreign keys, views, etc.)
75
140
  */
76
141
  async dropTable(tableName, cascade = false) {
77
- const url = `${this.baseUrl}/api/v2/schema/tables/${tableName}?cascade=${cascade}`;
78
- const response = await fetch(url, {
79
- method: 'DELETE',
80
- headers: {
81
- 'Authorization': `Bearer ${this.serviceKey}`
82
- }
142
+ const response = await this.client.delete(`/tables/${tableName}`, {
143
+ params: { cascade },
83
144
  });
84
- if (response.status === 403) {
85
- throw new Error('Schema operations require a SERVICE ROLE key.');
86
- }
87
- if (!response.ok) {
88
- const error = await response.json();
89
- throw new Error(`Failed to drop table: ${error.detail || response.statusText}`);
90
- }
91
- return response.json();
145
+ return response.data;
92
146
  }
93
147
  /**
94
148
  * Execute raw SQL for schema operations.
149
+ *
150
+ * Only DDL statements are allowed: CREATE TABLE, ALTER TABLE, DROP TABLE,
151
+ * CREATE INDEX, DROP INDEX, CREATE EXTENSION, CREATE TYPE, ALTER TYPE,
152
+ * CREATE SEQUENCE, ALTER SEQUENCE, DROP SEQUENCE.
153
+ *
154
+ * @param sql - SQL DDL statement
95
155
  */
96
156
  async executeSQL(sql) {
97
- const url = `${this.baseUrl}/api/v2/schema/execute`;
98
- const response = await fetch(url, {
99
- method: 'POST',
100
- headers: {
101
- 'Authorization': `Bearer ${this.serviceKey}`,
102
- 'Content-Type': 'application/json'
103
- },
104
- body: JSON.stringify({ sql })
157
+ const response = await this.client.post('/execute', { sql });
158
+ return response.data;
159
+ }
160
+ // ==================== Convenience Methods ====================
161
+ /**
162
+ * Add a column to an existing table.
163
+ */
164
+ async addColumn(tableName, columnName, columnType, options) {
165
+ return this.alterTable({
166
+ tableName,
167
+ operation: 'add_column',
168
+ columnName,
169
+ columnType,
170
+ nullable: options?.nullable,
171
+ default: options?.default,
105
172
  });
106
- if (response.status === 403) {
107
- throw new Error('Schema operations require a SERVICE ROLE key.');
108
- }
109
- if (!response.ok) {
110
- const error = await response.json();
111
- throw new Error(`Failed to execute SQL: ${error.detail || response.statusText}`);
112
- }
113
- return response.json();
173
+ }
174
+ /**
175
+ * Drop a column from a table.
176
+ */
177
+ async dropColumn(tableName, columnName) {
178
+ return this.alterTable({
179
+ tableName,
180
+ operation: 'drop_column',
181
+ columnName,
182
+ });
183
+ }
184
+ /**
185
+ * Rename a column in a table.
186
+ */
187
+ async renameColumn(tableName, oldName, newName) {
188
+ return this.alterTable({
189
+ tableName,
190
+ operation: 'rename_column',
191
+ columnName: oldName,
192
+ newColumnName: newName,
193
+ });
194
+ }
195
+ /**
196
+ * Modify a column's type or constraints.
197
+ */
198
+ async modifyColumn(tableName, columnName, columnType, options) {
199
+ return this.alterTable({
200
+ tableName,
201
+ operation: 'modify_column',
202
+ columnName,
203
+ columnType,
204
+ nullable: options?.nullable,
205
+ default: options?.default,
206
+ });
207
+ }
208
+ /**
209
+ * Create an index using raw SQL.
210
+ *
211
+ * @param tableName - Table to index
212
+ * @param columnNames - Column(s) to include in the index
213
+ * @param options - Index options
214
+ */
215
+ async createIndex(tableName, columnNames, options) {
216
+ const cols = Array.isArray(columnNames) ? columnNames : [columnNames];
217
+ const indexName = options?.name || `idx_${tableName}_${cols.join('_')}`;
218
+ const unique = options?.unique ? 'UNIQUE ' : '';
219
+ const using = options?.using ? ` USING ${options.using}` : '';
220
+ const colList = cols.map(c => `"${c}"`).join(', ');
221
+ const sql = `CREATE ${unique}INDEX IF NOT EXISTS "${indexName}" ON "${tableName}"${using} (${colList})`;
222
+ return this.executeSQL(sql);
223
+ }
224
+ /**
225
+ * List all tables in the database.
226
+ * Uses the v2 tables endpoint (GET /api/v2/tables).
227
+ */
228
+ async listTables() {
229
+ const response = await this.client.get('/tables', {
230
+ baseURL: `${this.baseUrl}/api/v2`,
231
+ });
232
+ return response.data.tables;
233
+ }
234
+ /**
235
+ * Get the schema for a specific table.
236
+ * Uses the v2 tables endpoint (GET /api/v2/tables/{name}/schema).
237
+ */
238
+ async getTableSchema(tableName) {
239
+ const response = await this.client.get(`/tables/${tableName}/schema`, {
240
+ baseURL: `${this.baseUrl}/api/v2`,
241
+ });
242
+ return response.data;
114
243
  }
115
244
  }
116
245
  exports.WowSQLSchema = WowSQLSchema;