@aerostack/sdk 0.1.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.mjs ADDED
@@ -0,0 +1,1169 @@
1
+ // src/client-errors.ts
2
+ var ClientErrorCode = /* @__PURE__ */ ((ClientErrorCode2) => {
3
+ ClientErrorCode2["AUTH_INVALID_CREDENTIALS"] = "AUTH_INVALID_CREDENTIALS";
4
+ ClientErrorCode2["AUTH_USER_EXISTS"] = "AUTH_USER_EXISTS";
5
+ ClientErrorCode2["AUTH_EMAIL_NOT_VERIFIED"] = "AUTH_EMAIL_NOT_VERIFIED";
6
+ ClientErrorCode2["AUTH_TOKEN_EXPIRED"] = "AUTH_TOKEN_EXPIRED";
7
+ ClientErrorCode2["AUTH_TOKEN_INVALID"] = "AUTH_TOKEN_INVALID";
8
+ ClientErrorCode2["AUTH_OTP_EXPIRED"] = "AUTH_OTP_EXPIRED";
9
+ ClientErrorCode2["AUTH_OTP_INVALID"] = "AUTH_OTP_INVALID";
10
+ ClientErrorCode2["AUTH_PASSWORD_WEAK"] = "AUTH_PASSWORD_WEAK";
11
+ ClientErrorCode2["AUTH_RESET_TOKEN_INVALID"] = "AUTH_RESET_TOKEN_INVALID";
12
+ ClientErrorCode2["NETWORK_ERROR"] = "NETWORK_ERROR";
13
+ ClientErrorCode2["NETWORK_TIMEOUT"] = "NETWORK_TIMEOUT";
14
+ ClientErrorCode2["VALIDATION_ERROR"] = "VALIDATION_ERROR";
15
+ ClientErrorCode2["REQUEST_FAILED"] = "REQUEST_FAILED";
16
+ ClientErrorCode2["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
17
+ return ClientErrorCode2;
18
+ })(ClientErrorCode || {});
19
+ var ClientError = class _ClientError extends Error {
20
+ constructor(code, message, details, statusCode) {
21
+ super(message);
22
+ this.code = code;
23
+ this.details = details;
24
+ this.statusCode = statusCode;
25
+ this.name = "ClientError";
26
+ Object.setPrototypeOf(this, _ClientError.prototype);
27
+ }
28
+ /**
29
+ * Check if this is an authentication error
30
+ */
31
+ isAuthError() {
32
+ return this.code.toString().startsWith("AUTH_");
33
+ }
34
+ /**
35
+ * Check if this is a network error
36
+ */
37
+ isNetworkError() {
38
+ return this.code === "NETWORK_ERROR" /* NETWORK_ERROR */ || this.code === "NETWORK_TIMEOUT" /* NETWORK_TIMEOUT */;
39
+ }
40
+ /**
41
+ * Check if this is a validation error
42
+ */
43
+ isValidationError() {
44
+ return this.code === "VALIDATION_ERROR" /* VALIDATION_ERROR */;
45
+ }
46
+ toJSON() {
47
+ return {
48
+ name: this.name,
49
+ code: this.code,
50
+ message: this.message,
51
+ details: this.details,
52
+ statusCode: this.statusCode
53
+ };
54
+ }
55
+ };
56
+ var AuthenticationError = class _AuthenticationError extends ClientError {
57
+ constructor(code, message, details, statusCode) {
58
+ super(code, message, details, statusCode);
59
+ this.name = "AuthenticationError";
60
+ Object.setPrototypeOf(this, _AuthenticationError.prototype);
61
+ }
62
+ };
63
+ var ValidationError = class _ValidationError extends ClientError {
64
+ constructor(message, field, suggestion) {
65
+ super(
66
+ "VALIDATION_ERROR" /* VALIDATION_ERROR */,
67
+ message,
68
+ { field, suggestion },
69
+ 400
70
+ );
71
+ this.name = "ValidationError";
72
+ Object.setPrototypeOf(this, _ValidationError.prototype);
73
+ }
74
+ };
75
+ var NetworkError = class _NetworkError extends ClientError {
76
+ constructor(message, suggestion) {
77
+ super(
78
+ "NETWORK_ERROR" /* NETWORK_ERROR */,
79
+ message,
80
+ { suggestion }
81
+ );
82
+ this.name = "NetworkError";
83
+ Object.setPrototypeOf(this, _NetworkError.prototype);
84
+ }
85
+ };
86
+
87
+ // src/client.ts
88
+ var AerostackClient = class {
89
+ projectSlug;
90
+ baseUrl;
91
+ constructor(config) {
92
+ this.projectSlug = config.projectSlug;
93
+ this.baseUrl = config.baseUrl || "https://api.aerostack.app";
94
+ }
95
+ /**
96
+ * Authentication operations
97
+ */
98
+ get auth() {
99
+ return {
100
+ /**
101
+ * Register a new user
102
+ */
103
+ register: async (data) => {
104
+ if (!data.email || !data.email.includes("@")) {
105
+ throw new ValidationError("Invalid email address", "email", "Provide a valid email address");
106
+ }
107
+ if (!data.password || data.password.length < 8) {
108
+ throw new ValidationError(
109
+ "Password must be at least 8 characters",
110
+ "password",
111
+ "Use a stronger password with minimum 8 characters"
112
+ );
113
+ }
114
+ return this.request("/auth/register", "POST", data);
115
+ },
116
+ /**
117
+ * Login with email and password
118
+ */
119
+ login: async (email, password) => {
120
+ if (!email || !password) {
121
+ throw new ValidationError("Email and password are required", "email");
122
+ }
123
+ return this.request("/auth/login", "POST", { email, password });
124
+ },
125
+ /**
126
+ * Send OTP code to email
127
+ */
128
+ sendOTP: async (email) => {
129
+ if (!email || !email.includes("@")) {
130
+ throw new ValidationError("Invalid email address", "email");
131
+ }
132
+ return this.request("/auth/otp/send", "POST", { email });
133
+ },
134
+ /**
135
+ * Verify OTP code and login
136
+ */
137
+ verifyOTP: async (email, code) => {
138
+ if (!email || !code) {
139
+ throw new ValidationError("Email and code are required");
140
+ }
141
+ if (code.length !== 6 || !/^\d+$/.test(code)) {
142
+ throw new ValidationError("OTP code must be 6 digits", "code");
143
+ }
144
+ return this.request("/auth/otp/verify", "POST", { email, code });
145
+ },
146
+ /**
147
+ * Verify email with token
148
+ */
149
+ verifyEmail: async (token) => {
150
+ if (!token) {
151
+ throw new ValidationError("Verification token is required", "token");
152
+ }
153
+ return this.request(`/auth/verify-email?token=${token}`, "GET");
154
+ },
155
+ /**
156
+ * Request password reset email
157
+ */
158
+ requestPasswordReset: async (email) => {
159
+ if (!email || !email.includes("@")) {
160
+ throw new ValidationError("Invalid email address", "email");
161
+ }
162
+ return this.request("/auth/password-reset/request", "POST", { email });
163
+ },
164
+ /**
165
+ * Reset password with token
166
+ */
167
+ resetPassword: async (token, newPassword) => {
168
+ if (!token) {
169
+ throw new ValidationError("Reset token is required", "token");
170
+ }
171
+ if (!newPassword || newPassword.length < 8) {
172
+ throw new ValidationError(
173
+ "Password must be at least 8 characters",
174
+ "password",
175
+ "Use a stronger password"
176
+ );
177
+ }
178
+ return this.request("/auth/password-reset/confirm", "POST", { token, newPassword });
179
+ },
180
+ /**
181
+ * Refresh access token using refresh token
182
+ */
183
+ refreshToken: async (refreshToken) => {
184
+ if (!refreshToken) {
185
+ throw new ValidationError("Refresh token is required", "refreshToken");
186
+ }
187
+ return this.request("/auth/refresh", "POST", { refreshToken });
188
+ },
189
+ /**
190
+ * Logout and invalidate tokens
191
+ */
192
+ logout: async (token) => {
193
+ return this.request("/auth/logout", "POST", {}, token);
194
+ },
195
+ /**
196
+ * Get current user profile
197
+ */
198
+ getCurrentUser: async (token) => {
199
+ if (!token) {
200
+ throw new AuthenticationError(
201
+ "AUTH_TOKEN_INVALID" /* AUTH_TOKEN_INVALID */,
202
+ "Authentication token is required",
203
+ { suggestion: "Please login first" }
204
+ );
205
+ }
206
+ const response = await this.request("/auth/me", "GET", void 0, token);
207
+ return response.user;
208
+ },
209
+ /**
210
+ * Update user profile
211
+ */
212
+ updateProfile: async (token, updates) => {
213
+ if (!token) {
214
+ throw new AuthenticationError(
215
+ "AUTH_TOKEN_INVALID" /* AUTH_TOKEN_INVALID */,
216
+ "Authentication token is required",
217
+ { suggestion: "Please login first" }
218
+ );
219
+ }
220
+ const response = await this.request("/auth/me", "PATCH", updates, token);
221
+ return response.user;
222
+ }
223
+ };
224
+ }
225
+ /**
226
+ * Make HTTP request with comprehensive error handling
227
+ */
228
+ async request(path, method, body, token) {
229
+ const url = `${this.baseUrl}/api/v1/public/projects/${this.projectSlug}${path}`;
230
+ const options = {
231
+ method,
232
+ headers: {
233
+ "Content-Type": "application/json",
234
+ ...token && { Authorization: `Bearer ${token}` }
235
+ }
236
+ };
237
+ if (body) {
238
+ options.body = JSON.stringify(body);
239
+ }
240
+ try {
241
+ const response = await fetch(url, options);
242
+ const data = await response.json();
243
+ if (!response.ok) {
244
+ const errorCode = this.mapErrorCode(data.code, response.status);
245
+ const errorMessage = data.message || data.error || "Request failed";
246
+ throw new ClientError(
247
+ errorCode,
248
+ errorMessage,
249
+ {
250
+ suggestion: this.getSuggestion(errorCode, data),
251
+ field: data.field
252
+ },
253
+ response.status
254
+ );
255
+ }
256
+ return data;
257
+ } catch (err) {
258
+ if (err instanceof ClientError) {
259
+ throw err;
260
+ }
261
+ if (err.name === "TypeError" && err.message.includes("fetch")) {
262
+ throw new NetworkError("Network request failed", "Check your internet connection");
263
+ }
264
+ if (err.name === "AbortError") {
265
+ throw new NetworkError("Request timeout", "The request took too long. Please try again");
266
+ }
267
+ throw new ClientError(
268
+ "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
269
+ err.message || "An unexpected error occurred",
270
+ {
271
+ suggestion: "Please try again or contact support"
272
+ }
273
+ );
274
+ }
275
+ }
276
+ /**
277
+ * Map API error codes to client error codes
278
+ */
279
+ mapErrorCode(apiCode, statusCode) {
280
+ if (apiCode) {
281
+ if (Object.values(ClientErrorCode).includes(apiCode)) {
282
+ return apiCode;
283
+ }
284
+ }
285
+ switch (statusCode) {
286
+ case 401:
287
+ return "AUTH_INVALID_CREDENTIALS" /* AUTH_INVALID_CREDENTIALS */;
288
+ case 409:
289
+ return "AUTH_USER_EXISTS" /* AUTH_USER_EXISTS */;
290
+ case 400:
291
+ return "VALIDATION_ERROR" /* VALIDATION_ERROR */;
292
+ case 403:
293
+ return "AUTH_TOKEN_INVALID" /* AUTH_TOKEN_INVALID */;
294
+ default:
295
+ return "REQUEST_FAILED" /* REQUEST_FAILED */;
296
+ }
297
+ }
298
+ /**
299
+ * Get helpful suggestion based on error code
300
+ */
301
+ getSuggestion(errorCode, data) {
302
+ switch (errorCode) {
303
+ case "AUTH_INVALID_CREDENTIALS" /* AUTH_INVALID_CREDENTIALS */:
304
+ return "Double-check your email and password";
305
+ case "AUTH_USER_EXISTS" /* AUTH_USER_EXISTS */:
306
+ return "Try logging in instead, or use password reset if you forgot your password";
307
+ case "AUTH_EMAIL_NOT_VERIFIED" /* AUTH_EMAIL_NOT_VERIFIED */:
308
+ return "Check your email for verification link";
309
+ case "AUTH_TOKEN_EXPIRED" /* AUTH_TOKEN_EXPIRED */:
310
+ return "Your session has expired. Please login again";
311
+ case "AUTH_OTP_EXPIRED" /* AUTH_OTP_EXPIRED */:
312
+ return "Request a new OTP code";
313
+ case "AUTH_OTP_INVALID" /* AUTH_OTP_INVALID */:
314
+ return "Check the code and try again, or request a new one";
315
+ case "AUTH_PASSWORD_WEAK" /* AUTH_PASSWORD_WEAK */:
316
+ return "Use at least 8 characters with a mix of letters, numbers, and symbols";
317
+ case "NETWORK_ERROR" /* NETWORK_ERROR */:
318
+ return "Check your internet connection and try again";
319
+ default:
320
+ return data?.details?.suggestion || "Please try again or contact support";
321
+ }
322
+ }
323
+ };
324
+
325
+ // src/server.ts
326
+ import { Pool } from "@neondatabase/serverless";
327
+
328
+ // src/server-errors.ts
329
+ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
330
+ ErrorCode2["DB_CONNECTION_FAILED"] = "DB_CONNECTION_FAILED";
331
+ ErrorCode2["DB_QUERY_FAILED"] = "DB_QUERY_FAILED";
332
+ ErrorCode2["DB_TABLE_NOT_FOUND"] = "DB_TABLE_NOT_FOUND";
333
+ ErrorCode2["DB_COLUMN_NOT_FOUND"] = "DB_COLUMN_NOT_FOUND";
334
+ ErrorCode2["DB_AUTH_FAILED"] = "DB_AUTH_FAILED";
335
+ ErrorCode2["DB_MIGRATION_FAILED"] = "DB_MIGRATION_FAILED";
336
+ ErrorCode2["DB_TRANSACTION_FAILED"] = "DB_TRANSACTION_FAILED";
337
+ ErrorCode2["CACHE_GET_FAILED"] = "CACHE_GET_FAILED";
338
+ ErrorCode2["CACHE_SET_FAILED"] = "CACHE_SET_FAILED";
339
+ ErrorCode2["CACHE_DELETE_FAILED"] = "CACHE_DELETE_FAILED";
340
+ ErrorCode2["CACHE_NOT_CONFIGURED"] = "CACHE_NOT_CONFIGURED";
341
+ ErrorCode2["QUEUE_ENQUEUE_FAILED"] = "QUEUE_ENQUEUE_FAILED";
342
+ ErrorCode2["QUEUE_NOT_CONFIGURED"] = "QUEUE_NOT_CONFIGURED";
343
+ ErrorCode2["QUEUE_JOB_NOT_FOUND"] = "QUEUE_JOB_NOT_FOUND";
344
+ ErrorCode2["STORAGE_UPLOAD_FAILED"] = "STORAGE_UPLOAD_FAILED";
345
+ ErrorCode2["STORAGE_DELETE_FAILED"] = "STORAGE_DELETE_FAILED";
346
+ ErrorCode2["STORAGE_NOT_CONFIGURED"] = "STORAGE_NOT_CONFIGURED";
347
+ ErrorCode2["STORAGE_FILE_TOO_LARGE"] = "STORAGE_FILE_TOO_LARGE";
348
+ ErrorCode2["AI_REQUEST_FAILED"] = "AI_REQUEST_FAILED";
349
+ ErrorCode2["AI_NOT_CONFIGURED"] = "AI_NOT_CONFIGURED";
350
+ ErrorCode2["AI_RATE_LIMIT"] = "AI_RATE_LIMIT";
351
+ ErrorCode2["SERVICE_INVOKE_FAILED"] = "SERVICE_INVOKE_FAILED";
352
+ ErrorCode2["SERVICE_NOT_FOUND"] = "SERVICE_NOT_FOUND";
353
+ ErrorCode2["CONFIGURATION_ERROR"] = "CONFIGURATION_ERROR";
354
+ ErrorCode2["VALIDATION_ERROR"] = "VALIDATION_ERROR";
355
+ ErrorCode2["INTERNAL_ERROR"] = "INTERNAL_ERROR";
356
+ return ErrorCode2;
357
+ })(ErrorCode || {});
358
+ var ServerError = class _ServerError extends Error {
359
+ constructor(code, message, details, context) {
360
+ super(message);
361
+ this.code = code;
362
+ this.details = details;
363
+ this.context = context;
364
+ this.name = "ServerError";
365
+ Object.setPrototypeOf(this, _ServerError.prototype);
366
+ }
367
+ toJSON() {
368
+ return {
369
+ name: this.name,
370
+ code: this.code,
371
+ message: this.message,
372
+ details: this.details,
373
+ context: this.context
374
+ };
375
+ }
376
+ };
377
+ var DatabaseError = class _DatabaseError extends ServerError {
378
+ constructor(code, message, details, context) {
379
+ super(code, message, details, context);
380
+ this.name = "DatabaseError";
381
+ Object.setPrototypeOf(this, _DatabaseError.prototype);
382
+ }
383
+ /**
384
+ * Create error from Postgres error
385
+ */
386
+ static fromPostgresError(err, context) {
387
+ switch (err.code) {
388
+ case "42P01":
389
+ return new _DatabaseError(
390
+ "DB_TABLE_NOT_FOUND" /* DB_TABLE_NOT_FOUND */,
391
+ `Table does not exist: ${err.table || "unknown"}`,
392
+ {
393
+ suggestion: "Run migrations first: aerostack db migrate apply",
394
+ recoveryAction: "CREATE_TABLE",
395
+ cause: err.message
396
+ },
397
+ context
398
+ );
399
+ case "42703":
400
+ return new _DatabaseError(
401
+ "DB_COLUMN_NOT_FOUND" /* DB_COLUMN_NOT_FOUND */,
402
+ `Column does not exist: ${err.column || "unknown"}`,
403
+ {
404
+ suggestion: "Check your schema or run latest migrations",
405
+ recoveryAction: "ALTER_TABLE",
406
+ cause: err.message
407
+ },
408
+ context
409
+ );
410
+ case "28P01":
411
+ // invalid_password
412
+ case "28000":
413
+ return new _DatabaseError(
414
+ "DB_AUTH_FAILED" /* DB_AUTH_FAILED */,
415
+ "Database authentication failed",
416
+ {
417
+ suggestion: "Check your DATABASE_URL environment variable",
418
+ recoveryAction: "UPDATE_CREDENTIALS",
419
+ cause: err.message
420
+ },
421
+ context
422
+ );
423
+ case "08006":
424
+ // connection_failure
425
+ case "08003":
426
+ return new _DatabaseError(
427
+ "DB_CONNECTION_FAILED" /* DB_CONNECTION_FAILED */,
428
+ "Failed to connect to database",
429
+ {
430
+ suggestion: "Verify your database connection string and network connectivity",
431
+ recoveryAction: "CHECK_CONNECTION",
432
+ cause: err.message
433
+ },
434
+ context
435
+ );
436
+ default:
437
+ return new _DatabaseError(
438
+ "DB_QUERY_FAILED" /* DB_QUERY_FAILED */,
439
+ err.message || "Database query failed",
440
+ {
441
+ suggestion: "Check your query syntax and parameters",
442
+ cause: err.code ? `Postgres error ${err.code}` : void 0
443
+ },
444
+ context
445
+ );
446
+ }
447
+ }
448
+ /**
449
+ * Create error from D1 error
450
+ */
451
+ static fromD1Error(err, context) {
452
+ const message = err.message || "D1 query failed";
453
+ if (message.includes("no such table")) {
454
+ return new _DatabaseError(
455
+ "DB_TABLE_NOT_FOUND" /* DB_TABLE_NOT_FOUND */,
456
+ message,
457
+ {
458
+ suggestion: "Run D1 migrations: aerostack db migrate apply",
459
+ recoveryAction: "CREATE_TABLE",
460
+ cause: err.message
461
+ },
462
+ context
463
+ );
464
+ }
465
+ if (message.includes("no such column")) {
466
+ return new _DatabaseError(
467
+ "DB_COLUMN_NOT_FOUND" /* DB_COLUMN_NOT_FOUND */,
468
+ message,
469
+ {
470
+ suggestion: "Check your schema or update migrations",
471
+ recoveryAction: "ALTER_TABLE",
472
+ cause: err.message
473
+ },
474
+ context
475
+ );
476
+ }
477
+ return new _DatabaseError(
478
+ "DB_QUERY_FAILED" /* DB_QUERY_FAILED */,
479
+ message,
480
+ {
481
+ suggestion: "Check your SQL syntax and D1 configuration",
482
+ cause: err.message
483
+ },
484
+ context
485
+ );
486
+ }
487
+ };
488
+ var CacheError = class _CacheError extends ServerError {
489
+ constructor(code, message, details, context) {
490
+ super(code, message, details, context);
491
+ this.name = "CacheError";
492
+ Object.setPrototypeOf(this, _CacheError.prototype);
493
+ }
494
+ };
495
+ var QueueError = class _QueueError extends ServerError {
496
+ constructor(code, message, details, context) {
497
+ super(code, message, details, context);
498
+ this.name = "QueueError";
499
+ Object.setPrototypeOf(this, _QueueError.prototype);
500
+ }
501
+ };
502
+ var StorageError = class _StorageError extends ServerError {
503
+ constructor(code, message, details, context) {
504
+ super(code, message, details, context);
505
+ this.name = "StorageError";
506
+ Object.setPrototypeOf(this, _StorageError.prototype);
507
+ }
508
+ };
509
+ var AIError = class _AIError extends ServerError {
510
+ constructor(code, message, details, context) {
511
+ super(code, message, details, context);
512
+ this.name = "AIError";
513
+ Object.setPrototypeOf(this, _AIError.prototype);
514
+ }
515
+ };
516
+ var ServiceError = class _ServiceError extends ServerError {
517
+ constructor(code, message, details, context) {
518
+ super(code, message, details, context);
519
+ this.name = "ServiceError";
520
+ Object.setPrototypeOf(this, _ServiceError.prototype);
521
+ }
522
+ };
523
+
524
+ // src/server.ts
525
+ var AerostackServer = class {
526
+ pgPool;
527
+ _d1;
528
+ _kv;
529
+ _queue;
530
+ _storage;
531
+ _ai;
532
+ _dispatcher;
533
+ routingRules;
534
+ env;
535
+ constructor(env, options = {}) {
536
+ this.env = env;
537
+ this._d1 = env.DB;
538
+ this._kv = env.CACHE;
539
+ this._queue = env.QUEUE;
540
+ this._storage = env.STORAGE;
541
+ this._ai = env.AI;
542
+ this._dispatcher = env.DISPATCHER;
543
+ this.routingRules = options.routing || { tables: {} };
544
+ const pgConnStr = this.findPostgresConnStr(env);
545
+ if (pgConnStr) {
546
+ this.pgPool = new Pool({ connectionString: pgConnStr });
547
+ }
548
+ }
549
+ /**
550
+ * Database operations with intelligent routing between D1 and Postgres
551
+ */
552
+ get db() {
553
+ return {
554
+ /**
555
+ * Execute a SQL query with automatic routing
556
+ */
557
+ query: async (sql, params = []) => {
558
+ return this.routeQuery(sql, params);
559
+ },
560
+ /**
561
+ * Get database schema information
562
+ */
563
+ getSchema: async (binding) => {
564
+ if (binding) {
565
+ if (this.pgPool && binding.toLowerCase().includes("postgres")) {
566
+ return this.introspectPostgres();
567
+ }
568
+ if (this._d1) {
569
+ return this.introspectD1();
570
+ }
571
+ }
572
+ if (this._d1) {
573
+ return this.introspectD1();
574
+ }
575
+ if (this.pgPool) {
576
+ return this.introspectPostgres();
577
+ }
578
+ throw new DatabaseError(
579
+ "DB_CONNECTION_FAILED" /* DB_CONNECTION_FAILED */,
580
+ "No database connection available",
581
+ {
582
+ suggestion: "Configure DB or Postgres connection in aerostack.toml"
583
+ }
584
+ );
585
+ },
586
+ /**
587
+ * Execute multiple queries in a batch
588
+ */
589
+ batch: async (queries) => {
590
+ const results = [];
591
+ const errors = [];
592
+ let success = true;
593
+ for (let i = 0; i < queries.length; i++) {
594
+ try {
595
+ const result = await this.routeQuery(queries[i].sql, queries[i].params || []);
596
+ results.push(result);
597
+ } catch (error) {
598
+ success = false;
599
+ errors.push({ index: i, error });
600
+ results.push({
601
+ results: [],
602
+ success: false,
603
+ meta: { target: "d1" }
604
+ });
605
+ }
606
+ }
607
+ return { results, success, errors: errors.length > 0 ? errors : void 0 };
608
+ }
609
+ };
610
+ }
611
+ /**
612
+ * KV Cache operations
613
+ */
614
+ get cache() {
615
+ return {
616
+ /**
617
+ * Get value from cache
618
+ */
619
+ get: async (key, options) => {
620
+ if (!this._kv) {
621
+ throw new CacheError(
622
+ "CACHE_NOT_CONFIGURED" /* CACHE_NOT_CONFIGURED */,
623
+ "KV cache not configured",
624
+ {
625
+ suggestion: "Add [[kv_namespaces]] binding to aerostack.toml"
626
+ }
627
+ );
628
+ }
629
+ try {
630
+ const type = options?.type || "json";
631
+ const value = type === "json" ? await this._kv.get(key, "json") : type === "text" ? await this._kv.get(key, "text") : type === "arrayBuffer" ? await this._kv.get(key, "arrayBuffer") : await this._kv.get(key, "stream");
632
+ return value;
633
+ } catch (err) {
634
+ throw new CacheError(
635
+ "CACHE_GET_FAILED" /* CACHE_GET_FAILED */,
636
+ `Failed to get cache key: ${key}`,
637
+ {
638
+ suggestion: "Check key format and KV namespace binding",
639
+ cause: err.message
640
+ },
641
+ { key }
642
+ );
643
+ }
644
+ },
645
+ /**
646
+ * Set value in cache
647
+ */
648
+ set: async (key, value, options) => {
649
+ if (!this._kv) {
650
+ throw new CacheError(
651
+ "CACHE_NOT_CONFIGURED" /* CACHE_NOT_CONFIGURED */,
652
+ "KV cache not configured",
653
+ {
654
+ suggestion: "Add [[kv_namespaces]] binding to aerostack.toml"
655
+ }
656
+ );
657
+ }
658
+ try {
659
+ const ttl = options?.ttl || options?.expirationTtl;
660
+ await this._kv.put(key, JSON.stringify(value), ttl ? { expirationTtl: ttl } : void 0);
661
+ } catch (err) {
662
+ throw new CacheError(
663
+ "CACHE_SET_FAILED" /* CACHE_SET_FAILED */,
664
+ `Failed to set cache key: ${key}`,
665
+ {
666
+ suggestion: "Check value serialization and KV limits (25MB)",
667
+ cause: err.message
668
+ },
669
+ { key, value }
670
+ );
671
+ }
672
+ },
673
+ /**
674
+ * Delete value from cache
675
+ */
676
+ delete: async (key) => {
677
+ if (!this._kv) {
678
+ throw new CacheError(
679
+ "CACHE_NOT_CONFIGURED" /* CACHE_NOT_CONFIGURED */,
680
+ "KV cache not configured",
681
+ {
682
+ suggestion: "Add [[kv_namespaces]] binding to aerostack.toml"
683
+ }
684
+ );
685
+ }
686
+ try {
687
+ await this._kv.delete(key);
688
+ } catch (err) {
689
+ throw new CacheError(
690
+ "CACHE_DELETE_FAILED" /* CACHE_DELETE_FAILED */,
691
+ `Failed to delete cache key: ${key}`,
692
+ {
693
+ cause: err.message
694
+ },
695
+ { key }
696
+ );
697
+ }
698
+ },
699
+ /**
700
+ * Check if key exists in cache
701
+ */
702
+ exists: async (key) => {
703
+ if (!this._kv) {
704
+ return false;
705
+ }
706
+ try {
707
+ const value = await this._kv.get(key);
708
+ return value !== null;
709
+ } catch {
710
+ return false;
711
+ }
712
+ }
713
+ };
714
+ }
715
+ /**
716
+ * Queue operations for background jobs
717
+ */
718
+ get queue() {
719
+ return {
720
+ /**
721
+ * Add job to queue
722
+ */
723
+ enqueue: async (job) => {
724
+ if (!this._queue) {
725
+ throw new QueueError(
726
+ "QUEUE_NOT_CONFIGURED" /* QUEUE_NOT_CONFIGURED */,
727
+ "Queue not configured",
728
+ {
729
+ suggestion: "Add [[queues]] binding to aerostack.toml"
730
+ }
731
+ );
732
+ }
733
+ try {
734
+ const message = {
735
+ type: job.type,
736
+ data: job.data,
737
+ queuedAt: (/* @__PURE__ */ new Date()).toISOString()
738
+ };
739
+ await this._queue.send(message, {
740
+ delaySeconds: job.delay
741
+ });
742
+ const jobId = `job_${Date.now()}_${Math.random().toString(36).substring(7)}`;
743
+ return {
744
+ jobId,
745
+ status: "queued",
746
+ queuedAt: /* @__PURE__ */ new Date()
747
+ };
748
+ } catch (err) {
749
+ throw new QueueError(
750
+ "QUEUE_ENQUEUE_FAILED" /* QUEUE_ENQUEUE_FAILED */,
751
+ "Failed to enqueue job",
752
+ {
753
+ suggestion: "Check queue binding and message size limits",
754
+ cause: err.message
755
+ },
756
+ { job }
757
+ );
758
+ }
759
+ }
760
+ };
761
+ }
762
+ /**
763
+ * R2 Storage operations
764
+ */
765
+ get storage() {
766
+ return {
767
+ /**
768
+ * Upload file to R2 storage
769
+ */
770
+ upload: async (file, key, options) => {
771
+ if (!this._storage) {
772
+ throw new StorageError(
773
+ "STORAGE_NOT_CONFIGURED" /* STORAGE_NOT_CONFIGURED */,
774
+ "R2 storage not configured",
775
+ {
776
+ suggestion: "Add [[r2_buckets]] binding to aerostack.toml"
777
+ }
778
+ );
779
+ }
780
+ try {
781
+ await this._storage.put(key, file, {
782
+ httpMetadata: {
783
+ contentType: options?.contentType,
784
+ cacheControl: options?.cacheControl
785
+ },
786
+ customMetadata: options?.metadata
787
+ });
788
+ const obj = await this._storage.get(key);
789
+ const size = obj?.size || 0;
790
+ return {
791
+ key,
792
+ url: `https://${this.env.STORAGE_PUBLIC_URL || "storage.aerostack.ai"}/${key}`,
793
+ size,
794
+ contentType: options?.contentType || "application/octet-stream"
795
+ };
796
+ } catch (err) {
797
+ throw new StorageError(
798
+ "STORAGE_UPLOAD_FAILED" /* STORAGE_UPLOAD_FAILED */,
799
+ `Failed to upload file: ${key}`,
800
+ {
801
+ suggestion: "Check R2 bucket binding and file size limits",
802
+ cause: err.message
803
+ },
804
+ { key }
805
+ );
806
+ }
807
+ },
808
+ /**
809
+ * Get presigned URL for object
810
+ */
811
+ getUrl: async (key, options) => {
812
+ if (!this._storage) {
813
+ throw new StorageError(
814
+ "STORAGE_NOT_CONFIGURED" /* STORAGE_NOT_CONFIGURED */,
815
+ "R2 storage not configured",
816
+ {
817
+ suggestion: "Add [[r2_buckets]] binding to aerostack.toml"
818
+ }
819
+ );
820
+ }
821
+ return `https://${this.env.STORAGE_PUBLIC_URL || "storage.aerostack.ai"}/${key}`;
822
+ },
823
+ /**
824
+ * Delete object from storage
825
+ */
826
+ delete: async (key) => {
827
+ if (!this._storage) {
828
+ throw new StorageError(
829
+ "STORAGE_NOT_CONFIGURED" /* STORAGE_NOT_CONFIGURED */,
830
+ "R2 storage not configured",
831
+ {
832
+ suggestion: "Add [[r2_buckets]] binding to aerostack.toml"
833
+ }
834
+ );
835
+ }
836
+ try {
837
+ await this._storage.delete(key);
838
+ } catch (err) {
839
+ throw new StorageError(
840
+ "STORAGE_DELETE_FAILED" /* STORAGE_DELETE_FAILED */,
841
+ `Failed to delete file: ${key}`,
842
+ {
843
+ cause: err.message
844
+ },
845
+ { key }
846
+ );
847
+ }
848
+ },
849
+ /**
850
+ * List objects in storage
851
+ */
852
+ list: async (prefix) => {
853
+ if (!this._storage) {
854
+ throw new StorageError(
855
+ "STORAGE_NOT_CONFIGURED" /* STORAGE_NOT_CONFIGURED */,
856
+ "R2 storage not configured",
857
+ {
858
+ suggestion: "Add [[r2_buckets]] binding to aerostack.toml"
859
+ }
860
+ );
861
+ }
862
+ try {
863
+ const listed = await this._storage.list({ prefix });
864
+ return listed.objects.map((obj) => ({
865
+ key: obj.key,
866
+ size: obj.size,
867
+ uploaded: obj.uploaded,
868
+ contentType: obj.httpMetadata?.contentType
869
+ }));
870
+ } catch (err) {
871
+ throw new StorageError(
872
+ "STORAGE_DELETE_FAILED" /* STORAGE_DELETE_FAILED */,
873
+ "Failed to list storage objects",
874
+ {
875
+ cause: err.message
876
+ },
877
+ { prefix }
878
+ );
879
+ }
880
+ }
881
+ };
882
+ }
883
+ /**
884
+ * AI operations using Cloudflare AI
885
+ */
886
+ get ai() {
887
+ return {
888
+ /**
889
+ * Generate chat completion
890
+ */
891
+ chat: async (messages, options) => {
892
+ if (!this._ai) {
893
+ throw new AIError(
894
+ "AI_NOT_CONFIGURED" /* AI_NOT_CONFIGURED */,
895
+ "AI binding not configured",
896
+ {
897
+ suggestion: "AI binding is automatically available in Workers"
898
+ }
899
+ );
900
+ }
901
+ try {
902
+ const model = options?.model || "@cf/meta/llama-3-8b-instruct";
903
+ const result = await this._ai.run(model, {
904
+ messages,
905
+ temperature: options?.temperature,
906
+ max_tokens: options?.maxTokens,
907
+ stream: options?.stream || false
908
+ });
909
+ return {
910
+ response: result.response || "",
911
+ usage: result.usage
912
+ };
913
+ } catch (err) {
914
+ throw new AIError(
915
+ "AI_REQUEST_FAILED" /* AI_REQUEST_FAILED */,
916
+ "AI chat request failed",
917
+ {
918
+ suggestion: "Check model name and message format",
919
+ cause: err.message
920
+ },
921
+ { messages, options }
922
+ );
923
+ }
924
+ },
925
+ /**
926
+ * Generate text embeddings
927
+ */
928
+ embed: async (text, options) => {
929
+ if (!this._ai) {
930
+ throw new AIError(
931
+ "AI_NOT_CONFIGURED" /* AI_NOT_CONFIGURED */,
932
+ "AI binding not configured",
933
+ {
934
+ suggestion: "AI binding is automatically available in Workers"
935
+ }
936
+ );
937
+ }
938
+ try {
939
+ const model = options?.model || "@cf/baai/bge-base-en-v1.5";
940
+ const result = await this._ai.run(model, { text });
941
+ return {
942
+ embedding: result.data[0],
943
+ model
944
+ };
945
+ } catch (err) {
946
+ throw new AIError(
947
+ "AI_REQUEST_FAILED" /* AI_REQUEST_FAILED */,
948
+ "AI embedding request failed",
949
+ {
950
+ suggestion: "Check model name and text input",
951
+ cause: err.message
952
+ },
953
+ { text, options }
954
+ );
955
+ }
956
+ },
957
+ /**
958
+ * Generate text from prompt
959
+ */
960
+ generate: async (prompt, options) => {
961
+ if (!this._ai) {
962
+ throw new AIError(
963
+ "AI_NOT_CONFIGURED" /* AI_NOT_CONFIGURED */,
964
+ "AI binding not configured",
965
+ {
966
+ suggestion: "AI binding is automatically available in Workers"
967
+ }
968
+ );
969
+ }
970
+ try {
971
+ const model = options?.model || "@cf/meta/llama-3-8b-instruct";
972
+ const result = await this._ai.run(model, {
973
+ prompt,
974
+ temperature: options?.temperature,
975
+ max_tokens: options?.maxTokens
976
+ });
977
+ return {
978
+ text: result.response || "",
979
+ usage: result.usage
980
+ };
981
+ } catch (err) {
982
+ throw new AIError(
983
+ "AI_REQUEST_FAILED" /* AI_REQUEST_FAILED */,
984
+ "AI generation request failed",
985
+ {
986
+ suggestion: "Check model name and prompt",
987
+ cause: err.message
988
+ },
989
+ { prompt, options }
990
+ );
991
+ }
992
+ }
993
+ };
994
+ }
995
+ /**
996
+ * Service invocation via Workers Dispatch
997
+ */
998
+ get services() {
999
+ return {
1000
+ /**
1001
+ * Invoke another service via RPC
1002
+ */
1003
+ invoke: async (serviceName, data, options) => {
1004
+ if (!this._dispatcher) {
1005
+ throw new ServiceError(
1006
+ "SERVICE_INVOKE_FAILED" /* SERVICE_INVOKE_FAILED */,
1007
+ "Service dispatcher not configured",
1008
+ {
1009
+ suggestion: "Configure Workers Dispatch namespace in aerostack.toml"
1010
+ }
1011
+ );
1012
+ }
1013
+ try {
1014
+ const id = this._dispatcher.idFromName(serviceName);
1015
+ const stub = this._dispatcher.get(id);
1016
+ const timeout = options?.timeout || 3e4;
1017
+ const controller = new AbortController();
1018
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
1019
+ const response = await stub.fetch(
1020
+ new Request("https://internal/", {
1021
+ method: "POST",
1022
+ body: JSON.stringify(data),
1023
+ signal: controller.signal
1024
+ })
1025
+ );
1026
+ clearTimeout(timeoutId);
1027
+ return await response.json();
1028
+ } catch (err) {
1029
+ throw new ServiceError(
1030
+ "SERVICE_INVOKE_FAILED" /* SERVICE_INVOKE_FAILED */,
1031
+ `Failed to invoke service: ${serviceName}`,
1032
+ {
1033
+ suggestion: "Check service name and dispatcher binding",
1034
+ cause: err.message
1035
+ },
1036
+ { serviceName, data }
1037
+ );
1038
+ }
1039
+ }
1040
+ };
1041
+ }
1042
+ // ========== Private Helper Methods ==========
1043
+ async routeQuery(sql, params) {
1044
+ const target = this.determineTarget(sql);
1045
+ if (target === "postgres" && this.pgPool) {
1046
+ try {
1047
+ const result = await this.pgPool.query(sql, params);
1048
+ return {
1049
+ results: result.rows,
1050
+ success: true,
1051
+ meta: { target: "postgres", rowCount: result.rowCount || 0 }
1052
+ };
1053
+ } catch (err) {
1054
+ throw DatabaseError.fromPostgresError(err, { sql, params, target: "postgres" });
1055
+ }
1056
+ }
1057
+ if (this._d1) {
1058
+ try {
1059
+ const result = await this._d1.prepare(sql).bind(...params).all();
1060
+ return {
1061
+ results: result.results || [],
1062
+ success: result.success,
1063
+ meta: { target: "d1", duration: result.meta?.duration }
1064
+ };
1065
+ } catch (err) {
1066
+ throw DatabaseError.fromD1Error(err, { sql, params, target: "d1" });
1067
+ }
1068
+ }
1069
+ throw new DatabaseError(
1070
+ "DB_CONNECTION_FAILED" /* DB_CONNECTION_FAILED */,
1071
+ "No database connection available",
1072
+ {
1073
+ suggestion: "Configure DB or Postgres connection in aerostack.toml"
1074
+ }
1075
+ );
1076
+ }
1077
+ determineTarget(sql) {
1078
+ const normalized = sql.toLowerCase();
1079
+ if (normalized.includes("aerostack:target=postgres")) return "postgres";
1080
+ if (normalized.includes("aerostack:target=d1")) return "d1";
1081
+ for (const [table, target] of Object.entries(this.routingRules.tables)) {
1082
+ if (normalized.includes(table.toLowerCase())) {
1083
+ return target;
1084
+ }
1085
+ }
1086
+ const complexTriggers = ["join", "group by", "having", "union", "intersect", "except"];
1087
+ if (complexTriggers.some((trigger) => normalized.includes(trigger))) {
1088
+ return "postgres";
1089
+ }
1090
+ return "d1";
1091
+ }
1092
+ async introspectD1() {
1093
+ if (!this._d1) {
1094
+ throw new DatabaseError("DB_CONNECTION_FAILED" /* DB_CONNECTION_FAILED */, "D1 not configured");
1095
+ }
1096
+ const result = await this._d1.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").all();
1097
+ const tables = [];
1098
+ for (const row of result.results) {
1099
+ const tableName = row.name;
1100
+ const columns = await this._d1.prepare(`PRAGMA table_info(${tableName})`).all();
1101
+ tables.push({
1102
+ name: tableName,
1103
+ columns: columns.results.map((col) => ({
1104
+ name: col.name,
1105
+ type: col.type,
1106
+ nullable: col.notnull === 0,
1107
+ defaultValue: col.dflt_value,
1108
+ isPrimaryKey: col.pk === 1
1109
+ })),
1110
+ database: "d1"
1111
+ });
1112
+ }
1113
+ return { tables, database: "d1" };
1114
+ }
1115
+ async introspectPostgres() {
1116
+ if (!this.pgPool) {
1117
+ throw new DatabaseError("DB_CONNECTION_FAILED" /* DB_CONNECTION_FAILED */, "Postgres not configured");
1118
+ }
1119
+ const result = await this.pgPool.query(`
1120
+ SELECT table_name
1121
+ FROM information_schema.tables
1122
+ WHERE table_schema = 'public'
1123
+ `);
1124
+ const tables = [];
1125
+ for (const row of result.rows) {
1126
+ const tableName = row.table_name;
1127
+ const columns = await this.pgPool.query(
1128
+ `
1129
+ SELECT column_name, data_type, is_nullable, column_default
1130
+ FROM information_schema.columns
1131
+ WHERE table_name = $1
1132
+ `,
1133
+ [tableName]
1134
+ );
1135
+ tables.push({
1136
+ name: tableName,
1137
+ columns: columns.rows.map((col) => ({
1138
+ name: col.column_name,
1139
+ type: col.data_type,
1140
+ nullable: col.is_nullable === "YES",
1141
+ defaultValue: col.column_default
1142
+ })),
1143
+ database: "postgres"
1144
+ });
1145
+ }
1146
+ return { tables, database: "postgres" };
1147
+ }
1148
+ findPostgresConnStr(env) {
1149
+ const entry = Object.entries(env).find(([key]) => key.endsWith("_DATABASE_URL"));
1150
+ return entry ? entry[1] : void 0;
1151
+ }
1152
+ };
1153
+ export {
1154
+ AIError,
1155
+ AerostackClient,
1156
+ AerostackServer,
1157
+ AuthenticationError,
1158
+ CacheError,
1159
+ ClientError,
1160
+ ClientErrorCode,
1161
+ DatabaseError,
1162
+ ErrorCode,
1163
+ NetworkError,
1164
+ QueueError,
1165
+ ServerError,
1166
+ ServiceError,
1167
+ StorageError,
1168
+ ValidationError
1169
+ };