@xtr-dev/payload-automation 0.0.51 → 0.0.53
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 +185 -56
- package/dist/core/expression-engine.js +2 -2
- package/dist/core/expression-engine.js.map +1 -1
- package/dist/steps/index.d.ts +0 -2
- package/dist/steps/index.js +0 -2
- package/dist/steps/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/steps/create-step.d.ts +0 -66
- package/dist/steps/create-step.js +0 -59
- package/dist/steps/create-step.js.map +0 -1
package/README.md
CHANGED
|
@@ -8,15 +8,13 @@ A workflow automation plugin for PayloadCMS 3.x. Build visual workflows triggere
|
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
- **Custom Steps** - Create your own step types with the step factory
|
|
19
|
-
- **JSONata Expressions** - Powerful data transformation between steps
|
|
11
|
+
- 🔄 Visual workflow builder in PayloadCMS admin
|
|
12
|
+
- ⚡ Run workflows when documents are created/updated/deleted
|
|
13
|
+
- 🎯 Trigger workflows via webhooks
|
|
14
|
+
- 📊 Track workflow execution history
|
|
15
|
+
- 🔧 HTTP requests, document operations, email sending
|
|
16
|
+
- 🔗 Use data from previous steps in templates
|
|
17
|
+
- ⚙️ Step dependencies with parallel and sequential execution
|
|
20
18
|
|
|
21
19
|
## Installation
|
|
22
20
|
|
|
@@ -30,20 +28,20 @@ npm install @xtr-dev/payload-automation
|
|
|
30
28
|
|
|
31
29
|
```typescript
|
|
32
30
|
import { buildConfig } from 'payload'
|
|
33
|
-
import { workflowsPlugin } from '@xtr-dev/payload-automation'
|
|
31
|
+
import { workflowsPlugin } from '@xtr-dev/payload-automation/server'
|
|
34
32
|
|
|
35
33
|
export default buildConfig({
|
|
34
|
+
// ... your config
|
|
36
35
|
plugins: [
|
|
37
36
|
workflowsPlugin({
|
|
38
|
-
enabled: true,
|
|
39
37
|
collectionTriggers: {
|
|
40
|
-
|
|
41
|
-
users: {
|
|
42
|
-
|
|
38
|
+
posts: true, // Enable all CRUD triggers for posts
|
|
39
|
+
users: {
|
|
40
|
+
create: true, // Only enable create trigger for users
|
|
41
|
+
update: true
|
|
43
42
|
}
|
|
44
43
|
},
|
|
45
|
-
|
|
46
|
-
steps: [myCustomStep],
|
|
44
|
+
enabled: true,
|
|
47
45
|
}),
|
|
48
46
|
],
|
|
49
47
|
})
|
|
@@ -52,10 +50,10 @@ export default buildConfig({
|
|
|
52
50
|
## Imports
|
|
53
51
|
|
|
54
52
|
```typescript
|
|
55
|
-
//
|
|
56
|
-
import { workflowsPlugin } from '@xtr-dev/payload-automation'
|
|
53
|
+
// Server plugin
|
|
54
|
+
import { workflowsPlugin } from '@xtr-dev/payload-automation/server'
|
|
57
55
|
|
|
58
|
-
// Client components
|
|
56
|
+
// Client components
|
|
59
57
|
import { StatusCell, ErrorDisplay } from '@xtr-dev/payload-automation/client'
|
|
60
58
|
|
|
61
59
|
// Types
|
|
@@ -89,13 +87,80 @@ const exampleWorkflows: SeedWorkflow[] = [
|
|
|
89
87
|
name: 'Send Email',
|
|
90
88
|
type: 'send-email',
|
|
91
89
|
input: {
|
|
92
|
-
to: '
|
|
90
|
+
to: '{{trigger.doc.email}}',
|
|
93
91
|
subject: 'Welcome!',
|
|
94
92
|
text: 'Thanks for joining us!',
|
|
95
93
|
},
|
|
96
94
|
},
|
|
97
95
|
],
|
|
98
96
|
},
|
|
97
|
+
{
|
|
98
|
+
slug: 'example-order-processing',
|
|
99
|
+
name: 'Example: Order Processing Pipeline',
|
|
100
|
+
description: 'Process order with validation, inventory check, and notifications',
|
|
101
|
+
triggers: [
|
|
102
|
+
{
|
|
103
|
+
type: 'collection-hook',
|
|
104
|
+
parameters: {
|
|
105
|
+
collectionSlug: 'orders',
|
|
106
|
+
hook: 'afterChange',
|
|
107
|
+
},
|
|
108
|
+
condition: 'trigger.operation = "create"',
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
steps: [
|
|
112
|
+
{
|
|
113
|
+
name: 'Validate Order',
|
|
114
|
+
type: 'http-request-step',
|
|
115
|
+
input: {
|
|
116
|
+
url: 'https://api.example.com/validate',
|
|
117
|
+
method: 'POST',
|
|
118
|
+
body: {
|
|
119
|
+
orderId: '{{trigger.doc.id}}',
|
|
120
|
+
items: '{{trigger.doc.items}}',
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'Check Inventory',
|
|
126
|
+
type: 'http-request-step',
|
|
127
|
+
input: {
|
|
128
|
+
url: 'https://api.example.com/inventory/check',
|
|
129
|
+
method: 'POST',
|
|
130
|
+
body: {
|
|
131
|
+
items: '{{trigger.doc.items}}',
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
// This step runs only after Validate Order succeeds
|
|
135
|
+
dependencies: ['Validate Order'],
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'Create Shipment',
|
|
139
|
+
type: 'create-document',
|
|
140
|
+
input: {
|
|
141
|
+
collection: 'shipments',
|
|
142
|
+
data: {
|
|
143
|
+
orderId: '{{trigger.doc.id}}',
|
|
144
|
+
status: 'pending',
|
|
145
|
+
items: '{{trigger.doc.items}}',
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
// This step waits for both validation and inventory check
|
|
149
|
+
dependencies: ['Validate Order', 'Check Inventory'],
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: 'Send Confirmation Email',
|
|
153
|
+
type: 'send-email',
|
|
154
|
+
input: {
|
|
155
|
+
to: '{{trigger.doc.customer.email}}',
|
|
156
|
+
subject: 'Order Confirmed',
|
|
157
|
+
text: 'Your order {{trigger.doc.id}} has been confirmed!',
|
|
158
|
+
},
|
|
159
|
+
// Only send email after shipment is created
|
|
160
|
+
dependencies: ['Create Shipment'],
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
},
|
|
99
164
|
]
|
|
100
165
|
|
|
101
166
|
workflowsPlugin({
|
|
@@ -114,8 +179,73 @@ Seeded workflows:
|
|
|
114
179
|
|
|
115
180
|
See [docs/SEEDING_WORKFLOWS.md](./docs/SEEDING_WORKFLOWS.md) for detailed documentation.
|
|
116
181
|
|
|
182
|
+
## Step Dependencies
|
|
183
|
+
|
|
184
|
+
Steps can declare dependencies on other steps to control execution order. The workflow executor uses topological sorting to determine the optimal execution order.
|
|
185
|
+
|
|
186
|
+
### How Dependencies Work
|
|
187
|
+
|
|
188
|
+
- **Parallel Execution**: Steps without dependencies run in parallel
|
|
189
|
+
- **Sequential Execution**: Steps with dependencies wait for their dependencies to complete successfully
|
|
190
|
+
- **Multiple Dependencies**: A step can depend on multiple other steps (all must succeed)
|
|
191
|
+
- **Failure Handling**: If a dependency fails, the dependent step is skipped
|
|
192
|
+
|
|
193
|
+
### Example: Parallel and Sequential Steps
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
steps: [
|
|
197
|
+
{
|
|
198
|
+
name: 'Fetch User Data',
|
|
199
|
+
type: 'http-request-step',
|
|
200
|
+
// No dependencies - runs immediately
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: 'Fetch Order Data',
|
|
204
|
+
type: 'http-request-step',
|
|
205
|
+
// No dependencies - runs in parallel with Fetch User Data
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
name: 'Generate Report',
|
|
209
|
+
type: 'http-request-step',
|
|
210
|
+
dependencies: ['Fetch User Data', 'Fetch Order Data'],
|
|
211
|
+
// Waits for both API calls to complete
|
|
212
|
+
input: {
|
|
213
|
+
url: 'https://api.example.com/reports',
|
|
214
|
+
body: {
|
|
215
|
+
user: '{{steps.FetchUserData.output.user}}',
|
|
216
|
+
orders: '{{steps.FetchOrderData.output.orders}}',
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
]
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Accessing Step Output
|
|
224
|
+
|
|
225
|
+
Use `{{steps.<stepName>.output.<field>}}` to reference data from completed steps:
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
{
|
|
229
|
+
name: 'Process Result',
|
|
230
|
+
type: 'create-document',
|
|
231
|
+
dependencies: ['API Call'],
|
|
232
|
+
input: {
|
|
233
|
+
collection: 'results',
|
|
234
|
+
data: {
|
|
235
|
+
// Access output from the "API Call" step
|
|
236
|
+
apiResponse: '{{steps.APICall.output.data}}',
|
|
237
|
+
status: '{{steps.APICall.output.status}}',
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
117
243
|
## Step Types
|
|
118
244
|
|
|
245
|
+
> **Note:** Steps are just regular [PayloadCMS tasks](https://payloadcms.com/docs/jobs/overview). This plugin uses Payload's built-in job queue system, so you can leverage all of Payload's task features including retries, scheduling, and monitoring. Any `TaskConfig` you create is a valid step.
|
|
246
|
+
|
|
247
|
+
The plugin comes with a few built-in step types found below.
|
|
248
|
+
|
|
119
249
|
### HTTP Request
|
|
120
250
|
Call external APIs with full configuration:
|
|
121
251
|
|
|
@@ -156,12 +286,13 @@ Send notifications via PayloadCMS email adapter:
|
|
|
156
286
|
|
|
157
287
|
## Custom Steps
|
|
158
288
|
|
|
159
|
-
|
|
289
|
+
Steps are standard PayloadCMS tasks, so creating custom steps is just defining a `TaskConfig`. No proprietary APIs to learn:
|
|
160
290
|
|
|
161
291
|
```typescript
|
|
162
|
-
import {
|
|
292
|
+
import type { TaskConfig } from 'payload'
|
|
163
293
|
|
|
164
|
-
|
|
294
|
+
// Define the step configuration
|
|
295
|
+
export const SlackNotificationStep: TaskConfig<'slack-notification'> = {
|
|
165
296
|
slug: 'slack-notification',
|
|
166
297
|
label: 'Send Slack Message',
|
|
167
298
|
|
|
@@ -175,42 +306,40 @@ export const SlackNotificationStep = createStep({
|
|
|
175
306
|
{ name: 'timestamp', type: 'text' },
|
|
176
307
|
],
|
|
177
308
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
309
|
+
handler: async ({ input, req }) => {
|
|
310
|
+
try {
|
|
311
|
+
const response = await fetch('https://slack.com/api/chat.postMessage', {
|
|
312
|
+
method: 'POST',
|
|
313
|
+
headers: {
|
|
314
|
+
'Authorization': `Bearer ${process.env.SLACK_TOKEN}`,
|
|
315
|
+
'Content-Type': 'application/json',
|
|
316
|
+
},
|
|
317
|
+
body: JSON.stringify({
|
|
318
|
+
channel: input.channel,
|
|
319
|
+
text: input.message,
|
|
320
|
+
}),
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
const data = await response.json()
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
output: {
|
|
327
|
+
messageId: data.message.ts,
|
|
328
|
+
timestamp: new Date().toISOString(),
|
|
329
|
+
},
|
|
330
|
+
state: 'succeeded'
|
|
331
|
+
}
|
|
332
|
+
} catch (error) {
|
|
333
|
+
return {
|
|
334
|
+
output: {},
|
|
335
|
+
state: 'failed',
|
|
336
|
+
errorMessage: error instanceof Error ? error.message : 'Unknown error'
|
|
337
|
+
}
|
|
206
338
|
}
|
|
207
339
|
},
|
|
208
|
-
}
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
Register custom steps in the plugin config:
|
|
340
|
+
}
|
|
212
341
|
|
|
213
|
-
|
|
342
|
+
// Register in plugin config
|
|
214
343
|
workflowsPlugin({
|
|
215
344
|
steps: [SlackNotificationStep],
|
|
216
345
|
})
|
|
@@ -142,9 +142,9 @@ const MAX_CACHE_SIZE = 1000;
|
|
|
142
142
|
*/ export async function transform(template, context, options = {}) {
|
|
143
143
|
if (typeof template === 'string') {
|
|
144
144
|
// Check if it looks like a JSONata expression (starts with common patterns)
|
|
145
|
-
if (template.startsWith('{')
|
|
145
|
+
if (template.startsWith('{{')) {
|
|
146
146
|
try {
|
|
147
|
-
return await evaluate(template, context, options);
|
|
147
|
+
return await evaluate(template.trim().slice(2, -2), context, options);
|
|
148
148
|
} catch (e) {
|
|
149
149
|
if (options.debug && options.logger) {
|
|
150
150
|
options.logger.info(e instanceof Error ? e.message : e, 'Failed to evaluate expression:');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/expression-engine.ts"],"sourcesContent":["import jsonata from 'jsonata'\nimport type {Payload} from \"payload\";\n\n/**\n * Expression engine using JSONata for safe, sandboxed expression evaluation.\n * Used for both conditions and data transformation in workflows.\n *\n * @example Conditions\n * ```\n * trigger.doc._status = \"published\"\n * trigger.doc.count > 10 and trigger.doc.enabled\n * $exists(steps.validate.output.error) = false\n * ```\n *\n * @example Data Transformation\n * ```\n * {\n * \"id\": trigger.doc.id,\n * \"title\": $uppercase(trigger.doc.title),\n * \"tags\": trigger.doc.tags[category = \"featured\"].name\n * }\n * ```\n */\n\nexport interface ExpressionContext {\n trigger: Record<string, unknown>\n steps: Record<string, unknown>\n [key: string]: unknown\n}\n\nexport interface EvaluateOptions {\n /** Timeout in milliseconds (default: 5000) */\n timeout?: number\n debug?: boolean\n logger?: Payload['logger']\n}\n\n// Cache compiled expressions for performance\nconst expressionCache = new Map<string, jsonata.Expression>()\nconst MAX_CACHE_SIZE = 1000\n\n/**\n * Compile a JSONata expression with caching\n */\nfunction compileExpression(expression: string): jsonata.Expression {\n let compiled = expressionCache.get(expression)\n\n if (!compiled) {\n compiled = jsonata(expression)\n\n // Register custom functions\n registerCustomFunctions(compiled)\n\n // Manage cache size\n if (expressionCache.size >= MAX_CACHE_SIZE) {\n const firstKey = expressionCache.keys().next().value\n if (firstKey) expressionCache.delete(firstKey)\n }\n\n expressionCache.set(expression, compiled)\n }\n\n return compiled\n}\n\n/**\n * Register custom functions on a JSONata expression\n */\nfunction registerCustomFunctions(expr: jsonata.Expression): void {\n // $env(name) - Get environment variable (only non-sensitive ones)\n expr.registerFunction('env', (name: string) => {\n // Only allow specific prefixes for security\n if (typeof name === 'string' && name.startsWith('PUBLIC_')) {\n return process.env[name]\n }\n return undefined\n }, '<s:s>')\n\n // $now() - Current ISO timestamp\n expr.registerFunction('now', () => new Date().toISOString(), '<:s>')\n\n // $timestamp() - Current Unix timestamp in milliseconds\n expr.registerFunction('timestamp', () => Date.now(), '<:n>')\n\n // $uuid() - Generate a UUID v4\n expr.registerFunction('uuid', () => {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0\n const v = c === 'x' ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n }, '<:s>')\n\n // $default(value, defaultValue) - Return default if value is null/undefined\n expr.registerFunction('default', (value: unknown, defaultValue: unknown) => {\n return value === null || value === undefined ? defaultValue : value\n }, '<xx:x>')\n\n // $json(value) - Parse JSON string\n expr.registerFunction('json', (value: string) => {\n try {\n return JSON.parse(value)\n } catch {\n return undefined\n }\n }, '<s:x>')\n\n // $stringify(value) - Convert to JSON string\n expr.registerFunction('stringify', (value: unknown) => {\n try {\n return JSON.stringify(value)\n } catch {\n return undefined\n }\n }, '<x:s>')\n\n // $keys(object) - Get object keys\n expr.registerFunction('keys', (obj: Record<string, unknown>) => {\n if (obj && typeof obj === 'object' && !Array.isArray(obj)) {\n return Object.keys(obj)\n }\n return []\n }, '<o:a>')\n\n // $values(object) - Get object values\n expr.registerFunction('values', (obj: Record<string, unknown>) => {\n if (obj && typeof obj === 'object' && !Array.isArray(obj)) {\n return Object.values(obj)\n }\n return []\n }, '<o:a>')\n\n // $has(object, key) - Check if object has key\n expr.registerFunction('has', (obj: Record<string, unknown>, key: string) => {\n if (obj && typeof obj === 'object') {\n return key in obj\n }\n return false\n }, '<os:b>')\n\n // $coalesce(values...) - Return first non-null value\n expr.registerFunction('coalesce', (...values: unknown[]) => {\n for (const v of values) {\n if (v !== null && v !== undefined) {\n return v\n }\n }\n return null\n }, '<x+:x>')\n}\n\n/**\n * Evaluate a JSONata expression against a context\n */\nexport async function evaluate(\n expression: string,\n context: ExpressionContext,\n options: EvaluateOptions = {}\n): Promise<unknown> {\n const { timeout = 5000 } = options\n\n const compiled = compileExpression(expression)\n\n // Create a promise that rejects on timeout\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => reject(new Error(`Expression evaluation timed out after ${timeout}ms`)), timeout)\n })\n\n // Race between evaluation and timeout\n return Promise.race([\n compiled.evaluate(context),\n timeoutPromise\n ])\n}\n\n/**\n * Evaluate a condition expression and return a boolean\n */\nexport async function evaluateCondition(\n expression: string,\n context: ExpressionContext,\n options: EvaluateOptions = {}\n): Promise<boolean> {\n try {\n const result = await evaluate(expression, context, options)\n\n // Convert result to boolean\n if (result === undefined || result === null) {\n return false\n }\n if (typeof result === 'boolean') {\n return result\n }\n if (typeof result === 'number') {\n return result !== 0\n }\n if (typeof result === 'string') {\n return result.length > 0\n }\n if (Array.isArray(result)) {\n return result.length > 0\n }\n return true\n } catch (error) {\n // Log error but return false for failed conditions\n console.warn('Condition evaluation failed:', error instanceof Error ? error.message : error)\n return false\n }\n}\n\n/**\n * Transform data using a JSONata expression\n * The expression can be a JSONata query or a JSON object with embedded expressions\n */\nexport async function transform(\n template: unknown,\n context: ExpressionContext,\n options: EvaluateOptions = {}\n): Promise<unknown> {\n if (typeof template === 'string') {\n // Check if it looks like a JSONata expression (starts with common patterns)\n if (\n template.startsWith('{') ||\n template.startsWith('[') ||\n template.startsWith('$') ||\n template.includes('.') ||\n template.includes('(')\n ) {\n try {\n return await evaluate(template, context, options)\n } catch (e) {\n if (options.debug && options.logger) {\n options.logger.info(\n e instanceof Error ? e.message : e,\n 'Failed to evaluate expression:',\n )\n }\n // If it fails to evaluate, return as literal string\n return template\n }\n }\n return template\n }\n\n if (Array.isArray(template)) {\n return Promise.all(template.map(item => transform(item, context, options)))\n }\n\n if (template && typeof template === 'object') {\n const result: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(template)) {\n result[key] = await transform(value, context, options)\n }\n return result\n }\n\n return template\n}\n\n/**\n * Resolve a step input configuration using JSONata\n * Handles both simple values and expressions\n */\nexport async function resolveStepInput(\n config: Record<string, unknown>,\n context: ExpressionContext,\n options: EvaluateOptions = {}\n): Promise<Record<string, unknown>> {\n const result: Record<string, unknown> = {}\n\n for (const [key, value] of Object.entries(config)) {\n result[key] = await transform(value, context, options)\n }\n\n return result\n}\n\n/**\n * Clear the expression cache\n */\nexport function clearCache(): void {\n expressionCache.clear()\n}\n\n/**\n * Get cache statistics\n */\nexport function getCacheStats(): { size: number; maxSize: number } {\n return {\n size: expressionCache.size,\n maxSize: MAX_CACHE_SIZE\n }\n}\n"],"names":["jsonata","expressionCache","Map","MAX_CACHE_SIZE","compileExpression","expression","compiled","get","registerCustomFunctions","size","firstKey","keys","next","value","delete","set","expr","registerFunction","name","startsWith","process","env","undefined","Date","toISOString","now","replace","c","r","Math","random","v","toString","defaultValue","JSON","parse","stringify","obj","Array","isArray","Object","values","key","evaluate","context","options","timeout","timeoutPromise","Promise","_","reject","setTimeout","Error","race","evaluateCondition","result","length","error","console","warn","message","transform","template","includes","e","debug","logger","info","all","map","item","entries","resolveStepInput","config","clearCache","clear","getCacheStats","maxSize"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/core/expression-engine.ts"],"sourcesContent":["import jsonata from 'jsonata'\nimport type {Payload} from \"payload\";\n\n/**\n * Expression engine using JSONata for safe, sandboxed expression evaluation.\n * Used for both conditions and data transformation in workflows.\n *\n * @example Conditions\n * ```\n * trigger.doc._status = \"published\"\n * trigger.doc.count > 10 and trigger.doc.enabled\n * $exists(steps.validate.output.error) = false\n * ```\n *\n * @example Data Transformation\n * ```\n * {\n * \"id\": trigger.doc.id,\n * \"title\": $uppercase(trigger.doc.title),\n * \"tags\": trigger.doc.tags[category = \"featured\"].name\n * }\n * ```\n */\n\nexport interface ExpressionContext {\n trigger: Record<string, unknown>\n steps: Record<string, unknown>\n [key: string]: unknown\n}\n\nexport interface EvaluateOptions {\n /** Timeout in milliseconds (default: 5000) */\n timeout?: number\n debug?: boolean\n logger?: Payload['logger']\n}\n\n// Cache compiled expressions for performance\nconst expressionCache = new Map<string, jsonata.Expression>()\nconst MAX_CACHE_SIZE = 1000\n\n/**\n * Compile a JSONata expression with caching\n */\nfunction compileExpression(expression: string): jsonata.Expression {\n let compiled = expressionCache.get(expression)\n\n if (!compiled) {\n compiled = jsonata(expression)\n\n // Register custom functions\n registerCustomFunctions(compiled)\n\n // Manage cache size\n if (expressionCache.size >= MAX_CACHE_SIZE) {\n const firstKey = expressionCache.keys().next().value\n if (firstKey) expressionCache.delete(firstKey)\n }\n\n expressionCache.set(expression, compiled)\n }\n\n return compiled\n}\n\n/**\n * Register custom functions on a JSONata expression\n */\nfunction registerCustomFunctions(expr: jsonata.Expression): void {\n // $env(name) - Get environment variable (only non-sensitive ones)\n expr.registerFunction('env', (name: string) => {\n // Only allow specific prefixes for security\n if (typeof name === 'string' && name.startsWith('PUBLIC_')) {\n return process.env[name]\n }\n return undefined\n }, '<s:s>')\n\n // $now() - Current ISO timestamp\n expr.registerFunction('now', () => new Date().toISOString(), '<:s>')\n\n // $timestamp() - Current Unix timestamp in milliseconds\n expr.registerFunction('timestamp', () => Date.now(), '<:n>')\n\n // $uuid() - Generate a UUID v4\n expr.registerFunction('uuid', () => {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0\n const v = c === 'x' ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n }, '<:s>')\n\n // $default(value, defaultValue) - Return default if value is null/undefined\n expr.registerFunction('default', (value: unknown, defaultValue: unknown) => {\n return value === null || value === undefined ? defaultValue : value\n }, '<xx:x>')\n\n // $json(value) - Parse JSON string\n expr.registerFunction('json', (value: string) => {\n try {\n return JSON.parse(value)\n } catch {\n return undefined\n }\n }, '<s:x>')\n\n // $stringify(value) - Convert to JSON string\n expr.registerFunction('stringify', (value: unknown) => {\n try {\n return JSON.stringify(value)\n } catch {\n return undefined\n }\n }, '<x:s>')\n\n // $keys(object) - Get object keys\n expr.registerFunction('keys', (obj: Record<string, unknown>) => {\n if (obj && typeof obj === 'object' && !Array.isArray(obj)) {\n return Object.keys(obj)\n }\n return []\n }, '<o:a>')\n\n // $values(object) - Get object values\n expr.registerFunction('values', (obj: Record<string, unknown>) => {\n if (obj && typeof obj === 'object' && !Array.isArray(obj)) {\n return Object.values(obj)\n }\n return []\n }, '<o:a>')\n\n // $has(object, key) - Check if object has key\n expr.registerFunction('has', (obj: Record<string, unknown>, key: string) => {\n if (obj && typeof obj === 'object') {\n return key in obj\n }\n return false\n }, '<os:b>')\n\n // $coalesce(values...) - Return first non-null value\n expr.registerFunction('coalesce', (...values: unknown[]) => {\n for (const v of values) {\n if (v !== null && v !== undefined) {\n return v\n }\n }\n return null\n }, '<x+:x>')\n}\n\n/**\n * Evaluate a JSONata expression against a context\n */\nexport async function evaluate(\n expression: string,\n context: ExpressionContext,\n options: EvaluateOptions = {}\n): Promise<unknown> {\n const { timeout = 5000 } = options\n\n const compiled = compileExpression(expression)\n\n // Create a promise that rejects on timeout\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => reject(new Error(`Expression evaluation timed out after ${timeout}ms`)), timeout)\n })\n\n // Race between evaluation and timeout\n return Promise.race([\n compiled.evaluate(context),\n timeoutPromise\n ])\n}\n\n/**\n * Evaluate a condition expression and return a boolean\n */\nexport async function evaluateCondition(\n expression: string,\n context: ExpressionContext,\n options: EvaluateOptions = {}\n): Promise<boolean> {\n try {\n const result = await evaluate(expression, context, options)\n\n // Convert result to boolean\n if (result === undefined || result === null) {\n return false\n }\n if (typeof result === 'boolean') {\n return result\n }\n if (typeof result === 'number') {\n return result !== 0\n }\n if (typeof result === 'string') {\n return result.length > 0\n }\n if (Array.isArray(result)) {\n return result.length > 0\n }\n return true\n } catch (error) {\n // Log error but return false for failed conditions\n console.warn('Condition evaluation failed:', error instanceof Error ? error.message : error)\n return false\n }\n}\n\n/**\n * Transform data using a JSONata expression\n * The expression can be a JSONata query or a JSON object with embedded expressions\n */\nexport async function transform(\n template: unknown,\n context: ExpressionContext,\n options: EvaluateOptions = {}\n): Promise<unknown> {\n if (typeof template === 'string') {\n // Check if it looks like a JSONata expression (starts with common patterns)\n if (\n template.startsWith('{{')\n ) {\n try {\n return await evaluate(template.trim().slice(2, -2), context, options)\n } catch (e) {\n if (options.debug && options.logger) {\n options.logger.info(\n e instanceof Error ? e.message : e,\n 'Failed to evaluate expression:',\n )\n }\n // If it fails to evaluate, return as literal string\n return template\n }\n }\n return template\n }\n\n if (Array.isArray(template)) {\n return Promise.all(template.map(item => transform(item, context, options)))\n }\n\n if (template && typeof template === 'object') {\n const result: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(template)) {\n result[key] = await transform(value, context, options)\n }\n return result\n }\n\n return template\n}\n\n/**\n * Resolve a step input configuration using JSONata\n * Handles both simple values and expressions\n */\nexport async function resolveStepInput(\n config: Record<string, unknown>,\n context: ExpressionContext,\n options: EvaluateOptions = {}\n): Promise<Record<string, unknown>> {\n const result: Record<string, unknown> = {}\n\n for (const [key, value] of Object.entries(config)) {\n result[key] = await transform(value, context, options)\n }\n\n return result\n}\n\n/**\n * Clear the expression cache\n */\nexport function clearCache(): void {\n expressionCache.clear()\n}\n\n/**\n * Get cache statistics\n */\nexport function getCacheStats(): { size: number; maxSize: number } {\n return {\n size: expressionCache.size,\n maxSize: MAX_CACHE_SIZE\n }\n}\n"],"names":["jsonata","expressionCache","Map","MAX_CACHE_SIZE","compileExpression","expression","compiled","get","registerCustomFunctions","size","firstKey","keys","next","value","delete","set","expr","registerFunction","name","startsWith","process","env","undefined","Date","toISOString","now","replace","c","r","Math","random","v","toString","defaultValue","JSON","parse","stringify","obj","Array","isArray","Object","values","key","evaluate","context","options","timeout","timeoutPromise","Promise","_","reject","setTimeout","Error","race","evaluateCondition","result","length","error","console","warn","message","transform","template","trim","slice","e","debug","logger","info","all","map","item","entries","resolveStepInput","config","clearCache","clear","getCacheStats","maxSize"],"mappings":"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"}
|
package/dist/steps/index.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
export { createStep } from './create-step.js';
|
|
2
|
-
export type { StepDefinition, StepTask } from './create-step.js';
|
|
3
1
|
export { CreateDocumentStepTask } from './create-document.js';
|
|
4
2
|
export { createDocumentHandler } from './create-document-handler.js';
|
|
5
3
|
export { DeleteDocumentStepTask } from './delete-document.js';
|
package/dist/steps/index.js
CHANGED
package/dist/steps/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/steps/index.ts"],"sourcesContent":["//
|
|
1
|
+
{"version":3,"sources":["../../src/steps/index.ts"],"sourcesContent":["// Built-in steps\nexport { CreateDocumentStepTask } from './create-document.js'\nexport { createDocumentHandler } from './create-document-handler.js'\nexport { DeleteDocumentStepTask } from './delete-document.js'\nexport { deleteDocumentHandler } from './delete-document-handler.js'\nexport { HttpRequestStepTask } from './http-request.js'\nexport { httpStepHandler } from './http-request-handler.js'\n\nexport { ReadDocumentStepTask } from './read-document.js'\nexport { readDocumentHandler } from './read-document-handler.js'\nexport { SendEmailStepTask } from './send-email.js'\nexport { sendEmailHandler } from './send-email-handler.js'\nexport { UpdateDocumentStepTask } from './update-document.js'\nexport { updateDocumentHandler } from './update-document-handler.js'\n"],"names":["CreateDocumentStepTask","createDocumentHandler","DeleteDocumentStepTask","deleteDocumentHandler","HttpRequestStepTask","httpStepHandler","ReadDocumentStepTask","readDocumentHandler","SendEmailStepTask","sendEmailHandler","UpdateDocumentStepTask","updateDocumentHandler"],"mappings":"AAAA,iBAAiB;AACjB,SAASA,sBAAsB,QAAQ,uBAAsB;AAC7D,SAASC,qBAAqB,QAAQ,+BAA8B;AACpE,SAASC,sBAAsB,QAAQ,uBAAsB;AAC7D,SAASC,qBAAqB,QAAQ,+BAA8B;AACpE,SAASC,mBAAmB,QAAQ,oBAAmB;AACvD,SAASC,eAAe,QAAQ,4BAA2B;AAE3D,SAASC,oBAAoB,QAAQ,qBAAoB;AACzD,SAASC,mBAAmB,QAAQ,6BAA4B;AAChE,SAASC,iBAAiB,QAAQ,kBAAiB;AACnD,SAASC,gBAAgB,QAAQ,0BAAyB;AAC1D,SAASC,sBAAsB,QAAQ,uBAAsB;AAC7D,SAASC,qBAAqB,QAAQ,+BAA8B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xtr-dev/payload-automation",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.53",
|
|
4
4
|
"description": "PayloadCMS Automation Plugin - Comprehensive workflow automation system with visual workflow building, execution tracking, and step types",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import type { Field, JsonObject, PayloadRequest, TaskConfig } from 'payload';
|
|
2
|
-
/**
|
|
3
|
-
* Configuration for creating a step with the factory.
|
|
4
|
-
*/
|
|
5
|
-
export interface StepDefinition<TSlug extends string> {
|
|
6
|
-
/** Unique identifier for the step */
|
|
7
|
-
slug: TSlug;
|
|
8
|
-
/** Human-readable label for the step (optional, defaults to slug) */
|
|
9
|
-
label?: string;
|
|
10
|
-
/** Input fields schema */
|
|
11
|
-
inputSchema: Field[];
|
|
12
|
-
/** Output fields schema */
|
|
13
|
-
outputSchema: Field[];
|
|
14
|
-
/**
|
|
15
|
-
* Optional validation function. Throw an error if validation fails.
|
|
16
|
-
* Runs before the execute function.
|
|
17
|
-
*/
|
|
18
|
-
validate?: (input: JsonObject) => void;
|
|
19
|
-
/**
|
|
20
|
-
* The main execution function for the step.
|
|
21
|
-
* Should return the output data on success, or throw an error on failure.
|
|
22
|
-
*/
|
|
23
|
-
execute: (input: JsonObject, req: PayloadRequest) => Promise<JsonObject>;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* The result type returned by createStep.
|
|
27
|
-
* Combines TaskConfig with the handler for convenience.
|
|
28
|
-
*/
|
|
29
|
-
export type StepTask<TSlug extends string> = TaskConfig<TSlug>;
|
|
30
|
-
/**
|
|
31
|
-
* Creates a step definition that combines TaskConfig and handler in one place.
|
|
32
|
-
*
|
|
33
|
-
* This factory eliminates the need for separate `{step}.ts` and `{step}-handler.ts` files
|
|
34
|
-
* by providing a single unified definition.
|
|
35
|
-
*
|
|
36
|
-
* @example
|
|
37
|
-
* ```typescript
|
|
38
|
-
* export const myStep = createStep({
|
|
39
|
-
* slug: 'my-step',
|
|
40
|
-
* inputSchema: [
|
|
41
|
-
* { name: 'url', type: 'text', required: true }
|
|
42
|
-
* ],
|
|
43
|
-
* outputSchema: [
|
|
44
|
-
* { name: 'result', type: 'json' }
|
|
45
|
-
* ],
|
|
46
|
-
* validate: (input) => {
|
|
47
|
-
* if (!input.url) throw new Error('URL is required')
|
|
48
|
-
* },
|
|
49
|
-
* execute: async (input, req) => {
|
|
50
|
-
* const response = await fetch(input.url)
|
|
51
|
-
* return { result: await response.json() }
|
|
52
|
-
* }
|
|
53
|
-
* })
|
|
54
|
-
* ```
|
|
55
|
-
*/
|
|
56
|
-
export declare function createStep<TSlug extends string>(definition: StepDefinition<TSlug>): StepTask<TSlug>;
|
|
57
|
-
/**
|
|
58
|
-
* Helper type to extract input type from a step's inputSchema.
|
|
59
|
-
* Note: This provides a basic type based on field names, not full type inference.
|
|
60
|
-
*/
|
|
61
|
-
export type StepInput<T extends StepTask<string>> = T extends StepTask<infer _> ? Record<string, unknown> : never;
|
|
62
|
-
/**
|
|
63
|
-
* Helper type to extract output type from a step's outputSchema.
|
|
64
|
-
* Note: This provides a basic type based on field names, not full type inference.
|
|
65
|
-
*/
|
|
66
|
-
export type StepOutput<T extends StepTask<string>> = T extends StepTask<infer _> ? Record<string, unknown> : never;
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Creates a step definition that combines TaskConfig and handler in one place.
|
|
3
|
-
*
|
|
4
|
-
* This factory eliminates the need for separate `{step}.ts` and `{step}-handler.ts` files
|
|
5
|
-
* by providing a single unified definition.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```typescript
|
|
9
|
-
* export const myStep = createStep({
|
|
10
|
-
* slug: 'my-step',
|
|
11
|
-
* inputSchema: [
|
|
12
|
-
* { name: 'url', type: 'text', required: true }
|
|
13
|
-
* ],
|
|
14
|
-
* outputSchema: [
|
|
15
|
-
* { name: 'result', type: 'json' }
|
|
16
|
-
* ],
|
|
17
|
-
* validate: (input) => {
|
|
18
|
-
* if (!input.url) throw new Error('URL is required')
|
|
19
|
-
* },
|
|
20
|
-
* execute: async (input, req) => {
|
|
21
|
-
* const response = await fetch(input.url)
|
|
22
|
-
* return { result: await response.json() }
|
|
23
|
-
* }
|
|
24
|
-
* })
|
|
25
|
-
* ```
|
|
26
|
-
*/ export function createStep(definition) {
|
|
27
|
-
const { slug, label, inputSchema, outputSchema, validate, execute } = definition;
|
|
28
|
-
// Create the handler that wraps validation and execution
|
|
29
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
30
|
-
const handler = async ({ input, req })=>{
|
|
31
|
-
try {
|
|
32
|
-
const jsonInput = input ?? {};
|
|
33
|
-
// Run validation if provided
|
|
34
|
-
if (validate) {
|
|
35
|
-
validate(jsonInput);
|
|
36
|
-
}
|
|
37
|
-
// Execute the step
|
|
38
|
-
const output = await execute(jsonInput, req);
|
|
39
|
-
return {
|
|
40
|
-
output,
|
|
41
|
-
state: 'succeeded'
|
|
42
|
-
};
|
|
43
|
-
} catch (error) {
|
|
44
|
-
return {
|
|
45
|
-
errorMessage: error instanceof Error ? error.message : 'Unknown error',
|
|
46
|
-
state: 'failed'
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
return {
|
|
51
|
-
slug,
|
|
52
|
-
label,
|
|
53
|
-
handler,
|
|
54
|
-
inputSchema,
|
|
55
|
-
outputSchema
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
//# sourceMappingURL=create-step.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/steps/create-step.ts"],"sourcesContent":["import type { Field, JsonObject, PayloadRequest, TaskConfig, TaskHandler } from 'payload'\n\n/**\n * Configuration for creating a step with the factory.\n */\nexport interface StepDefinition<TSlug extends string> {\n /** Unique identifier for the step */\n slug: TSlug\n /** Human-readable label for the step (optional, defaults to slug) */\n label?: string\n /** Input fields schema */\n inputSchema: Field[]\n /** Output fields schema */\n outputSchema: Field[]\n /**\n * Optional validation function. Throw an error if validation fails.\n * Runs before the execute function.\n */\n validate?: (input: JsonObject) => void\n /**\n * The main execution function for the step.\n * Should return the output data on success, or throw an error on failure.\n */\n execute: (input: JsonObject, req: PayloadRequest) => Promise<JsonObject>\n}\n\n/**\n * The result type returned by createStep.\n * Combines TaskConfig with the handler for convenience.\n */\nexport type StepTask<TSlug extends string> = TaskConfig<TSlug>\n\n/**\n * Creates a step definition that combines TaskConfig and handler in one place.\n *\n * This factory eliminates the need for separate `{step}.ts` and `{step}-handler.ts` files\n * by providing a single unified definition.\n *\n * @example\n * ```typescript\n * export const myStep = createStep({\n * slug: 'my-step',\n * inputSchema: [\n * { name: 'url', type: 'text', required: true }\n * ],\n * outputSchema: [\n * { name: 'result', type: 'json' }\n * ],\n * validate: (input) => {\n * if (!input.url) throw new Error('URL is required')\n * },\n * execute: async (input, req) => {\n * const response = await fetch(input.url)\n * return { result: await response.json() }\n * }\n * })\n * ```\n */\nexport function createStep<TSlug extends string>(\n definition: StepDefinition<TSlug>\n): StepTask<TSlug> {\n const { slug, label, inputSchema, outputSchema, validate, execute } = definition\n\n // Create the handler that wraps validation and execution\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const handler = async ({ input, req }: any) => {\n try {\n const jsonInput = (input ?? {}) as JsonObject\n\n // Run validation if provided\n if (validate) {\n validate(jsonInput)\n }\n\n // Execute the step\n const output = await execute(jsonInput, req)\n return {\n output,\n state: 'succeeded'\n }\n } catch (error) {\n return {\n errorMessage: error instanceof Error ? error.message : 'Unknown error',\n state: 'failed'\n }\n }\n }\n\n return {\n slug,\n label,\n handler,\n inputSchema,\n outputSchema\n } as StepTask<TSlug>\n}\n\n/**\n * Helper type to extract input type from a step's inputSchema.\n * Note: This provides a basic type based on field names, not full type inference.\n */\nexport type StepInput<T extends StepTask<string>> = T extends StepTask<infer _>\n ? Record<string, unknown>\n : never\n\n/**\n * Helper type to extract output type from a step's outputSchema.\n * Note: This provides a basic type based on field names, not full type inference.\n */\nexport type StepOutput<T extends StepTask<string>> = T extends StepTask<infer _>\n ? Record<string, unknown>\n : never\n"],"names":["createStep","definition","slug","label","inputSchema","outputSchema","validate","execute","handler","input","req","jsonInput","output","state","error","errorMessage","Error","message"],"mappings":"AAgCA;;;;;;;;;;;;;;;;;;;;;;;;;CAyBC,GACD,OAAO,SAASA,WACdC,UAAiC;IAEjC,MAAM,EAAEC,IAAI,EAAEC,KAAK,EAAEC,WAAW,EAAEC,YAAY,EAAEC,QAAQ,EAAEC,OAAO,EAAE,GAAGN;IAEtE,yDAAyD;IACzD,8DAA8D;IAC9D,MAAMO,UAAU,OAAO,EAAEC,KAAK,EAAEC,GAAG,EAAO;QACxC,IAAI;YACF,MAAMC,YAAaF,SAAS,CAAC;YAE7B,6BAA6B;YAC7B,IAAIH,UAAU;gBACZA,SAASK;YACX;YAEA,mBAAmB;YACnB,MAAMC,SAAS,MAAML,QAAQI,WAAWD;YACxC,OAAO;gBACLE;gBACAC,OAAO;YACT;QACF,EAAE,OAAOC,OAAO;YACd,OAAO;gBACLC,cAAcD,iBAAiBE,QAAQF,MAAMG,OAAO,GAAG;gBACvDJ,OAAO;YACT;QACF;IACF;IAEA,OAAO;QACLX;QACAC;QACAK;QACAJ;QACAC;IACF;AACF"}
|