@useatlas/types 0.0.1 → 0.0.3

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/errors.d.ts CHANGED
@@ -1,7 +1,17 @@
1
1
  import type { AuthMode } from "./auth";
2
- export declare const CHAT_ERROR_CODES: readonly ["auth_error", "rate_limited", "configuration_error", "no_datasource", "invalid_request", "provider_model_not_found", "provider_auth_error", "provider_rate_limit", "provider_timeout", "provider_unreachable", "provider_error", "internal_error"];
2
+ export declare const CHAT_ERROR_CODES: readonly ["auth_error", "session_expired", "rate_limited", "configuration_error", "no_datasource", "invalid_request", "provider_model_not_found", "provider_auth_error", "provider_rate_limit", "provider_timeout", "provider_unreachable", "provider_error", "internal_error", "validation_error", "not_found", "forbidden", "forbidden_role", "org_not_found", "plan_limit_exceeded", "trial_expired", "billing_check_failed", "workspace_check_failed", "workspace_suspended", "workspace_throttled", "workspace_deleted"];
3
3
  /** Union of all error codes the server can return in the `error` field. */
4
4
  export type ChatErrorCode = (typeof CHAT_ERROR_CODES)[number];
5
+ /** Type guard — checks whether a string is a known `ChatErrorCode`. */
6
+ export declare function isChatErrorCode(value: string): value is ChatErrorCode;
7
+ /** Returns `true` if the given error code represents a transient, retryable failure. */
8
+ export declare function isRetryableError(code: ChatErrorCode): boolean;
9
+ /**
10
+ * Client-side error codes for conditions detected before/without a server response.
11
+ * These are distinct from `ChatErrorCode` (server-originated codes).
12
+ */
13
+ export declare const CLIENT_ERROR_CODES: readonly ["api_unreachable", "auth_failure", "rate_limited_http", "server_error", "offline"];
14
+ export type ClientErrorCode = (typeof CLIENT_ERROR_CODES)[number];
5
15
  /**
6
16
  * Structured error info extracted from a chat error response.
7
17
  *
@@ -10,13 +20,48 @@ export type ChatErrorCode = (typeof CHAT_ERROR_CODES)[number];
10
20
  * - `retryAfterSeconds` — Seconds to wait before retrying (rate_limited only).
11
21
  * Clamped to [0, 300].
12
22
  * - `code` — The server error code, if the response was valid JSON with a known code.
23
+ * - `clientCode` — Client-side error classification (network/offline/HTTP status).
24
+ * Present when the error is detected client-side before parsing a server response.
25
+ * - `retryable` — Whether the client should offer to retry. Three states:
26
+ * - `true` — transient error, retrying may succeed.
27
+ * - `false` — permanent error, retrying will not help.
28
+ * - `undefined` — error code is unknown or response was not valid JSON;
29
+ * the client cannot determine retryability.
30
+ * - `requestId` — Server-assigned request ID (UUID) for log correlation.
13
31
  */
14
32
  export interface ChatErrorInfo {
15
33
  title: string;
16
34
  detail?: string;
17
35
  retryAfterSeconds?: number;
18
36
  code?: ChatErrorCode;
37
+ clientCode?: ClientErrorCode;
38
+ retryable?: boolean;
39
+ requestId?: string;
40
+ }
41
+ /**
42
+ * Result from `matchError()` — a pattern-matched, user-safe error.
43
+ *
44
+ * `code` is a suggested `ChatErrorCode`. Callers may override it based
45
+ * on context (e.g. ECONNREFUSED in the chat route is `provider_unreachable`,
46
+ * but in startup it's `internal_error`).
47
+ */
48
+ export interface MatchedError {
49
+ code: ChatErrorCode;
50
+ message: string;
19
51
  }
52
+ /**
53
+ * Pattern-match common runtime errors into user-safe messages.
54
+ *
55
+ * Returns `null` when no pattern matches — the caller should fall through
56
+ * to a generic `internal_error` response with a request ID.
57
+ *
58
+ * @param error - The caught error (any type).
59
+ * @param opts.timeoutSeconds - Configured query/request timeout, included in
60
+ * timeout messages so users know the limit. Defaults to 30.
61
+ */
62
+ export declare function matchError(error: unknown, opts?: {
63
+ timeoutSeconds?: number;
64
+ }): MatchedError | null;
20
65
  /**
21
66
  * Map an auth mode to a user-friendly error message.
22
67
  *
@@ -27,11 +72,27 @@ export interface ChatErrorInfo {
27
72
  * - `none`: auth should not fail in this mode; a generic message is shown.
28
73
  */
29
74
  export declare function authErrorMessage(authMode: AuthMode): string;
75
+ /**
76
+ * Detect client-side error conditions from the raw Error object.
77
+ *
78
+ * The AI SDK wraps fetch failures in an Error whose message is the response body
79
+ * (for HTTP errors) or the fetch error message (for network failures). We detect:
80
+ *
81
+ * - `TypeError` / "fetch failed" / "Failed to fetch" / "NetworkError" → `api_unreachable`
82
+ * - `navigator.onLine === false` → `offline`
83
+ * - HTTP status text patterns: "401" → `auth_failure`, "429" → `rate_limited_http`,
84
+ * "5xx" → `server_error`
85
+ */
86
+ export declare function classifyClientError(error: Error): ClientErrorCode | null;
30
87
  /**
31
88
  * Parse an AI SDK chat error into a user-friendly `ChatErrorInfo`.
32
89
  *
33
90
  * Expects `error.message` to contain a JSON string with `{ error, message, retryAfterSeconds? }`.
34
- * Falls back to a generic message when the body is not valid JSON (e.g. network failures,
35
- * HTML error pages, or unexpected formats).
91
+ * Falls back to a generic title when the body is not valid JSON (e.g. network failures,
92
+ * HTML error pages, or unexpected formats), preserving the original message as `detail`
93
+ * (truncated to 200 characters).
94
+ *
95
+ * Also classifies client-side errors (network failures, offline, HTTP status) before
96
+ * attempting JSON parse, setting the `clientCode` field on the result.
36
97
  */
37
98
  export declare function parseChatError(error: Error, authMode: AuthMode): ChatErrorInfo;
package/dist/errors.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // src/errors.ts
2
2
  var CHAT_ERROR_CODES = [
3
3
  "auth_error",
4
+ "session_expired",
4
5
  "rate_limited",
5
6
  "configuration_error",
6
7
  "no_datasource",
@@ -11,8 +12,118 @@ var CHAT_ERROR_CODES = [
11
12
  "provider_timeout",
12
13
  "provider_unreachable",
13
14
  "provider_error",
14
- "internal_error"
15
+ "internal_error",
16
+ "validation_error",
17
+ "not_found",
18
+ "forbidden",
19
+ "forbidden_role",
20
+ "org_not_found",
21
+ "plan_limit_exceeded",
22
+ "trial_expired",
23
+ "billing_check_failed",
24
+ "workspace_check_failed",
25
+ "workspace_suspended",
26
+ "workspace_throttled",
27
+ "workspace_deleted"
15
28
  ];
29
+ function isChatErrorCode(value) {
30
+ return CHAT_ERROR_CODES.includes(value);
31
+ }
32
+ var RETRYABLE_MAP = {
33
+ rate_limited: true,
34
+ provider_timeout: true,
35
+ provider_unreachable: true,
36
+ provider_error: true,
37
+ provider_rate_limit: true,
38
+ internal_error: true,
39
+ auth_error: false,
40
+ session_expired: false,
41
+ configuration_error: false,
42
+ no_datasource: false,
43
+ invalid_request: false,
44
+ provider_model_not_found: false,
45
+ provider_auth_error: false,
46
+ validation_error: false,
47
+ not_found: false,
48
+ forbidden: false,
49
+ forbidden_role: false,
50
+ org_not_found: false,
51
+ plan_limit_exceeded: false,
52
+ trial_expired: false,
53
+ billing_check_failed: true,
54
+ workspace_check_failed: true,
55
+ workspace_suspended: false,
56
+ workspace_throttled: true,
57
+ workspace_deleted: false
58
+ };
59
+ function isRetryableError(code) {
60
+ return RETRYABLE_MAP[code];
61
+ }
62
+ var CLIENT_ERROR_CODES = [
63
+ "api_unreachable",
64
+ "auth_failure",
65
+ "rate_limited_http",
66
+ "server_error",
67
+ "offline"
68
+ ];
69
+ function extractHostFromError(msg) {
70
+ const refusedMatch = msg.match(/ECONNREFUSED\s+(\S+)/i);
71
+ if (refusedMatch)
72
+ return refusedMatch[1];
73
+ const notFoundMatch = msg.match(/ENOTFOUND\s+(\S+)/i);
74
+ if (notFoundMatch)
75
+ return notFoundMatch[1];
76
+ return "(unknown host)";
77
+ }
78
+ function matchError(error, opts) {
79
+ const msg = error instanceof Error ? error.message : String(error);
80
+ if (/too many (clients already|connections)|connection pool exhausted|remaining connection slots are reserved/i.test(msg)) {
81
+ return {
82
+ code: "rate_limited",
83
+ message: "Database connection pool exhausted — try again in a few seconds, or reduce concurrent queries"
84
+ };
85
+ }
86
+ if (/ECONNREFUSED/i.test(msg)) {
87
+ const host = extractHostFromError(msg);
88
+ return {
89
+ code: "internal_error",
90
+ message: `Database unreachable at ${host} — check that the database is running and accessible`
91
+ };
92
+ }
93
+ if (/ENOTFOUND/i.test(msg)) {
94
+ const host = extractHostFromError(msg);
95
+ return {
96
+ code: "internal_error",
97
+ message: `Could not resolve hostname "${host}" — check your connection URL`
98
+ };
99
+ }
100
+ if (/SELF_SIGNED_CERT|UNABLE_TO_VERIFY_LEAF_SIGNATURE|ssl\s+connection|tls\s+handshake|certificate\s+(has expired|verify|error|rejected)/i.test(msg)) {
101
+ return {
102
+ code: "internal_error",
103
+ message: "SSL connection failed — check sslmode in your connection string"
104
+ };
105
+ }
106
+ if (/timeout|timed out|AbortError/i.test(msg)) {
107
+ const seconds = Math.max(1, opts?.timeoutSeconds ?? 30);
108
+ return {
109
+ code: "provider_timeout",
110
+ message: `Query exceeded the ${seconds}-second timeout — try a simpler query or increase ATLAS_QUERY_TIMEOUT`
111
+ };
112
+ }
113
+ if (/\b502\s+Bad Gateway\b|\b503\s+Service Unavailable\b/i.test(msg)) {
114
+ return {
115
+ code: "provider_unreachable",
116
+ message: "AI provider API unavailable — this is usually temporary, retry in a few seconds"
117
+ };
118
+ }
119
+ if (/fetch failed/i.test(msg)) {
120
+ return {
121
+ code: "provider_unreachable",
122
+ message: "Network request failed — the remote service may be down or unreachable"
123
+ };
124
+ }
125
+ return null;
126
+ }
16
127
  function authErrorMessage(authMode) {
17
128
  switch (authMode) {
18
129
  case "simple-key":
@@ -29,54 +140,164 @@ function authErrorMessage(authMode) {
29
140
  }
30
141
  }
31
142
  }
143
+ function classifyClientError(error) {
144
+ const msg = error.message;
145
+ if (msg.startsWith("{") || msg.startsWith("[")) {
146
+ return null;
147
+ }
148
+ if (typeof window !== "undefined" && typeof navigator !== "undefined" && navigator.onLine === false) {
149
+ return "offline";
150
+ }
151
+ if (error.name === "TypeError" || /fetch failed|failed to fetch|networkerror|network\s+request\s+failed|ECONNREFUSED|ENOTFOUND|ERR_CONNECTION_REFUSED/i.test(msg)) {
152
+ return "api_unreachable";
153
+ }
154
+ if (/\b401\b|Unauthorized/i.test(msg) && !/\bretry/i.test(msg)) {
155
+ return "auth_failure";
156
+ }
157
+ if (/\b429\b|Too Many Requests/i.test(msg)) {
158
+ return "rate_limited_http";
159
+ }
160
+ if (/\b50[0-9]\b|Internal Server Error|Bad Gateway|Service Unavailable/i.test(msg)) {
161
+ return "server_error";
162
+ }
163
+ return null;
164
+ }
165
+ function clientErrorInfo(clientCode, authMode) {
166
+ switch (clientCode) {
167
+ case "offline":
168
+ return {
169
+ title: "You appear to be offline.",
170
+ detail: "Reconnecting when your network is restored...",
171
+ clientCode,
172
+ retryable: true
173
+ };
174
+ case "api_unreachable":
175
+ return {
176
+ title: "Unable to connect to Atlas.",
177
+ detail: "Check your API URL configuration and ensure the server is running.",
178
+ clientCode,
179
+ retryable: true
180
+ };
181
+ case "auth_failure":
182
+ return {
183
+ title: authErrorMessage(authMode),
184
+ clientCode,
185
+ retryable: false
186
+ };
187
+ case "rate_limited_http":
188
+ return {
189
+ title: "Too many requests.",
190
+ detail: "Please try again in a moment.",
191
+ retryAfterSeconds: 30,
192
+ clientCode,
193
+ retryable: true
194
+ };
195
+ case "server_error":
196
+ return {
197
+ title: "Something went wrong on our end.",
198
+ detail: "Please try again.",
199
+ clientCode,
200
+ retryable: true
201
+ };
202
+ default: {
203
+ const _exhaustive = clientCode;
204
+ return { title: `Unknown error (${_exhaustive}).` };
205
+ }
206
+ }
207
+ }
32
208
  function parseChatError(error, authMode) {
209
+ const clientCode = classifyClientError(error);
33
210
  let parsed;
34
211
  try {
35
212
  parsed = JSON.parse(error.message);
36
213
  } catch {
37
- return { title: "Something went wrong. Please try again." };
214
+ if (clientCode) {
215
+ return clientErrorInfo(clientCode, authMode);
216
+ }
217
+ const raw = error.message.length > 200 ? error.message.slice(0, 200) + "..." : error.message;
218
+ return { title: "Something went wrong. Please try again.", detail: raw };
38
219
  }
39
- const code = typeof parsed.error === "string" ? parsed.error : undefined;
220
+ const rawCode = typeof parsed.error === "string" ? parsed.error : undefined;
40
221
  const serverMessage = typeof parsed.message === "string" ? parsed.message : undefined;
41
- switch (code) {
222
+ const requestId = typeof parsed.requestId === "string" ? parsed.requestId : undefined;
223
+ if (rawCode === undefined || !isChatErrorCode(rawCode)) {
224
+ return { title: serverMessage ?? "Something went wrong. Please try again.", requestId };
225
+ }
226
+ const retryable = isRetryableError(rawCode);
227
+ switch (rawCode) {
42
228
  case "auth_error":
43
- return { title: authErrorMessage(authMode), code };
229
+ return { title: authErrorMessage(authMode), code: rawCode, retryable, requestId };
44
230
  case "rate_limited": {
45
231
  const raw = typeof parsed.retryAfterSeconds === "number" ? parsed.retryAfterSeconds : undefined;
46
232
  const clamped = raw !== undefined ? Math.max(0, Math.min(raw, 300)) : undefined;
47
233
  return {
48
234
  title: "Too many requests.",
49
- detail: clamped !== undefined ? `Try again in ${clamped} seconds.` : "Please wait before trying again.",
235
+ detail: clamped !== undefined ? `Try again in ${clamped} seconds.` : serverMessage ?? "Please wait before trying again.",
50
236
  retryAfterSeconds: clamped,
51
- code
237
+ code: rawCode,
238
+ retryable,
239
+ requestId
52
240
  };
53
241
  }
54
242
  case "configuration_error":
55
- return { title: "Atlas is not fully configured.", detail: serverMessage, code };
243
+ return { title: "Atlas is not fully configured.", detail: serverMessage, code: rawCode, retryable, requestId };
56
244
  case "no_datasource":
57
- return { title: "No data source configured.", detail: serverMessage, code };
245
+ return { title: "No data source configured.", detail: serverMessage, code: rawCode, retryable, requestId };
58
246
  case "invalid_request":
59
- return { title: "Invalid request.", detail: serverMessage, code };
247
+ return { title: "Invalid request.", detail: serverMessage, code: rawCode, retryable, requestId };
60
248
  case "provider_model_not_found":
61
- return { title: "The configured AI model was not found.", detail: serverMessage, code };
249
+ return { title: "The configured AI model was not found.", detail: serverMessage, code: rawCode, retryable, requestId };
62
250
  case "provider_auth_error":
63
- return { title: "The AI provider could not authenticate.", detail: serverMessage, code };
251
+ return { title: "The AI provider could not authenticate.", detail: serverMessage, code: rawCode, retryable, requestId };
64
252
  case "provider_rate_limit":
65
- return { title: "The AI provider is rate limiting requests.", detail: serverMessage, code };
253
+ return { title: "The AI provider is rate limiting requests.", detail: serverMessage, code: rawCode, retryable, requestId };
66
254
  case "provider_timeout":
67
- return { title: "The AI provider timed out.", detail: serverMessage, code };
255
+ return { title: "The AI provider timed out.", detail: serverMessage, code: rawCode, retryable, requestId };
68
256
  case "provider_unreachable":
69
- return { title: "Could not reach the AI provider.", detail: serverMessage, code };
257
+ return { title: "Could not reach the AI provider.", detail: serverMessage, code: rawCode, retryable, requestId };
70
258
  case "provider_error":
71
- return { title: "The AI provider returned an error.", detail: serverMessage, code };
259
+ return { title: "The AI provider returned an error.", detail: serverMessage, code: rawCode, retryable, requestId };
72
260
  case "internal_error":
73
- return { title: serverMessage ?? "An unexpected error occurred.", code };
74
- default:
75
- return { title: serverMessage ?? "Something went wrong. Please try again." };
261
+ return { title: serverMessage ?? "An unexpected error occurred.", code: rawCode, retryable, requestId };
262
+ case "validation_error":
263
+ return { title: "Validation error.", detail: serverMessage, code: rawCode, retryable, requestId };
264
+ case "not_found":
265
+ return { title: "Not found.", detail: serverMessage, code: rawCode, retryable, requestId };
266
+ case "forbidden":
267
+ return { title: "Access denied.", detail: serverMessage, code: rawCode, retryable, requestId };
268
+ case "session_expired":
269
+ return { title: "Your session has expired.", detail: serverMessage ?? "Please sign in again.", code: rawCode, retryable, requestId };
270
+ case "forbidden_role":
271
+ return { title: "Admin role required.", detail: serverMessage, code: rawCode, retryable, requestId };
272
+ case "org_not_found":
273
+ return { title: "No active organization.", detail: serverMessage ?? "Select an organization and try again.", code: rawCode, retryable, requestId };
274
+ case "plan_limit_exceeded":
275
+ return { title: "Plan limit exceeded.", detail: serverMessage ?? "Upgrade your plan or wait until the next billing period.", code: rawCode, retryable, requestId };
276
+ case "trial_expired":
277
+ return { title: "Trial expired.", detail: serverMessage ?? "Upgrade to a paid plan to continue using Atlas.", code: rawCode, retryable, requestId };
278
+ case "billing_check_failed":
279
+ return { title: "Billing check failed.", detail: serverMessage ?? "Unable to verify billing status. Please try again.", code: rawCode, retryable, requestId };
280
+ case "workspace_check_failed":
281
+ return { title: "Workspace check failed.", detail: serverMessage ?? "Unable to verify workspace status. Please try again.", code: rawCode, retryable, requestId };
282
+ case "workspace_suspended":
283
+ return { title: "Workspace suspended.", detail: serverMessage ?? "Contact your workspace administrator to reactivate it.", code: rawCode, retryable, requestId };
284
+ case "workspace_throttled":
285
+ return { title: "Workspace throttled.", detail: serverMessage ?? "Your workspace has been temporarily throttled due to unusual activity. Requests will be delayed.", code: rawCode, retryable, requestId };
286
+ case "workspace_deleted":
287
+ return { title: "Workspace deleted.", detail: serverMessage ?? "This workspace has been permanently deleted. Create a new workspace to continue.", code: rawCode, retryable, requestId };
288
+ default: {
289
+ const _exhaustive = rawCode;
290
+ return { title: serverMessage ?? `Something went wrong (${_exhaustive}).`, requestId };
291
+ }
76
292
  }
77
293
  }
78
294
  export {
79
295
  parseChatError,
296
+ matchError,
297
+ isRetryableError,
298
+ isChatErrorCode,
299
+ classifyClientError,
80
300
  authErrorMessage,
301
+ CLIENT_ERROR_CODES,
81
302
  CHAT_ERROR_CODES
82
303
  };
package/dist/index.d.ts CHANGED
@@ -4,5 +4,23 @@ export * from "./connection";
4
4
  export * from "./action";
5
5
  export * from "./scheduled-task";
6
6
  export * from "./errors";
7
+ export * from "./compliance";
7
8
  export * from "./semantic";
8
9
  export * from "./share";
10
+ export * from "./organization";
11
+ export * from "./learned-pattern";
12
+ export * from "./prompt";
13
+ export * from "./query-suggestion";
14
+ export * from "./sso";
15
+ export * from "./billing";
16
+ export * from "./profiler";
17
+ export * from "./model-config";
18
+ export * from "./approval";
19
+ export * from "./platform";
20
+ export * from "./branding";
21
+ export * from "./onboarding-email";
22
+ export * from "./abuse";
23
+ export * from "./sla";
24
+ export * from "./backups";
25
+ export * from "./residency";
26
+ export * from "./domain";