@baasix/baasix 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.MD +85 -0
- package/README.md +526 -0
- package/assets/banner.jpg +0 -0
- package/assets/banner_small.jpg +0 -0
- package/assets/logo_icon.svg +20 -0
- package/assets/logo_icon_rounded.svg +20 -0
- package/dist/LICENSE.MD +85 -0
- package/dist/README.md +526 -0
- package/dist/app/404/index.html +1 -0
- package/dist/app/404.html +1 -0
- package/dist/app/_next/static/chunks/041e1f03-56ae8a902a7f2fe6.js +24 -0
- package/dist/app/_next/static/chunks/1117-05479929a8da73e3.js +1 -0
- package/dist/app/_next/static/chunks/1299.77cc7b7b76b75cba.js +1 -0
- package/dist/app/_next/static/chunks/1303-35a96e9c9cdeab9d.js +1 -0
- package/dist/app/_next/static/chunks/1509-56ac00cdaaecdf53.js +1 -0
- package/dist/app/_next/static/chunks/1668-e3eabd0f6753c780.js +1 -0
- package/dist/app/_next/static/chunks/1783-d9fb550fd324300c.js +1 -0
- package/dist/app/_next/static/chunks/2117-29b5fa47421595ad.js +2 -0
- package/dist/app/_next/static/chunks/2344.35b46d2179a765b5.js +1 -0
- package/dist/app/_next/static/chunks/257.990da16794a31292.js +1 -0
- package/dist/app/_next/static/chunks/2676-73b0ee7c80073a84.js +1 -0
- package/dist/app/_next/static/chunks/3563-b8842744384391fe.js +1 -0
- package/dist/app/_next/static/chunks/363642f4-933b579ed3c85f60.js +1 -0
- package/dist/app/_next/static/chunks/3817-e20c8f0a0810fc95.js +1 -0
- package/dist/app/_next/static/chunks/3834.84944e390d902509.js +2 -0
- package/dist/app/_next/static/chunks/4043-3a30c8a75896f241.js +1 -0
- package/dist/app/_next/static/chunks/4225-14090c7c0cd9dec6.js +1 -0
- package/dist/app/_next/static/chunks/4438-c9a12ca15b6e9160.js +1 -0
- package/dist/app/_next/static/chunks/4458-679fd0c6884f456a.js +1 -0
- package/dist/app/_next/static/chunks/4475-8bdfbd536fba8c48.js +1 -0
- package/dist/app/_next/static/chunks/4883-8a924721bb21b3b0.js +1 -0
- package/dist/app/_next/static/chunks/489-683ab07188f9df2b.js +1 -0
- package/dist/app/_next/static/chunks/4952-1b97320cf61f3f21.js +1 -0
- package/dist/app/_next/static/chunks/5094-8d53e403235d4ca6.js +1 -0
- package/dist/app/_next/static/chunks/5101-3a146e0625747ad1.js +1 -0
- package/dist/app/_next/static/chunks/54a60aa6-d9747982e0a81f58.js +79 -0
- package/dist/app/_next/static/chunks/5650-f096291df402bfc2.js +1 -0
- package/dist/app/_next/static/chunks/600-539045311240f579.js +1 -0
- package/dist/app/_next/static/chunks/6170-803b82e19d3ade6d.js +89 -0
- package/dist/app/_next/static/chunks/6241-30d7169d1010e5a4.js +1 -0
- package/dist/app/_next/static/chunks/6530-a91e10cffa4200c4.js +1 -0
- package/dist/app/_next/static/chunks/6547-4bbbdb5c399aef1e.js +1 -0
- package/dist/app/_next/static/chunks/6712-781937c53a2c49da.js +1 -0
- package/dist/app/_next/static/chunks/6fcbdc68-90be1a5480b8d353.js +1 -0
- package/dist/app/_next/static/chunks/70e0d97a-aeaf0cdc26ba1a58.js +1 -0
- package/dist/app/_next/static/chunks/7214-5154a89d08d24dde.js +1 -0
- package/dist/app/_next/static/chunks/7324-b53229c59a640880.js +10 -0
- package/dist/app/_next/static/chunks/7636-66424f0b51d350e9.js +1 -0
- package/dist/app/_next/static/chunks/7874-39a3f2541165a675.js +1 -0
- package/dist/app/_next/static/chunks/7982-9da12b83f11e3f5f.js +1 -0
- package/dist/app/_next/static/chunks/8213a2eb-da25a3b3c5521b2b.js +1 -0
- package/dist/app/_next/static/chunks/8473-6598318371eca31b.js +1 -0
- package/dist/app/_next/static/chunks/8640fa6b-72e43370f68e5587.js +1 -0
- package/dist/app/_next/static/chunks/9090-3ef676f29c95f1c7.js +1 -0
- package/dist/app/_next/static/chunks/9124-a02f9e209e6e3cce.js +1 -0
- package/dist/app/_next/static/chunks/926-156f32067d111d6b.js +1 -0
- package/dist/app/_next/static/chunks/9487-b17481605e513b83.js +1 -0
- package/dist/app/_next/static/chunks/9599-a7e572bb88c3392b.js +1 -0
- package/dist/app/_next/static/chunks/9881-419697138376e755.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/activity-log/all-activity/page-8917930b4d663405.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/activity-log/email-log/page-b27a6ee32782d7df.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/activity-log/notifications/page-b7eda523ede2702c.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/activity-log/page-1cfa62d1caedaed0.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/activity-log/sessions/page-3e21e20db90aeff7.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/activity-log/workflow-executions/page-27bcc26b747fb29b.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/activity-log/workflow-logs/page-9f9e9e952aef436e.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/change-password/page-8d61aa499eabb127.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/dashboard/page-1ceeac9e72997a8a.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/data-browser/page-8cda2b57759dd670.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/file-manager/page-8c6f1b1da66ad7e4.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/layout-f70d225b2759c998.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/settings/migrations/page-aacec8f7cfb40ab2.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/settings/permissions/page-828110cfcde429c6.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/settings/project/page-420e794bb76bd204.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/settings/roles/page-9001d02b28f70708.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/settings/schema/page-899574f35091dd58.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/settings/tasks/page-ad7ab3e27c83f44f.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/settings/templates/edit/page-bd83414cb8c4cb04.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/settings/templates/page-3181447f8772b1d3.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/settings/tenants/page-ef9bfbacef5a1d73.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/users/invites/page-480306b7b2bbac7e.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/users/list/page-74da51254c2606b3.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/users/page-e99c6f0b915001b2.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/users/preferences/page-1a935630ce8f2b12.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/users/user-roles/page-901dfb8ea1f39ca8.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/workflows/detail/page-9a6b839aea688ca4.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/workflows/edit/page-11774efbc2fecae2.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/workflows/execution/page-8ec1aea90412c03d.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/workflows/page-88bc5b36ccb0a1f7.js +1 -0
- package/dist/app/_next/static/chunks/app/(public)/forgot-password/page-ed263fd46ef81c20.js +1 -0
- package/dist/app/_next/static/chunks/app/(public)/layout-f538977545844af8.js +1 -0
- package/dist/app/_next/static/chunks/app/(public)/login/page-c0a10b137f346096.js +1 -0
- package/dist/app/_next/static/chunks/app/(public)/register/page-4cb7644893efd9b3.js +1 -0
- package/dist/app/_next/static/chunks/app/_not-found/page-653f8815b78256cc.js +1 -0
- package/dist/app/_next/static/chunks/app/layout-591ca7a3e16528a1.js +1 -0
- package/dist/app/_next/static/chunks/app/page-dd19d124b5fa2577.js +1 -0
- package/dist/app/_next/static/chunks/c37d3baf.c2ff165f5b02c692.js +1 -0
- package/dist/app/_next/static/chunks/d0deef33.0379166a4ec23470.js +1 -0
- package/dist/app/_next/static/chunks/fd9d1056-54169f07cd680d6c.js +1 -0
- package/dist/app/_next/static/chunks/framework-8e0e0f4a6b83a956.js +1 -0
- package/dist/app/_next/static/chunks/main-324e91f5a430cddf.js +1 -0
- package/dist/app/_next/static/chunks/main-app-55bcae20c77aaf0e.js +1 -0
- package/dist/app/_next/static/chunks/pages/_app-3c9ca398d360b709.js +1 -0
- package/dist/app/_next/static/chunks/pages/_error-cf5ca766ac8f493f.js +1 -0
- package/dist/app/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/dist/app/_next/static/chunks/webpack-2c306566f7ee1b63.js +1 -0
- package/dist/app/_next/static/css/6c4002bae4e236b2.css +3 -0
- package/dist/app/_next/static/css/a275cc2b185e04f8.css +1 -0
- package/dist/app/_next/static/eCWhKA8XHqmB1zgFcEtN2/_buildManifest.js +1 -0
- package/dist/app/_next/static/eCWhKA8XHqmB1zgFcEtN2/_ssgManifest.js +1 -0
- package/dist/app/activity-log/all-activity/index.html +1 -0
- package/dist/app/activity-log/all-activity/index.txt +14 -0
- package/dist/app/activity-log/email-log/index.html +1 -0
- package/dist/app/activity-log/email-log/index.txt +14 -0
- package/dist/app/activity-log/index.html +1 -0
- package/dist/app/activity-log/index.txt +14 -0
- package/dist/app/activity-log/notifications/index.html +1 -0
- package/dist/app/activity-log/notifications/index.txt +14 -0
- package/dist/app/activity-log/sessions/index.html +1 -0
- package/dist/app/activity-log/sessions/index.txt +14 -0
- package/dist/app/activity-log/workflow-executions/index.html +1 -0
- package/dist/app/activity-log/workflow-executions/index.txt +14 -0
- package/dist/app/activity-log/workflow-logs/index.html +1 -0
- package/dist/app/activity-log/workflow-logs/index.txt +14 -0
- package/dist/app/change-password/index.html +1 -0
- package/dist/app/change-password/index.txt +14 -0
- package/dist/app/dashboard/index.html +1 -0
- package/dist/app/dashboard/index.txt +14 -0
- package/dist/app/data-browser/index.html +1 -0
- package/dist/app/data-browser/index.txt +14 -0
- package/dist/app/file-manager/index.html +1 -0
- package/dist/app/file-manager/index.txt +14 -0
- package/dist/app/forgot-password/index.html +1 -0
- package/dist/app/forgot-password/index.txt +13 -0
- package/dist/app/index.html +1 -0
- package/dist/app/index.txt +9 -0
- package/dist/app/login/index.html +1 -0
- package/dist/app/login/index.txt +13 -0
- package/dist/app/logo-dark.png +0 -0
- package/dist/app/logo-icon.svg +81 -0
- package/dist/app/logo-light.png +0 -0
- package/dist/app/register/index.html +1 -0
- package/dist/app/register/index.txt +13 -0
- package/dist/app/settings/migrations/index.html +1 -0
- package/dist/app/settings/migrations/index.txt +14 -0
- package/dist/app/settings/permissions/index.html +1 -0
- package/dist/app/settings/permissions/index.txt +14 -0
- package/dist/app/settings/project/index.html +1 -0
- package/dist/app/settings/project/index.txt +14 -0
- package/dist/app/settings/roles/index.html +1 -0
- package/dist/app/settings/roles/index.txt +14 -0
- package/dist/app/settings/schema/index.html +1 -0
- package/dist/app/settings/schema/index.txt +14 -0
- package/dist/app/settings/tasks/index.html +1 -0
- package/dist/app/settings/tasks/index.txt +14 -0
- package/dist/app/settings/templates/edit/index.html +1 -0
- package/dist/app/settings/templates/edit/index.txt +14 -0
- package/dist/app/settings/templates/index.html +1 -0
- package/dist/app/settings/templates/index.txt +14 -0
- package/dist/app/settings/tenants/index.html +1 -0
- package/dist/app/settings/tenants/index.txt +14 -0
- package/dist/app/users/index.html +1 -0
- package/dist/app/users/index.txt +14 -0
- package/dist/app/users/invites/index.html +1 -0
- package/dist/app/users/invites/index.txt +14 -0
- package/dist/app/users/list/index.html +1 -0
- package/dist/app/users/list/index.txt +14 -0
- package/dist/app/users/preferences/index.html +1 -0
- package/dist/app/users/preferences/index.txt +14 -0
- package/dist/app/users/user-roles/index.html +1 -0
- package/dist/app/users/user-roles/index.txt +14 -0
- package/dist/app/workflows/detail/index.html +1 -0
- package/dist/app/workflows/detail/index.txt +14 -0
- package/dist/app/workflows/edit/index.html +1 -0
- package/dist/app/workflows/edit/index.txt +14 -0
- package/dist/app/workflows/execution/index.html +1 -0
- package/dist/app/workflows/execution/index.txt +14 -0
- package/dist/app/workflows/index.html +1 -0
- package/dist/app/workflows/index.txt +14 -0
- package/dist/app.d.ts +36 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +546 -0
- package/dist/app.js.map +1 -0
- package/dist/auth/adapters/baasix-adapter.d.ts +12 -0
- package/dist/auth/adapters/baasix-adapter.d.ts.map +1 -0
- package/dist/auth/adapters/baasix-adapter.js +318 -0
- package/dist/auth/adapters/baasix-adapter.js.map +1 -0
- package/dist/auth/adapters/index.d.ts +6 -0
- package/dist/auth/adapters/index.d.ts.map +1 -0
- package/dist/auth/adapters/index.js +5 -0
- package/dist/auth/adapters/index.js.map +1 -0
- package/dist/auth/core.d.ts +73 -0
- package/dist/auth/core.d.ts.map +1 -0
- package/dist/auth/core.js +528 -0
- package/dist/auth/core.js.map +1 -0
- package/dist/auth/index.d.ts +56 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +58 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/oauth2/index.d.ts +5 -0
- package/dist/auth/oauth2/index.d.ts.map +1 -0
- package/dist/auth/oauth2/index.js +5 -0
- package/dist/auth/oauth2/index.js.map +1 -0
- package/dist/auth/oauth2/utils.d.ts +90 -0
- package/dist/auth/oauth2/utils.d.ts.map +1 -0
- package/dist/auth/oauth2/utils.js +167 -0
- package/dist/auth/oauth2/utils.js.map +1 -0
- package/dist/auth/providers/apple.d.ts +28 -0
- package/dist/auth/providers/apple.d.ts.map +1 -0
- package/dist/auth/providers/apple.js +192 -0
- package/dist/auth/providers/apple.js.map +1 -0
- package/dist/auth/providers/credential.d.ts +87 -0
- package/dist/auth/providers/credential.d.ts.map +1 -0
- package/dist/auth/providers/credential.js +162 -0
- package/dist/auth/providers/credential.js.map +1 -0
- package/dist/auth/providers/facebook.d.ts +26 -0
- package/dist/auth/providers/facebook.d.ts.map +1 -0
- package/dist/auth/providers/facebook.js +112 -0
- package/dist/auth/providers/facebook.js.map +1 -0
- package/dist/auth/providers/github.d.ts +29 -0
- package/dist/auth/providers/github.d.ts.map +1 -0
- package/dist/auth/providers/github.js +144 -0
- package/dist/auth/providers/github.js.map +1 -0
- package/dist/auth/providers/google.d.ts +32 -0
- package/dist/auth/providers/google.d.ts.map +1 -0
- package/dist/auth/providers/google.js +145 -0
- package/dist/auth/providers/google.js.map +1 -0
- package/dist/auth/providers/index.d.ts +22 -0
- package/dist/auth/providers/index.d.ts.map +1 -0
- package/dist/auth/providers/index.js +17 -0
- package/dist/auth/providers/index.js.map +1 -0
- package/dist/auth/routes.d.ts +63 -0
- package/dist/auth/routes.d.ts.map +1 -0
- package/dist/auth/routes.js +827 -0
- package/dist/auth/routes.js.map +1 -0
- package/dist/auth/services/index.d.ts +10 -0
- package/dist/auth/services/index.d.ts.map +1 -0
- package/dist/auth/services/index.js +7 -0
- package/dist/auth/services/index.js.map +1 -0
- package/dist/auth/services/session.d.ts +81 -0
- package/dist/auth/services/session.d.ts.map +1 -0
- package/dist/auth/services/session.js +186 -0
- package/dist/auth/services/session.js.map +1 -0
- package/dist/auth/services/token.d.ts +41 -0
- package/dist/auth/services/token.d.ts.map +1 -0
- package/dist/auth/services/token.js +44 -0
- package/dist/auth/services/token.js.map +1 -0
- package/dist/auth/services/verification.d.ts +77 -0
- package/dist/auth/services/verification.d.ts.map +1 -0
- package/dist/auth/services/verification.js +143 -0
- package/dist/auth/services/verification.js.map +1 -0
- package/dist/auth/types.d.ts +318 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +6 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/customTypes/arrays.d.ts +200 -0
- package/dist/customTypes/arrays.d.ts.map +1 -0
- package/dist/customTypes/arrays.js +309 -0
- package/dist/customTypes/arrays.js.map +1 -0
- package/dist/customTypes/index.d.ts +8 -0
- package/dist/customTypes/index.d.ts.map +1 -0
- package/dist/customTypes/index.js +11 -0
- package/dist/customTypes/index.js.map +1 -0
- package/dist/customTypes/postgis.d.ts +146 -0
- package/dist/customTypes/postgis.d.ts.map +1 -0
- package/dist/customTypes/postgis.js +315 -0
- package/dist/customTypes/postgis.js.map +1 -0
- package/dist/customTypes/ranges.d.ts +128 -0
- package/dist/customTypes/ranges.d.ts.map +1 -0
- package/dist/customTypes/ranges.js +257 -0
- package/dist/customTypes/ranges.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations/0.1.0-alpha.0_initial_setup.d.ts +29 -0
- package/dist/migrations/0.1.0-alpha.0_initial_setup.d.ts.map +1 -0
- package/dist/migrations/0.1.0-alpha.0_initial_setup.js +72 -0
- package/dist/migrations/0.1.0-alpha.0_initial_setup.js.map +1 -0
- package/dist/migrations/_example_migration.d.ts +31 -0
- package/dist/migrations/_example_migration.d.ts.map +1 -0
- package/dist/migrations/_example_migration.js +75 -0
- package/dist/migrations/_example_migration.js.map +1 -0
- package/dist/plugins/definePlugin.d.ts +49 -0
- package/dist/plugins/definePlugin.d.ts.map +1 -0
- package/dist/plugins/definePlugin.js +131 -0
- package/dist/plugins/definePlugin.js.map +1 -0
- package/dist/plugins/softDelete.d.ts +179 -0
- package/dist/plugins/softDelete.d.ts.map +1 -0
- package/dist/plugins/softDelete.js +235 -0
- package/dist/plugins/softDelete.js.map +1 -0
- package/dist/routes/auth.route.d.ts +14 -0
- package/dist/routes/auth.route.d.ts.map +1 -0
- package/dist/routes/auth.route.js +421 -0
- package/dist/routes/auth.route.js.map +1 -0
- package/dist/routes/file.route.d.ts +7 -0
- package/dist/routes/file.route.d.ts.map +1 -0
- package/dist/routes/file.route.js +274 -0
- package/dist/routes/file.route.js.map +1 -0
- package/dist/routes/items.route.d.ts +7 -0
- package/dist/routes/items.route.d.ts.map +1 -0
- package/dist/routes/items.route.js +369 -0
- package/dist/routes/items.route.js.map +1 -0
- package/dist/routes/migration.route.d.ts +7 -0
- package/dist/routes/migration.route.d.ts.map +1 -0
- package/dist/routes/migration.route.js +225 -0
- package/dist/routes/migration.route.js.map +1 -0
- package/dist/routes/notification.route.d.ts +7 -0
- package/dist/routes/notification.route.d.ts.map +1 -0
- package/dist/routes/notification.route.js +124 -0
- package/dist/routes/notification.route.js.map +1 -0
- package/dist/routes/openapi.route.d.ts +7 -0
- package/dist/routes/openapi.route.d.ts.map +1 -0
- package/dist/routes/openapi.route.js +2169 -0
- package/dist/routes/openapi.route.js.map +1 -0
- package/dist/routes/permission.route.d.ts +7 -0
- package/dist/routes/permission.route.d.ts.map +1 -0
- package/dist/routes/permission.route.js +158 -0
- package/dist/routes/permission.route.js.map +1 -0
- package/dist/routes/realtime.route.d.ts +21 -0
- package/dist/routes/realtime.route.d.ts.map +1 -0
- package/dist/routes/realtime.route.js +243 -0
- package/dist/routes/realtime.route.js.map +1 -0
- package/dist/routes/reports.route.d.ts +7 -0
- package/dist/routes/reports.route.d.ts.map +1 -0
- package/dist/routes/reports.route.js +95 -0
- package/dist/routes/reports.route.js.map +1 -0
- package/dist/routes/schema.route.d.ts +7 -0
- package/dist/routes/schema.route.d.ts.map +1 -0
- package/dist/routes/schema.route.js +1780 -0
- package/dist/routes/schema.route.js.map +1 -0
- package/dist/routes/settings.route.d.ts +7 -0
- package/dist/routes/settings.route.d.ts.map +1 -0
- package/dist/routes/settings.route.js +154 -0
- package/dist/routes/settings.route.js.map +1 -0
- package/dist/routes/templates.route.d.ts +7 -0
- package/dist/routes/templates.route.d.ts.map +1 -0
- package/dist/routes/templates.route.js +91 -0
- package/dist/routes/templates.route.js.map +1 -0
- package/dist/routes/utils.route.d.ts +7 -0
- package/dist/routes/utils.route.d.ts.map +1 -0
- package/dist/routes/utils.route.js +33 -0
- package/dist/routes/utils.route.js.map +1 -0
- package/dist/routes/workflow.route.d.ts +7 -0
- package/dist/routes/workflow.route.d.ts.map +1 -0
- package/dist/routes/workflow.route.js +787 -0
- package/dist/routes/workflow.route.js.map +1 -0
- package/dist/services/AssetsService.d.ts +39 -0
- package/dist/services/AssetsService.d.ts.map +1 -0
- package/dist/services/AssetsService.js +255 -0
- package/dist/services/AssetsService.js.map +1 -0
- package/dist/services/CacheService.d.ts +169 -0
- package/dist/services/CacheService.d.ts.map +1 -0
- package/dist/services/CacheService.js +722 -0
- package/dist/services/CacheService.js.map +1 -0
- package/dist/services/FilesService.d.ts +30 -0
- package/dist/services/FilesService.d.ts.map +1 -0
- package/dist/services/FilesService.js +268 -0
- package/dist/services/FilesService.js.map +1 -0
- package/dist/services/HooksManager.d.ts +38 -0
- package/dist/services/HooksManager.d.ts.map +1 -0
- package/dist/services/HooksManager.js +165 -0
- package/dist/services/HooksManager.js.map +1 -0
- package/dist/services/ItemsService.d.ts +273 -0
- package/dist/services/ItemsService.d.ts.map +1 -0
- package/dist/services/ItemsService.js +2458 -0
- package/dist/services/ItemsService.js.map +1 -0
- package/dist/services/MailService.d.ts +76 -0
- package/dist/services/MailService.d.ts.map +1 -0
- package/dist/services/MailService.js +585 -0
- package/dist/services/MailService.js.map +1 -0
- package/dist/services/MigrationService.d.ts +243 -0
- package/dist/services/MigrationService.d.ts.map +1 -0
- package/dist/services/MigrationService.js +914 -0
- package/dist/services/MigrationService.js.map +1 -0
- package/dist/services/NotificationService.d.ts +35 -0
- package/dist/services/NotificationService.d.ts.map +1 -0
- package/dist/services/NotificationService.js +159 -0
- package/dist/services/NotificationService.js.map +1 -0
- package/dist/services/PermissionService.d.ts +128 -0
- package/dist/services/PermissionService.d.ts.map +1 -0
- package/dist/services/PermissionService.js +373 -0
- package/dist/services/PermissionService.js.map +1 -0
- package/dist/services/PluginManager.d.ts +138 -0
- package/dist/services/PluginManager.d.ts.map +1 -0
- package/dist/services/PluginManager.js +463 -0
- package/dist/services/PluginManager.js.map +1 -0
- package/dist/services/RealtimeService.d.ts +209 -0
- package/dist/services/RealtimeService.d.ts.map +1 -0
- package/dist/services/RealtimeService.js +978 -0
- package/dist/services/RealtimeService.js.map +1 -0
- package/dist/services/ReportService.d.ts +13 -0
- package/dist/services/ReportService.d.ts.map +1 -0
- package/dist/services/ReportService.js +91 -0
- package/dist/services/ReportService.js.map +1 -0
- package/dist/services/SettingsService.d.ts +60 -0
- package/dist/services/SettingsService.d.ts.map +1 -0
- package/dist/services/SettingsService.js +474 -0
- package/dist/services/SettingsService.js.map +1 -0
- package/dist/services/SocketService.d.ts +129 -0
- package/dist/services/SocketService.d.ts.map +1 -0
- package/dist/services/SocketService.js +600 -0
- package/dist/services/SocketService.js.map +1 -0
- package/dist/services/StatsService.d.ts +10 -0
- package/dist/services/StatsService.d.ts.map +1 -0
- package/dist/services/StatsService.js +40 -0
- package/dist/services/StatsService.js.map +1 -0
- package/dist/services/StorageService.d.ts +20 -0
- package/dist/services/StorageService.d.ts.map +1 -0
- package/dist/services/StorageService.js +164 -0
- package/dist/services/StorageService.js.map +1 -0
- package/dist/services/TasksService.d.ts +74 -0
- package/dist/services/TasksService.d.ts.map +1 -0
- package/dist/services/TasksService.js +404 -0
- package/dist/services/TasksService.js.map +1 -0
- package/dist/services/WorkflowService.d.ts +305 -0
- package/dist/services/WorkflowService.d.ts.map +1 -0
- package/dist/services/WorkflowService.js +1811 -0
- package/dist/services/WorkflowService.js.map +1 -0
- package/dist/templates/logo/logo.png +0 -0
- package/dist/templates/mails/default.liquid +23 -0
- package/dist/types/aggregation.d.ts +40 -0
- package/dist/types/aggregation.d.ts.map +1 -0
- package/dist/types/aggregation.js +6 -0
- package/dist/types/aggregation.js.map +1 -0
- package/dist/types/assets.d.ts +32 -0
- package/dist/types/assets.d.ts.map +1 -0
- package/dist/types/assets.js +6 -0
- package/dist/types/assets.js.map +1 -0
- package/dist/types/auth.d.ts +50 -0
- package/dist/types/auth.d.ts.map +1 -0
- package/dist/types/auth.js +6 -0
- package/dist/types/auth.js.map +1 -0
- package/dist/types/cache.d.ts +47 -0
- package/dist/types/cache.d.ts.map +1 -0
- package/dist/types/cache.js +6 -0
- package/dist/types/cache.js.map +1 -0
- package/dist/types/database.d.ts +16 -0
- package/dist/types/database.d.ts.map +1 -0
- package/dist/types/database.js +6 -0
- package/dist/types/database.js.map +1 -0
- package/dist/types/fields.d.ts +71 -0
- package/dist/types/fields.d.ts.map +1 -0
- package/dist/types/fields.js +6 -0
- package/dist/types/fields.js.map +1 -0
- package/dist/types/files.d.ts +33 -0
- package/dist/types/files.d.ts.map +1 -0
- package/dist/types/files.js +6 -0
- package/dist/types/files.js.map +1 -0
- package/dist/types/hooks.d.ts +29 -0
- package/dist/types/hooks.d.ts.map +1 -0
- package/dist/types/hooks.js +6 -0
- package/dist/types/hooks.js.map +1 -0
- package/dist/types/import-export.d.ts +62 -0
- package/dist/types/import-export.d.ts.map +1 -0
- package/dist/types/import-export.js +6 -0
- package/dist/types/import-export.js.map +1 -0
- package/dist/types/index.d.ts +31 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +58 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/mail.d.ts +34 -0
- package/dist/types/mail.d.ts.map +1 -0
- package/dist/types/mail.js +6 -0
- package/dist/types/mail.js.map +1 -0
- package/dist/types/notifications.d.ts +16 -0
- package/dist/types/notifications.d.ts.map +1 -0
- package/dist/types/notifications.js +6 -0
- package/dist/types/notifications.js.map +1 -0
- package/dist/types/plugin.d.ts +351 -0
- package/dist/types/plugin.d.ts.map +1 -0
- package/dist/types/plugin.js +8 -0
- package/dist/types/plugin.js.map +1 -0
- package/dist/types/query.d.ts +71 -0
- package/dist/types/query.d.ts.map +1 -0
- package/dist/types/query.js +6 -0
- package/dist/types/query.js.map +1 -0
- package/dist/types/relations.d.ts +111 -0
- package/dist/types/relations.d.ts.map +1 -0
- package/dist/types/relations.js +6 -0
- package/dist/types/relations.js.map +1 -0
- package/dist/types/reports.d.ts +17 -0
- package/dist/types/reports.d.ts.map +1 -0
- package/dist/types/reports.js +6 -0
- package/dist/types/reports.js.map +1 -0
- package/dist/types/schema.d.ts +26 -0
- package/dist/types/schema.d.ts.map +1 -0
- package/dist/types/schema.js +6 -0
- package/dist/types/schema.js.map +1 -0
- package/dist/types/seed.d.ts +27 -0
- package/dist/types/seed.d.ts.map +1 -0
- package/dist/types/seed.js +6 -0
- package/dist/types/seed.js.map +1 -0
- package/dist/types/services.d.ts +68 -0
- package/dist/types/services.d.ts.map +1 -0
- package/dist/types/services.js +6 -0
- package/dist/types/services.js.map +1 -0
- package/dist/types/settings.d.ts +36 -0
- package/dist/types/settings.d.ts.map +1 -0
- package/dist/types/settings.js +6 -0
- package/dist/types/settings.js.map +1 -0
- package/dist/types/sockets.d.ts +26 -0
- package/dist/types/sockets.d.ts.map +1 -0
- package/dist/types/sockets.js +6 -0
- package/dist/types/sockets.js.map +1 -0
- package/dist/types/sort.d.ts +25 -0
- package/dist/types/sort.d.ts.map +1 -0
- package/dist/types/sort.js +6 -0
- package/dist/types/sort.js.map +1 -0
- package/dist/types/spatial.d.ts +19 -0
- package/dist/types/spatial.d.ts.map +1 -0
- package/dist/types/spatial.js +6 -0
- package/dist/types/spatial.js.map +1 -0
- package/dist/types/stats.d.ts +21 -0
- package/dist/types/stats.d.ts.map +1 -0
- package/dist/types/stats.js +6 -0
- package/dist/types/stats.js.map +1 -0
- package/dist/types/storage.d.ts +19 -0
- package/dist/types/storage.d.ts.map +1 -0
- package/dist/types/storage.js +6 -0
- package/dist/types/storage.js.map +1 -0
- package/dist/types/tasks.d.ts +14 -0
- package/dist/types/tasks.d.ts.map +1 -0
- package/dist/types/tasks.js +6 -0
- package/dist/types/tasks.js.map +1 -0
- package/dist/types/utils.d.ts +54 -0
- package/dist/types/utils.d.ts.map +1 -0
- package/dist/types/utils.js +6 -0
- package/dist/types/utils.js.map +1 -0
- package/dist/types/workflow.d.ts +17 -0
- package/dist/types/workflow.d.ts.map +1 -0
- package/dist/types/workflow.js +6 -0
- package/dist/types/workflow.js.map +1 -0
- package/dist/utils/aggregationUtils.d.ts +192 -0
- package/dist/utils/aggregationUtils.d.ts.map +1 -0
- package/dist/utils/aggregationUtils.js +450 -0
- package/dist/utils/aggregationUtils.js.map +1 -0
- package/dist/utils/auth.d.ts +93 -0
- package/dist/utils/auth.d.ts.map +1 -0
- package/dist/utils/auth.js +557 -0
- package/dist/utils/auth.js.map +1 -0
- package/dist/utils/cache.d.ts +64 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +464 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/common.d.ts +53 -0
- package/dist/utils/common.d.ts.map +1 -0
- package/dist/utils/common.js +162 -0
- package/dist/utils/common.js.map +1 -0
- package/dist/utils/db.d.ts +101 -0
- package/dist/utils/db.d.ts.map +1 -0
- package/dist/utils/db.js +413 -0
- package/dist/utils/db.js.map +1 -0
- package/dist/utils/dirname.d.ts +30 -0
- package/dist/utils/dirname.d.ts.map +1 -0
- package/dist/utils/dirname.js +95 -0
- package/dist/utils/dirname.js.map +1 -0
- package/dist/utils/dynamicVariableResolver.d.ts +17 -0
- package/dist/utils/dynamicVariableResolver.d.ts.map +1 -0
- package/dist/utils/dynamicVariableResolver.js +262 -0
- package/dist/utils/dynamicVariableResolver.js.map +1 -0
- package/dist/utils/env.d.ts +38 -0
- package/dist/utils/env.d.ts.map +1 -0
- package/dist/utils/env.js +80 -0
- package/dist/utils/env.js.map +1 -0
- package/dist/utils/errorHandler.d.ts +14 -0
- package/dist/utils/errorHandler.d.ts.map +1 -0
- package/dist/utils/errorHandler.js +79 -0
- package/dist/utils/errorHandler.js.map +1 -0
- package/dist/utils/fieldExpansion.d.ts +30 -0
- package/dist/utils/fieldExpansion.d.ts.map +1 -0
- package/dist/utils/fieldExpansion.js +145 -0
- package/dist/utils/fieldExpansion.js.map +1 -0
- package/dist/utils/fieldUtils.d.ts +179 -0
- package/dist/utils/fieldUtils.d.ts.map +1 -0
- package/dist/utils/fieldUtils.js +424 -0
- package/dist/utils/fieldUtils.js.map +1 -0
- package/dist/utils/filterOperators.d.ts +472 -0
- package/dist/utils/filterOperators.d.ts.map +1 -0
- package/dist/utils/filterOperators.js +1229 -0
- package/dist/utils/filterOperators.js.map +1 -0
- package/dist/utils/importUtils.d.ts +127 -0
- package/dist/utils/importUtils.d.ts.map +1 -0
- package/dist/utils/importUtils.js +437 -0
- package/dist/utils/importUtils.js.map +1 -0
- package/dist/utils/index.d.ts +75 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +101 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +41 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +217 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/orderUtils.d.ts +117 -0
- package/dist/utils/orderUtils.d.ts.map +1 -0
- package/dist/utils/orderUtils.js +249 -0
- package/dist/utils/orderUtils.js.map +1 -0
- package/dist/utils/queryBuilder.d.ts +118 -0
- package/dist/utils/queryBuilder.d.ts.map +1 -0
- package/dist/utils/queryBuilder.js +489 -0
- package/dist/utils/queryBuilder.js.map +1 -0
- package/dist/utils/relationLoader.d.ts +65 -0
- package/dist/utils/relationLoader.d.ts.map +1 -0
- package/dist/utils/relationLoader.js +1081 -0
- package/dist/utils/relationLoader.js.map +1 -0
- package/dist/utils/relationPathResolver.d.ts +30 -0
- package/dist/utils/relationPathResolver.d.ts.map +1 -0
- package/dist/utils/relationPathResolver.js +173 -0
- package/dist/utils/relationPathResolver.js.map +1 -0
- package/dist/utils/relationUtils.d.ts +139 -0
- package/dist/utils/relationUtils.d.ts.map +1 -0
- package/dist/utils/relationUtils.js +711 -0
- package/dist/utils/relationUtils.js.map +1 -0
- package/dist/utils/router.d.ts +6 -0
- package/dist/utils/router.d.ts.map +1 -0
- package/dist/utils/router.js +95 -0
- package/dist/utils/router.js.map +1 -0
- package/dist/utils/schema.d.ts +88 -0
- package/dist/utils/schema.d.ts.map +1 -0
- package/dist/utils/schema.js +24 -0
- package/dist/utils/schema.js.map +1 -0
- package/dist/utils/schemaManager.d.ts +238 -0
- package/dist/utils/schemaManager.d.ts.map +1 -0
- package/dist/utils/schemaManager.js +1992 -0
- package/dist/utils/schemaManager.js.map +1 -0
- package/dist/utils/schemaValidator.d.ts +83 -0
- package/dist/utils/schemaValidator.d.ts.map +1 -0
- package/dist/utils/schemaValidator.js +491 -0
- package/dist/utils/schemaValidator.js.map +1 -0
- package/dist/utils/seed.d.ts +45 -0
- package/dist/utils/seed.d.ts.map +1 -0
- package/dist/utils/seed.js +248 -0
- package/dist/utils/seed.js.map +1 -0
- package/dist/utils/sessionCleanup.d.ts +10 -0
- package/dist/utils/sessionCleanup.d.ts.map +1 -0
- package/dist/utils/sessionCleanup.js +49 -0
- package/dist/utils/sessionCleanup.js.map +1 -0
- package/dist/utils/sortUtils.d.ts +117 -0
- package/dist/utils/sortUtils.d.ts.map +1 -0
- package/dist/utils/sortUtils.js +232 -0
- package/dist/utils/sortUtils.js.map +1 -0
- package/dist/utils/spatialUtils.d.ts +244 -0
- package/dist/utils/spatialUtils.d.ts.map +1 -0
- package/dist/utils/spatialUtils.js +359 -0
- package/dist/utils/spatialUtils.js.map +1 -0
- package/dist/utils/systemschema.d.ts +11040 -0
- package/dist/utils/systemschema.d.ts.map +1 -0
- package/dist/utils/systemschema.js +1777 -0
- package/dist/utils/systemschema.js.map +1 -0
- package/dist/utils/tenantUtils.d.ts +34 -0
- package/dist/utils/tenantUtils.d.ts.map +1 -0
- package/dist/utils/tenantUtils.js +124 -0
- package/dist/utils/tenantUtils.js.map +1 -0
- package/dist/utils/typeMapper.d.ts +25 -0
- package/dist/utils/typeMapper.d.ts.map +1 -0
- package/dist/utils/typeMapper.js +282 -0
- package/dist/utils/typeMapper.js.map +1 -0
- package/dist/utils/valueValidator.d.ts +60 -0
- package/dist/utils/valueValidator.d.ts.map +1 -0
- package/dist/utils/valueValidator.js +303 -0
- package/dist/utils/valueValidator.js.map +1 -0
- package/dist/utils/workflow.d.ts +87 -0
- package/dist/utils/workflow.d.ts.map +1 -0
- package/dist/utils/workflow.js +205 -0
- package/dist/utils/workflow.js.map +1 -0
- package/package.json +115 -0
|
@@ -0,0 +1,1811 @@
|
|
|
1
|
+
import { createRequire } from 'module';
|
|
2
|
+
import { getCache } from "../utils/cache.js";
|
|
3
|
+
import ItemsService from "./ItemsService.js";
|
|
4
|
+
import { hooksManager } from "./HooksManager.js";
|
|
5
|
+
import socketService from "./SocketService.js";
|
|
6
|
+
import mailService from "./MailService.js";
|
|
7
|
+
import statsService from "./StatsService.js";
|
|
8
|
+
import schedule from "node-schedule";
|
|
9
|
+
import { schemaManager } from "../utils/schemaManager.js";
|
|
10
|
+
import { getProjectPath } from "../utils/dirname.js";
|
|
11
|
+
/**
|
|
12
|
+
* WorkflowService - Comprehensive workflow execution engine
|
|
13
|
+
* Supports: HTTP requests, data transformation, conditions, loops, service operations
|
|
14
|
+
*/
|
|
15
|
+
class WorkflowService {
|
|
16
|
+
initialized = false;
|
|
17
|
+
scheduledJobs = new Map();
|
|
18
|
+
customModules = new Map(); // Registry for custom modules
|
|
19
|
+
stepProcessors;
|
|
20
|
+
constructor() {
|
|
21
|
+
this.stepProcessors = {
|
|
22
|
+
trigger: this.processTriggerNode.bind(this),
|
|
23
|
+
http: this.processHttpNode.bind(this),
|
|
24
|
+
transform: this.processTransformNode.bind(this),
|
|
25
|
+
condition: this.processConditionNode.bind(this),
|
|
26
|
+
service: this.processServiceNode.bind(this),
|
|
27
|
+
loop: this.processLoopNode.bind(this),
|
|
28
|
+
filter: this.processFilterNode.bind(this),
|
|
29
|
+
aggregate: this.processAggregateNode.bind(this),
|
|
30
|
+
delay: this.processDelayNode.bind(this),
|
|
31
|
+
notification: this.processNotificationNode.bind(this),
|
|
32
|
+
email: this.processEmailNode.bind(this),
|
|
33
|
+
workflow: this.processWorkflowNode.bind(this),
|
|
34
|
+
stats: this.processStatsNode.bind(this),
|
|
35
|
+
file: this.processFileNode.bind(this),
|
|
36
|
+
variable: this.processVariableNode.bind(this),
|
|
37
|
+
script: this.processScriptNode.bind(this),
|
|
38
|
+
try: this.processTryNode.bind(this),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Register a custom module that can be used in script nodes
|
|
43
|
+
* @param moduleName - Name of the module (used in require())
|
|
44
|
+
* @param moduleExport - The module export (function, object, or class)
|
|
45
|
+
* @param options - Optional configuration
|
|
46
|
+
*/
|
|
47
|
+
registerCustomModule(moduleName, moduleExport, options = {}) {
|
|
48
|
+
if (!moduleName || typeof moduleName !== "string") {
|
|
49
|
+
throw new Error("Module name must be a non-empty string");
|
|
50
|
+
}
|
|
51
|
+
if (!moduleExport) {
|
|
52
|
+
throw new Error("Module export is required");
|
|
53
|
+
}
|
|
54
|
+
this.customModules.set(moduleName, {
|
|
55
|
+
name: moduleName,
|
|
56
|
+
export: moduleExport,
|
|
57
|
+
description: options.description || "",
|
|
58
|
+
allowRequire: options.allowRequire !== false, // Default to true
|
|
59
|
+
registeredAt: new Date(),
|
|
60
|
+
});
|
|
61
|
+
console.info(`WorkflowService: Registered custom module "${moduleName}"`);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Unregister a custom module
|
|
65
|
+
* @param moduleName - Name of the module to unregister
|
|
66
|
+
*/
|
|
67
|
+
unregisterCustomModule(moduleName) {
|
|
68
|
+
if (this.customModules.has(moduleName)) {
|
|
69
|
+
this.customModules.delete(moduleName);
|
|
70
|
+
console.info(`WorkflowService: Unregistered custom module "${moduleName}"`);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Get all registered custom modules
|
|
77
|
+
* @returns List of registered modules with metadata
|
|
78
|
+
*/
|
|
79
|
+
getRegisteredModules() {
|
|
80
|
+
return Array.from(this.customModules.values()).map((module) => ({
|
|
81
|
+
name: module.name,
|
|
82
|
+
description: module.description,
|
|
83
|
+
allowRequire: module.allowRequire,
|
|
84
|
+
registeredAt: module.registeredAt,
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Check if a module is available (built-in or custom)
|
|
89
|
+
* @param moduleName - Name of the module
|
|
90
|
+
* @returns boolean
|
|
91
|
+
*/
|
|
92
|
+
isModuleAvailable(moduleName) {
|
|
93
|
+
// Check built-in modules
|
|
94
|
+
const builtInModules = [
|
|
95
|
+
"lodash",
|
|
96
|
+
"_",
|
|
97
|
+
"dayjs",
|
|
98
|
+
"axios",
|
|
99
|
+
"crypto",
|
|
100
|
+
"uuid",
|
|
101
|
+
"joi",
|
|
102
|
+
"validator",
|
|
103
|
+
"bcrypt",
|
|
104
|
+
"jsonwebtoken",
|
|
105
|
+
];
|
|
106
|
+
if (builtInModules.includes(moduleName)) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
// Check custom modules
|
|
110
|
+
return this.customModules.has(moduleName);
|
|
111
|
+
}
|
|
112
|
+
async init() {
|
|
113
|
+
if (this.initialized) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
// Register hooks for workflow triggers
|
|
118
|
+
this.registerWorkflowHooks();
|
|
119
|
+
// Initialize scheduled workflows
|
|
120
|
+
await this.initializeScheduledWorkflows();
|
|
121
|
+
this.initialized = true;
|
|
122
|
+
console.info("WorkflowService initialized successfully");
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
console.warn("WorkflowService: Initialization failed:", error.message);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async ensureInitialized() {
|
|
129
|
+
if (!this.initialized) {
|
|
130
|
+
await this.init();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Create execution record (without starting execution)
|
|
135
|
+
*/
|
|
136
|
+
async createExecutionRecord(workflowId, userId = null, tenantId = null) {
|
|
137
|
+
await this.ensureInitialized();
|
|
138
|
+
const workflowService = new ItemsService("baasix_Workflow");
|
|
139
|
+
const executionService = new ItemsService("baasix_WorkflowExecution");
|
|
140
|
+
// Fetch workflow
|
|
141
|
+
const workflowResult = await workflowService.readOne(workflowId);
|
|
142
|
+
if (!workflowResult) {
|
|
143
|
+
throw new Error(`Workflow ${workflowId} not found`);
|
|
144
|
+
}
|
|
145
|
+
if (workflowResult.status !== "active") {
|
|
146
|
+
throw new Error(`Workflow ${workflowId} is not active`);
|
|
147
|
+
}
|
|
148
|
+
// Create execution record
|
|
149
|
+
const executionId = await executionService.createOne({
|
|
150
|
+
workflow_Id: workflowId,
|
|
151
|
+
status: "queued",
|
|
152
|
+
trigger_data: {},
|
|
153
|
+
context_data: {
|
|
154
|
+
variables: { ...workflowResult.variables },
|
|
155
|
+
trigger: {},
|
|
156
|
+
},
|
|
157
|
+
tenant_Id: tenantId || workflowResult.tenant_Id,
|
|
158
|
+
triggered_by_Id: userId,
|
|
159
|
+
});
|
|
160
|
+
// Read the full execution record to return
|
|
161
|
+
const execution = await executionService.readOne(executionId);
|
|
162
|
+
// Ensure execution record has 'id' property (primary key might have different name)
|
|
163
|
+
execution.id = executionId;
|
|
164
|
+
return execution;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Execute workflow asynchronously using existing execution record
|
|
168
|
+
*/
|
|
169
|
+
async executeWorkflowAsync(workflowId, triggerData = {}, userId = null, tenantId = null, executionId = null) {
|
|
170
|
+
await this.ensureInitialized();
|
|
171
|
+
const workflowService = new ItemsService("baasix_Workflow");
|
|
172
|
+
try {
|
|
173
|
+
// Fetch workflow
|
|
174
|
+
const workflow = await workflowService.readOne(workflowId);
|
|
175
|
+
if (!workflow) {
|
|
176
|
+
throw new Error(`Workflow ${workflowId} not found`);
|
|
177
|
+
}
|
|
178
|
+
// Update trigger data in execution record
|
|
179
|
+
if (executionId) {
|
|
180
|
+
const executionService = new ItemsService("baasix_WorkflowExecution");
|
|
181
|
+
await executionService.updateOne(executionId, { trigger_data: triggerData });
|
|
182
|
+
}
|
|
183
|
+
// Execute asynchronously
|
|
184
|
+
await this.runWorkflowExecution(executionId, workflow, triggerData);
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
console.error("Error executing workflow:", error);
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Execute a workflow (legacy method - creates record and executes)
|
|
193
|
+
*/
|
|
194
|
+
async executeWorkflow(workflowId, triggerData = {}, userId = null, tenantId = null) {
|
|
195
|
+
await this.ensureInitialized();
|
|
196
|
+
const workflowService = new ItemsService("baasix_Workflow");
|
|
197
|
+
const executionService = new ItemsService("baasix_WorkflowExecution");
|
|
198
|
+
try {
|
|
199
|
+
// Fetch workflow
|
|
200
|
+
const workflow = await workflowService.readOne(workflowId);
|
|
201
|
+
if (!workflow) {
|
|
202
|
+
throw new Error(`Workflow ${workflowId} not found`);
|
|
203
|
+
}
|
|
204
|
+
if (workflow.status !== "active") {
|
|
205
|
+
throw new Error(`Workflow ${workflowId} is not active`);
|
|
206
|
+
}
|
|
207
|
+
// Create execution record
|
|
208
|
+
const executionId = await executionService.createOne({
|
|
209
|
+
workflow_Id: workflowId,
|
|
210
|
+
status: "queued",
|
|
211
|
+
trigger_data: triggerData,
|
|
212
|
+
context_data: {
|
|
213
|
+
variables: { ...workflow.variables },
|
|
214
|
+
trigger: triggerData,
|
|
215
|
+
},
|
|
216
|
+
tenant_Id: tenantId || workflow.tenant_Id,
|
|
217
|
+
triggered_by_Id: userId,
|
|
218
|
+
});
|
|
219
|
+
// Read the full execution record to return
|
|
220
|
+
const execution = await executionService.readOne(executionId);
|
|
221
|
+
// Ensure execution record has 'id' property (primary key might have different name)
|
|
222
|
+
execution.id = executionId;
|
|
223
|
+
// Execute asynchronously
|
|
224
|
+
this.runWorkflowExecution(executionId, workflow, triggerData).catch((error) => {
|
|
225
|
+
console.error(`Workflow execution ${executionId} failed:`, error);
|
|
226
|
+
});
|
|
227
|
+
return execution;
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
console.error("Error executing workflow:", error);
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Run workflow execution
|
|
236
|
+
*/
|
|
237
|
+
async runWorkflowExecution(executionId, workflow, triggerData) {
|
|
238
|
+
const executionService = new ItemsService("baasix_WorkflowExecution");
|
|
239
|
+
const logService = new ItemsService("baasix_WorkflowExecutionLog");
|
|
240
|
+
const startTime = Date.now();
|
|
241
|
+
try {
|
|
242
|
+
// Update execution status
|
|
243
|
+
await executionService.updateOne(executionId, {
|
|
244
|
+
status: "running",
|
|
245
|
+
startedAt: new Date(),
|
|
246
|
+
});
|
|
247
|
+
// Emit execution started event
|
|
248
|
+
socketService.emitWorkflowExecutionUpdate(executionId, {
|
|
249
|
+
status: "running",
|
|
250
|
+
});
|
|
251
|
+
const { nodes, edges } = workflow.flow_data;
|
|
252
|
+
// Initialize context with workflow variables and trigger data
|
|
253
|
+
const context = {
|
|
254
|
+
variables: { ...workflow.variables },
|
|
255
|
+
trigger: triggerData,
|
|
256
|
+
outputs: {}, // Store outputs from each node
|
|
257
|
+
};
|
|
258
|
+
// Find start node (trigger node)
|
|
259
|
+
const startNode = nodes.find((n) => n.type === "trigger");
|
|
260
|
+
if (!startNode) {
|
|
261
|
+
throw new Error("No trigger node found in workflow");
|
|
262
|
+
}
|
|
263
|
+
// Execute workflow graph
|
|
264
|
+
await this.executeNode(startNode, nodes, edges, context, executionId, logService);
|
|
265
|
+
// Mark execution as completed
|
|
266
|
+
const duration = Date.now() - startTime;
|
|
267
|
+
await executionService.updateOne(executionId, {
|
|
268
|
+
status: "completed",
|
|
269
|
+
completedAt: new Date(),
|
|
270
|
+
durationMs: duration,
|
|
271
|
+
result_data: context.outputs,
|
|
272
|
+
});
|
|
273
|
+
// Emit execution completed event
|
|
274
|
+
socketService.emitWorkflowExecutionComplete(executionId, {
|
|
275
|
+
status: "completed",
|
|
276
|
+
duration,
|
|
277
|
+
});
|
|
278
|
+
console.info(`Workflow execution ${executionId} completed in ${duration}ms`);
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
const duration = Date.now() - startTime;
|
|
282
|
+
await executionService.updateOne(executionId, {
|
|
283
|
+
status: "failed",
|
|
284
|
+
completedAt: new Date(),
|
|
285
|
+
durationMs: duration,
|
|
286
|
+
errorMessage: error.message,
|
|
287
|
+
});
|
|
288
|
+
// Emit execution failed event
|
|
289
|
+
socketService.emitWorkflowExecutionComplete(executionId, {
|
|
290
|
+
status: "failed",
|
|
291
|
+
error: error.message,
|
|
292
|
+
});
|
|
293
|
+
console.error(`Workflow execution ${executionId} failed:`, error);
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Execute a single node and its descendants
|
|
299
|
+
*/
|
|
300
|
+
async executeNode(node, allNodes, edges, context, executionId, LogService) {
|
|
301
|
+
const nodeStartTime = Date.now();
|
|
302
|
+
let logId;
|
|
303
|
+
try {
|
|
304
|
+
// Emit node started event
|
|
305
|
+
socketService.emitWorkflowExecutionUpdate(executionId, {
|
|
306
|
+
currentNodeId: node.id,
|
|
307
|
+
nodeId: node.id,
|
|
308
|
+
nodeStatus: "running",
|
|
309
|
+
inputData: {
|
|
310
|
+
trigger: context.trigger,
|
|
311
|
+
variables: context.variables,
|
|
312
|
+
loop: context.loop,
|
|
313
|
+
error: context.error,
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
// Create log entry
|
|
317
|
+
logId = await LogService.createOne({
|
|
318
|
+
execution_Id: executionId,
|
|
319
|
+
nodeId: node.id,
|
|
320
|
+
nodeType: node.type,
|
|
321
|
+
nodeLabel: node.data?.label || node.type,
|
|
322
|
+
status: "running",
|
|
323
|
+
inputData: {
|
|
324
|
+
trigger: context.trigger,
|
|
325
|
+
variables: context.variables,
|
|
326
|
+
loop: context.loop,
|
|
327
|
+
error: context.error,
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
// Process node based on type
|
|
331
|
+
const processor = this.stepProcessors[node.type];
|
|
332
|
+
if (!processor) {
|
|
333
|
+
throw new Error(`Unknown node type: ${node.type}`);
|
|
334
|
+
}
|
|
335
|
+
const result = await processor(node, context, allNodes, edges, executionId, LogService);
|
|
336
|
+
// Store node output in context
|
|
337
|
+
context.outputs[node.id] = result;
|
|
338
|
+
// Update log with success - but only if not already in a final state
|
|
339
|
+
const nodeDuration = Date.now() - nodeStartTime;
|
|
340
|
+
const currentLog = await LogService.readOne(logId);
|
|
341
|
+
if (currentLog && currentLog.status !== "success" && currentLog.status !== "failed") {
|
|
342
|
+
await LogService.updateOne(logId, {
|
|
343
|
+
status: "success",
|
|
344
|
+
outputData: result,
|
|
345
|
+
durationMs: nodeDuration,
|
|
346
|
+
});
|
|
347
|
+
// Only emit node completed event if we actually updated the status
|
|
348
|
+
socketService.emitWorkflowExecutionUpdate(executionId, {
|
|
349
|
+
nodeId: node.id,
|
|
350
|
+
nodeStatus: "completed",
|
|
351
|
+
outputData: result,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
// For try/loop/condition nodes, edges are handled internally by the processor
|
|
355
|
+
// Don't auto-execute their edges
|
|
356
|
+
if (node.type === "try" || node.type === "loop" || node.type === "condition") {
|
|
357
|
+
return result;
|
|
358
|
+
}
|
|
359
|
+
// Find and execute next nodes
|
|
360
|
+
const nextEdges = edges.filter((e) => e.source === node.id);
|
|
361
|
+
for (const edge of nextEdges) {
|
|
362
|
+
// For condition nodes, check the sourceHandle (true/false branch)
|
|
363
|
+
if (node.type === "condition" && edge.sourceHandle) {
|
|
364
|
+
const conditionResult = result.conditionMet;
|
|
365
|
+
// Only follow the edge that matches the condition result
|
|
366
|
+
if (edge.sourceHandle === "true" && !conditionResult) {
|
|
367
|
+
continue; // Skip true branch if condition is false
|
|
368
|
+
}
|
|
369
|
+
if (edge.sourceHandle === "false" && conditionResult) {
|
|
370
|
+
continue; // Skip false branch if condition is true
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
// Check if edge has a custom condition
|
|
374
|
+
if (edge.data?.condition) {
|
|
375
|
+
const conditionMet = this.evaluateCondition(edge.data.condition, context);
|
|
376
|
+
if (!conditionMet) {
|
|
377
|
+
continue; // Skip this edge
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const nextNode = allNodes.find((n) => n.id === edge.target);
|
|
381
|
+
if (nextNode) {
|
|
382
|
+
await this.executeNode(nextNode, allNodes, edges, context, executionId, LogService);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return result;
|
|
386
|
+
}
|
|
387
|
+
catch (error) {
|
|
388
|
+
const nodeDuration = Date.now() - nodeStartTime;
|
|
389
|
+
// Update the log we created earlier (using logId from creation)
|
|
390
|
+
try {
|
|
391
|
+
if (logId) {
|
|
392
|
+
const currentLog = await LogService.readOne(logId);
|
|
393
|
+
// Only update if not already in a final state (success or failed)
|
|
394
|
+
if (currentLog && currentLog.status !== "success" && currentLog.status !== "failed") {
|
|
395
|
+
await LogService.updateOne(logId, {
|
|
396
|
+
status: "failed",
|
|
397
|
+
errorMessage: error.message,
|
|
398
|
+
durationMs: nodeDuration,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
catch (logError) {
|
|
404
|
+
// If log update fails, just log it but don't fail the workflow
|
|
405
|
+
console.warn(`Failed to update log for node ${node.id}:`, logError);
|
|
406
|
+
}
|
|
407
|
+
// Emit node failed event
|
|
408
|
+
socketService.emitWorkflowExecutionUpdate(executionId, {
|
|
409
|
+
nodeId: node.id,
|
|
410
|
+
nodeStatus: "failed",
|
|
411
|
+
error: error.message,
|
|
412
|
+
});
|
|
413
|
+
throw error;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Process trigger node
|
|
418
|
+
*/
|
|
419
|
+
async processTriggerNode(_node, context) {
|
|
420
|
+
// Trigger node just passes data through
|
|
421
|
+
return context.trigger;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Process HTTP request node
|
|
425
|
+
*/
|
|
426
|
+
async processHttpNode(node, context) {
|
|
427
|
+
const { url, method = "GET", headers = {}, body, timeout = 30000 } = node.data;
|
|
428
|
+
// Replace template variables in URL and body
|
|
429
|
+
const processedUrl = this.replaceTemplateVariables(url, context);
|
|
430
|
+
const processedHeaders = this.replaceTemplateVariables(headers, context);
|
|
431
|
+
const processedBody = body ? this.replaceTemplateVariables(body, context) : undefined;
|
|
432
|
+
try {
|
|
433
|
+
const controller = new AbortController();
|
|
434
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
435
|
+
const options = {
|
|
436
|
+
method: method.toUpperCase(),
|
|
437
|
+
headers: {
|
|
438
|
+
"Content-Type": "application/json",
|
|
439
|
+
...processedHeaders,
|
|
440
|
+
},
|
|
441
|
+
signal: controller.signal,
|
|
442
|
+
};
|
|
443
|
+
if (processedBody && ["POST", "PUT", "PATCH"].includes(options.method)) {
|
|
444
|
+
options.body = typeof processedBody === "string" ? processedBody : JSON.stringify(processedBody);
|
|
445
|
+
}
|
|
446
|
+
const response = await fetch(processedUrl, options);
|
|
447
|
+
clearTimeout(timeoutId);
|
|
448
|
+
const contentType = response.headers.get("content-type");
|
|
449
|
+
let responseData;
|
|
450
|
+
if (contentType && contentType.includes("application/json")) {
|
|
451
|
+
responseData = await response.json();
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
responseData = await response.text();
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
status: response.status,
|
|
458
|
+
statusText: response.statusText,
|
|
459
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
460
|
+
data: responseData,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
catch (error) {
|
|
464
|
+
throw new Error(`HTTP request failed: ${error.message}`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Process transformation node
|
|
469
|
+
*/
|
|
470
|
+
async processTransformNode(node, context) {
|
|
471
|
+
const { transformType, script, mapping } = node.data;
|
|
472
|
+
try {
|
|
473
|
+
if (transformType === "script") {
|
|
474
|
+
// Execute JavaScript transformation
|
|
475
|
+
return this.executeTransformScript(script, context);
|
|
476
|
+
}
|
|
477
|
+
else if (transformType === "mapping") {
|
|
478
|
+
// Apply field mapping
|
|
479
|
+
return this.applyFieldMapping(mapping, context);
|
|
480
|
+
}
|
|
481
|
+
throw new Error(`Unknown transform type: ${transformType}`);
|
|
482
|
+
}
|
|
483
|
+
catch (error) {
|
|
484
|
+
throw new Error(`Transform failed: ${error.message}`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Execute JavaScript transformation script
|
|
489
|
+
*/
|
|
490
|
+
executeTransformScript(script, context) {
|
|
491
|
+
try {
|
|
492
|
+
// Create a safe execution context
|
|
493
|
+
const func = new Function("context", "data", "trigger", "outputs", "loop", "variables", "error", script);
|
|
494
|
+
return func(context, context.outputs[Object.keys(context.outputs).pop() || ""], // Last output
|
|
495
|
+
context.trigger, context.outputs, context.loop || null, // Loop context (null if not in a loop)
|
|
496
|
+
context.variables || {}, context.error || null // Error context (null if not in catch block)
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
catch (error) {
|
|
500
|
+
throw new Error(`Script execution failed: ${error.message}`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Apply field mapping transformation
|
|
505
|
+
*/
|
|
506
|
+
applyFieldMapping(mapping, context) {
|
|
507
|
+
const result = {};
|
|
508
|
+
const lastOutput = context.outputs[Object.keys(context.outputs).pop() || ""];
|
|
509
|
+
for (const [targetField, sourceExpression] of Object.entries(mapping)) {
|
|
510
|
+
result[targetField] = this.evaluateExpression(sourceExpression, context, lastOutput);
|
|
511
|
+
}
|
|
512
|
+
return result;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Process condition node
|
|
516
|
+
*/
|
|
517
|
+
async processConditionNode(node, context, allNodes, edges, executionId, LogService) {
|
|
518
|
+
const { conditions, operator = "AND" } = node.data;
|
|
519
|
+
const results = conditions.map((condition) => {
|
|
520
|
+
return this.evaluateCondition(condition, context);
|
|
521
|
+
});
|
|
522
|
+
let conditionMet = false;
|
|
523
|
+
if (operator === "AND") {
|
|
524
|
+
conditionMet = results.every((r) => r);
|
|
525
|
+
}
|
|
526
|
+
else if (operator === "OR") {
|
|
527
|
+
conditionMet = results.some((r) => r);
|
|
528
|
+
}
|
|
529
|
+
// Find edges for true and false branches
|
|
530
|
+
const trueEdges = edges.filter((e) => e.source === node.id && e.sourceHandle === "true");
|
|
531
|
+
const falseEdges = edges.filter((e) => e.source === node.id && e.sourceHandle === "false");
|
|
532
|
+
// Execute the appropriate branch based on condition
|
|
533
|
+
const branchEdges = conditionMet ? trueEdges : falseEdges;
|
|
534
|
+
for (const edge of branchEdges) {
|
|
535
|
+
const nextNode = allNodes.find((n) => n.id === edge.target);
|
|
536
|
+
if (nextNode) {
|
|
537
|
+
await this.executeConditionBranch(node.id, nextNode, allNodes, edges, context, executionId, LogService);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
// After branch completes (via condition-end), execute "done" handle nodes
|
|
541
|
+
const doneEdges = edges.filter((e) => e.source === node.id && e.sourceHandle === "done");
|
|
542
|
+
for (const edge of doneEdges) {
|
|
543
|
+
const nextNode = allNodes.find((n) => n.id === edge.target);
|
|
544
|
+
if (nextNode) {
|
|
545
|
+
await this.executeNode(nextNode, allNodes, edges, context, executionId, LogService);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return { conditionMet, results, branch: conditionMet ? "true" : "false" };
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Execute a condition branch
|
|
552
|
+
* Executes nodes until they connect back to the condition's "condition-end" handle
|
|
553
|
+
*/
|
|
554
|
+
async executeConditionBranch(conditionNodeId, startNode, allNodes, edges, context, executionId, LogService) {
|
|
555
|
+
const executedInBranch = new Set();
|
|
556
|
+
const executeNodeInBranch = async (currentNode) => {
|
|
557
|
+
if (executedInBranch.has(currentNode.id)) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
executedInBranch.add(currentNode.id);
|
|
561
|
+
// Execute the node
|
|
562
|
+
const result = await this.executeSingleNode(currentNode, context, executionId, LogService, allNodes, edges);
|
|
563
|
+
context.outputs[currentNode.id] = result;
|
|
564
|
+
// Find outgoing edges
|
|
565
|
+
const outgoingEdges = edges.filter((e) => e.source === currentNode.id);
|
|
566
|
+
for (const edge of outgoingEdges) {
|
|
567
|
+
// Check if this edge connects back to the condition node's "condition-end" handle
|
|
568
|
+
if (edge.target === conditionNodeId && edge.targetHandle === "condition-end") {
|
|
569
|
+
// Branch complete, return to condition node
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
// Continue executing the branch
|
|
573
|
+
const nextNode = allNodes.find((n) => n.id === edge.target);
|
|
574
|
+
if (nextNode) {
|
|
575
|
+
await executeNodeInBranch(nextNode);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
await executeNodeInBranch(startNode);
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Evaluate a single condition
|
|
583
|
+
*/
|
|
584
|
+
evaluateCondition(condition, context) {
|
|
585
|
+
const { field, operator, value } = condition;
|
|
586
|
+
const fieldValue = this.evaluateExpression(field, context);
|
|
587
|
+
switch (operator) {
|
|
588
|
+
case "equals":
|
|
589
|
+
case "==":
|
|
590
|
+
return fieldValue == value;
|
|
591
|
+
case "not_equals":
|
|
592
|
+
case "!=":
|
|
593
|
+
return fieldValue != value;
|
|
594
|
+
case "greater_than":
|
|
595
|
+
case ">":
|
|
596
|
+
return fieldValue > value;
|
|
597
|
+
case "less_than":
|
|
598
|
+
case "<":
|
|
599
|
+
return fieldValue < value;
|
|
600
|
+
case "greater_or_equal":
|
|
601
|
+
case ">=":
|
|
602
|
+
return fieldValue >= value;
|
|
603
|
+
case "less_or_equal":
|
|
604
|
+
case "<=":
|
|
605
|
+
return fieldValue <= value;
|
|
606
|
+
case "contains":
|
|
607
|
+
return String(fieldValue).includes(value);
|
|
608
|
+
case "not_contains":
|
|
609
|
+
return !String(fieldValue).includes(value);
|
|
610
|
+
case "starts_with":
|
|
611
|
+
return String(fieldValue).startsWith(value);
|
|
612
|
+
case "ends_with":
|
|
613
|
+
return String(fieldValue).endsWith(value);
|
|
614
|
+
case "is_empty":
|
|
615
|
+
return !fieldValue || fieldValue.length === 0;
|
|
616
|
+
case "is_not_empty":
|
|
617
|
+
return fieldValue && fieldValue.length > 0;
|
|
618
|
+
case "in":
|
|
619
|
+
return Array.isArray(value) && value.includes(fieldValue);
|
|
620
|
+
case "not_in":
|
|
621
|
+
return Array.isArray(value) && !value.includes(fieldValue);
|
|
622
|
+
default:
|
|
623
|
+
return false;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Process service operation node
|
|
628
|
+
*/
|
|
629
|
+
async processServiceNode(node, context) {
|
|
630
|
+
const { operation, collection, itemId, data, filter, sort, limit, bypassPermissions = false, executeAsAnonymous = false, } = node.data;
|
|
631
|
+
const itemsService = new ItemsService(collection, {
|
|
632
|
+
accountability: executeAsAnonymous ? undefined : context.accountability,
|
|
633
|
+
});
|
|
634
|
+
// Parse data if it's a JSON string
|
|
635
|
+
let parsedData = data;
|
|
636
|
+
if (typeof data === "string") {
|
|
637
|
+
try {
|
|
638
|
+
parsedData = JSON.parse(data);
|
|
639
|
+
}
|
|
640
|
+
catch (e) {
|
|
641
|
+
// If parsing fails, keep as string
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
const processedData = parsedData ? this.replaceTemplateVariables(parsedData, context) : undefined;
|
|
645
|
+
// Parse filter if it's a JSON string
|
|
646
|
+
let parsedFilter = filter;
|
|
647
|
+
if (typeof filter === "string") {
|
|
648
|
+
try {
|
|
649
|
+
parsedFilter = JSON.parse(filter);
|
|
650
|
+
}
|
|
651
|
+
catch (e) {
|
|
652
|
+
// If parsing fails, keep as string
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
const processedFilter = parsedFilter ? this.replaceTemplateVariables(parsedFilter, context) : undefined;
|
|
656
|
+
try {
|
|
657
|
+
switch (operation) {
|
|
658
|
+
case "create": {
|
|
659
|
+
const created = await itemsService.createOne(processedData, { bypassPermissions });
|
|
660
|
+
return created;
|
|
661
|
+
}
|
|
662
|
+
case "read": {
|
|
663
|
+
if (itemId) {
|
|
664
|
+
const item = await itemsService.readOne(this.evaluateExpression(itemId, context), {}, bypassPermissions);
|
|
665
|
+
return item;
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
const items = await itemsService.readByQuery({
|
|
669
|
+
filter: processedFilter,
|
|
670
|
+
sort,
|
|
671
|
+
limit,
|
|
672
|
+
}, bypassPermissions);
|
|
673
|
+
return items;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
case "update": {
|
|
677
|
+
const updated = await itemsService.updateOne(this.evaluateExpression(itemId, context), processedData, { bypassPermissions });
|
|
678
|
+
return updated;
|
|
679
|
+
}
|
|
680
|
+
case "delete": {
|
|
681
|
+
const deleted = await itemsService.deleteOne(this.evaluateExpression(itemId, context), { bypassPermissions });
|
|
682
|
+
return deleted;
|
|
683
|
+
}
|
|
684
|
+
default:
|
|
685
|
+
throw new Error(`Unknown service operation: ${operation}`);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
catch (error) {
|
|
689
|
+
throw new Error(`Service operation failed: ${error.message}`);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Process loop node
|
|
694
|
+
*/
|
|
695
|
+
async processLoopNode(node, context, allNodes, edges, executionId, LogService) {
|
|
696
|
+
const { loopType, arraySource, startIndex = 0, endIndex, maxIterations = 1000 } = node.data;
|
|
697
|
+
let items = [];
|
|
698
|
+
if (loopType === "array") {
|
|
699
|
+
// Get array from context
|
|
700
|
+
items = this.evaluateExpression(arraySource, context);
|
|
701
|
+
if (!Array.isArray(items)) {
|
|
702
|
+
throw new Error("Loop source is not an array");
|
|
703
|
+
}
|
|
704
|
+
// Apply slice if indices specified
|
|
705
|
+
if (endIndex !== undefined) {
|
|
706
|
+
items = items.slice(startIndex, endIndex);
|
|
707
|
+
}
|
|
708
|
+
else if (startIndex > 0) {
|
|
709
|
+
items = items.slice(startIndex);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
else if (loopType === "count") {
|
|
713
|
+
// Create array of indices
|
|
714
|
+
const count = Math.min(endIndex || maxIterations, maxIterations);
|
|
715
|
+
items = Array.from({ length: count }, (_, i) => i + startIndex);
|
|
716
|
+
}
|
|
717
|
+
// Limit iterations
|
|
718
|
+
items = items.slice(0, maxIterations);
|
|
719
|
+
const results = [];
|
|
720
|
+
// Find loop body entry edges (from "loop" source handle)
|
|
721
|
+
const loopBodyEdges = edges.filter((e) => e.source === node.id && e.sourceHandle === "loop");
|
|
722
|
+
// Execute loop iterations
|
|
723
|
+
for (let i = 0; i < items.length; i++) {
|
|
724
|
+
// Create loop iteration context
|
|
725
|
+
const loopContext = {
|
|
726
|
+
...context,
|
|
727
|
+
loop: {
|
|
728
|
+
index: i,
|
|
729
|
+
item: items[i],
|
|
730
|
+
total: items.length,
|
|
731
|
+
},
|
|
732
|
+
};
|
|
733
|
+
// Execute loop body for each item
|
|
734
|
+
const iterationResult = await this.executeLoopIteration(node.id, loopBodyEdges, allNodes, edges, loopContext, executionId, LogService);
|
|
735
|
+
results.push(iterationResult);
|
|
736
|
+
}
|
|
737
|
+
// After all iterations complete, execute "done" handle nodes
|
|
738
|
+
const doneEdges = edges.filter((e) => e.source === node.id && e.sourceHandle === "done");
|
|
739
|
+
for (const edge of doneEdges) {
|
|
740
|
+
const nextNode = allNodes.find((n) => n.id === edge.target);
|
|
741
|
+
if (nextNode) {
|
|
742
|
+
await this.executeNode(nextNode, allNodes, edges, context, executionId, LogService);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
return { items: results, count: results.length };
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Execute a single loop iteration
|
|
749
|
+
* Executes nodes until they connect back to the loop's "loop-end" handle
|
|
750
|
+
*/
|
|
751
|
+
async executeLoopIteration(loopNodeId, loopBodyEdges, allNodes, edges, context, executionId, LogService) {
|
|
752
|
+
const iterationResults = {};
|
|
753
|
+
// Track which nodes have been executed in this iteration
|
|
754
|
+
const executedInIteration = new Set();
|
|
755
|
+
// Internal function to execute a node and follow its chain
|
|
756
|
+
const executeNodeInLoop = async (currentNode) => {
|
|
757
|
+
// Prevent re-executing the same node in this iteration
|
|
758
|
+
if (executedInIteration.has(currentNode.id)) {
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
executedInIteration.add(currentNode.id);
|
|
762
|
+
// Execute the node
|
|
763
|
+
const result = await this.executeSingleNode(currentNode, context, executionId, LogService, allNodes, edges);
|
|
764
|
+
// Store result
|
|
765
|
+
iterationResults[currentNode.id] = result;
|
|
766
|
+
context.outputs[currentNode.id] = result;
|
|
767
|
+
// Find outgoing edges from this node
|
|
768
|
+
const outgoingEdges = edges.filter((e) => e.source === currentNode.id);
|
|
769
|
+
for (const edge of outgoingEdges) {
|
|
770
|
+
// Check if this edge connects back to the loop node's "loop-end" handle
|
|
771
|
+
if (edge.target === loopNodeId && edge.targetHandle === "loop-end") {
|
|
772
|
+
// This marks the end of the loop body - don't continue execution
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
// For condition nodes, check sourceHandle
|
|
776
|
+
if (currentNode.type === "condition" && edge.sourceHandle) {
|
|
777
|
+
const conditionResult = result.conditionMet;
|
|
778
|
+
if (edge.sourceHandle === "true" && !conditionResult) {
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
if (edge.sourceHandle === "false" && conditionResult) {
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
// For try-catch nodes, handle based on sourceHandle
|
|
786
|
+
if (currentNode.type === "try" && edge.sourceHandle) {
|
|
787
|
+
// Try-catch logic is handled internally by processTryNode
|
|
788
|
+
// We shouldn't auto-execute edges here for try nodes
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
// Continue executing the chain
|
|
792
|
+
const nextNode = allNodes.find((n) => n.id === edge.target);
|
|
793
|
+
if (nextNode && nextNode.id !== loopNodeId) {
|
|
794
|
+
await executeNodeInLoop(nextNode);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
// Start execution from each loop body entry point
|
|
799
|
+
for (const edge of loopBodyEdges) {
|
|
800
|
+
const startNode = allNodes.find((n) => n.id === edge.target);
|
|
801
|
+
if (startNode) {
|
|
802
|
+
await executeNodeInLoop(startNode);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return iterationResults;
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Execute a single node without following its edges
|
|
809
|
+
* Used by loop iteration to have finer control over execution flow
|
|
810
|
+
*/
|
|
811
|
+
async executeSingleNode(node, context, executionId, LogService, allNodes, edges) {
|
|
812
|
+
const nodeStartTime = Date.now();
|
|
813
|
+
// Create execution log
|
|
814
|
+
const logId = await LogService.createOne({
|
|
815
|
+
execution_Id: executionId,
|
|
816
|
+
nodeId: node.id,
|
|
817
|
+
nodeType: node.type,
|
|
818
|
+
nodeLabel: node.data?.label || node.type,
|
|
819
|
+
status: "running",
|
|
820
|
+
inputData: {
|
|
821
|
+
trigger: context.trigger,
|
|
822
|
+
variables: context.variables,
|
|
823
|
+
loop: context.loop,
|
|
824
|
+
error: context.error,
|
|
825
|
+
},
|
|
826
|
+
});
|
|
827
|
+
try {
|
|
828
|
+
// Get the processor for this node type
|
|
829
|
+
const processor = this.stepProcessors[node.type];
|
|
830
|
+
if (!processor) {
|
|
831
|
+
throw new Error(`Unknown node type: ${node.type}`);
|
|
832
|
+
}
|
|
833
|
+
// Execute the node processor
|
|
834
|
+
// For branching nodes (condition, loop, try), pass all parameters
|
|
835
|
+
// For other nodes, just pass node and context
|
|
836
|
+
const branchingNodeTypes = ['condition', 'loop', 'try'];
|
|
837
|
+
let result;
|
|
838
|
+
if (branchingNodeTypes.includes(node.type) && allNodes && edges) {
|
|
839
|
+
result = await processor(node, context, allNodes, edges, executionId, LogService);
|
|
840
|
+
}
|
|
841
|
+
else {
|
|
842
|
+
result = await processor(node, context);
|
|
843
|
+
}
|
|
844
|
+
// Update log with success - but only if not already in a final state
|
|
845
|
+
const nodeDuration = Date.now() - nodeStartTime;
|
|
846
|
+
const currentLog = await LogService.readOne(logId);
|
|
847
|
+
if (currentLog && currentLog.status !== "success" && currentLog.status !== "failed") {
|
|
848
|
+
await LogService.updateOne(logId, {
|
|
849
|
+
status: "success",
|
|
850
|
+
outputData: result,
|
|
851
|
+
durationMs: nodeDuration,
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
return result;
|
|
855
|
+
}
|
|
856
|
+
catch (error) {
|
|
857
|
+
const nodeDuration = Date.now() - nodeStartTime;
|
|
858
|
+
// Only update if not already in a final state (success or failed)
|
|
859
|
+
const currentLog = await LogService.readOne(logId);
|
|
860
|
+
if (currentLog && currentLog.status !== "success" && currentLog.status !== "failed") {
|
|
861
|
+
await LogService.updateOne(logId, {
|
|
862
|
+
status: "failed",
|
|
863
|
+
errorMessage: error.message,
|
|
864
|
+
durationMs: nodeDuration,
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
throw error;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Process filter node
|
|
872
|
+
*/
|
|
873
|
+
async processFilterNode(node, context) {
|
|
874
|
+
const { arraySource, conditions, operator = "AND" } = node.data;
|
|
875
|
+
const array = this.evaluateExpression(arraySource, context);
|
|
876
|
+
if (!Array.isArray(array)) {
|
|
877
|
+
throw new Error("Filter source is not an array");
|
|
878
|
+
}
|
|
879
|
+
const filtered = array.filter((item) => {
|
|
880
|
+
const itemContext = { ...context, current: item };
|
|
881
|
+
const results = conditions.map((cond) => this.evaluateCondition(cond, itemContext));
|
|
882
|
+
if (operator === "AND") {
|
|
883
|
+
return results.every((r) => r);
|
|
884
|
+
}
|
|
885
|
+
else {
|
|
886
|
+
return results.some((r) => r);
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
return { items: filtered, count: filtered.length };
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Process aggregate node
|
|
893
|
+
*/
|
|
894
|
+
async processAggregateNode(node, context) {
|
|
895
|
+
const { arraySource, operation, field } = node.data;
|
|
896
|
+
const array = this.evaluateExpression(arraySource, context);
|
|
897
|
+
if (!Array.isArray(array)) {
|
|
898
|
+
throw new Error("Aggregate source is not an array");
|
|
899
|
+
}
|
|
900
|
+
const values = field ? array.map((item) => item[field]) : array;
|
|
901
|
+
switch (operation) {
|
|
902
|
+
case "count":
|
|
903
|
+
return { result: array.length };
|
|
904
|
+
case "sum":
|
|
905
|
+
return { result: values.reduce((a, b) => a + (Number(b) || 0), 0) };
|
|
906
|
+
case "average":
|
|
907
|
+
return { result: values.reduce((a, b) => a + (Number(b) || 0), 0) / values.length };
|
|
908
|
+
case "min":
|
|
909
|
+
return { result: Math.min(...values) };
|
|
910
|
+
case "max":
|
|
911
|
+
return { result: Math.max(...values) };
|
|
912
|
+
case "first":
|
|
913
|
+
return { result: array[0] };
|
|
914
|
+
case "last":
|
|
915
|
+
return { result: array[array.length - 1] };
|
|
916
|
+
default:
|
|
917
|
+
throw new Error(`Unknown aggregate operation: ${operation}`);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Process delay node
|
|
922
|
+
*/
|
|
923
|
+
async processDelayNode(node, context) {
|
|
924
|
+
const { delay = 1000 } = node.data;
|
|
925
|
+
const processedDelay = this.evaluateExpression(delay, context);
|
|
926
|
+
await new Promise((resolve) => setTimeout(resolve, Number(processedDelay)));
|
|
927
|
+
return { delayed: processedDelay };
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Process notification node
|
|
931
|
+
*/
|
|
932
|
+
async processNotificationNode(node, context) {
|
|
933
|
+
const { userId, title, message, type = "info", data, bypassPermissions = false, executeAsAnonymous = false, } = node.data;
|
|
934
|
+
const itemsService = new ItemsService("baasix_Notification", {
|
|
935
|
+
accountability: executeAsAnonymous ? undefined : context.accountability,
|
|
936
|
+
});
|
|
937
|
+
const processedUserId = this.evaluateExpression(userId, context);
|
|
938
|
+
const processedTitle = this.replaceTemplateVariables(title, context);
|
|
939
|
+
const processedMessage = this.replaceTemplateVariables(message, context);
|
|
940
|
+
const processedData = data ? this.replaceTemplateVariables(data, context) : {};
|
|
941
|
+
try {
|
|
942
|
+
const notification = await itemsService.createOne({
|
|
943
|
+
userId: processedUserId,
|
|
944
|
+
title: processedTitle,
|
|
945
|
+
message: processedMessage,
|
|
946
|
+
type,
|
|
947
|
+
data: processedData,
|
|
948
|
+
seen: false,
|
|
949
|
+
}, { bypassPermissions });
|
|
950
|
+
return notification;
|
|
951
|
+
}
|
|
952
|
+
catch (error) {
|
|
953
|
+
throw new Error(`Notification failed: ${error.message}`);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Replace template variables in a value (string, object, or array)
|
|
958
|
+
*/
|
|
959
|
+
replaceTemplateVariables(value, context) {
|
|
960
|
+
if (typeof value === "string") {
|
|
961
|
+
// Replace {{variable}} syntax
|
|
962
|
+
return value.replace(/\{\{(.+?)\}\}/g, (match, expression) => {
|
|
963
|
+
const result = this.evaluateExpression(expression.trim(), context);
|
|
964
|
+
return result !== undefined ? result : match;
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
else if (Array.isArray(value)) {
|
|
968
|
+
return value.map((item) => this.replaceTemplateVariables(item, context));
|
|
969
|
+
}
|
|
970
|
+
else if (typeof value === "object" && value !== null) {
|
|
971
|
+
const result = {};
|
|
972
|
+
for (const [key, val] of Object.entries(value)) {
|
|
973
|
+
result[key] = this.replaceTemplateVariables(val, context);
|
|
974
|
+
}
|
|
975
|
+
return result;
|
|
976
|
+
}
|
|
977
|
+
return value;
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Evaluate expression to get value from context
|
|
981
|
+
*/
|
|
982
|
+
evaluateExpression(expression, context, defaultData = null) {
|
|
983
|
+
if (!expression)
|
|
984
|
+
return defaultData;
|
|
985
|
+
// Handle direct context references
|
|
986
|
+
if (typeof expression === "string") {
|
|
987
|
+
// Parse expression to handle both dot and bracket notation
|
|
988
|
+
// e.g., "outputs['service-read'].data" or "outputs.someKey.data"
|
|
989
|
+
const tokens = [];
|
|
990
|
+
let currentToken = "";
|
|
991
|
+
let inBracket = false;
|
|
992
|
+
for (let i = 0; i < expression.length; i++) {
|
|
993
|
+
const char = expression[i];
|
|
994
|
+
if (char === "[") {
|
|
995
|
+
if (currentToken) {
|
|
996
|
+
tokens.push(currentToken);
|
|
997
|
+
currentToken = "";
|
|
998
|
+
}
|
|
999
|
+
inBracket = true;
|
|
1000
|
+
}
|
|
1001
|
+
else if (char === "]") {
|
|
1002
|
+
inBracket = false;
|
|
1003
|
+
}
|
|
1004
|
+
else if (char === "." && !inBracket) {
|
|
1005
|
+
if (currentToken) {
|
|
1006
|
+
tokens.push(currentToken);
|
|
1007
|
+
currentToken = "";
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
else if (char !== "'" && char !== '"') {
|
|
1011
|
+
currentToken += char;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
if (currentToken) {
|
|
1015
|
+
tokens.push(currentToken);
|
|
1016
|
+
}
|
|
1017
|
+
let current = tokens[0] === "trigger"
|
|
1018
|
+
? context.trigger
|
|
1019
|
+
: tokens[0] === "outputs"
|
|
1020
|
+
? context.outputs
|
|
1021
|
+
: tokens[0] === "variables"
|
|
1022
|
+
? context.variables
|
|
1023
|
+
: tokens[0] === "loop"
|
|
1024
|
+
? context.loop
|
|
1025
|
+
: tokens[0] === "current"
|
|
1026
|
+
? context.current
|
|
1027
|
+
: defaultData;
|
|
1028
|
+
for (let i = 1; i < tokens.length && current; i++) {
|
|
1029
|
+
current = current[tokens[i]];
|
|
1030
|
+
}
|
|
1031
|
+
return current !== undefined ? current : defaultData;
|
|
1032
|
+
}
|
|
1033
|
+
return expression;
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Register hooks for workflow triggers
|
|
1037
|
+
*/
|
|
1038
|
+
registerWorkflowHooks() {
|
|
1039
|
+
const hookAfterActions = [
|
|
1040
|
+
"items.read.after",
|
|
1041
|
+
"items.read.one.after",
|
|
1042
|
+
"items.create.after",
|
|
1043
|
+
"items.update.after",
|
|
1044
|
+
"items.delete.after",
|
|
1045
|
+
];
|
|
1046
|
+
for (const action of hookAfterActions) {
|
|
1047
|
+
hooksManager.registerHook("*", action, async (hookData) => {
|
|
1048
|
+
const { collection, accountability, document, id, result, query, data, previousDocument } = hookData;
|
|
1049
|
+
// Trigger workflows with the hook data
|
|
1050
|
+
const workflowResult = await this.triggerWorkflowsByHook(collection, action, {
|
|
1051
|
+
collection,
|
|
1052
|
+
itemId: id,
|
|
1053
|
+
data: document || data,
|
|
1054
|
+
result,
|
|
1055
|
+
query,
|
|
1056
|
+
previousDocument,
|
|
1057
|
+
action,
|
|
1058
|
+
user: accountability?.user,
|
|
1059
|
+
tenant: accountability?.tenant,
|
|
1060
|
+
accountability,
|
|
1061
|
+
});
|
|
1062
|
+
// If workflow modified the data, return the modified hookData
|
|
1063
|
+
// Otherwise return the original hookData
|
|
1064
|
+
if (workflowResult && workflowResult.modifiedData) {
|
|
1065
|
+
return {
|
|
1066
|
+
...hookData,
|
|
1067
|
+
...workflowResult.modifiedData,
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
return hookData;
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
const hookBeforeActions = ["items.read", "items.read.one", "items.create", "items.update", "items.delete"];
|
|
1074
|
+
for (const action of hookBeforeActions) {
|
|
1075
|
+
hooksManager.registerHook("*", action, async (hookData) => {
|
|
1076
|
+
const { collection, accountability, data, id, query } = hookData;
|
|
1077
|
+
// Trigger workflows with the hook data
|
|
1078
|
+
const workflowResult = await this.triggerWorkflowsByHook(collection, action, {
|
|
1079
|
+
collection,
|
|
1080
|
+
itemId: id,
|
|
1081
|
+
data,
|
|
1082
|
+
query,
|
|
1083
|
+
action,
|
|
1084
|
+
user: accountability?.user,
|
|
1085
|
+
tenant: accountability?.tenant,
|
|
1086
|
+
accountability,
|
|
1087
|
+
});
|
|
1088
|
+
// If workflow modified the data, return the modified hookData
|
|
1089
|
+
// Otherwise return the original hookData
|
|
1090
|
+
if (workflowResult && workflowResult.modifiedData) {
|
|
1091
|
+
return {
|
|
1092
|
+
...hookData,
|
|
1093
|
+
...workflowResult.modifiedData,
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
return hookData;
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
console.info("WorkflowService: Registered workflow trigger hooks");
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Trigger workflows by hook event
|
|
1103
|
+
* @returns Object with modifiedData if workflows modified the data, null otherwise
|
|
1104
|
+
*/
|
|
1105
|
+
async triggerWorkflowsByHook(collection, action, data) {
|
|
1106
|
+
try {
|
|
1107
|
+
// Skip workflow triggers for system tables to prevent infinite recursion
|
|
1108
|
+
// When workflow hooks try to read baasix_Workflow table, it would trigger hooks again
|
|
1109
|
+
if (collection.startsWith("baasix_")) {
|
|
1110
|
+
return null;
|
|
1111
|
+
}
|
|
1112
|
+
const workflowService = new ItemsService("baasix_Workflow");
|
|
1113
|
+
const workflowsResult = await workflowService.readByQuery({
|
|
1114
|
+
filter: {
|
|
1115
|
+
status: "active",
|
|
1116
|
+
trigger_type: "hook",
|
|
1117
|
+
trigger_hook_collection: collection,
|
|
1118
|
+
trigger_hook_action: action,
|
|
1119
|
+
},
|
|
1120
|
+
});
|
|
1121
|
+
const workflows = workflowsResult.data || [];
|
|
1122
|
+
// Track if any workflow modified the data
|
|
1123
|
+
let modifiedData = null;
|
|
1124
|
+
for (const workflow of workflows) {
|
|
1125
|
+
// Check role-based access for hook workflows
|
|
1126
|
+
if (workflow.allowed_roles && workflow.allowed_roles.length > 0) {
|
|
1127
|
+
// Get user's role from accountability
|
|
1128
|
+
const userRole = data.accountability?.role;
|
|
1129
|
+
// If no user role, skip this workflow
|
|
1130
|
+
if (!userRole) {
|
|
1131
|
+
console.log(`⏭️ Skipping workflow ${workflow.name} (${workflow.id}) - no user role and role restrictions apply`);
|
|
1132
|
+
continue;
|
|
1133
|
+
}
|
|
1134
|
+
// Check if user's role is in the allowed_roles array
|
|
1135
|
+
const hasAccess = workflow.allowed_roles.includes(userRole);
|
|
1136
|
+
if (!hasAccess) {
|
|
1137
|
+
console.log(`⏭️ Skipping workflow ${workflow.name} (${workflow.id}) - user does not have required role`);
|
|
1138
|
+
continue;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
console.log(`🚀 Triggering workflow: ${workflow.name} (${workflow.id})`);
|
|
1142
|
+
// For "before" hooks, execute synchronously to allow data modification
|
|
1143
|
+
// For "after" hooks, execute asynchronously
|
|
1144
|
+
if (action.indexOf(".after") === -1) {
|
|
1145
|
+
// This is a "before" hook - execute synchronously
|
|
1146
|
+
try {
|
|
1147
|
+
const executionService = new ItemsService("baasix_WorkflowExecution");
|
|
1148
|
+
// Create execution record
|
|
1149
|
+
const executionId = await executionService.createOne({
|
|
1150
|
+
workflow_Id: workflow.id,
|
|
1151
|
+
status: "queued",
|
|
1152
|
+
trigger_data: data,
|
|
1153
|
+
context_data: {
|
|
1154
|
+
variables: { ...workflow.variables },
|
|
1155
|
+
trigger: data,
|
|
1156
|
+
},
|
|
1157
|
+
tenant_Id: data.tenant?.id || workflow.tenant_Id,
|
|
1158
|
+
triggered_by_Id: data.user?.id,
|
|
1159
|
+
});
|
|
1160
|
+
// Execute synchronously and wait for result
|
|
1161
|
+
await this.runWorkflowExecution(executionId, workflow, data);
|
|
1162
|
+
// Get the completed execution with results
|
|
1163
|
+
const completedExecution = await executionService.readOne(executionId);
|
|
1164
|
+
if (completedExecution.status === "completed" && completedExecution.result_data) {
|
|
1165
|
+
modifiedData = modifiedData || {};
|
|
1166
|
+
// Iterate through all node outputs to find hook modifications
|
|
1167
|
+
// Priority: later nodes override earlier ones
|
|
1168
|
+
const nodeEntries = Object.entries(completedExecution.result_data);
|
|
1169
|
+
for (const [nodeId, nodeOutput] of nodeEntries) {
|
|
1170
|
+
// Skip trigger nodes as they just pass through original data
|
|
1171
|
+
if (nodeId.startsWith("trigger-"))
|
|
1172
|
+
continue;
|
|
1173
|
+
if (!nodeOutput || typeof nodeOutput !== "object")
|
|
1174
|
+
continue;
|
|
1175
|
+
// Script nodes wrap their return value in a 'result' key
|
|
1176
|
+
// So if nodeOutput has a 'result' key, extract modifications from it
|
|
1177
|
+
const output = nodeOutput.result || nodeOutput;
|
|
1178
|
+
// Check for specific modification keys in the output
|
|
1179
|
+
// Workflows can return: document, data, query to modify hook data
|
|
1180
|
+
if (output.document !== undefined) {
|
|
1181
|
+
modifiedData.document = output.document;
|
|
1182
|
+
}
|
|
1183
|
+
if (output.data !== undefined) {
|
|
1184
|
+
modifiedData.data = output.data;
|
|
1185
|
+
}
|
|
1186
|
+
if (output.query !== undefined) {
|
|
1187
|
+
modifiedData.query = output.query;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
else if (completedExecution.status === "failed") {
|
|
1192
|
+
console.error(`❌ Workflow ${workflow.name} failed:`, completedExecution.errorMessage);
|
|
1193
|
+
// For before hooks, throw the error to prevent the operation
|
|
1194
|
+
throw new Error(completedExecution.errorMessage || "Workflow execution failed");
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
catch (error) {
|
|
1198
|
+
console.error(`Error executing before hook workflow ${workflow.name}:`, error);
|
|
1199
|
+
// Re-throw the error so it propagates to the operation
|
|
1200
|
+
throw error;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
else {
|
|
1204
|
+
// This is an "after" hook - execute asynchronously (fire and forget)
|
|
1205
|
+
this.executeWorkflow(workflow.id, data, data.user?.id, data.tenant?.id).catch((error) => {
|
|
1206
|
+
console.error(`Error executing after hook workflow ${workflow.name}:`, error);
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
// Return modified data if any workflow made changes
|
|
1211
|
+
return modifiedData ? { modifiedData } : null;
|
|
1212
|
+
}
|
|
1213
|
+
catch (error) {
|
|
1214
|
+
console.error("Error triggering workflows by hook:", error);
|
|
1215
|
+
return null;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Initialize scheduled workflows
|
|
1220
|
+
*/
|
|
1221
|
+
async initializeScheduledWorkflows() {
|
|
1222
|
+
try {
|
|
1223
|
+
// Check if baasix_Workflow table exists before trying to query it
|
|
1224
|
+
// This prevents errors during initial setup or testing
|
|
1225
|
+
try {
|
|
1226
|
+
schemaManager.getTable("baasix_Workflow");
|
|
1227
|
+
}
|
|
1228
|
+
catch (error) {
|
|
1229
|
+
console.info("WorkflowService: baasix_Workflow table not found, skipping scheduled workflow initialization");
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
const workflowService = new ItemsService("baasix_Workflow");
|
|
1233
|
+
const scheduledWorkflowsResult = await workflowService.readByQuery({
|
|
1234
|
+
filter: {
|
|
1235
|
+
status: "active",
|
|
1236
|
+
trigger_type: "schedule",
|
|
1237
|
+
},
|
|
1238
|
+
});
|
|
1239
|
+
const scheduledWorkflows = scheduledWorkflowsResult.data || [];
|
|
1240
|
+
for (const workflow of scheduledWorkflows) {
|
|
1241
|
+
this.scheduleWorkflow(workflow);
|
|
1242
|
+
}
|
|
1243
|
+
console.info(`WorkflowService: Initialized ${scheduledWorkflows.length} scheduled workflows`);
|
|
1244
|
+
}
|
|
1245
|
+
catch (error) {
|
|
1246
|
+
console.error("Error initializing scheduled workflows:", error);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Try to acquire distributed lock for scheduled workflow execution
|
|
1251
|
+
* @param workflowId - Workflow ID
|
|
1252
|
+
* @param lockTimeout - Lock timeout in seconds (default: 300 = 5 minutes)
|
|
1253
|
+
* @returns True if lock acquired, false otherwise
|
|
1254
|
+
*/
|
|
1255
|
+
async tryAcquireScheduledWorkflowLock(workflowId, lockTimeout = 300) {
|
|
1256
|
+
const cache = getCache();
|
|
1257
|
+
const lockKey = `workflow:schedule:lock:${workflowId}`;
|
|
1258
|
+
const lockValue = `${process.pid}-${Date.now()}`;
|
|
1259
|
+
try {
|
|
1260
|
+
const acquired = await cache.setIfNotExists(lockKey, lockValue, lockTimeout);
|
|
1261
|
+
if (acquired) {
|
|
1262
|
+
console.info(`✓ Acquired scheduled workflow lock for: ${workflowId}`);
|
|
1263
|
+
}
|
|
1264
|
+
return acquired;
|
|
1265
|
+
}
|
|
1266
|
+
catch (error) {
|
|
1267
|
+
console.error(`Error acquiring scheduled workflow lock for ${workflowId}:`, error);
|
|
1268
|
+
return false;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Release distributed lock for scheduled workflow
|
|
1273
|
+
* @param workflowId - Workflow ID
|
|
1274
|
+
*/
|
|
1275
|
+
async releaseScheduledWorkflowLock(workflowId) {
|
|
1276
|
+
const cache = getCache();
|
|
1277
|
+
const lockKey = `workflow:schedule:lock:${workflowId}`;
|
|
1278
|
+
try {
|
|
1279
|
+
await cache.delete(lockKey);
|
|
1280
|
+
console.info(`✓ Released scheduled workflow lock for: ${workflowId}`);
|
|
1281
|
+
}
|
|
1282
|
+
catch (error) {
|
|
1283
|
+
console.error(`Error releasing scheduled workflow lock for ${workflowId}:`, error);
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* Schedule a workflow
|
|
1288
|
+
*/
|
|
1289
|
+
scheduleWorkflow(workflow) {
|
|
1290
|
+
const cron = workflow.trigger_cron;
|
|
1291
|
+
if (!cron) {
|
|
1292
|
+
console.warn(`Workflow ${workflow.id} has no cron configuration`);
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
try {
|
|
1296
|
+
// Cancel existing job if any
|
|
1297
|
+
if (this.scheduledJobs.has(workflow.id)) {
|
|
1298
|
+
this.scheduledJobs.get(workflow.id)?.cancel();
|
|
1299
|
+
}
|
|
1300
|
+
// Schedule new job with distributed locking
|
|
1301
|
+
const job = schedule.scheduleJob(cron, async () => {
|
|
1302
|
+
console.info(`⏰ Cron triggered for workflow ${workflow.id}`);
|
|
1303
|
+
// Try to acquire distributed lock (multi-instance safe)
|
|
1304
|
+
const lockAcquired = await this.tryAcquireScheduledWorkflowLock(workflow.id);
|
|
1305
|
+
if (!lockAcquired) {
|
|
1306
|
+
console.info(`⏭️ Skipping workflow ${workflow.id} - another instance is processing it`);
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
try {
|
|
1310
|
+
console.info(`🚀 Executing scheduled workflow ${workflow.id}`);
|
|
1311
|
+
await this.executeWorkflow(workflow.id, { scheduledAt: new Date() });
|
|
1312
|
+
}
|
|
1313
|
+
finally {
|
|
1314
|
+
// Always release lock when done
|
|
1315
|
+
await this.releaseScheduledWorkflowLock(workflow.id);
|
|
1316
|
+
}
|
|
1317
|
+
});
|
|
1318
|
+
if (job) {
|
|
1319
|
+
this.scheduledJobs.set(workflow.id, job);
|
|
1320
|
+
console.info(`Scheduled workflow ${workflow.id} with cron: ${cron}`);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
catch (error) {
|
|
1324
|
+
console.error(`Error scheduling workflow ${workflow.id}:`, error);
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Cancel a scheduled workflow
|
|
1329
|
+
*/
|
|
1330
|
+
cancelScheduledWorkflow(workflowId) {
|
|
1331
|
+
if (this.scheduledJobs.has(workflowId)) {
|
|
1332
|
+
this.scheduledJobs.get(workflowId)?.cancel();
|
|
1333
|
+
this.scheduledJobs.delete(workflowId);
|
|
1334
|
+
console.info(`Cancelled scheduled workflow ${workflowId}`);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Process email node - Send email using MailService
|
|
1339
|
+
*/
|
|
1340
|
+
async processEmailNode(node, context) {
|
|
1341
|
+
const config = node.data || {};
|
|
1342
|
+
// Replace template variables
|
|
1343
|
+
const to = this.replaceTemplateVariables(config.to || "", context);
|
|
1344
|
+
const subject = this.replaceTemplateVariables(config.subject || "", context);
|
|
1345
|
+
const body = this.replaceTemplateVariables(config.body || "", context);
|
|
1346
|
+
const from = this.replaceTemplateVariables(config.from || "", context);
|
|
1347
|
+
// Parse recipients (can be comma-separated)
|
|
1348
|
+
const recipients = to
|
|
1349
|
+
.split(",")
|
|
1350
|
+
.map((email) => email.trim())
|
|
1351
|
+
.filter(Boolean);
|
|
1352
|
+
if (recipients.length === 0) {
|
|
1353
|
+
throw new Error("Email node: No recipients specified");
|
|
1354
|
+
}
|
|
1355
|
+
if (!subject || !body) {
|
|
1356
|
+
throw new Error("Email node: Subject and body are required");
|
|
1357
|
+
}
|
|
1358
|
+
// Send email to each recipient
|
|
1359
|
+
const results = [];
|
|
1360
|
+
for (const recipient of recipients) {
|
|
1361
|
+
try {
|
|
1362
|
+
await mailService.sendMail({
|
|
1363
|
+
to: recipient,
|
|
1364
|
+
from: from || undefined,
|
|
1365
|
+
subject: subject,
|
|
1366
|
+
html: body,
|
|
1367
|
+
});
|
|
1368
|
+
results.push({ recipient, status: "sent" });
|
|
1369
|
+
}
|
|
1370
|
+
catch (error) {
|
|
1371
|
+
results.push({ recipient, status: "failed", error: error.message });
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
return {
|
|
1375
|
+
success: true,
|
|
1376
|
+
sent: results.filter((r) => r.status === "sent").length,
|
|
1377
|
+
failed: results.filter((r) => r.status === "failed").length,
|
|
1378
|
+
results,
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
/**
|
|
1382
|
+
* Process workflow node - Execute another workflow and return its result
|
|
1383
|
+
*/
|
|
1384
|
+
async processWorkflowNode(node, context) {
|
|
1385
|
+
const config = node.data || {};
|
|
1386
|
+
if (!config.workflowId) {
|
|
1387
|
+
throw new Error("Workflow node: workflowId is required");
|
|
1388
|
+
}
|
|
1389
|
+
// Prepare trigger data for the child workflow
|
|
1390
|
+
let triggerData = {};
|
|
1391
|
+
if (config.passData) {
|
|
1392
|
+
// Pass specific data or entire context
|
|
1393
|
+
if (config.dataMapping) {
|
|
1394
|
+
// Map specific fields
|
|
1395
|
+
for (const [key, value] of Object.entries(config.dataMapping)) {
|
|
1396
|
+
triggerData[key] = this.replaceTemplateVariables(value, context);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
else {
|
|
1400
|
+
// Pass entire context
|
|
1401
|
+
triggerData = { ...context };
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
// Execute the child workflow
|
|
1405
|
+
const childExecution = await this.executeWorkflow(config.workflowId, triggerData, context.accountability?.user?.id, context.accountability?.tenant?.id);
|
|
1406
|
+
// Wait for completion if configured
|
|
1407
|
+
if (config.waitForCompletion !== false) {
|
|
1408
|
+
// Poll for completion (with timeout)
|
|
1409
|
+
const maxWaitTime = config.timeout || 300000; // 5 minutes default
|
|
1410
|
+
const startTime = Date.now();
|
|
1411
|
+
const pollInterval = 1000; // 1 second
|
|
1412
|
+
const executionService = new ItemsService("baasix_WorkflowExecution");
|
|
1413
|
+
while (Date.now() - startTime < maxWaitTime) {
|
|
1414
|
+
const execution = await executionService.readOne(childExecution.id);
|
|
1415
|
+
if (execution.status === "completed") {
|
|
1416
|
+
return {
|
|
1417
|
+
success: true,
|
|
1418
|
+
executionId: childExecution.id,
|
|
1419
|
+
status: "completed",
|
|
1420
|
+
result: execution.result,
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
else if (execution.status === "failed") {
|
|
1424
|
+
throw new Error(`Child workflow failed: ${execution.errorMessage}`);
|
|
1425
|
+
}
|
|
1426
|
+
else if (execution.status === "cancelled") {
|
|
1427
|
+
throw new Error("Child workflow was cancelled");
|
|
1428
|
+
}
|
|
1429
|
+
// Wait before next poll
|
|
1430
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
1431
|
+
}
|
|
1432
|
+
throw new Error("Child workflow execution timed out");
|
|
1433
|
+
}
|
|
1434
|
+
// Don't wait - return execution ID
|
|
1435
|
+
return {
|
|
1436
|
+
success: true,
|
|
1437
|
+
executionId: childExecution.id,
|
|
1438
|
+
status: "started",
|
|
1439
|
+
async: true,
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
/**
|
|
1443
|
+
* Process stats node - Generate statistics using StatsService
|
|
1444
|
+
*/
|
|
1445
|
+
async processStatsNode(node, context) {
|
|
1446
|
+
const config = node.data || {};
|
|
1447
|
+
if (!config.collection) {
|
|
1448
|
+
throw new Error("Stats node: collection is required");
|
|
1449
|
+
}
|
|
1450
|
+
// Use StatsService
|
|
1451
|
+
const collection = this.replaceTemplateVariables(config.collection, context);
|
|
1452
|
+
const groupBy = config.groupBy ? this.replaceTemplateVariables(config.groupBy, context) : null;
|
|
1453
|
+
const operation = config.operation || "count"; // count, sum, avg, min, max
|
|
1454
|
+
// Build filter from template variables
|
|
1455
|
+
let filter = {};
|
|
1456
|
+
if (config.filter) {
|
|
1457
|
+
filter = JSON.parse(this.replaceTemplateVariables(JSON.stringify(config.filter), context));
|
|
1458
|
+
}
|
|
1459
|
+
// Get stats
|
|
1460
|
+
const stats = await statsService.getStats({
|
|
1461
|
+
collection,
|
|
1462
|
+
groupBy: groupBy ? [groupBy] : [],
|
|
1463
|
+
aggregate: [
|
|
1464
|
+
{
|
|
1465
|
+
operation,
|
|
1466
|
+
field: config.field || "id",
|
|
1467
|
+
},
|
|
1468
|
+
],
|
|
1469
|
+
filter,
|
|
1470
|
+
});
|
|
1471
|
+
return {
|
|
1472
|
+
success: true,
|
|
1473
|
+
collection,
|
|
1474
|
+
operation,
|
|
1475
|
+
stats,
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* Process file node - File operations using FilesService
|
|
1480
|
+
*/
|
|
1481
|
+
async processFileNode(node, context) {
|
|
1482
|
+
const config = node.data || {};
|
|
1483
|
+
const operation = config.operation;
|
|
1484
|
+
const bypassPermissions = config.bypassPermissions || false;
|
|
1485
|
+
const executeAsAnonymous = config.executeAsAnonymous || false;
|
|
1486
|
+
if (!operation) {
|
|
1487
|
+
throw new Error("File node: operation is required");
|
|
1488
|
+
}
|
|
1489
|
+
const itemsService = new ItemsService("baasix_File", {
|
|
1490
|
+
accountability: executeAsAnonymous ? undefined : context.accountability,
|
|
1491
|
+
});
|
|
1492
|
+
switch (operation) {
|
|
1493
|
+
case "info": {
|
|
1494
|
+
const fileId = this.replaceTemplateVariables(config.fileId || "", context);
|
|
1495
|
+
if (!fileId) {
|
|
1496
|
+
throw new Error("File node: fileId is required for info operation");
|
|
1497
|
+
}
|
|
1498
|
+
const fileInfo = await itemsService.readOne(fileId, {}, bypassPermissions);
|
|
1499
|
+
return {
|
|
1500
|
+
success: true,
|
|
1501
|
+
operation: "info",
|
|
1502
|
+
file: fileInfo,
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
case "delete": {
|
|
1506
|
+
const deleteFileId = this.replaceTemplateVariables(config.fileId || "", context);
|
|
1507
|
+
if (!deleteFileId) {
|
|
1508
|
+
throw new Error("File node: fileId is required for delete operation");
|
|
1509
|
+
}
|
|
1510
|
+
await itemsService.deleteOne(deleteFileId, { bypassPermissions });
|
|
1511
|
+
return {
|
|
1512
|
+
success: true,
|
|
1513
|
+
operation: "delete",
|
|
1514
|
+
fileId: deleteFileId,
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
case "list": {
|
|
1518
|
+
const query = {
|
|
1519
|
+
limit: config.limit || 100,
|
|
1520
|
+
};
|
|
1521
|
+
if (config.filter) {
|
|
1522
|
+
query.filter = JSON.parse(this.replaceTemplateVariables(JSON.stringify(config.filter), context));
|
|
1523
|
+
}
|
|
1524
|
+
const files = await itemsService.readByQuery(query, bypassPermissions);
|
|
1525
|
+
return {
|
|
1526
|
+
success: true,
|
|
1527
|
+
operation: "list",
|
|
1528
|
+
files: files.data,
|
|
1529
|
+
total: files.meta?.total || files.data.length,
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
default:
|
|
1533
|
+
throw new Error(`File node: unknown operation "${operation}"`);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Process variable node - Set workflow variables
|
|
1538
|
+
*/
|
|
1539
|
+
async processVariableNode(node, context) {
|
|
1540
|
+
const config = node.data || {};
|
|
1541
|
+
if (!config.variables || typeof config.variables !== "object") {
|
|
1542
|
+
throw new Error("Variable node: variables object is required");
|
|
1543
|
+
}
|
|
1544
|
+
// Set variables in context
|
|
1545
|
+
if (!context.variables) {
|
|
1546
|
+
context.variables = {};
|
|
1547
|
+
}
|
|
1548
|
+
// Process each variable with template replacement
|
|
1549
|
+
const setVariables = {};
|
|
1550
|
+
for (const [key, value] of Object.entries(config.variables)) {
|
|
1551
|
+
const resolvedValue = this.replaceTemplateVariables(String(value), context);
|
|
1552
|
+
context.variables[key] = resolvedValue;
|
|
1553
|
+
setVariables[key] = resolvedValue;
|
|
1554
|
+
}
|
|
1555
|
+
return {
|
|
1556
|
+
success: true,
|
|
1557
|
+
variables: setVariables,
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
/**
|
|
1561
|
+
* Process script node - Execute custom JavaScript
|
|
1562
|
+
*/
|
|
1563
|
+
async processScriptNode(node, context) {
|
|
1564
|
+
const config = node.data || {};
|
|
1565
|
+
if (!config.script) {
|
|
1566
|
+
throw new Error("Script node: script code is required");
|
|
1567
|
+
}
|
|
1568
|
+
// Create require function for ES module compatibility
|
|
1569
|
+
// Use a file path that works in both Jest and production
|
|
1570
|
+
const require = createRequire(getProjectPath('package.json'));
|
|
1571
|
+
// Import commonly used libraries
|
|
1572
|
+
const lodash = require("lodash");
|
|
1573
|
+
const dayjs = require("dayjs");
|
|
1574
|
+
const axios = require("axios");
|
|
1575
|
+
// Create safe execution context with available libraries
|
|
1576
|
+
const sandbox = {
|
|
1577
|
+
// Workflow context
|
|
1578
|
+
context: context,
|
|
1579
|
+
trigger: context.trigger,
|
|
1580
|
+
outputs: context.outputs,
|
|
1581
|
+
variables: context.variables,
|
|
1582
|
+
loop: context.loop, // Loop iteration context (if inside a loop)
|
|
1583
|
+
// Built-in JavaScript
|
|
1584
|
+
console: console,
|
|
1585
|
+
JSON: JSON,
|
|
1586
|
+
Math: Math,
|
|
1587
|
+
Date: Date,
|
|
1588
|
+
String: String,
|
|
1589
|
+
Number: Number,
|
|
1590
|
+
Boolean: Boolean,
|
|
1591
|
+
Array: Array,
|
|
1592
|
+
Object: Object,
|
|
1593
|
+
Promise: Promise,
|
|
1594
|
+
setTimeout: setTimeout,
|
|
1595
|
+
setInterval: setInterval,
|
|
1596
|
+
clearTimeout: clearTimeout,
|
|
1597
|
+
clearInterval: clearInterval,
|
|
1598
|
+
// Utility libraries
|
|
1599
|
+
_: lodash, // Lodash for array/object manipulation
|
|
1600
|
+
lodash: lodash, // Alternative name
|
|
1601
|
+
dayjs: dayjs, // Date manipulation
|
|
1602
|
+
axios: axios, // HTTP requests
|
|
1603
|
+
// Helper to dynamically require other modules
|
|
1604
|
+
require: (moduleName) => {
|
|
1605
|
+
// Check if it's a custom registered module
|
|
1606
|
+
if (this.customModules.has(moduleName)) {
|
|
1607
|
+
const customModule = this.customModules.get(moduleName);
|
|
1608
|
+
if (customModule.allowRequire) {
|
|
1609
|
+
return customModule.export;
|
|
1610
|
+
}
|
|
1611
|
+
else {
|
|
1612
|
+
throw new Error(`Custom module "${moduleName}" is registered but require() is disabled for it`);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
// Whitelist of allowed built-in modules for security
|
|
1616
|
+
const allowedModules = [
|
|
1617
|
+
"lodash",
|
|
1618
|
+
"dayjs",
|
|
1619
|
+
"axios",
|
|
1620
|
+
"crypto",
|
|
1621
|
+
"uuid",
|
|
1622
|
+
"joi",
|
|
1623
|
+
"validator",
|
|
1624
|
+
"bcrypt",
|
|
1625
|
+
"jsonwebtoken",
|
|
1626
|
+
];
|
|
1627
|
+
if (!allowedModules.includes(moduleName)) {
|
|
1628
|
+
const customModuleNames = Array.from(this.customModules.keys());
|
|
1629
|
+
const availableModules = [...allowedModules, ...customModuleNames];
|
|
1630
|
+
throw new Error(`Module "${moduleName}" is not allowed. Available modules: ${availableModules.join(", ")}`);
|
|
1631
|
+
}
|
|
1632
|
+
try {
|
|
1633
|
+
return require(moduleName);
|
|
1634
|
+
}
|
|
1635
|
+
catch (error) {
|
|
1636
|
+
throw new Error(`Failed to require module "${moduleName}": ${error.message}`);
|
|
1637
|
+
}
|
|
1638
|
+
},
|
|
1639
|
+
};
|
|
1640
|
+
// Add custom modules directly to sandbox if allowRequire is true
|
|
1641
|
+
for (const [moduleName, moduleInfo] of this.customModules.entries()) {
|
|
1642
|
+
if (moduleInfo.allowRequire) {
|
|
1643
|
+
// Make custom modules available as direct variables in the sandbox
|
|
1644
|
+
// e.g., if module name is "myUtils", it's available as both myUtils and via require('myUtils')
|
|
1645
|
+
sandbox[moduleName] = moduleInfo.export;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
try {
|
|
1649
|
+
// Execute script in sandboxed context
|
|
1650
|
+
const AsyncFunction = Object.getPrototypeOf(async function () { }).constructor;
|
|
1651
|
+
const func = new AsyncFunction(...Object.keys(sandbox), config.script);
|
|
1652
|
+
const result = await func(...Object.values(sandbox));
|
|
1653
|
+
return {
|
|
1654
|
+
success: true,
|
|
1655
|
+
result: result,
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
catch (error) {
|
|
1659
|
+
throw new Error(`Script execution error: ${error.message}`);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
/**
|
|
1663
|
+
* Process try-catch node - Error handling
|
|
1664
|
+
*/
|
|
1665
|
+
async processTryNode(node, context, allNodes, edges, executionId, LogService) {
|
|
1666
|
+
// Find try and catch edges
|
|
1667
|
+
const tryEdges = edges.filter((e) => e.source === node.id && e.sourceHandle === "try");
|
|
1668
|
+
const catchEdges = edges.filter((e) => e.source === node.id && e.sourceHandle === "catch");
|
|
1669
|
+
let executedBranch = "try";
|
|
1670
|
+
try {
|
|
1671
|
+
// Execute try branch
|
|
1672
|
+
for (const edge of tryEdges) {
|
|
1673
|
+
const nextNode = allNodes.find((n) => n.id === edge.target);
|
|
1674
|
+
if (nextNode) {
|
|
1675
|
+
await this.executeTryBranch(node.id, nextNode, allNodes, edges, context, executionId, LogService);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
catch (error) {
|
|
1680
|
+
executedBranch = "catch";
|
|
1681
|
+
// Store error in context for catch branch
|
|
1682
|
+
context.error = {
|
|
1683
|
+
message: error.message,
|
|
1684
|
+
stack: error.stack,
|
|
1685
|
+
nodeId: node.id,
|
|
1686
|
+
};
|
|
1687
|
+
// Execute catch branch
|
|
1688
|
+
for (const edge of catchEdges) {
|
|
1689
|
+
const nextNode = allNodes.find((n) => n.id === edge.target);
|
|
1690
|
+
if (nextNode) {
|
|
1691
|
+
await this.executeTryBranch(node.id, nextNode, allNodes, edges, context, executionId, LogService);
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
// After branch completes (via try-end), execute "done" handle nodes
|
|
1696
|
+
const doneEdges = edges.filter((e) => e.source === node.id && e.sourceHandle === "done");
|
|
1697
|
+
for (const edge of doneEdges) {
|
|
1698
|
+
const nextNode = allNodes.find((n) => n.id === edge.target);
|
|
1699
|
+
if (nextNode) {
|
|
1700
|
+
await this.executeNode(nextNode, allNodes, edges, context, executionId, LogService);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
return {
|
|
1704
|
+
success: true,
|
|
1705
|
+
branch: executedBranch,
|
|
1706
|
+
error: context.error || null,
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
/**
|
|
1710
|
+
* Execute a try/catch branch
|
|
1711
|
+
* Executes nodes until they connect back to the try node's "try-end" handle
|
|
1712
|
+
*/
|
|
1713
|
+
async executeTryBranch(tryNodeId, startNode, allNodes, edges, context, executionId, LogService) {
|
|
1714
|
+
const executedInBranch = new Set();
|
|
1715
|
+
const executeNodeInBranch = async (currentNode) => {
|
|
1716
|
+
if (executedInBranch.has(currentNode.id)) {
|
|
1717
|
+
return;
|
|
1718
|
+
}
|
|
1719
|
+
executedInBranch.add(currentNode.id);
|
|
1720
|
+
// Execute the node
|
|
1721
|
+
const result = await this.executeSingleNode(currentNode, context, executionId, LogService, allNodes, edges);
|
|
1722
|
+
context.outputs[currentNode.id] = result;
|
|
1723
|
+
// Find outgoing edges
|
|
1724
|
+
const outgoingEdges = edges.filter((e) => e.source === currentNode.id);
|
|
1725
|
+
for (const edge of outgoingEdges) {
|
|
1726
|
+
// Check if this edge connects back to the try node's "try-end" handle
|
|
1727
|
+
if (edge.target === tryNodeId && edge.targetHandle === "try-end") {
|
|
1728
|
+
// Branch complete, return to try node
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
// Continue executing the branch
|
|
1732
|
+
const nextNode = allNodes.find((n) => n.id === edge.target);
|
|
1733
|
+
if (nextNode) {
|
|
1734
|
+
await executeNodeInBranch(nextNode);
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
};
|
|
1738
|
+
await executeNodeInBranch(startNode);
|
|
1739
|
+
}
|
|
1740
|
+
/**
|
|
1741
|
+
* Execute a single node (for testing individual steps)
|
|
1742
|
+
*/
|
|
1743
|
+
async executeSingleNodeFromAPI(workflowId, nodeId, inputData, userId, tenantId, workflowData = null) {
|
|
1744
|
+
await this.ensureInitialized();
|
|
1745
|
+
const workflowService = new ItemsService("baasix_Workflow");
|
|
1746
|
+
try {
|
|
1747
|
+
// Use provided workflow data or fetch it
|
|
1748
|
+
const workflow = workflowData || (await workflowService.readOne(workflowId));
|
|
1749
|
+
if (!workflow) {
|
|
1750
|
+
throw new Error(`Workflow ${workflowId} not found`);
|
|
1751
|
+
}
|
|
1752
|
+
// Find the node to execute
|
|
1753
|
+
const { nodes, edges } = workflow.flow_data;
|
|
1754
|
+
const node = nodes.find((n) => n.id === nodeId);
|
|
1755
|
+
if (!node) {
|
|
1756
|
+
throw new Error(`Node ${nodeId} not found in workflow`);
|
|
1757
|
+
}
|
|
1758
|
+
// Create minimal context with provided input data
|
|
1759
|
+
const context = {
|
|
1760
|
+
variables: { ...workflow.variables },
|
|
1761
|
+
trigger: inputData.trigger || {},
|
|
1762
|
+
outputs: inputData.outputs || {},
|
|
1763
|
+
loop: inputData.loop || null,
|
|
1764
|
+
error: inputData.error || null,
|
|
1765
|
+
};
|
|
1766
|
+
// Process the node
|
|
1767
|
+
const processor = this.stepProcessors[node.type];
|
|
1768
|
+
if (!processor) {
|
|
1769
|
+
throw new Error(`Unknown node type: ${node.type}`);
|
|
1770
|
+
}
|
|
1771
|
+
const result = await processor(node, context, nodes, edges, null, null);
|
|
1772
|
+
return {
|
|
1773
|
+
success: true,
|
|
1774
|
+
nodeId,
|
|
1775
|
+
nodeType: node.type,
|
|
1776
|
+
input: {
|
|
1777
|
+
trigger: context.trigger,
|
|
1778
|
+
variables: context.variables,
|
|
1779
|
+
loop: context.loop,
|
|
1780
|
+
error: context.error,
|
|
1781
|
+
},
|
|
1782
|
+
output: result,
|
|
1783
|
+
workflow: workflow, // Return workflow for route to use
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1786
|
+
catch (error) {
|
|
1787
|
+
console.error(`Single node execution ${nodeId} failed:`, error);
|
|
1788
|
+
throw error;
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
/**
|
|
1792
|
+
* Graceful shutdown
|
|
1793
|
+
*/
|
|
1794
|
+
async shutdown() {
|
|
1795
|
+
console.info("WorkflowService: Starting graceful shutdown...");
|
|
1796
|
+
// Cancel all scheduled jobs
|
|
1797
|
+
for (const [workflowId, job] of this.scheduledJobs) {
|
|
1798
|
+
job.cancel();
|
|
1799
|
+
console.info(`Cancelled scheduled workflow ${workflowId}`);
|
|
1800
|
+
}
|
|
1801
|
+
this.scheduledJobs.clear();
|
|
1802
|
+
console.info("WorkflowService: Shutdown completed");
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
// Create singleton instance only if it doesn't exist
|
|
1806
|
+
if (!globalThis.__baasix_workflowService) {
|
|
1807
|
+
globalThis.__baasix_workflowService = new WorkflowService();
|
|
1808
|
+
}
|
|
1809
|
+
const workflowService = globalThis.__baasix_workflowService;
|
|
1810
|
+
export default workflowService;
|
|
1811
|
+
//# sourceMappingURL=WorkflowService.js.map
|