@synergenius/flow-weaver 0.14.2 → 0.15.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.
@@ -1,1128 +0,0 @@
1
- /**
2
- * Cloudflare Workers export target
3
- *
4
- * Generates Cloudflare Workers with Wrangler configuration.
5
- */
6
- import { BaseExportTarget, } from './base.js';
7
- import { getGeneratedBranding } from '../../generated-branding.js';
8
- import { generateStandaloneRuntimeModule } from '../../api/inline-runtime.js';
9
- /**
10
- * Handler template for Cloudflare Workers - Basic version
11
- */
12
- const CLOUDFLARE_HANDLER_TEMPLATE = `{{GENERATED_HEADER}}
13
- {{WORKFLOW_IMPORT}}
14
-
15
- interface Env {
16
- // Add your bindings here (D1, KV, R2, etc.)
17
- }
18
-
19
- export default {
20
- async fetch(request: Request, env: Env): Promise<Response> {
21
- // Only allow POST requests
22
- if (request.method !== 'POST') {
23
- return new Response(JSON.stringify({
24
- success: false,
25
- error: { code: 'METHOD_NOT_ALLOWED', message: 'Only POST requests are allowed' },
26
- }), {
27
- status: 405,
28
- headers: { 'Content-Type': 'application/json' },
29
- });
30
- }
31
-
32
- const requestId = request.headers.get('cf-ray') || crypto.randomUUID();
33
-
34
- try {
35
- const body = await request.json() as Record<string, unknown>;
36
- const startTime = performance.now();
37
-
38
- const result = await {{FUNCTION_NAME}}(true, body);
39
-
40
- return new Response(JSON.stringify({
41
- success: true,
42
- result,
43
- executionTime: Math.round(performance.now() - startTime),
44
- requestId,
45
- }), {
46
- status: 200,
47
- headers: {
48
- 'Content-Type': 'application/json',
49
- 'X-Request-Id': requestId,
50
- 'X-Execution-Time': \`\${Math.round(performance.now() - startTime)}ms\`,
51
- },
52
- });
53
- } catch (error) {
54
- return new Response(JSON.stringify({
55
- success: false,
56
- error: {
57
- code: 'EXECUTION_ERROR',
58
- message: error instanceof Error ? error.message : String(error),
59
- },
60
- requestId,
61
- }), {
62
- status: 500,
63
- headers: {
64
- 'Content-Type': 'application/json',
65
- 'X-Request-Id': requestId,
66
- },
67
- });
68
- }
69
- },
70
- } satisfies ExportedHandler<Env>;
71
- `;
72
- /**
73
- * Handler template for Cloudflare Workers with API documentation
74
- */
75
- const CLOUDFLARE_HANDLER_WITH_DOCS_TEMPLATE = `{{GENERATED_HEADER}}
76
- {{WORKFLOW_IMPORT}}
77
- import { openApiSpec } from './openapi.js';
78
-
79
- interface Env {
80
- // Add your bindings here (D1, KV, R2, etc.)
81
- }
82
-
83
- const SWAGGER_UI_HTML = \`<!DOCTYPE html>
84
- <html lang="en">
85
- <head>
86
- <meta charset="UTF-8">
87
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
88
- <title>{{WORKFLOW_NAME}} API Documentation</title>
89
- <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
90
- </head>
91
- <body>
92
- <div id="swagger-ui"></div>
93
- <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
94
- <script>
95
- SwaggerUIBundle({
96
- url: '/openapi.json',
97
- dom_id: '#swagger-ui',
98
- presets: [SwaggerUIBundle.presets.apis, SwaggerUIBundle.SwaggerUIStandalonePreset],
99
- layout: 'BaseLayout'
100
- });
101
- </script>
102
- </body>
103
- </html>\`;
104
-
105
- export default {
106
- async fetch(request: Request, env: Env): Promise<Response> {
107
- const url = new URL(request.url);
108
- const path = url.pathname;
109
- const method = request.method;
110
-
111
- // Serve OpenAPI spec
112
- if (path === '/openapi.json' && method === 'GET') {
113
- return new Response(JSON.stringify(openApiSpec), {
114
- status: 200,
115
- headers: { 'Content-Type': 'application/json' },
116
- });
117
- }
118
-
119
- // Serve Swagger UI
120
- if (path === '/docs' && method === 'GET') {
121
- return new Response(SWAGGER_UI_HTML, {
122
- status: 200,
123
- headers: { 'Content-Type': 'text/html' },
124
- });
125
- }
126
-
127
- // Only allow POST requests for workflow execution
128
- if (method !== 'POST') {
129
- return new Response(JSON.stringify({
130
- success: false,
131
- error: { code: 'METHOD_NOT_ALLOWED', message: 'Only POST requests are allowed for workflow execution' },
132
- }), {
133
- status: 405,
134
- headers: { 'Content-Type': 'application/json' },
135
- });
136
- }
137
-
138
- const requestId = request.headers.get('cf-ray') || crypto.randomUUID();
139
-
140
- try {
141
- const body = await request.json() as Record<string, unknown>;
142
- const startTime = performance.now();
143
-
144
- const result = await {{FUNCTION_NAME}}(true, body);
145
-
146
- return new Response(JSON.stringify({
147
- success: true,
148
- result,
149
- executionTime: Math.round(performance.now() - startTime),
150
- requestId,
151
- }), {
152
- status: 200,
153
- headers: {
154
- 'Content-Type': 'application/json',
155
- 'X-Request-Id': requestId,
156
- 'X-Execution-Time': \`\${Math.round(performance.now() - startTime)}ms\`,
157
- },
158
- });
159
- } catch (error) {
160
- return new Response(JSON.stringify({
161
- success: false,
162
- error: {
163
- code: 'EXECUTION_ERROR',
164
- message: error instanceof Error ? error.message : String(error),
165
- },
166
- requestId,
167
- }), {
168
- status: 500,
169
- headers: {
170
- 'Content-Type': 'application/json',
171
- 'X-Request-Id': requestId,
172
- },
173
- });
174
- }
175
- },
176
- } satisfies ExportedHandler<Env>;
177
- `;
178
- /**
179
- * OpenAPI spec file template
180
- */
181
- const OPENAPI_SPEC_TEMPLATE = `// Generated OpenAPI specification
182
- export const openApiSpec = {{OPENAPI_SPEC}};
183
- `;
184
- /**
185
- * Wrangler configuration template
186
- */
187
- const WRANGLER_TEMPLATE = `name = "{{WORKFLOW_NAME}}"
188
- main = "dist/index.js"
189
- compatibility_date = "2024-01-01"
190
-
191
- [build]
192
- command = "npm run build"
193
- `;
194
- /**
195
- * Node type handler template for Cloudflare Workers
196
- */
197
- const CLOUDFLARE_NODE_TYPE_HANDLER_TEMPLATE = `{{GENERATED_HEADER}}
198
- {{NODE_TYPE_IMPORTS}}
199
- import { openApiSpec } from './openapi.js';
200
-
201
- interface Env {
202
- // Add your bindings here (D1, KV, R2, etc.)
203
- }
204
-
205
- // Handler type for node type functions
206
- type NodeTypeHandler = (execute: boolean, params: Record<string, unknown>) => unknown;
207
-
208
- // Node type router
209
- const nodeTypes: Record<string, NodeTypeHandler> = {
210
- {{NODE_TYPE_ENTRIES}}
211
- };
212
-
213
- const SWAGGER_UI_HTML = \`<!DOCTYPE html>
214
- <html lang="en">
215
- <head>
216
- <meta charset="UTF-8">
217
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
218
- <title>{{SERVICE_NAME}} API Documentation</title>
219
- <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
220
- </head>
221
- <body>
222
- <div id="swagger-ui"></div>
223
- <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
224
- <script>
225
- SwaggerUIBundle({
226
- url: '/api/openapi.json',
227
- dom_id: '#swagger-ui',
228
- presets: [SwaggerUIBundle.presets.apis, SwaggerUIBundle.SwaggerUIStandalonePreset],
229
- layout: 'BaseLayout'
230
- });
231
- </script>
232
- </body>
233
- </html>\`;
234
-
235
- export default {
236
- async fetch(request: Request, env: Env): Promise<Response> {
237
- const url = new URL(request.url);
238
- const path = url.pathname;
239
- const method = request.method;
240
-
241
- // Serve OpenAPI spec
242
- if (path === '/api/openapi.json' && method === 'GET') {
243
- return new Response(JSON.stringify(openApiSpec), {
244
- status: 200,
245
- headers: { 'Content-Type': 'application/json' },
246
- });
247
- }
248
-
249
- // Serve Swagger UI
250
- if (path === '/api/docs' && method === 'GET') {
251
- return new Response(SWAGGER_UI_HTML, {
252
- status: 200,
253
- headers: { 'Content-Type': 'text/html' },
254
- });
255
- }
256
-
257
- // Route to node type
258
- const nodeTypeMatch = path.match(/^\\/api\\/([^\\/]+)$/);
259
- if (!nodeTypeMatch) {
260
- return new Response(JSON.stringify({ error: 'Not found' }), {
261
- status: 404,
262
- headers: { 'Content-Type': 'application/json' },
263
- });
264
- }
265
-
266
- const nodeTypeName = nodeTypeMatch[1];
267
- const nodeType = nodeTypes[nodeTypeName];
268
-
269
- if (!nodeType) {
270
- return new Response(JSON.stringify({
271
- error: \`Node type '\${nodeTypeName}' not found\`,
272
- availableNodeTypes: Object.keys(nodeTypes),
273
- }), {
274
- status: 404,
275
- headers: { 'Content-Type': 'application/json' },
276
- });
277
- }
278
-
279
- // Only POST for node type execution
280
- if (method !== 'POST') {
281
- return new Response(JSON.stringify({
282
- success: false,
283
- error: { code: 'METHOD_NOT_ALLOWED', message: 'Only POST requests are allowed for node type execution' },
284
- }), {
285
- status: 405,
286
- headers: { 'Content-Type': 'application/json' },
287
- });
288
- }
289
-
290
- const requestId = request.headers.get('cf-ray') || crypto.randomUUID();
291
-
292
- try {
293
- const body = await request.json() as Record<string, unknown>;
294
- const startTime = performance.now();
295
-
296
- const result = await nodeType(true, body);
297
-
298
- return new Response(JSON.stringify({
299
- success: true,
300
- result,
301
- executionTime: Math.round(performance.now() - startTime),
302
- requestId,
303
- }), {
304
- status: 200,
305
- headers: {
306
- 'Content-Type': 'application/json',
307
- 'X-Request-Id': requestId,
308
- 'X-Execution-Time': \`\${Math.round(performance.now() - startTime)}ms\`,
309
- },
310
- });
311
- } catch (error) {
312
- return new Response(JSON.stringify({
313
- success: false,
314
- error: {
315
- code: 'EXECUTION_ERROR',
316
- message: error instanceof Error ? error.message : String(error),
317
- },
318
- requestId,
319
- }), {
320
- status: 500,
321
- headers: {
322
- 'Content-Type': 'application/json',
323
- 'X-Request-Id': requestId,
324
- },
325
- });
326
- }
327
- },
328
- } satisfies ExportedHandler<Env>;
329
- `;
330
- /**
331
- * Multi-workflow handler template for Cloudflare Workers
332
- */
333
- const CLOUDFLARE_MULTI_HANDLER_TEMPLATE = `{{GENERATED_HEADER}}
334
- {{WORKFLOW_IMPORTS}}
335
- import { functionRegistry } from './runtime/function-registry.js';
336
- import './runtime/builtin-functions.js';
337
- import { openApiSpec } from './openapi.js';
338
-
339
- interface Env {
340
- // Add your bindings here (D1, KV, R2, etc.)
341
- }
342
-
343
- // Handler type for workflow functions
344
- type WorkflowHandler = (execute: boolean, params: Record<string, unknown>) => unknown;
345
-
346
- // Workflow router
347
- const workflows: Record<string, WorkflowHandler> = {
348
- {{WORKFLOW_ENTRIES}}
349
- };
350
-
351
- const SWAGGER_UI_HTML = \`<!DOCTYPE html>
352
- <html lang="en">
353
- <head>
354
- <meta charset="UTF-8">
355
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
356
- <title>{{SERVICE_NAME}} API Documentation</title>
357
- <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
358
- </head>
359
- <body>
360
- <div id="swagger-ui"></div>
361
- <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
362
- <script>
363
- SwaggerUIBundle({
364
- url: '/api/openapi.json',
365
- dom_id: '#swagger-ui',
366
- presets: [SwaggerUIBundle.presets.apis, SwaggerUIBundle.SwaggerUIStandalonePreset],
367
- layout: 'BaseLayout'
368
- });
369
- </script>
370
- </body>
371
- </html>\`;
372
-
373
- export default {
374
- async fetch(request: Request, env: Env): Promise<Response> {
375
- const url = new URL(request.url);
376
- const path = url.pathname;
377
- const method = request.method;
378
-
379
- // Serve OpenAPI spec
380
- if (path === '/api/openapi.json' && method === 'GET') {
381
- return new Response(JSON.stringify(openApiSpec), {
382
- status: 200,
383
- headers: { 'Content-Type': 'application/json' },
384
- });
385
- }
386
-
387
- // Serve Swagger UI
388
- if (path === '/api/docs' && method === 'GET') {
389
- return new Response(SWAGGER_UI_HTML, {
390
- status: 200,
391
- headers: { 'Content-Type': 'text/html' },
392
- });
393
- }
394
-
395
- // List available functions
396
- if (path === '/api/functions' && method === 'GET') {
397
- const category = url.searchParams.get('category');
398
- const functions = functionRegistry.list(category as any);
399
- return new Response(JSON.stringify(functions), {
400
- status: 200,
401
- headers: { 'Content-Type': 'application/json' },
402
- });
403
- }
404
-
405
- // Route to workflow
406
- const workflowMatch = path.match(/^\\/api\\/([^\\/]+)$/);
407
- if (!workflowMatch) {
408
- return new Response(JSON.stringify({ error: 'Not found' }), {
409
- status: 404,
410
- headers: { 'Content-Type': 'application/json' },
411
- });
412
- }
413
-
414
- const workflowName = workflowMatch[1];
415
- const workflow = workflows[workflowName];
416
-
417
- if (!workflow) {
418
- return new Response(JSON.stringify({
419
- error: \`Workflow '\${workflowName}' not found\`,
420
- availableWorkflows: Object.keys(workflows),
421
- }), {
422
- status: 404,
423
- headers: { 'Content-Type': 'application/json' },
424
- });
425
- }
426
-
427
- // Only POST for workflow execution
428
- if (method !== 'POST') {
429
- return new Response(JSON.stringify({
430
- success: false,
431
- error: { code: 'METHOD_NOT_ALLOWED', message: 'Only POST requests are allowed for workflow execution' },
432
- }), {
433
- status: 405,
434
- headers: { 'Content-Type': 'application/json' },
435
- });
436
- }
437
-
438
- const requestId = request.headers.get('cf-ray') || crypto.randomUUID();
439
-
440
- try {
441
- const body = await request.json() as Record<string, unknown>;
442
- const startTime = performance.now();
443
-
444
- const result = await workflow(true, body);
445
-
446
- return new Response(JSON.stringify({
447
- success: true,
448
- result,
449
- executionTime: Math.round(performance.now() - startTime),
450
- requestId,
451
- }), {
452
- status: 200,
453
- headers: {
454
- 'Content-Type': 'application/json',
455
- 'X-Request-Id': requestId,
456
- 'X-Execution-Time': \`\${Math.round(performance.now() - startTime)}ms\`,
457
- },
458
- });
459
- } catch (error) {
460
- return new Response(JSON.stringify({
461
- success: false,
462
- error: {
463
- code: 'EXECUTION_ERROR',
464
- message: error instanceof Error ? error.message : String(error),
465
- },
466
- requestId,
467
- }), {
468
- status: 500,
469
- headers: {
470
- 'Content-Type': 'application/json',
471
- 'X-Request-Id': requestId,
472
- },
473
- });
474
- }
475
- },
476
- } satisfies ExportedHandler<Env>;
477
- `;
478
- /**
479
- * Bundle handler template for Cloudflare Workers - unified workflows and node types
480
- */
481
- const CLOUDFLARE_BUNDLE_HANDLER_TEMPLATE = `{{GENERATED_HEADER}}
482
- {{WORKFLOW_IMPORTS}}
483
- {{NODE_TYPE_IMPORTS}}
484
- import { functionRegistry } from './runtime/function-registry.js';
485
- import './runtime/builtin-functions.js';
486
- import { openApiSpec } from './openapi.js';
487
-
488
- interface Env {
489
- // Add your bindings here (D1, KV, R2, etc.)
490
- }
491
-
492
- // Handler type for workflow/nodeType functions
493
- type FunctionHandler = (execute: boolean, params: Record<string, unknown>) => unknown;
494
-
495
- // Exposed workflows (have HTTP endpoints)
496
- const exposedWorkflows: Record<string, FunctionHandler> = {
497
- {{EXPOSED_WORKFLOW_ENTRIES}}
498
- };
499
-
500
- // Exposed node types (have HTTP endpoints)
501
- const exposedNodeTypes: Record<string, FunctionHandler> = {
502
- {{EXPOSED_NODE_TYPE_ENTRIES}}
503
- };
504
-
505
- const SWAGGER_UI_HTML = \`<!DOCTYPE html>
506
- <html lang="en">
507
- <head>
508
- <meta charset="UTF-8">
509
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
510
- <title>{{SERVICE_NAME}} API Documentation</title>
511
- <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
512
- </head>
513
- <body>
514
- <div id="swagger-ui"></div>
515
- <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
516
- <script>
517
- SwaggerUIBundle({
518
- url: '/api/openapi.json',
519
- dom_id: '#swagger-ui',
520
- presets: [SwaggerUIBundle.presets.apis, SwaggerUIBundle.SwaggerUIStandalonePreset],
521
- layout: 'BaseLayout'
522
- });
523
- </script>
524
- </body>
525
- </html>\`;
526
-
527
- export default {
528
- async fetch(request: Request, env: Env): Promise<Response> {
529
- const url = new URL(request.url);
530
- const path = url.pathname;
531
- const method = request.method;
532
-
533
- // Serve OpenAPI spec
534
- if (path === '/api/openapi.json' && method === 'GET') {
535
- return new Response(JSON.stringify(openApiSpec), {
536
- status: 200,
537
- headers: { 'Content-Type': 'application/json' },
538
- });
539
- }
540
-
541
- // Serve Swagger UI
542
- if (path === '/api/docs' && method === 'GET') {
543
- return new Response(SWAGGER_UI_HTML, {
544
- status: 200,
545
- headers: { 'Content-Type': 'text/html' },
546
- });
547
- }
548
-
549
- // List available functions
550
- if (path === '/api/functions' && method === 'GET') {
551
- const category = url.searchParams.get('category');
552
- const functions = functionRegistry.list(category as any);
553
- return new Response(JSON.stringify(functions), {
554
- status: 200,
555
- headers: { 'Content-Type': 'application/json' },
556
- });
557
- }
558
-
559
- // Route to workflow: /api/workflows/{name}
560
- const workflowMatch = path.match(/^\\/api\\/workflows\\/([^\\/]+)$/);
561
- if (workflowMatch) {
562
- const workflowName = workflowMatch[1];
563
- const workflow = exposedWorkflows[workflowName];
564
-
565
- if (!workflow) {
566
- return new Response(JSON.stringify({
567
- error: \`Workflow '\${workflowName}' not found\`,
568
- availableWorkflows: Object.keys(exposedWorkflows),
569
- }), {
570
- status: 404,
571
- headers: { 'Content-Type': 'application/json' },
572
- });
573
- }
574
-
575
- if (method !== 'POST') {
576
- return new Response(JSON.stringify({
577
- success: false,
578
- error: { code: 'METHOD_NOT_ALLOWED', message: 'Only POST requests are allowed for workflow execution' },
579
- }), {
580
- status: 405,
581
- headers: { 'Content-Type': 'application/json' },
582
- });
583
- }
584
-
585
- const requestId = request.headers.get('cf-ray') || crypto.randomUUID();
586
-
587
- try {
588
- const body = await request.json() as Record<string, unknown>;
589
- const startTime = performance.now();
590
-
591
- const result = await workflow(true, body);
592
-
593
- return new Response(JSON.stringify({
594
- success: true,
595
- result,
596
- executionTime: Math.round(performance.now() - startTime),
597
- requestId,
598
- }), {
599
- status: 200,
600
- headers: {
601
- 'Content-Type': 'application/json',
602
- 'X-Request-Id': requestId,
603
- 'X-Execution-Time': \`\${Math.round(performance.now() - startTime)}ms\`,
604
- },
605
- });
606
- } catch (error) {
607
- return new Response(JSON.stringify({
608
- success: false,
609
- error: { code: 'EXECUTION_ERROR', message: error instanceof Error ? error.message : String(error) },
610
- requestId,
611
- }), {
612
- status: 500,
613
- headers: { 'Content-Type': 'application/json', 'X-Request-Id': requestId },
614
- });
615
- }
616
- }
617
-
618
- // Route to node type: /api/nodes/{name}
619
- const nodeTypeMatch = path.match(/^\\/api\\/nodes\\/([^\\/]+)$/);
620
- if (nodeTypeMatch) {
621
- const nodeTypeName = nodeTypeMatch[1];
622
- const nodeType = exposedNodeTypes[nodeTypeName];
623
-
624
- if (!nodeType) {
625
- return new Response(JSON.stringify({
626
- error: \`Node type '\${nodeTypeName}' not found\`,
627
- availableNodeTypes: Object.keys(exposedNodeTypes),
628
- }), {
629
- status: 404,
630
- headers: { 'Content-Type': 'application/json' },
631
- });
632
- }
633
-
634
- if (method !== 'POST') {
635
- return new Response(JSON.stringify({
636
- success: false,
637
- error: { code: 'METHOD_NOT_ALLOWED', message: 'Only POST requests are allowed for node type execution' },
638
- }), {
639
- status: 405,
640
- headers: { 'Content-Type': 'application/json' },
641
- });
642
- }
643
-
644
- const requestId = request.headers.get('cf-ray') || crypto.randomUUID();
645
-
646
- try {
647
- const body = await request.json() as Record<string, unknown>;
648
- const startTime = performance.now();
649
-
650
- const result = await nodeType(true, body);
651
-
652
- return new Response(JSON.stringify({
653
- success: true,
654
- result,
655
- executionTime: Math.round(performance.now() - startTime),
656
- requestId,
657
- }), {
658
- status: 200,
659
- headers: {
660
- 'Content-Type': 'application/json',
661
- 'X-Request-Id': requestId,
662
- 'X-Execution-Time': \`\${Math.round(performance.now() - startTime)}ms\`,
663
- },
664
- });
665
- } catch (error) {
666
- return new Response(JSON.stringify({
667
- success: false,
668
- error: { code: 'EXECUTION_ERROR', message: error instanceof Error ? error.message : String(error) },
669
- requestId,
670
- }), {
671
- status: 500,
672
- headers: { 'Content-Type': 'application/json', 'X-Request-Id': requestId },
673
- });
674
- }
675
- }
676
-
677
- return new Response(JSON.stringify({ error: 'Not found' }), {
678
- status: 404,
679
- headers: { 'Content-Type': 'application/json' },
680
- });
681
- },
682
- } satisfies ExportedHandler<Env>;
683
- `;
684
- /**
685
- * Cloudflare Workers export target
686
- */
687
- export class CloudflareTarget extends BaseExportTarget {
688
- name = 'cloudflare';
689
- description = 'Cloudflare Workers';
690
- deploySchema = {
691
- compatDate: { type: 'string', description: 'Compatibility date' },
692
- };
693
- async generate(options) {
694
- const files = [];
695
- const includeDocs = options.includeDocs ?? false;
696
- // Select appropriate handler template
697
- const handlerTemplate = includeDocs
698
- ? CLOUDFLARE_HANDLER_WITH_DOCS_TEMPLATE
699
- : CLOUDFLARE_HANDLER_TEMPLATE;
700
- // Generate handler
701
- const handlerContent = handlerTemplate
702
- .replace('{{GENERATED_HEADER}}', getGeneratedBranding().header('export --target cloudflare'))
703
- .replace('{{WORKFLOW_IMPORT}}', `import { ${options.workflowName} } from './workflow.js';`)
704
- .replace(/\{\{FUNCTION_NAME\}\}/g, options.workflowName)
705
- .replace(/\{\{WORKFLOW_NAME\}\}/g, options.displayName);
706
- files.push(this.createFile(options.outputDir, 'index.ts', handlerContent, 'handler'));
707
- // Generate OpenAPI spec file if docs are enabled
708
- if (includeDocs) {
709
- const openApiSpec = this.generateOpenAPISpec(options);
710
- const openApiContent = OPENAPI_SPEC_TEMPLATE.replace('{{OPENAPI_SPEC}}', JSON.stringify(openApiSpec, null, 2));
711
- files.push(this.createFile(options.outputDir, 'openapi.ts', openApiContent, 'config'));
712
- }
713
- // Generate wrangler.toml
714
- const wranglerContent = WRANGLER_TEMPLATE.replace(/\{\{WORKFLOW_NAME\}\}/g, options.displayName);
715
- files.push(this.createFile(options.outputDir, 'wrangler.toml', wranglerContent, 'config'));
716
- // Generate package.json
717
- const packageJson = this.generatePackageJson({
718
- name: options.displayName,
719
- description: options.description,
720
- main: 'dist/index.js',
721
- scripts: {
722
- build: 'tsc',
723
- dev: 'wrangler dev',
724
- deploy: 'wrangler deploy',
725
- },
726
- devDependencies: {
727
- '@cloudflare/workers-types': '^4.0.0',
728
- wrangler: '^3.0.0',
729
- },
730
- });
731
- files.push(this.createFile(options.outputDir, 'package.json', packageJson, 'package'));
732
- // Generate tsconfig.json
733
- const tsConfig = this.generateTsConfig({
734
- outDir: './dist',
735
- module: 'ESNext',
736
- moduleResolution: 'Bundler',
737
- types: ['@cloudflare/workers-types'],
738
- });
739
- files.push(this.createFile(options.outputDir, 'tsconfig.json', tsConfig, 'config'));
740
- // Generate README from deploy instructions
741
- const artifacts = { files, target: this.name, workflowName: options.displayName, entryPoint: 'index.ts' };
742
- const instructions = this.getDeployInstructions(artifacts);
743
- const readme = this.generateReadme(instructions, options.displayName, 'Cloudflare Workers');
744
- files.push(this.createFile(options.outputDir, 'README.md', readme, 'other'));
745
- return artifacts;
746
- }
747
- /**
748
- * Generate OpenAPI specification for the workflow
749
- */
750
- generateOpenAPISpec(options) {
751
- return {
752
- openapi: '3.0.3',
753
- info: {
754
- title: `${options.displayName} API`,
755
- version: '1.0.0',
756
- description: options.description || `API for the ${options.displayName} workflow`,
757
- },
758
- servers: [{ url: '/', description: 'Cloudflare Worker' }],
759
- paths: {
760
- '/': {
761
- post: {
762
- operationId: `execute_${options.workflowName}`,
763
- summary: `Execute ${options.displayName} workflow`,
764
- description: options.description || `Execute the ${options.displayName} workflow`,
765
- tags: ['workflows'],
766
- requestBody: {
767
- description: 'Workflow input parameters',
768
- required: true,
769
- content: {
770
- 'application/json': {
771
- schema: { type: 'object' },
772
- },
773
- },
774
- },
775
- responses: {
776
- '200': {
777
- description: 'Successful workflow execution',
778
- content: {
779
- 'application/json': {
780
- schema: {
781
- type: 'object',
782
- properties: {
783
- success: { type: 'boolean' },
784
- result: { type: 'object' },
785
- executionTime: { type: 'number' },
786
- requestId: { type: 'string' },
787
- },
788
- },
789
- },
790
- },
791
- },
792
- '500': {
793
- description: 'Execution error',
794
- content: {
795
- 'application/json': {
796
- schema: {
797
- type: 'object',
798
- properties: {
799
- success: { type: 'boolean' },
800
- error: { type: 'object' },
801
- requestId: { type: 'string' },
802
- },
803
- },
804
- },
805
- },
806
- },
807
- },
808
- },
809
- },
810
- '/docs': {
811
- get: {
812
- operationId: 'get_docs',
813
- summary: 'API Documentation',
814
- description: 'Swagger UI documentation',
815
- tags: ['documentation'],
816
- responses: {
817
- '200': {
818
- description: 'Swagger UI HTML page',
819
- content: {
820
- 'text/html': {},
821
- },
822
- },
823
- },
824
- },
825
- },
826
- '/openapi.json': {
827
- get: {
828
- operationId: 'get_openapi',
829
- summary: 'OpenAPI Specification',
830
- description: 'OpenAPI 3.0 specification in JSON format',
831
- tags: ['documentation'],
832
- responses: {
833
- '200': {
834
- description: 'OpenAPI specification',
835
- content: {
836
- 'application/json': {},
837
- },
838
- },
839
- },
840
- },
841
- },
842
- },
843
- tags: [
844
- { name: 'workflows', description: 'Workflow execution endpoints' },
845
- { name: 'documentation', description: 'API documentation endpoints' },
846
- ],
847
- };
848
- }
849
- async generateMultiWorkflow(workflows, options) {
850
- const files = [];
851
- const serviceName = options.displayName || 'multi-workflow-service';
852
- // Generate workflow imports and entries
853
- const workflowImports = workflows
854
- .map((w) => `import { ${w.functionName} } from './workflows/${w.name}.js';`)
855
- .join('\n');
856
- const workflowEntries = workflows.map((w) => ` '${w.name}': ${w.functionName},`).join('\n');
857
- // Generate multi-workflow handler
858
- const handlerContent = CLOUDFLARE_MULTI_HANDLER_TEMPLATE
859
- .replace('{{GENERATED_HEADER}}', getGeneratedBranding().header('export --target cloudflare --multi'))
860
- .replace('{{WORKFLOW_IMPORTS}}', workflowImports)
861
- .replace('{{WORKFLOW_ENTRIES}}', workflowEntries)
862
- .replace(/\{\{SERVICE_NAME\}\}/g, serviceName);
863
- files.push(this.createFile(options.outputDir, 'index.ts', handlerContent, 'handler'));
864
- // Generate consolidated OpenAPI spec
865
- const openApiSpec = this.generateConsolidatedOpenAPI(workflows, {
866
- title: `${serviceName} API`,
867
- version: '1.0.0',
868
- });
869
- const openApiContent = `// Generated OpenAPI specification
870
- export const openApiSpec = ${JSON.stringify(openApiSpec, null, 2)};
871
- `;
872
- files.push(this.createFile(options.outputDir, 'openapi.ts', openApiContent, 'config'));
873
- // Generate wrangler.toml
874
- const wranglerContent = WRANGLER_TEMPLATE.replace(/\{\{WORKFLOW_NAME\}\}/g, serviceName);
875
- files.push(this.createFile(options.outputDir, 'wrangler.toml', wranglerContent, 'config'));
876
- // Generate package.json
877
- const packageJson = this.generatePackageJson({
878
- name: serviceName,
879
- description: `Multi-workflow service with ${workflows.length} workflows`,
880
- main: 'dist/index.js',
881
- scripts: {
882
- build: 'tsc',
883
- dev: 'wrangler dev',
884
- deploy: 'wrangler deploy',
885
- },
886
- devDependencies: {
887
- '@cloudflare/workers-types': '^4.0.0',
888
- wrangler: '^3.0.0',
889
- },
890
- });
891
- files.push(this.createFile(options.outputDir, 'package.json', packageJson, 'package'));
892
- // Generate tsconfig.json
893
- const tsConfig = this.generateTsConfig({
894
- outDir: './dist',
895
- module: 'ESNext',
896
- moduleResolution: 'Bundler',
897
- types: ['@cloudflare/workers-types'],
898
- });
899
- files.push(this.createFile(options.outputDir, 'tsconfig.json', tsConfig, 'config'));
900
- // Generate workflow content files
901
- files.push(...this.generateWorkflowContentFiles(workflows, options.outputDir));
902
- return {
903
- files,
904
- target: this.name,
905
- workflowName: serviceName,
906
- workflowNames: workflows.map((w) => w.name),
907
- entryPoint: 'index.ts',
908
- openApiSpec,
909
- };
910
- }
911
- async generateNodeTypeService(nodeTypes, options) {
912
- const files = [];
913
- const serviceName = options.serviceName || 'node-type-service';
914
- // Generate node type imports and entries
915
- // Use lowercase functionName for import paths to match the generated file names
916
- const nodeTypeImports = nodeTypes
917
- .map((nt) => `import { ${nt.functionName} } from './node-types/${nt.functionName.toLowerCase()}.js';`)
918
- .join('\n');
919
- const nodeTypeEntries = nodeTypes.map((nt) => ` '${nt.name}': ${nt.functionName},`).join('\n');
920
- // Generate node type handler
921
- const handlerContent = CLOUDFLARE_NODE_TYPE_HANDLER_TEMPLATE
922
- .replace('{{GENERATED_HEADER}}', getGeneratedBranding().header('export --target cloudflare --node-types'))
923
- .replace('{{NODE_TYPE_IMPORTS}}', nodeTypeImports)
924
- .replace('{{NODE_TYPE_ENTRIES}}', nodeTypeEntries)
925
- .replace(/\{\{SERVICE_NAME\}\}/g, serviceName);
926
- files.push(this.createFile(options.outputDir, 'index.ts', handlerContent, 'handler'));
927
- // Generate OpenAPI spec
928
- const openApiSpec = this.generateNodeTypeOpenAPI(nodeTypes, {
929
- title: `${serviceName} API`,
930
- version: '1.0.0',
931
- });
932
- const openApiContent = `// Generated OpenAPI specification
933
- export const openApiSpec = ${JSON.stringify(openApiSpec, null, 2)};
934
- `;
935
- files.push(this.createFile(options.outputDir, 'openapi.ts', openApiContent, 'config'));
936
- // Generate wrangler.toml
937
- const wranglerContent = WRANGLER_TEMPLATE.replace(/\{\{WORKFLOW_NAME\}\}/g, serviceName);
938
- files.push(this.createFile(options.outputDir, 'wrangler.toml', wranglerContent, 'config'));
939
- // Generate package.json
940
- const packageJson = this.generatePackageJson({
941
- name: serviceName,
942
- description: `Node type service with ${nodeTypes.length} endpoints`,
943
- main: 'dist/index.js',
944
- scripts: {
945
- build: 'tsc',
946
- dev: 'wrangler dev',
947
- deploy: 'wrangler deploy',
948
- },
949
- devDependencies: {
950
- '@cloudflare/workers-types': '^4.0.0',
951
- wrangler: '^3.0.0',
952
- },
953
- });
954
- files.push(this.createFile(options.outputDir, 'package.json', packageJson, 'package'));
955
- // Generate tsconfig.json
956
- const tsConfig = this.generateTsConfig({
957
- outDir: './dist',
958
- module: 'ESNext',
959
- moduleResolution: 'Bundler',
960
- types: ['@cloudflare/workers-types'],
961
- });
962
- files.push(this.createFile(options.outputDir, 'tsconfig.json', tsConfig, 'config'));
963
- // Generate node-type content files
964
- files.push(...this.generateNodeTypeContentFiles(nodeTypes, options.outputDir));
965
- return {
966
- files,
967
- target: this.name,
968
- workflowName: serviceName,
969
- nodeTypeNames: nodeTypes.map((nt) => nt.name),
970
- entryPoint: 'index.ts',
971
- openApiSpec,
972
- };
973
- }
974
- async generateBundle(workflows, nodeTypes, options) {
975
- const files = [];
976
- const serviceName = options.displayName || 'bundle-service';
977
- // Separate exposed and bundled-only items
978
- const exposedWorkflows = workflows.filter((w) => w.expose);
979
- const exposedNodeTypes = nodeTypes.filter((nt) => nt.expose);
980
- // Filter to only include items that have generated code
981
- // Also skip npm imports (names containing '/') as they should be installed via package.json
982
- const workflowsWithCode = workflows.filter((w) => w.code);
983
- const nodeTypesWithCode = nodeTypes.filter((nt) => nt.code && !nt.name.includes('/'));
984
- // Detect name collisions between workflows and nodeTypes
985
- const workflowNames = new Set(workflowsWithCode.map((w) => w.functionName));
986
- const nodeTypeAliases = new Map();
987
- for (const nt of nodeTypesWithCode) {
988
- if (workflowNames.has(nt.functionName)) {
989
- // Use alias for colliding nodeType names
990
- nodeTypeAliases.set(nt.functionName, `${nt.functionName}_nodeType`);
991
- }
992
- }
993
- // Generate all workflow imports (both exposed and bundled-only) - only for those with code
994
- const workflowImports = workflowsWithCode.length > 0
995
- ? workflowsWithCode
996
- .map((w) => `import { ${w.functionName} } from './workflows/${w.name}.js';`)
997
- .join('\n')
998
- : '// No workflows';
999
- // Generate all node type imports (both exposed and bundled-only) with aliases for collisions - only for those with code
1000
- // Use lowercase functionName for import paths to match the generated file names
1001
- const nodeTypeImports = nodeTypesWithCode.length > 0
1002
- ? nodeTypesWithCode
1003
- .map((nt) => {
1004
- const alias = nodeTypeAliases.get(nt.functionName);
1005
- const lowerFunctionName = nt.functionName.toLowerCase();
1006
- if (alias) {
1007
- return `import { ${nt.functionName} as ${alias} } from './node-types/${lowerFunctionName}.js';`;
1008
- }
1009
- return `import { ${nt.functionName} } from './node-types/${lowerFunctionName}.js';`;
1010
- })
1011
- .join('\n')
1012
- : '// No node types';
1013
- // Generate entries only for exposed items
1014
- // Filter exposed items to only include those with code
1015
- const exposedWorkflowsWithCode = exposedWorkflows.filter((w) => w.code);
1016
- const exposedNodeTypesWithCode = exposedNodeTypes.filter((nt) => nt.code);
1017
- const exposedWorkflowEntries = exposedWorkflowsWithCode.length > 0
1018
- ? exposedWorkflowsWithCode.map((w) => ` '${w.name}': ${w.functionName},`).join('\n')
1019
- : ' // No exposed workflows';
1020
- const exposedNodeTypeEntries = exposedNodeTypesWithCode.length > 0
1021
- ? exposedNodeTypesWithCode
1022
- .map((nt) => {
1023
- const alias = nodeTypeAliases.get(nt.functionName);
1024
- return ` '${nt.name}': ${alias || nt.functionName},`;
1025
- })
1026
- .join('\n')
1027
- : ' // No exposed node types';
1028
- // Generate bundle handler
1029
- const handlerContent = CLOUDFLARE_BUNDLE_HANDLER_TEMPLATE
1030
- .replace('{{GENERATED_HEADER}}', getGeneratedBranding().header('export --target cloudflare --bundle'))
1031
- .replace('{{WORKFLOW_IMPORTS}}', workflowImports)
1032
- .replace('{{NODE_TYPE_IMPORTS}}', nodeTypeImports)
1033
- .replace('{{EXPOSED_WORKFLOW_ENTRIES}}', exposedWorkflowEntries)
1034
- .replace('{{EXPOSED_NODE_TYPE_ENTRIES}}', exposedNodeTypeEntries)
1035
- .replace(/\{\{SERVICE_NAME\}\}/g, serviceName);
1036
- files.push(this.createFile(options.outputDir, 'index.ts', handlerContent, 'handler'));
1037
- // Generate OpenAPI spec for exposed items only
1038
- const openApiSpec = this.generateBundleOpenAPI(workflows, nodeTypes, {
1039
- title: `${serviceName} API`,
1040
- version: '1.0.0',
1041
- });
1042
- const openApiContent = `// Generated OpenAPI specification
1043
- export const openApiSpec = ${JSON.stringify(openApiSpec, null, 2)};
1044
- `;
1045
- files.push(this.createFile(options.outputDir, 'openapi.ts', openApiContent, 'config'));
1046
- // Generate wrangler.toml
1047
- const wranglerContent = WRANGLER_TEMPLATE.replace(/\{\{WORKFLOW_NAME\}\}/g, serviceName);
1048
- files.push(this.createFile(options.outputDir, 'wrangler.toml', wranglerContent, 'config'));
1049
- // Generate package.json
1050
- const packageJson = this.generatePackageJson({
1051
- name: serviceName,
1052
- description: `Bundle service with ${workflows.length} workflows and ${nodeTypes.length} node types`,
1053
- main: 'dist/index.js',
1054
- scripts: {
1055
- build: 'tsc',
1056
- dev: 'wrangler dev',
1057
- deploy: 'wrangler deploy',
1058
- },
1059
- devDependencies: {
1060
- '@cloudflare/workers-types': '^4.0.0',
1061
- wrangler: '^3.0.0',
1062
- },
1063
- });
1064
- files.push(this.createFile(options.outputDir, 'package.json', packageJson, 'package'));
1065
- // Generate tsconfig.json
1066
- const tsConfig = this.generateTsConfig({
1067
- outDir: './dist',
1068
- module: 'ESNext',
1069
- moduleResolution: 'Bundler',
1070
- types: ['@cloudflare/workers-types'],
1071
- });
1072
- files.push(this.createFile(options.outputDir, 'tsconfig.json', tsConfig, 'config'));
1073
- // Generate shared runtime types module (workflows import from this)
1074
- const isProduction = options.production ?? true;
1075
- const runtimeTypesContent = generateStandaloneRuntimeModule(isProduction, 'esm');
1076
- files.push(this.createFile(options.outputDir, 'runtime/types.ts', runtimeTypesContent, 'other'));
1077
- // Generate real runtime files (function registry, builtin functions, parameter resolver)
1078
- files.push(...this.generateRuntimeFiles(options.outputDir, workflows, nodeTypes));
1079
- // Generate workflow and node-type content files
1080
- files.push(...this.generateBundleContentFiles(workflows, nodeTypes, options.outputDir));
1081
- return {
1082
- files,
1083
- target: this.name,
1084
- workflowName: serviceName,
1085
- workflowNames: workflows.map((w) => w.name),
1086
- nodeTypeNames: nodeTypes.map((nt) => nt.name),
1087
- entryPoint: 'index.ts',
1088
- openApiSpec,
1089
- };
1090
- }
1091
- getDeployInstructions(artifacts) {
1092
- const outputDir = artifacts.files[0]?.absolutePath
1093
- ? artifacts.files[0].absolutePath.replace(/\/[^/]+$/, '')
1094
- : '.';
1095
- const hasDocs = artifacts.files.some((f) => f.relativePath === 'openapi.ts');
1096
- const steps = [`cd ${outputDir}`, 'npm install', 'npm run deploy'];
1097
- if (hasDocs) {
1098
- steps.push('Visit /docs for API documentation');
1099
- }
1100
- return {
1101
- title: 'Deploy to Cloudflare Workers',
1102
- prerequisites: [
1103
- 'Wrangler CLI installed (npm install -g wrangler)',
1104
- 'Cloudflare account',
1105
- 'First run: wrangler login',
1106
- ],
1107
- steps,
1108
- localTestSteps: [
1109
- `cd ${outputDir}`,
1110
- 'npm install',
1111
- 'npm run dev',
1112
- '# Worker will be available at http://localhost:8787',
1113
- '# Test with: curl -X POST http://localhost:8787 -H "Content-Type: application/json" -d \'{"key": "value"}\'',
1114
- ],
1115
- links: [
1116
- {
1117
- label: 'Cloudflare Workers Documentation',
1118
- url: 'https://developers.cloudflare.com/workers/',
1119
- },
1120
- {
1121
- label: 'Wrangler CLI',
1122
- url: 'https://developers.cloudflare.com/workers/wrangler/',
1123
- },
1124
- ],
1125
- };
1126
- }
1127
- }
1128
- //# sourceMappingURL=cloudflare.js.map