handleRequest("NestJS")}
+ onClick={() => handleRequest('NestJS')}
>
handleRequest("TanStack")}
+ onClick={() => handleRequest('TanStack')}
>
diff --git a/packages/core/e2e/local-build.test.ts b/packages/core/e2e/local-build.test.ts
index 4daca2b01..0eb264c9b 100644
--- a/packages/core/e2e/local-build.test.ts
+++ b/packages/core/e2e/local-build.test.ts
@@ -14,6 +14,7 @@ describe.each([
'nuxt',
'hono',
'express',
+ 'fastify',
])('e2e', (project) => {
test('builds without errors', { timeout: 180_000 }, async () => {
// skip if we're targeting specific app to test
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 797a82b2f..764d53ec7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1220,6 +1220,40 @@ importers:
specifier: 'catalog:'
version: 4.1.11
+ workbench/fastify:
+ dependencies:
+ fastify:
+ specifier: ^5.6.2
+ version: 5.6.2
+ nitro:
+ specifier: 'catalog:'
+ version: 3.0.1-alpha.1(@netlify/blobs@9.1.2)(@vercel/functions@3.1.4(@aws-sdk/credential-provider-web-identity@3.844.0))(better-sqlite3@11.10.0)(chokidar@4.0.3)(drizzle-orm@0.44.7(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7))(ioredis@5.8.2)(lru-cache@11.2.2)(rollup@4.53.2)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))
+ devDependencies:
+ '@types/node':
+ specifier: 'catalog:'
+ version: 22.19.0
+ '@workflow/world-postgres':
+ specifier: workspace:*
+ version: link:../../packages/world-postgres
+ ai:
+ specifier: 'catalog:'
+ version: 5.0.76(zod@4.1.11)
+ lodash.chunk:
+ specifier: ^4.2.0
+ version: 4.2.0
+ openai:
+ specifier: ^6.6.0
+ version: 6.6.0(ws@8.18.3)(zod@4.1.11)
+ typescript:
+ specifier: 'catalog:'
+ version: 5.9.3
+ workflow:
+ specifier: workspace:*
+ version: link:../../packages/workflow
+ zod:
+ specifier: 'catalog:'
+ version: 4.1.11
+
workbench/hono:
devDependencies:
'@workflow/world-postgres':
@@ -2614,9 +2648,27 @@ packages:
resolution: {integrity: sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@fastify/ajv-compiler@4.0.5':
+ resolution: {integrity: sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==}
+
'@fastify/busboy@3.2.0':
resolution: {integrity: sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==}
+ '@fastify/error@4.2.0':
+ resolution: {integrity: sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==}
+
+ '@fastify/fast-json-stringify-compiler@5.0.3':
+ resolution: {integrity: sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==}
+
+ '@fastify/forwarded@3.0.1':
+ resolution: {integrity: sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==}
+
+ '@fastify/merge-json-schemas@0.2.1':
+ resolution: {integrity: sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==}
+
+ '@fastify/proxy-addr@5.1.0':
+ resolution: {integrity: sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==}
+
'@floating-ui/core@1.7.3':
resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==}
@@ -4308,6 +4360,9 @@ packages:
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
engines: {node: '>= 10.0.0'}
+ '@pinojs/redact@0.4.0':
+ resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==}
+
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
@@ -6507,6 +6562,9 @@ packages:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'}
+ abstract-logging@2.0.1:
+ resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==}
+
accepts@2.0.0:
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
engines: {node: '>= 0.6'}
@@ -6536,9 +6594,20 @@ packages:
peerDependencies:
zod: ^3.25.76 || ^4.1.8
+ ajv-formats@3.0.1:
+ resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
+ peerDependencies:
+ ajv: ^8.0.0
+ peerDependenciesMeta:
+ ajv:
+ optional: true
+
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+ ajv@8.17.1:
+ resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
+
alien-signals@3.0.3:
resolution: {integrity: sha512-2JXjom6R7ZwrISpUphLhf4htUq1aKRCennTJ6u9kFfr3sLmC9+I4CxxVi+McoFnIg+p1HnVrfLT/iCt4Dlz//Q==}
@@ -6649,6 +6718,10 @@ packages:
async@3.2.6:
resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
+ atomic-sleep@1.0.0:
+ resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
+ engines: {node: '>=8.0.0'}
+
autoprefixer@10.4.21:
resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==}
engines: {node: ^10 || ^12 || >=14}
@@ -6656,6 +6729,9 @@ packages:
peerDependencies:
postcss: ^8.1.0
+ avvio@9.1.0:
+ resolution: {integrity: sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==}
+
axobject-query@4.1.0:
resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
engines: {node: '>= 0.4'}
@@ -8077,6 +8153,9 @@ packages:
fast-content-type-parse@3.0.0:
resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==}
+ fast-decode-uri-component@1.0.1:
+ resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==}
+
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
@@ -8094,16 +8173,28 @@ packages:
fast-json-stable-stringify@2.1.0:
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+ fast-json-stringify@6.1.1:
+ resolution: {integrity: sha512-DbgptncYEXZqDUOEl4krff4mUiVrTZZVI7BBrQR/T3BqMj/eM1flTC1Uk2uUoLcWCxjT95xKulV/Lc6hhOZsBQ==}
+
fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
fast-npm-meta@0.4.7:
resolution: {integrity: sha512-aZU3i3eRcSb2NCq8i6N6IlyiTyF6vqAqzBGl2NBF6ngNx/GIqfYbkLDIKZ4z4P0o/RmtsFnVqHwdrSm13o4tnQ==}
+ fast-querystring@1.1.2:
+ resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==}
+
+ fast-uri@3.1.0:
+ resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
+
fast-xml-parser@5.2.5:
resolution: {integrity: sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==}
hasBin: true
+ fastify@5.6.2:
+ resolution: {integrity: sha512-dPugdGnsvYkBlENLhCgX8yhyGCsCPrpA8lFWbTNU428l+YOnLgYHR69hzV8HWPC79n536EqzqQtvhtdaCE0dKg==}
+
fastq@1.19.1:
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
@@ -8156,6 +8247,10 @@ packages:
resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==}
engines: {node: '>= 0.8'}
+ find-my-way@9.3.0:
+ resolution: {integrity: sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==}
+ engines: {node: '>=20'}
+
find-package@1.0.0:
resolution: {integrity: sha512-yVn71XCCaNgxz58ERTl8nA/8YYtIQDY9mHSrgFBfiFtdNNfY0h183Vh8BRkKxD8x9TUw3ec290uJKhDVxqGZBw==}
@@ -8740,6 +8835,10 @@ packages:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'}
+ ipaddr.js@2.2.0:
+ resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==}
+ engines: {node: '>= 10'}
+
iron-webcrypto@1.2.1:
resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==}
@@ -8984,9 +9083,15 @@ packages:
json-parse-even-better-errors@2.3.1:
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
+ json-schema-ref-resolver@3.0.0:
+ resolution: {integrity: sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==}
+
json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+ json-schema-traverse@1.0.0:
+ resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+
json-schema@0.4.0:
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
@@ -9079,6 +9184,9 @@ packages:
lie@3.1.1:
resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==}
+ light-my-request@6.6.0:
+ resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==}
+
lightningcss-darwin-arm64@1.30.1:
resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==}
engines: {node: '>= 12.0.0'}
@@ -9966,6 +10074,10 @@ packages:
resolution: {integrity: sha512-J7kocOS+ZNyjmW6tUUTtA7jLt8GjQlrOdz9z3yLNTvdsswO+b5lYSdMVzDczWnooyFAkkQiKyap5g/Zba+cFRA==}
engines: {node: '>=20'}
+ on-exit-leak-free@2.1.2:
+ resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
+ engines: {node: '>=14.0.0'}
+
on-finished@2.4.1:
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
engines: {node: '>= 0.8'}
@@ -10305,6 +10417,16 @@ packages:
resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
engines: {node: '>=6'}
+ pino-abstract-transport@2.0.0:
+ resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==}
+
+ pino-std-serializers@7.0.0:
+ resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==}
+
+ pino@10.1.0:
+ resolution: {integrity: sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==}
+ hasBin: true
+
pkg-types@1.3.1:
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
@@ -10563,6 +10685,12 @@ packages:
process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
+ process-warning@4.0.1:
+ resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==}
+
+ process-warning@5.0.0:
+ resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==}
+
process@0.11.10:
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
engines: {node: '>= 0.6.0'}
@@ -10618,6 +10746,9 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+ quick-format-unescaped@4.0.4:
+ resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
+
quote-unquote@1.0.0:
resolution: {integrity: sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg==}
@@ -10797,6 +10928,10 @@ packages:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'}
+ real-require@0.2.0:
+ resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
+ engines: {node: '>= 12.13.0'}
+
recharts-scale@0.4.5:
resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==}
@@ -10886,6 +11021,10 @@ packages:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
+ require-from-string@2.0.2:
+ resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
+ engines: {node: '>=0.10.0'}
+
require-in-the-middle@7.5.2:
resolution: {integrity: sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==}
engines: {node: '>=8.6.0'}
@@ -10917,6 +11056,10 @@ packages:
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
engines: {node: '>=18'}
+ ret@0.5.0:
+ resolution: {integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==}
+ engines: {node: '>=10'}
+
retry@0.12.0:
resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==}
engines: {node: '>= 4'}
@@ -10986,6 +11129,9 @@ packages:
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+ safe-regex2@5.0.0:
+ resolution: {integrity: sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==}
+
safe-stable-stringify@2.5.0:
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
engines: {node: '>=10'}
@@ -11015,6 +11161,9 @@ packages:
resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
engines: {node: '>=4'}
+ secure-json-parse@4.1.0:
+ resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==}
+
seedrandom@3.0.5:
resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==}
@@ -11142,6 +11291,9 @@ packages:
smob@1.5.0:
resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==}
+ sonic-boom@4.2.0:
+ resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==}
+
sonner@2.0.7:
resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==}
peerDependencies:
@@ -11457,6 +11609,9 @@ packages:
text-hex@1.0.0:
resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==}
+ thread-stream@3.1.0:
+ resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
+
throttleit@2.1.0:
resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==}
engines: {node: '>=18'}
@@ -13683,8 +13838,31 @@ snapshots:
levn: 0.4.1
optional: true
+ '@fastify/ajv-compiler@4.0.5':
+ dependencies:
+ ajv: 8.17.1
+ ajv-formats: 3.0.1(ajv@8.17.1)
+ fast-uri: 3.1.0
+
'@fastify/busboy@3.2.0': {}
+ '@fastify/error@4.2.0': {}
+
+ '@fastify/fast-json-stringify-compiler@5.0.3':
+ dependencies:
+ fast-json-stringify: 6.1.1
+
+ '@fastify/forwarded@3.0.1': {}
+
+ '@fastify/merge-json-schemas@0.2.1':
+ dependencies:
+ dequal: 2.0.3
+
+ '@fastify/proxy-addr@5.1.0':
+ dependencies:
+ '@fastify/forwarded': 3.0.1
+ ipaddr.js: 2.2.0
+
'@floating-ui/core@1.7.3':
dependencies:
'@floating-ui/utils': 0.2.10
@@ -15391,6 +15569,8 @@ snapshots:
'@parcel/watcher-win32-ia32': 2.5.1
'@parcel/watcher-win32-x64': 2.5.1
+ '@pinojs/redact@0.4.0': {}
+
'@pkgjs/parseargs@0.11.0':
optional: true
@@ -18132,6 +18312,8 @@ snapshots:
dependencies:
event-target-shim: 5.0.1
+ abstract-logging@2.0.1: {}
+
accepts@2.0.0:
dependencies:
mime-types: 3.0.1
@@ -18157,6 +18339,10 @@ snapshots:
'@opentelemetry/api': 1.9.0
zod: 4.1.11
+ ajv-formats@3.0.1(ajv@8.17.1):
+ optionalDependencies:
+ ajv: 8.17.1
+
ajv@6.12.6:
dependencies:
fast-deep-equal: 3.1.3
@@ -18165,6 +18351,13 @@ snapshots:
uri-js: 4.4.1
optional: true
+ ajv@8.17.1:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-uri: 3.1.0
+ json-schema-traverse: 1.0.0
+ require-from-string: 2.0.2
+
alien-signals@3.0.3: {}
ansi-align@3.0.1:
@@ -18271,6 +18464,8 @@ snapshots:
async@3.2.6: {}
+ atomic-sleep@1.0.0: {}
+
autoprefixer@10.4.21(postcss@8.5.6):
dependencies:
browserslist: 4.27.0
@@ -18281,6 +18476,11 @@ snapshots:
postcss: 8.5.6
postcss-value-parser: 4.2.0
+ avvio@9.1.0:
+ dependencies:
+ '@fastify/error': 4.2.0
+ fastq: 1.19.1
+
axobject-query@4.1.0: {}
b4a@1.7.3: {}
@@ -19791,6 +19991,8 @@ snapshots:
fast-content-type-parse@3.0.0: {}
+ fast-decode-uri-component@1.0.1: {}
+
fast-deep-equal@3.1.3: {}
fast-equals@5.3.2: {}
@@ -19808,15 +20010,48 @@ snapshots:
fast-json-stable-stringify@2.1.0:
optional: true
+ fast-json-stringify@6.1.1:
+ dependencies:
+ '@fastify/merge-json-schemas': 0.2.1
+ ajv: 8.17.1
+ ajv-formats: 3.0.1(ajv@8.17.1)
+ fast-uri: 3.1.0
+ json-schema-ref-resolver: 3.0.0
+ rfdc: 1.4.1
+
fast-levenshtein@2.0.6:
optional: true
fast-npm-meta@0.4.7: {}
+ fast-querystring@1.1.2:
+ dependencies:
+ fast-decode-uri-component: 1.0.1
+
+ fast-uri@3.1.0: {}
+
fast-xml-parser@5.2.5:
dependencies:
strnum: 2.1.1
+ fastify@5.6.2:
+ dependencies:
+ '@fastify/ajv-compiler': 4.0.5
+ '@fastify/error': 4.2.0
+ '@fastify/fast-json-stringify-compiler': 5.0.3
+ '@fastify/proxy-addr': 5.1.0
+ abstract-logging: 2.0.1
+ avvio: 9.1.0
+ fast-json-stringify: 6.1.1
+ find-my-way: 9.3.0
+ light-my-request: 6.6.0
+ pino: 10.1.0
+ process-warning: 5.0.0
+ rfdc: 1.4.1
+ secure-json-parse: 4.1.0
+ semver: 7.7.3
+ toad-cache: 3.7.0
+
fastq@1.19.1:
dependencies:
reusify: 1.1.0
@@ -19872,6 +20107,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ find-my-way@9.3.0:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-querystring: 1.1.2
+ safe-regex2: 5.0.0
+
find-package@1.0.0:
dependencies:
parents: 1.0.1
@@ -20576,6 +20817,8 @@ snapshots:
ipaddr.js@1.9.1: {}
+ ipaddr.js@2.2.0: {}
+
iron-webcrypto@1.2.1: {}
is-alphabetical@2.0.1: {}
@@ -20761,9 +21004,15 @@ snapshots:
json-parse-even-better-errors@2.3.1: {}
+ json-schema-ref-resolver@3.0.0:
+ dependencies:
+ dequal: 2.0.3
+
json-schema-traverse@0.4.1:
optional: true
+ json-schema-traverse@1.0.0: {}
+
json-schema@0.4.0: {}
json-stable-stringify-without-jsonify@1.0.1:
@@ -20849,6 +21098,12 @@ snapshots:
dependencies:
immediate: 3.0.6
+ light-my-request@6.6.0:
+ dependencies:
+ cookie: 1.0.2
+ process-warning: 4.0.1
+ set-cookie-parser: 2.7.2
+
lightningcss-darwin-arm64@1.30.1:
optional: true
@@ -22530,6 +22785,8 @@ snapshots:
on-change@6.0.0: {}
+ on-exit-leak-free@2.1.2: {}
+
on-finished@2.4.1:
dependencies:
ee-first: 1.1.1
@@ -22964,6 +23221,26 @@ snapshots:
pify@4.0.1: {}
+ pino-abstract-transport@2.0.0:
+ dependencies:
+ split2: 4.2.0
+
+ pino-std-serializers@7.0.0: {}
+
+ pino@10.1.0:
+ dependencies:
+ '@pinojs/redact': 0.4.0
+ atomic-sleep: 1.0.0
+ on-exit-leak-free: 2.1.2
+ pino-abstract-transport: 2.0.0
+ pino-std-serializers: 7.0.0
+ process-warning: 5.0.0
+ quick-format-unescaped: 4.0.4
+ real-require: 0.2.0
+ safe-stable-stringify: 2.5.0
+ sonic-boom: 4.2.0
+ thread-stream: 3.1.0
+
pkg-types@1.3.1:
dependencies:
confbox: 0.1.8
@@ -23232,6 +23509,10 @@ snapshots:
process-nextick-args@2.0.1: {}
+ process-warning@4.0.1: {}
+
+ process-warning@5.0.0: {}
+
process@0.11.10: {}
prompts@2.4.2:
@@ -23299,6 +23580,8 @@ snapshots:
queue-microtask@1.2.3: {}
+ quick-format-unescaped@4.0.4: {}
+
quote-unquote@1.0.0: {}
radix-ui@1.4.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
@@ -23596,6 +23879,8 @@ snapshots:
readdirp@4.1.2: {}
+ real-require@0.2.0: {}
+
recharts-scale@0.4.5:
dependencies:
decimal.js-light: 2.5.1
@@ -23751,6 +24036,8 @@ snapshots:
require-directory@2.1.1: {}
+ require-from-string@2.0.2: {}
+
require-in-the-middle@7.5.2:
dependencies:
debug: 4.4.3(supports-color@8.1.1)
@@ -23784,6 +24071,8 @@ snapshots:
onetime: 7.0.0
signal-exit: 4.1.0
+ ret@0.5.0: {}
+
retry@0.12.0: {}
reusify@1.1.0: {}
@@ -23872,6 +24161,10 @@ snapshots:
safe-buffer@5.2.1: {}
+ safe-regex2@5.0.0:
+ dependencies:
+ ret: 0.5.0
+
safe-stable-stringify@2.5.0: {}
safer-buffer@2.1.2: {}
@@ -23895,6 +24188,8 @@ snapshots:
extend-shallow: 2.0.1
kind-of: 6.0.3
+ secure-json-parse@4.1.0: {}
+
seedrandom@3.0.5: {}
semver@6.3.1: {}
@@ -24082,6 +24377,10 @@ snapshots:
smob@1.5.0: {}
+ sonic-boom@4.2.0:
+ dependencies:
+ atomic-sleep: 1.0.0
+
sonner@2.0.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
react: 19.1.0
@@ -24480,6 +24779,10 @@ snapshots:
text-hex@1.0.0: {}
+ thread-stream@3.1.0:
+ dependencies:
+ real-require: 0.2.0
+
throttleit@2.1.0: {}
tiktok-video-element@0.1.1: {}
diff --git a/scripts/create-test-matrix.mjs b/scripts/create-test-matrix.mjs
index 369ec344d..d7cc3c18f 100644
--- a/scripts/create-test-matrix.mjs
+++ b/scripts/create-test-matrix.mjs
@@ -49,6 +49,12 @@ const DEV_TEST_CONFIGS = {
apiFilePath: './src/index.ts',
apiFileImportPath: '..',
},
+ fastify: {
+ generatedStepPath: 'node_modules/.nitro/workflow/steps.mjs',
+ generatedWorkflowPath: 'node_modules/.nitro/workflow/workflows.mjs',
+ apiFilePath: './src/index.ts',
+ apiFileImportPath: '..',
+ },
};
const matrix = {
@@ -112,4 +118,10 @@ matrix.app.push({
...DEV_TEST_CONFIGS.express,
});
+matrix.app.push({
+ name: 'fastify',
+ project: 'workbench-fastify-workflow',
+ ...DEV_TEST_CONFIGS.fastify,
+});
+
console.log(JSON.stringify(matrix));
diff --git a/workbench/fastify/.gitignore b/workbench/fastify/.gitignore
new file mode 100644
index 000000000..f1af274f2
--- /dev/null
+++ b/workbench/fastify/.gitignore
@@ -0,0 +1,26 @@
+node_modules
+
+# Output
+.output
+.vercel
+.netlify
+.wrangler
+.well-known/
+/build
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Env
+.env
+.env.*
+!.env.example
+!.env.test
+
+# Vite
+vite.config.js.timestamp-*
+vite.config.ts.timestamp-*
+
+# Workflows
+_workflows.ts
\ No newline at end of file
diff --git a/workbench/fastify/README.md b/workbench/fastify/README.md
new file mode 100644
index 000000000..f68da1fba
--- /dev/null
+++ b/workbench/fastify/README.md
@@ -0,0 +1,26 @@
+# Workflows with Fastify (Nitro v3)
+
+- Learn more about Fastify: https://fastify.dev
+- Learn more about Nitro: https://v3.nitro.build/
+
+## Commands
+
+**Local development:**
+
+```sh
+npm run dev
+```
+
+**Production build (Vercel):**
+
+```sh
+NITRO_PRESET=vercel npm run build
+npx vercel --prebuilt
+```
+
+**Production build (Node.js):**
+
+```sh
+npm run build
+node .output/server/index.mjs
+```
diff --git a/workbench/fastify/index.html b/workbench/fastify/index.html
new file mode 120000
index 000000000..62524c9be
--- /dev/null
+++ b/workbench/fastify/index.html
@@ -0,0 +1 @@
+../nitro-v3/index.html
\ No newline at end of file
diff --git a/workbench/fastify/nitro.config.ts b/workbench/fastify/nitro.config.ts
new file mode 100644
index 000000000..175d1ace4
--- /dev/null
+++ b/workbench/fastify/nitro.config.ts
@@ -0,0 +1,10 @@
+import { defineNitroConfig } from 'nitro/config';
+
+export default defineNitroConfig({
+ modules: ['workflow/nitro'],
+ vercel: { entryFormat: 'node' },
+ routes: {
+ '/**': { handler: './src/index.ts', format: 'node' },
+ },
+ plugins: ['plugins/start-pg-world.ts'],
+});
diff --git a/workbench/fastify/package.json b/workbench/fastify/package.json
new file mode 100644
index 000000000..3148bda5b
--- /dev/null
+++ b/workbench/fastify/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "@workflow/example-fastify",
+ "version": "0.0.0",
+ "private": true,
+ "description": "",
+ "type": "module",
+ "main": "index.js",
+ "scripts": {
+ "generate:workflows": "node ../scripts/generate-workflows-registry.js",
+ "predev": "pnpm generate:workflows",
+ "copy:index": "mkdir -p public && cp index.html public/index.html",
+ "prebuild": "pnpm generate:workflows && pnpm copy:index",
+ "postbuild": "rm -f public/index.html",
+ "build": "nitro build",
+ "dev": "nitro dev",
+ "start": "node .output/server/index.mjs"
+ },
+ "dependencies": {
+ "fastify": "^5.6.2",
+ "nitro": "catalog:"
+ },
+ "devDependencies": {
+ "@types/node": "catalog:",
+ "@workflow/world-postgres": "workspace:*",
+ "ai": "catalog:",
+ "lodash.chunk": "^4.2.0",
+ "openai": "^6.6.0",
+ "typescript": "catalog:",
+ "workflow": "workspace:*",
+ "zod": "catalog:"
+ }
+}
diff --git a/workbench/fastify/plugins b/workbench/fastify/plugins
new file mode 120000
index 000000000..2c8250f9c
--- /dev/null
+++ b/workbench/fastify/plugins
@@ -0,0 +1 @@
+../nitro-v3/plugins
\ No newline at end of file
diff --git a/workbench/fastify/src/index.ts b/workbench/fastify/src/index.ts
new file mode 100644
index 000000000..7c354822c
--- /dev/null
+++ b/workbench/fastify/src/index.ts
@@ -0,0 +1,273 @@
+import Fastify from 'fastify';
+import { getHookByToken, getRun, resumeHook, start } from 'workflow/api';
+import { hydrateWorkflowArguments } from 'workflow/internal/serialization';
+import {
+ WorkflowRunFailedError,
+ WorkflowRunNotCompletedError,
+} from 'workflow/internal/errors';
+import { allWorkflows } from '../_workflows.js';
+import { resolve } from 'node:path';
+import { readFile } from 'node:fs/promises';
+
+type JsonResult = { ok: true; value: any } | { ok: false; error: Error };
+const parseJson = (text: string): JsonResult => {
+ try {
+ return { ok: true, value: JSON.parse(text) };
+ } catch (error) {
+ return { ok: false, error: error as Error };
+ }
+};
+
+const server = Fastify({
+ logger: true,
+});
+
+server.addContentTypeParser(
+ 'text/*',
+ { parseAs: 'string' },
+ server.getDefaultJsonParser('ignore', 'ignore')
+);
+
+// allow fastify to parse empty json requests
+server.addContentTypeParser(
+ 'application/json',
+ { parseAs: 'string' },
+ (req, body, done) => {
+ const text = typeof body === 'string' ? body : body.toString();
+ if (!text) return done(null, {});
+ const parsed = parseJson(text);
+ return parsed.ok ? done(null, parsed.value) : done(parsed.error);
+ }
+);
+
+server.get('/', async (req, reply) => {
+ const html = await readFile(resolve('./index.html'), 'utf-8');
+ return reply.type('text/html').send(html);
+});
+
+server.post('/api/hook', async (req: any, reply) => {
+ const body = typeof req.body === 'string' ? JSON.parse(req.body) : req.body;
+ const { token, data } = body;
+
+ let hook: Awaited>;
+ try {
+ hook = await getHookByToken(token);
+ console.log('hook', hook);
+ } catch (error) {
+ console.log('error during getHookByToken', error);
+ return reply.code(422).send(null);
+ }
+
+ await resumeHook(hook.token, {
+ ...data,
+ // @ts-expect-error metadata is not typed
+ customData: hook.metadata?.customData,
+ });
+
+ return hook;
+});
+
+server.post('/api/trigger', async (req: any, reply) => {
+ const workflowFile =
+ (req.query.workflowFile as string) || 'workflows/99_e2e.ts';
+ if (!workflowFile) {
+ return reply.code(400).send('No workflowFile query parameter provided');
+ }
+ const workflows = allWorkflows[workflowFile as keyof typeof allWorkflows];
+ if (!workflows) {
+ return reply.code(400).send(`Workflow file "${workflowFile}" not found`);
+ }
+
+ const workflowFn = (req.query.workflowFn as string) || 'simple';
+ if (!workflowFn) {
+ return reply.code(400).send('No workflow query parameter provided');
+ }
+ const workflow = workflows[workflowFn as keyof typeof workflows];
+ if (!workflow) {
+ return reply.code(400).send('Workflow not found');
+ }
+
+ let args: any[] = [];
+
+ // Args from query string
+ const argsParam = req.query.args as string;
+ if (argsParam) {
+ args = argsParam.split(',').map((arg) => {
+ const num = parseFloat(arg);
+ return Number.isNaN(num) ? arg.trim() : num;
+ });
+ } else {
+ // Args from body
+ const body = req.body;
+ if (body && typeof body === 'string') {
+ args = hydrateWorkflowArguments(JSON.parse(body), globalThis);
+ } else if (body && typeof body === 'object') {
+ args = hydrateWorkflowArguments(body, globalThis);
+ } else {
+ args = [42];
+ }
+ }
+ console.log(`Starting "${workflowFn}" workflow with args: ${args}`);
+
+ try {
+ const run = await start(workflow as any, args as any);
+ console.log('Run:', run);
+ return run;
+ } catch (err) {
+ console.error(`Failed to start!!`, err);
+ throw err;
+ }
+});
+
+server.get('/api/trigger', async (req: any, reply) => {
+ const runId = req.query.runId as string | undefined;
+ if (!runId) {
+ return reply.code(400).send('No runId provided');
+ }
+
+ const outputStreamParam = req.query['output-stream'] as string | undefined;
+
+ try {
+ const run = getRun(runId);
+
+ if (outputStreamParam) {
+ const namespace =
+ outputStreamParam === '1' ? undefined : outputStreamParam;
+ const stream = run.getReadable({ namespace });
+ const reader = stream.getReader();
+
+ const toFramedChunk = (value: unknown) => {
+ if (typeof value === 'string') {
+ return { data: Buffer.from(value).toString('base64') };
+ }
+ if (value instanceof ArrayBuffer) {
+ return { data: Buffer.from(value).toString('base64') };
+ }
+ if (ArrayBuffer.isView(value)) {
+ const view = value as ArrayBufferView;
+ const buf = Buffer.from(
+ view.buffer,
+ view.byteOffset,
+ view.byteLength
+ );
+ return { data: buf.toString('base64') };
+ }
+ return value;
+ };
+
+ reply.type('application/octet-stream');
+ // Fastify runs on Node and doesn’t send Web ReadableStreams directly
+ // read from the Web reader and write framed chunks to the raw response
+ try {
+ let chunkCount = 0;
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+
+ chunkCount += 1;
+
+ const framed = toFramedChunk(value);
+ reply.raw.write(`${JSON.stringify(framed)}\n`);
+ }
+ reply.raw.end();
+ } catch (error) {
+ console.error('Error streaming data:', error);
+ reply.raw.end();
+ } finally {
+ reader.releaseLock();
+ }
+ return;
+ }
+
+ const returnValue = await run.returnValue;
+ console.log('Return value:', returnValue);
+
+ if (returnValue instanceof ReadableStream) {
+ const reader = returnValue.getReader();
+ // reply.type() doesn't apply when we write directly to reply.raw
+ reply.raw.setHeader('Content-Type', 'application/octet-stream');
+
+ // Workflow returns a Web ReadableStream; stream it by pulling from
+ // its reader and writing to reply.raw so Fastify can flush it to the client
+ try {
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+ reply.raw.write(value);
+ }
+ reply.raw.end();
+ } catch (streamError) {
+ console.error('Error streaming return value:', streamError);
+ reply.raw.end();
+ } finally {
+ reader.releaseLock();
+ }
+ return;
+ }
+
+ // Fastify sends strings as text/plain by default
+ const payload =
+ typeof returnValue === 'string' ||
+ typeof returnValue === 'number' ||
+ typeof returnValue === 'boolean'
+ ? JSON.stringify(returnValue)
+ : returnValue;
+ return reply.type('application/json').send(payload);
+ } catch (error) {
+ if (error instanceof Error) {
+ if (WorkflowRunNotCompletedError.is(error)) {
+ return reply.code(202).send({
+ ...error,
+ name: error.name,
+ message: error.message,
+ });
+ }
+
+ if (WorkflowRunFailedError.is(error)) {
+ const cause = error.cause;
+ return reply.code(400).send({
+ ...error,
+ name: error.name,
+ message: error.message,
+ cause: {
+ message: cause.message,
+ stack: cause.stack,
+ code: cause.code,
+ },
+ });
+ }
+ }
+
+ console.error(
+ 'Unexpected error while getting workflow return value:',
+ error
+ );
+
+ return reply.code(500).send({
+ error: 'Internal server error',
+ });
+ }
+});
+
+server.post('/api/test-direct-step-call', async (req: any, reply) => {
+ // This route tests calling step functions directly outside of any workflow context
+ // After the SWC compiler changes, step functions in client mode have their directive removed
+ // and keep their original implementation, allowing them to be called as regular async functions
+ const { add } = await import('../workflows/99_e2e.js');
+
+ const { x, y } = req.body;
+
+ console.log(`Calling step function directly with x=${x}, y=${y}`);
+
+ // Call step function directly as a regular async function (no workflow context)
+ const result = await add(x, y);
+ console.log(`add(${x}, ${y}) = ${result}`);
+
+ return reply.send({ result });
+});
+
+await server.ready();
+
+export default (req: any, res: any) => {
+ server.server.emit('request', req, res);
+};
diff --git a/workbench/fastify/workflows b/workbench/fastify/workflows
new file mode 120000
index 000000000..876d7a80c
--- /dev/null
+++ b/workbench/fastify/workflows
@@ -0,0 +1 @@
+../nitro-v3/workflows
\ No newline at end of file