@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 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
- - **Visual Workflow Builder** - Drag-and-drop workflow editor in PayloadCMS admin
12
- - **Workflow Visualizer** - React component for displaying workflows with real-time execution status
13
- - **Collection Triggers** - Run workflows when documents are created, updated, or deleted
14
- - **Webhook Triggers** - Trigger workflows via HTTP endpoints
15
- - **Step Dependencies** - Define execution order with parallel and sequential steps
16
- - **Execution Tracking** - Full history with step-by-step results, duration, and logs
17
- - **Built-in Steps** - HTTP requests, document CRUD, email sending
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
- orders: true, // Enable all hooks for orders
41
- users: {
42
- afterChange: true, // Only specific hooks
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
- // Register custom steps
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
- // Main plugin
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: '$.trigger.doc.email',
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
- Create reusable step types with the `createStep` factory:
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 { createStep } from '@xtr-dev/payload-automation/steps'
292
+ import type { TaskConfig } from 'payload'
163
293
 
164
- export const SlackNotificationStep = createStep({
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
- visual: {
179
- icon: '💬',
180
- color: '#4A154B',
181
- },
182
-
183
- validate: (input) => {
184
- if (!input.channel?.startsWith('#')) {
185
- throw new Error('Channel must start with #')
186
- }
187
- },
188
-
189
- execute: async (input, req) => {
190
- const response = await fetch('https://slack.com/api/chat.postMessage', {
191
- method: 'POST',
192
- headers: {
193
- 'Authorization': `Bearer ${process.env.SLACK_TOKEN}`,
194
- 'Content-Type': 'application/json',
195
- },
196
- body: JSON.stringify({
197
- channel: input.channel,
198
- text: input.message,
199
- }),
200
- })
201
-
202
- const data = await response.json()
203
- return {
204
- messageId: data.message.ts,
205
- timestamp: new Date().toISOString(),
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
- ```typescript
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('{') || template.startsWith('[') || template.startsWith('$') || template.includes('.') || template.includes('(')) {
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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"}
@@ -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';
@@ -1,5 +1,3 @@
1
- // Step factory for creating new steps
2
- export { createStep } from './create-step.js';
3
1
  // Built-in steps
4
2
  export { CreateDocumentStepTask } from './create-document.js';
5
3
  export { createDocumentHandler } from './create-document-handler.js';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/steps/index.ts"],"sourcesContent":["// Step factory for creating new steps\nexport { createStep } from './create-step.js'\nexport type { StepDefinition, StepTask } from './create-step.js'\n\n// 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":["createStep","CreateDocumentStepTask","createDocumentHandler","DeleteDocumentStepTask","deleteDocumentHandler","HttpRequestStepTask","httpStepHandler","ReadDocumentStepTask","readDocumentHandler","SendEmailStepTask","sendEmailHandler","UpdateDocumentStepTask","updateDocumentHandler"],"mappings":"AAAA,sCAAsC;AACtC,SAASA,UAAU,QAAQ,mBAAkB;AAG7C,iBAAiB;AACjB,SAASC,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"}
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.51",
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"}