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 +165 -0
- package/dist/chunk-3MSAF2TH.js +438 -0
- package/dist/chunk-5YYA34YV.js +96 -0
- package/dist/chunk-7MUXUHEL.js +66 -0
- package/dist/chunk-BNYDOC3I.js +169 -0
- package/dist/chunk-D44JFQYX.js +546 -0
- package/dist/chunk-DJ7ANVJ3.js +51 -0
- package/dist/chunk-O3I2INCD.js +71 -0
- package/dist/chunk-OQHML4II.js +36 -0
- package/dist/chunk-P3DTV3QS.js +105 -0
- package/dist/chunk-XJ67EFQA.js +1162 -0
- package/dist/chunk-Z3BJJYHM.js +246 -0
- package/dist/client/index.d.ts +32 -0
- package/dist/client/index.js +13700 -0
- package/dist/condition-SFT7Y5YJ.js +29 -0
- package/dist/database-query-GRWP3S3M.js +99 -0
- package/dist/http-request-2HVCXQHK.js +76 -0
- package/dist/next/index.d.ts +42 -0
- package/dist/next/index.js +66 -0
- package/dist/plugins/index.d.ts +113 -0
- package/dist/plugins/index.js +52 -0
- package/dist/server/api/index.d.ts +8 -0
- package/dist/server/api/index.js +2672 -0
- package/dist/server/index.d.ts +2911 -0
- package/dist/server/index.js +60 -0
- package/dist/style-prefixed.css +5167 -0
- package/dist/styles.css +5167 -0
- package/dist/types-BACZx2Ft.d.ts +139 -0
- package/package.json +112 -0
- package/src/scripts/nwb.ts +54 -0
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
|
+
};
|