next-workflow-builder 0.3.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/README.md ADDED
@@ -0,0 +1,165 @@
1
+ # next-workflow-builder
2
+
3
+ A Next.js plugin for building visual workflow automation platforms with drag-and-drop editing, code generation, and AI-powered automation.
4
+
5
+ > **Full documentation available at [next-workflow-builder.vercel.app](https://next-workflow-builder.vercel.app)**
6
+
7
+ ## Features
8
+
9
+ - Visual drag-and-drop workflow editor powered by React Flow
10
+ - Code generation — export workflows as TypeScript
11
+ - AI-powered workflow creation from natural language
12
+ - Real-time execution tracking and logs
13
+ - Extensible plugin system for third-party integrations
14
+ - Built-in authentication via Better Auth
15
+ - Dark / light / system theme support
16
+
17
+ ## Requirements
18
+
19
+ - Node.js >= 22
20
+ - Next.js >= 16
21
+ - React >= 19
22
+ - PostgreSQL database
23
+
24
+ ## Quick Start
25
+
26
+ ### 1. Install
27
+
28
+ ```bash
29
+ npm install next-workflow-builder
30
+ # or
31
+ pnpm add next-workflow-builder
32
+ ```
33
+
34
+ ### 2. Configure Next.js
35
+
36
+ ```ts
37
+ // next.config.ts
38
+ import type { NextConfig } from "next";
39
+ import nextWorkflowBuilder from "next-workflow-builder";
40
+
41
+ const withNextWorkflowBuilder = nextWorkflowBuilder({
42
+ // debug: true,
43
+ // authOptions: { ... }
44
+ });
45
+
46
+ export default withNextWorkflowBuilder({
47
+ // Regular Next.js options
48
+ } satisfies NextConfig);
49
+ ```
50
+
51
+ ### 3. Create the API route
52
+
53
+ ```ts
54
+ // app/api/[[...slug]]/route.ts
55
+ export { GET, POST, PUT, PATCH, DELETE, OPTIONS } from "next-workflow-builder/api";
56
+ ```
57
+
58
+ ### 4. Add the layout
59
+
60
+ ```tsx
61
+ // app/layout.tsx
62
+ import { Layout } from "next-workflow-builder/client";
63
+ import "next-workflow-builder/styles.css";
64
+
65
+ export default function RootLayout({
66
+ children,
67
+ }: {
68
+ children: React.ReactNode;
69
+ }) {
70
+ return (
71
+ <html lang="en" suppressHydrationWarning>
72
+ <body>
73
+ <Layout>{children}</Layout>
74
+ </body>
75
+ </html>
76
+ );
77
+ }
78
+ ```
79
+
80
+ ### 5. Add the workflow pages
81
+
82
+ ```tsx
83
+ // app/[[...slug]]/page.tsx
84
+ export { WorkflowPage as default } from "next-workflow-builder/client";
85
+ export { generateWorkflowMetadata as generateMetadata } from "next-workflow-builder/server";
86
+ ```
87
+
88
+ ### 6. Set environment variables
89
+
90
+ Create a `.env.local` file:
91
+
92
+ ```env
93
+ DATABASE_URL=postgres://user:password@localhost:5432/workflow
94
+ BETTER_AUTH_SECRET=your-secret-key
95
+ BETTER_AUTH_URL=http://localhost:3000
96
+ INTEGRATION_ENCRYPTION_KEY=your-encryption-key
97
+ ```
98
+
99
+ | Variable | Required | Description |
100
+ | --- | --- | --- |
101
+ | `DATABASE_URL` | Yes | PostgreSQL connection string |
102
+ | `BETTER_AUTH_SECRET` | Yes | Secret key for session encryption |
103
+ | `BETTER_AUTH_URL` | Yes | Base URL for auth callbacks |
104
+ | `INTEGRATION_ENCRYPTION_KEY` | Yes | Key to encrypt stored credentials |
105
+
106
+ ### 7. Run
107
+
108
+ ```bash
109
+ pnpm dev
110
+ ```
111
+
112
+ Open [http://localhost:3000](http://localhost:3000) to see the workflow builder.
113
+
114
+ ## Package Exports
115
+
116
+ | Import path | Description |
117
+ | --- | --- |
118
+ | `next-workflow-builder` | Next.js plugin — `nextWorkflowBuilder()` |
119
+ | `next-workflow-builder/client` | React components — `Layout`, `WorkflowPage`, `WorkflowEditor` |
120
+ | `next-workflow-builder/server` | Server utilities — `auth`, `db`, credentials, logging |
121
+ | `next-workflow-builder/api` | HTTP handlers — `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS` |
122
+ | `next-workflow-builder/plugins` | Plugin registry — `registerIntegration()`, query helpers |
123
+ | `next-workflow-builder/server/db/schema` | Drizzle ORM schema exports |
124
+ | `next-workflow-builder/styles.css` | Required CSS styles |
125
+
126
+ ## Plugin System
127
+
128
+ Extend the workflow builder with custom integrations. Each plugin can provide triggers, actions, credentials configuration, and custom UI components.
129
+
130
+ ```bash
131
+ # Scaffold a new plugin
132
+ npx nwb create-plugin
133
+
134
+ # Discover and register plugins
135
+ npx nwb discover-plugins
136
+ ```
137
+
138
+ See the [Plugins documentation](https://next-workflow-builder.vercel.app/docs/plugins) for details.
139
+
140
+ ## CLI Commands
141
+
142
+ | Command | Description |
143
+ | --- | --- |
144
+ | `nwb create-plugin` | Scaffold a new plugin interactively |
145
+ | `nwb discover-plugins` | Scan and register all plugins |
146
+ | `nwb migrate-prod` | Run database migrations for production |
147
+
148
+ ## Documentation
149
+
150
+ For full documentation including configuration, authentication, database setup, deployment, and plugin development:
151
+
152
+ **[https://next-workflow-builder.vercel.app](https://next-workflow-builder.vercel.app)**
153
+
154
+ - [Getting Started](https://next-workflow-builder.vercel.app/docs/getting-started)
155
+ - [Configuration](https://next-workflow-builder.vercel.app/docs/configuration)
156
+ - [Plugins](https://next-workflow-builder.vercel.app/docs/plugins)
157
+ - [Creating Plugins](https://next-workflow-builder.vercel.app/docs/creating-plugins)
158
+ - [API Reference](https://next-workflow-builder.vercel.app/docs/api-reference)
159
+ - [CLI Reference](https://next-workflow-builder.vercel.app/docs/cli-reference)
160
+ - [Database](https://next-workflow-builder.vercel.app/docs/database)
161
+ - [Deployment](https://next-workflow-builder.vercel.app/docs/deployment)
162
+
163
+ ## License
164
+
165
+ Apache-2.0
@@ -0,0 +1,438 @@
1
+ // src/server/lib/utils/id.ts
2
+ import { webcrypto } from "crypto";
3
+ var ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz";
4
+ var ID_LENGTH = 21;
5
+ function generateId() {
6
+ const mask = 63;
7
+ const bytes = webcrypto.getRandomValues(new Uint8Array(ID_LENGTH * 2));
8
+ let id = "";
9
+ let cursor = 0;
10
+ while (id.length < ID_LENGTH) {
11
+ const val = bytes[cursor++] & mask;
12
+ if (val < ALPHABET.length) {
13
+ id += ALPHABET[val];
14
+ }
15
+ if (cursor >= bytes.length) {
16
+ webcrypto.getRandomValues(bytes);
17
+ cursor = 0;
18
+ }
19
+ }
20
+ return id;
21
+ }
22
+
23
+ // src/server/db/schema.ts
24
+ import { relations } from "drizzle-orm";
25
+ import { boolean, jsonb, pgTable, text, timestamp } from "drizzle-orm/pg-core";
26
+ var users = pgTable("users", {
27
+ id: text("id").primaryKey(),
28
+ name: text("name"),
29
+ email: text("email").unique(),
30
+ emailVerified: boolean("email_verified").notNull().default(false),
31
+ image: text("image"),
32
+ createdAt: timestamp("created_at").notNull(),
33
+ updatedAt: timestamp("updated_at").notNull(),
34
+ // Anonymous user tracking
35
+ isAnonymous: boolean("is_anonymous").default(false)
36
+ });
37
+ var sessions = pgTable("sessions", {
38
+ id: text("id").primaryKey(),
39
+ expiresAt: timestamp("expires_at").notNull(),
40
+ token: text("token").notNull().unique(),
41
+ createdAt: timestamp("created_at").notNull(),
42
+ updatedAt: timestamp("updated_at").notNull(),
43
+ ipAddress: text("ip_address"),
44
+ userAgent: text("user_agent"),
45
+ userId: text("user_id").notNull().references(() => users.id)
46
+ });
47
+ var accounts = pgTable("accounts", {
48
+ id: text("id").primaryKey(),
49
+ accountId: text("account_id").notNull(),
50
+ providerId: text("provider_id").notNull(),
51
+ userId: text("user_id").notNull().references(() => users.id),
52
+ accessToken: text("access_token"),
53
+ refreshToken: text("refresh_token"),
54
+ idToken: text("id_token"),
55
+ accessTokenExpiresAt: timestamp("access_token_expires_at"),
56
+ refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
57
+ scope: text("scope"),
58
+ password: text("password"),
59
+ createdAt: timestamp("created_at").notNull(),
60
+ updatedAt: timestamp("updated_at").notNull()
61
+ });
62
+ var verifications = pgTable("verifications", {
63
+ id: text("id").primaryKey(),
64
+ identifier: text("identifier").notNull(),
65
+ value: text("value").notNull(),
66
+ expiresAt: timestamp("expires_at").notNull(),
67
+ createdAt: timestamp("created_at"),
68
+ updatedAt: timestamp("updated_at")
69
+ });
70
+ var workflows = pgTable("workflows", {
71
+ id: text("id").primaryKey().$defaultFn(() => generateId()),
72
+ name: text("name").notNull(),
73
+ description: text("description"),
74
+ userId: text("user_id").notNull().references(() => users.id),
75
+ // biome-ignore lint/suspicious/noExplicitAny: JSONB type - structure validated at application level
76
+ nodes: jsonb("nodes").notNull().$type(),
77
+ // biome-ignore lint/suspicious/noExplicitAny: JSONB type - structure validated at application level
78
+ edges: jsonb("edges").notNull().$type(),
79
+ visibility: text("visibility").notNull().default("private").$type(),
80
+ createdAt: timestamp("created_at").notNull().defaultNow(),
81
+ updatedAt: timestamp("updated_at").notNull().defaultNow()
82
+ });
83
+ var integrations = pgTable("integrations", {
84
+ id: text("id").primaryKey().$defaultFn(() => generateId()),
85
+ userId: text("user_id").notNull().references(() => users.id),
86
+ name: text("name").notNull(),
87
+ type: text("type").notNull().$type(),
88
+ // biome-ignore lint/suspicious/noExplicitAny: JSONB type - encrypted credentials stored as JSON
89
+ config: jsonb("config").notNull().$type(),
90
+ // Whether this integration was created via OAuth (managed by app) vs manual entry
91
+ isManaged: boolean("is_managed").default(false),
92
+ createdAt: timestamp("created_at").notNull().defaultNow(),
93
+ updatedAt: timestamp("updated_at").notNull().defaultNow()
94
+ });
95
+ var workflowExecutions = pgTable("workflow_executions", {
96
+ id: text("id").primaryKey().$defaultFn(() => generateId()),
97
+ workflowId: text("workflow_id").notNull().references(() => workflows.id),
98
+ userId: text("user_id").notNull().references(() => users.id),
99
+ status: text("status").notNull().$type(),
100
+ // biome-ignore lint/suspicious/noExplicitAny: JSONB type - structure validated at application level
101
+ input: jsonb("input").$type(),
102
+ // biome-ignore lint/suspicious/noExplicitAny: JSONB type - structure validated at application level
103
+ output: jsonb("output").$type(),
104
+ error: text("error"),
105
+ startedAt: timestamp("started_at").notNull().defaultNow(),
106
+ completedAt: timestamp("completed_at"),
107
+ duration: text("duration")
108
+ // Duration in milliseconds
109
+ });
110
+ var workflowExecutionLogs = pgTable("workflow_execution_logs", {
111
+ id: text("id").primaryKey().$defaultFn(() => generateId()),
112
+ executionId: text("execution_id").notNull().references(() => workflowExecutions.id),
113
+ nodeId: text("node_id").notNull(),
114
+ nodeName: text("node_name").notNull(),
115
+ nodeType: text("node_type").notNull(),
116
+ status: text("status").notNull().$type(),
117
+ // biome-ignore lint/suspicious/noExplicitAny: JSONB type - structure validated at application level
118
+ input: jsonb("input").$type(),
119
+ // biome-ignore lint/suspicious/noExplicitAny: JSONB type - structure validated at application level
120
+ output: jsonb("output").$type(),
121
+ error: text("error"),
122
+ startedAt: timestamp("started_at").notNull().defaultNow(),
123
+ completedAt: timestamp("completed_at"),
124
+ duration: text("duration"),
125
+ // Duration in milliseconds
126
+ timestamp: timestamp("timestamp").notNull().defaultNow()
127
+ });
128
+ var apiKeys = pgTable("api_keys", {
129
+ id: text("id").primaryKey().$defaultFn(() => generateId()),
130
+ userId: text("user_id").notNull().references(() => users.id),
131
+ name: text("name"),
132
+ // Optional label for the API key
133
+ keyHash: text("key_hash").notNull(),
134
+ // Store hashed version of the key
135
+ keyPrefix: text("key_prefix").notNull(),
136
+ // Store first few chars for display (e.g., "wf_abc...")
137
+ createdAt: timestamp("created_at").notNull().defaultNow(),
138
+ lastUsedAt: timestamp("last_used_at")
139
+ });
140
+ var workflowExecutionsRelations = relations(
141
+ workflowExecutions,
142
+ ({ one }) => ({
143
+ workflow: one(workflows, {
144
+ fields: [workflowExecutions.workflowId],
145
+ references: [workflows.id]
146
+ })
147
+ })
148
+ );
149
+
150
+ // src/server/db/index.ts
151
+ import { drizzle } from "drizzle-orm/postgres-js";
152
+ import postgres from "postgres";
153
+ var schema = {
154
+ users,
155
+ sessions,
156
+ accounts,
157
+ verifications,
158
+ workflows,
159
+ workflowExecutions,
160
+ workflowExecutionLogs,
161
+ workflowExecutionsRelations,
162
+ apiKeys,
163
+ integrations
164
+ };
165
+ var connectionString = process.env.DATABASE_URL || "postgres://localhost:5432/workflow";
166
+ var migrationClient = postgres(connectionString, { max: 1 });
167
+ var globalForDb = globalThis;
168
+ var queryClient = globalForDb.queryClient ?? postgres(connectionString, { max: 10 });
169
+ var db = globalForDb.db ?? drizzle(queryClient, { schema });
170
+ if (process.env.NODE_ENV !== "production") {
171
+ globalForDb.queryClient = queryClient;
172
+ globalForDb.db = db;
173
+ }
174
+
175
+ // src/server/lib/steps/step-handler.ts
176
+ import "server-only";
177
+
178
+ // src/server/lib/utils/redact.ts
179
+ var SENSITIVE_KEYS = /* @__PURE__ */ new Set([
180
+ // API Keys
181
+ "apiKey",
182
+ "api_key",
183
+ "apikey",
184
+ "key",
185
+ // Credentials
186
+ "password",
187
+ "passwd",
188
+ "pwd",
189
+ "secret",
190
+ "token",
191
+ "accessToken",
192
+ "access_token",
193
+ "refreshToken",
194
+ "refresh_token",
195
+ "privateKey",
196
+ "private_key",
197
+ // Database
198
+ "databaseUrl",
199
+ "database_url",
200
+ "connectionString",
201
+ "connection_string",
202
+ // Email
203
+ "fromEmail",
204
+ "from_email",
205
+ // Authentication
206
+ "authorization",
207
+ "auth",
208
+ "bearer",
209
+ // Credit Card/Payment
210
+ "creditCard",
211
+ "credit_card",
212
+ "cardNumber",
213
+ "card_number",
214
+ "cvv",
215
+ "ssn",
216
+ // Personal Info
217
+ "phoneNumber",
218
+ "phone_number",
219
+ "socialSecurity",
220
+ "social_security"
221
+ ]);
222
+ var SENSITIVE_PATTERNS = [
223
+ /api[_-]?key/i,
224
+ /token/i,
225
+ /secret/i,
226
+ /password/i,
227
+ /credential/i,
228
+ /auth/i
229
+ ];
230
+ function isSensitiveKey(key) {
231
+ if (SENSITIVE_KEYS.has(key.toLowerCase())) {
232
+ return true;
233
+ }
234
+ return SENSITIVE_PATTERNS.some((pattern) => pattern.test(key));
235
+ }
236
+ function maskValue(value) {
237
+ if (!value || value.length === 0) {
238
+ return "[REDACTED]";
239
+ }
240
+ if (value.length <= 4) {
241
+ return "****";
242
+ }
243
+ const last4 = value.slice(-4);
244
+ const stars = "*".repeat(Math.min(8, value.length - 4));
245
+ return `${stars}${last4}`;
246
+ }
247
+ function redactObject(obj, depth = 0) {
248
+ if (depth > 10) {
249
+ return obj;
250
+ }
251
+ if (obj === null || obj === void 0) {
252
+ return obj;
253
+ }
254
+ if (typeof obj === "string") {
255
+ return obj;
256
+ }
257
+ if (typeof obj === "number" || typeof obj === "boolean") {
258
+ return obj;
259
+ }
260
+ if (Array.isArray(obj)) {
261
+ return obj.map((item) => redactObject(item, depth + 1));
262
+ }
263
+ if (typeof obj === "object") {
264
+ const redacted = {};
265
+ for (const [key, value] of Object.entries(obj)) {
266
+ if (isSensitiveKey(key)) {
267
+ if (typeof value === "string") {
268
+ redacted[key] = maskValue(value);
269
+ } else {
270
+ redacted[key] = "[REDACTED]";
271
+ }
272
+ } else {
273
+ redacted[key] = redactObject(value, depth + 1);
274
+ }
275
+ }
276
+ return redacted;
277
+ }
278
+ return obj;
279
+ }
280
+ function redactSensitiveData(data) {
281
+ if (data === null || data === void 0) {
282
+ return data;
283
+ }
284
+ try {
285
+ return redactObject(data);
286
+ } catch (error) {
287
+ console.error("[Redact] Error redacting data:", error);
288
+ return "[REDACTION_ERROR]";
289
+ }
290
+ }
291
+
292
+ // src/server/lib/workflow-logging.ts
293
+ import "server-only";
294
+ import { eq } from "drizzle-orm";
295
+ async function logStepStartDb(params) {
296
+ const [log] = await db.insert(workflowExecutionLogs).values({
297
+ executionId: params.executionId,
298
+ nodeId: params.nodeId,
299
+ nodeName: params.nodeName,
300
+ nodeType: params.nodeType,
301
+ status: "running",
302
+ input: params.input,
303
+ startedAt: /* @__PURE__ */ new Date()
304
+ }).returning();
305
+ return {
306
+ logId: log.id,
307
+ startTime: Date.now()
308
+ };
309
+ }
310
+ async function logStepCompleteDb(params) {
311
+ const duration = Date.now() - params.startTime;
312
+ await db.update(workflowExecutionLogs).set({
313
+ status: params.status,
314
+ output: params.output,
315
+ error: params.error,
316
+ completedAt: /* @__PURE__ */ new Date(),
317
+ duration: duration.toString()
318
+ }).where(eq(workflowExecutionLogs.id, params.logId));
319
+ }
320
+ async function logWorkflowCompleteDb(params) {
321
+ const duration = Date.now() - params.startTime;
322
+ await db.update(workflowExecutions).set({
323
+ status: params.status,
324
+ output: params.output,
325
+ error: params.error,
326
+ completedAt: /* @__PURE__ */ new Date(),
327
+ duration: duration.toString()
328
+ }).where(eq(workflowExecutions.id, params.executionId));
329
+ }
330
+
331
+ // src/server/lib/steps/step-handler.ts
332
+ async function logStepStart(context, input) {
333
+ if (!context?.executionId) {
334
+ return { logId: "", startTime: Date.now() };
335
+ }
336
+ try {
337
+ const redactedInput = redactSensitiveData(input);
338
+ const result = await logStepStartDb({
339
+ executionId: context.executionId,
340
+ nodeId: context.nodeId,
341
+ nodeName: context.nodeName,
342
+ nodeType: context.nodeType,
343
+ input: redactedInput
344
+ });
345
+ return result;
346
+ } catch (error) {
347
+ console.error("[stepHandler] Failed to log start:", error);
348
+ return { logId: "", startTime: Date.now() };
349
+ }
350
+ }
351
+ async function logStepComplete(logInfo, status, output, error) {
352
+ if (!logInfo.logId) {
353
+ return;
354
+ }
355
+ try {
356
+ const redactedOutput = redactSensitiveData(output);
357
+ await logStepCompleteDb({
358
+ logId: logInfo.logId,
359
+ startTime: logInfo.startTime,
360
+ status,
361
+ output: redactedOutput,
362
+ error
363
+ });
364
+ } catch (err) {
365
+ console.error("[stepHandler] Failed to log completion:", err);
366
+ }
367
+ }
368
+ var INTERNAL_FIELDS = ["_context", "actionType", "integrationId"];
369
+ function stripInternalFields(input) {
370
+ const result = { ...input };
371
+ for (const field of INTERNAL_FIELDS) {
372
+ delete result[field];
373
+ }
374
+ return result;
375
+ }
376
+ async function logWorkflowComplete(options) {
377
+ try {
378
+ const redactedOutput = redactSensitiveData(options.output);
379
+ await logWorkflowCompleteDb({
380
+ executionId: options.executionId,
381
+ status: options.status,
382
+ output: redactedOutput,
383
+ error: options.error,
384
+ startTime: options.startTime
385
+ });
386
+ } catch (err) {
387
+ console.error("[stepHandler] Failed to log workflow completion:", err);
388
+ }
389
+ }
390
+ async function withStepLogging(input, stepLogic) {
391
+ const context = input._context;
392
+ const loggedInput = stripInternalFields(input);
393
+ const logInfo = await logStepStart(context, loggedInput);
394
+ try {
395
+ const result = await stepLogic();
396
+ const isStandardizedResult = result && typeof result === "object" && "success" in result && typeof result.success === "boolean";
397
+ const isErrorResult = isStandardizedResult && result.success === false;
398
+ if (isErrorResult) {
399
+ const errorResult = result;
400
+ const errorMessage = typeof errorResult.error === "string" ? errorResult.error : errorResult.error?.message || "Step execution failed";
401
+ const loggedOutput = errorResult.error ?? { message: errorMessage };
402
+ await logStepComplete(logInfo, "error", loggedOutput, errorMessage);
403
+ } else if (isStandardizedResult) {
404
+ const successResult = result;
405
+ await logStepComplete(logInfo, "success", successResult.data ?? result);
406
+ } else {
407
+ await logStepComplete(logInfo, "success", result);
408
+ }
409
+ if (context?._workflowComplete && context.executionId) {
410
+ await logWorkflowComplete({
411
+ executionId: context.executionId,
412
+ ...context._workflowComplete
413
+ });
414
+ }
415
+ return result;
416
+ } catch (error) {
417
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
418
+ await logStepComplete(logInfo, "error", void 0, errorMessage);
419
+ throw error;
420
+ }
421
+ }
422
+
423
+ export {
424
+ generateId,
425
+ users,
426
+ sessions,
427
+ accounts,
428
+ verifications,
429
+ workflows,
430
+ integrations,
431
+ workflowExecutions,
432
+ workflowExecutionLogs,
433
+ apiKeys,
434
+ workflowExecutionsRelations,
435
+ db,
436
+ logWorkflowComplete,
437
+ withStepLogging
438
+ };
@@ -0,0 +1,96 @@
1
+ // src/client/lib/utils.ts
2
+ import { clsx } from "clsx";
3
+ import { twMerge } from "tailwind-merge";
4
+ function cn(...inputs) {
5
+ return twMerge(clsx(inputs));
6
+ }
7
+ function getErrorMessage(error) {
8
+ if (error === null || error === void 0) {
9
+ return "Unknown error";
10
+ }
11
+ if (error instanceof Error) {
12
+ if (error.cause && error.cause instanceof Error) {
13
+ return `${error.message}: ${error.cause.message}`;
14
+ }
15
+ return error.message;
16
+ }
17
+ if (typeof error === "string") {
18
+ return error;
19
+ }
20
+ if (typeof error === "object") {
21
+ const obj = error;
22
+ if (typeof obj.message === "string" && obj.message) {
23
+ return obj.message;
24
+ }
25
+ if (obj.responseBody && typeof obj.responseBody === "object") {
26
+ const body = obj.responseBody;
27
+ if (typeof body.error === "string") {
28
+ return body.error;
29
+ }
30
+ if (body.error && typeof body.error === "object" && typeof body.error.message === "string") {
31
+ return body.error.message;
32
+ }
33
+ }
34
+ if (typeof obj.error === "string" && obj.error) {
35
+ return obj.error;
36
+ }
37
+ if (obj.error && typeof obj.error === "object") {
38
+ const nestedError = obj.error;
39
+ if (typeof nestedError.message === "string") {
40
+ return nestedError.message;
41
+ }
42
+ }
43
+ if (obj.data && typeof obj.data === "object") {
44
+ const data = obj.data;
45
+ if (typeof data.error === "string") {
46
+ return data.error;
47
+ }
48
+ if (typeof data.message === "string") {
49
+ return data.message;
50
+ }
51
+ }
52
+ if (typeof obj.reason === "string" && obj.reason) {
53
+ return obj.reason;
54
+ }
55
+ if (typeof obj.statusText === "string" && obj.statusText) {
56
+ const status = typeof obj.status === "number" ? ` (${obj.status})` : "";
57
+ return `${obj.statusText}${status}`;
58
+ }
59
+ try {
60
+ const stringified = JSON.stringify(error, null, 0);
61
+ if (stringified && stringified !== "{}" && stringified.length < 500) {
62
+ return stringified;
63
+ }
64
+ } catch {
65
+ }
66
+ const toString = Object.prototype.toString.call(error);
67
+ if (toString !== "[object Object]") {
68
+ return toString;
69
+ }
70
+ }
71
+ return "Unknown error";
72
+ }
73
+ async function getErrorMessageAsync(error) {
74
+ if (error instanceof Promise) {
75
+ try {
76
+ const resolvedValue = await error;
77
+ return getErrorMessage(resolvedValue);
78
+ } catch (rejectedError) {
79
+ return getErrorMessage(rejectedError);
80
+ }
81
+ }
82
+ if (error && typeof error === "object" && "then" in error && typeof error.then === "function") {
83
+ try {
84
+ const resolvedValue = await error;
85
+ return getErrorMessage(resolvedValue);
86
+ } catch (rejectedError) {
87
+ return getErrorMessage(rejectedError);
88
+ }
89
+ }
90
+ return getErrorMessage(error);
91
+ }
92
+
93
+ export {
94
+ cn,
95
+ getErrorMessageAsync
96
+ };