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